Compare commits

..
674 Commits
Author SHA1 Message Date
lbl b4301a8106 v2 2026-02-10 18:20:39 +08:00
vnt-dev 71da72da34 Merge pull request #171 from cfdworld/main
Update Cargo.toml for client to add support for tls1.2 when use wss
2026-01-27 13:49:46 +08:00
hello c6ef2031fa Update Cargo.toml for client to add support for tls1.2 when use wss
client add support for tls1.2 when use wss
2026-01-27 13:25:40 +08:00
lbl a82933a7a8 调整运行时 2025-09-23 09:27:54 +08:00
lbl 76444ef17d Merge remote-tracking branch 'refs/remotes/origin/1.2.x' 2025-09-23 09:16:58 +08:00
lubeilin fde33380be 调整超时时间 2025-09-18 18:27:31 +08:00
vnt-dev eedb6684a5 Merge pull request #158 from vnt-dev/vnt-dev-patch-2
Update README.md
2025-09-17 18:25:55 +08:00
vnt-dev a335a5bcdf Update README.md 2025-09-17 18:25:24 +08:00
vnt-dev 4a347d853e Merge pull request #157 from radish0416/main
修改rust.yml文件,云编译通过
2025-09-17 10:01:01 +08:00
radish0416 79f55fe7fd Update rust.yml 2025-09-17 09:30:52 +08:00
lixianjiang affe8fb719 修改rust.yml文件,云编译通过 2025-09-17 08:41:44 +08:00
lbl 22eee048d0 Use the latest version of tun-rs 2025-07-19 22:10:53 +08:00
lbl 2b2a550a98 not specify the rust version 2025-06-28 17:38:46 +08:00
lbl 14427ce437 Update Dependencies 2025-06-28 17:31:00 +08:00
lbl 39c170892e fmt 2025-06-28 17:29:59 +08:00
lbl b82276e6fa Merge branch 'refs/heads/main' into 1.2.x
# Conflicts:
#	vnt/src/channel/ws_channel.rs
#	vnt/src/util/dns_query.rs
2025-06-28 14:57:29 +08:00
vnt-dev 154e8ef09a Merge pull request #146 from lmq8267/1.2.x
1.2.x
2025-04-27 11:40:00 +08:00
VNT-[bot] 97a2f7c306 无网络不终止程序继续运行 2025-04-26 10:20:09 +08:00
VNT-[bot] 8d39d6045a 添加thunk-rs链接Windows 7兼容库 2025-04-26 10:15:27 +08:00
VNT-[bot] 04b60773b9 增加30x重定向功能(服务器地址以http:前缀)例如 tcp://http:域名 2025-04-26 10:08:48 +08:00
VNT-[bot] 774a80dc46 升级mips和i686的编译 2025-04-26 09:57:33 +08:00
vnt-dev 1cba257531 Merge pull request #131 from lmq8267/patch-4
升级mips和i686的编译
2025-04-25 18:02:05 +08:00
vnt-dev 16cc2abe7a Merge pull request #130 from lmq8267/patch-3
替换前缀
2025-04-25 18:01:29 +08:00
vnt-dev 595a49ee3f Merge pull request #129 from lmq8267/patch-2
添加一下 http 302重定向
2025-04-25 18:00:57 +08:00
vnt-dev f894f62eb2 Merge pull request #128 from lmq8267/patch-1
添加一下http 302重定向
2025-04-25 18:00:41 +08:00
lmq8267 a98d6a7594 升级mips和i686的编译 2025-03-10 22:18:10 +08:00
lmq8267 9674dfa5dc 替换前缀 2025-03-10 20:07:43 +08:00
lmq8267 00068cdcfd 添加一下 http 302重定向 2025-03-10 20:03:54 +08:00
lmq8267 846f128689 添加一下http 302重定向 2025-03-10 20:02:43 +08:00
lbl 2225edeb2e add unsafe block 2025-02-24 11:09:22 +08:00
lbl ef48007922 fix type error 2025-02-24 10:57:11 +08:00
lbl 1d5257363c use SyncDevice 2025-02-24 10:24:48 +08:00
lbl 0238d1ab7e Modify the compilation method for the MIPS target platform. 2025-02-21 16:31:48 +08:00
lbl ffbb85dfff Merge branch 'refs/heads/1.2.x-' into main-1 2025-02-21 15:29:51 +08:00
lbl 563aca39aa Use the new version of tun-rs 2025-02-13 17:02:06 +08:00
lbl 274f4df43a upload file 2025-02-06 19:06:13 +08:00
lbl 1fae7c9979 modify component name 2025-02-06 18:15:51 +08:00
lbl 46b37c0753 use v4 2025-02-06 17:56:29 +08:00
lbl f737f591b6 version = "1.2.16" 2025-02-06 16:28:43 +08:00
lbl ebf8337ad7 default tun name use vnt-tun 2025-02-06 16:26:47 +08:00
lbl e2d8a462a2 version = "1.2.15" 2025-01-05 10:48:27 +08:00
lbl b09cd690f0 去除tap 2025-01-05 10:42:27 +08:00
lbl 76bd79d97a update tun config 2024-12-31 14:20:15 +08:00
lbl c0d8645833 replace tun 2024-12-31 11:26:59 +08:00
vnt-dev 501a9e8198 Merge pull request #99 from vnt-dev/1.2.x
1.2.x
2024-10-20 14:13:57 +08:00
lubeilin 8aa4736b01 Merge remote-tracking branch 'vnt-release/1.2.x' into 1.2.x- 2024-10-14 16:53:45 +08:00
lubeilin d5e10a8210 调整数据读取 2024-10-14 16:53:35 +08:00
lbl 1165ee52c7 v3 2024-10-13 12:36:37 +08:00
lbl 8a66bbe1af version=1.2.14 2024-10-13 12:12:41 +08:00
lbl c91d8a2734 log 2024-10-13 12:11:16 +08:00
lbl 09a315a0f1 绑定本地网卡名称 2024-10-13 12:09:30 +08:00
vnt-dev 2979f2ef6f Merge pull request #97 from vnt-dev/vnt-dev-patch-1
Update README.md
2024-10-03 15:49:38 +08:00
vnt-dev 8225cb7877 Update README.md 2024-10-03 15:49:18 +08:00
vnt-dev 129ca1ec85 Merge pull request #86 from vnt-dev/1.2.x
1.2.x
2024-08-21 17:00:34 +08:00
lbl8603 96b67c6162 更新文档 2024-08-12 22:04:31 +08:00
lbl8603 ce02b30b2b 去除默认绑定网卡的逻辑 2024-08-12 22:04:17 +08:00
lbl8603 2fbdfe920e 增加绑定网卡 2024-08-08 22:02:46 +08:00
lbl8603 7901407d69 解决已知问题 2024-08-08 21:30:19 +08:00
lbl8603 e6cfdec9c6 调整默认网卡的逻辑 2024-08-08 20:38:58 +08:00
lbl8603 7e0de2c4e6 忽略绑定失败的错误 2024-08-07 23:14:13 +08:00
lbl8603 803af55246 增加日志 2024-08-07 22:34:48 +08:00
lbl8603 8e1a8fa7f0 增加feature判断 2024-08-07 21:48:58 +08:00
lbl8603 fc6bf407c4 wintun.dll校验失败则返回提示 2024-08-07 21:32:19 +08:00
lbl8603 dc4e472f20 提示虚拟网卡创建失败的信息 2024-08-07 21:29:55 +08:00
lbl8603 afff49e61a set_reuse_port 2024-08-06 22:03:18 +08:00
lbl8603 0f9ac2ac87 NAT1下的tcp打洞 2024-08-05 20:44:54 +08:00
vnt-dev cce23b9661 Merge pull request #67 from vnt-dev/1.2.x
1.2.x
2024-08-04 16:29:29 +08:00
lbl8603 4347ed68c3 Merge branch 'refs/heads/bugfix/2024-08-01-修复打洞问题' into 1.2.x 2024-08-01 23:24:10 +08:00
lbl8603 64d68c8a84 修复打洞问题 2024-08-01 23:23:23 +08:00
lbl8603 210b7c959e 调整日志 2024-08-01 21:56:09 +08:00
lbl8603 e8322adbcc 修复重启后不打洞的问题 2024-08-01 21:26:31 +08:00
lbl8603 e523b02b58 android增加wg选项 2024-07-31 21:37:19 +08:00
lbl8603 13df028cd9 更新版本号 2024-07-31 11:01:31 +08:00
lbl8603 4550bea063 更新版本 2024-07-31 11:00:20 +08:00
lbl8603 be8d4cbb8b fmt 2024-07-31 10:00:28 +08:00
lbl8603 ace0268a8c Merge branch 'refs/heads/1.2.x' into features/2024-07-30-分离协议栈
# Conflicts:
#	vnt/src/channel/punch.rs
2024-07-31 09:57:55 +08:00
lbl8603 56fd0d7a4f v4 socket绑定网卡 2024-07-30 23:37:54 +08:00
lbl8603 91ee751b07 Merge branch 'refs/heads/bugfix-2024-07-30-issues#65' into 1.2.x 2024-07-30 22:54:37 +08:00
lbl8603 fa9903ace1 修复配置文件没有读取加密方式的问题 2024-07-30 22:54:17 +08:00
lbl8603 c2854352df Merge branch 'refs/heads/features/2024-07-30-调整文档' into 1.2.x 2024-07-30 22:52:26 +08:00
lbl8603 b3472fb5b3 调整说明 2024-07-30 22:52:04 +08:00
lbl8603 e373c9ee16 支持选择tcp或udp打洞 2024-07-25 20:54:08 +08:00
vnt-dev 13b7b1761d Merge pull request #63 from vnt-dev/1.2.x
1.2.x
2024-07-25 10:28:28 +08:00
lbl8603 352f322f61 生成guid 2024-07-24 22:11:50 +08:00
lbl8603 be966c4f83 简化广播 2024-07-23 22:25:55 +08:00
lbl8603 b8cbf2cdac 不在线的可以不转发 2024-07-20 23:26:11 +08:00
lbl8603 05cf0e1a6f 修改版本 2024-07-20 14:48:56 +08:00
lbl8603 f90ef96a5d 增加sys-locale 2024-07-20 11:12:45 +08:00
lbl8603 81c6c8c1fc Merge branch 'refs/heads/1.2.x' into 1.2.x-2024-07-20-1
# Conflicts:
#	common/src/cli.rs
2024-07-20 11:10:15 +08:00
lbl8603 7ebdffcdd3 支持wg 2024-07-20 10:38:18 +08:00
lbl8603 1abe33cb16 Merge pull request #61 from jslyrd/main
添加英文帮助信息
2024-07-16 23:11:44 +08:00
jslyrd 965cbfbb7e 添加英文帮助信息 2024-07-16 22:32:48 +08:00
lbl8603 aa332b4f4d Merge pull request #60 from lbl8603/1.2.x
1.2.x
2024-07-13 15:53:53 +08:00
lbl8603 14ee9c4632 Merge branch 'refs/heads/1.2.x' into 1.2.x-2024-07-04 2024-07-13 12:11:24 +08:00
lbl8603 ddfa89167d 修改地址探测和转发路径 2024-07-13 12:09:15 +08:00
lbl8603 1f9ed7b314 修改features 2024-07-13 12:01:41 +08:00
lbl8603 dc45602661 Merge pull request #58 from lbl8603/1.2.x
调整添加转发路径的逻辑
2024-07-10 23:09:49 +08:00
lbl8603 27c0f389ee 调整添加转发路径的逻辑 2024-07-10 22:54:13 +08:00
lbl8603 59d6bfd957 Merge pull request #56 from lbl8603/1.2.x
1.2.x
2024-07-07 23:29:16 +08:00
lbl8603 31b8661b7d 设置证书 2024-07-07 22:38:01 +08:00
lbl8603 8ae20497c6 避免除0 2024-07-07 13:05:31 +08:00
lbl8603 a70b4f30b5 增加统计模块 2024-07-06 17:18:44 +08:00
lbl8603 df4795c9c4 加入少量轮询 2024-07-06 17:13:32 +08:00
lbl8603 c21a73f950 忽略初次错误 2024-07-06 16:45:55 +08:00
lbl8603 29cd111a49 fmt 2024-07-06 16:44:30 +08:00
lbl8603 367a56d346 Merge branch 'refs/heads/1.2.x' into 1.2.x-2024-07-04 2024-07-06 16:28:14 +08:00
lbl8603 d81ac6368e 增加流量统计 2024-07-06 16:27:08 +08:00
lbl8603 a228b62d23 Merge pull request #55 from Iron-Yang-Go/1.2.x
Try to execute 'dmidecode' command to get the system identifier first
2024-07-03 22:58:24 +08:00
Iron-Yang 995ff9b059 Try to execute 'dmidecode' command to get the system identifier first 2024-07-03 22:46:59 +08:00
lbl8603 7dc78cc170 修复时间相差导致的异常 2024-07-03 22:34:29 +08:00
lbl8603 7e218355f2 编译websocket 2024-07-02 21:18:08 +08:00
lbl8603 98c394c993 修改错误输出 2024-07-02 21:17:08 +08:00
lbl8603 31b06cd713 去除缓冲区设置 2024-07-01 23:04:08 +08:00
lbl8603 d7266926d8 增加Sync 2024-07-01 23:03:52 +08:00
lbl8603 1df5a1cfb1 去除不安全的计数器 2024-06-30 23:02:50 +08:00
lbl8603 8658bd3601 去除println 2024-06-30 22:19:05 +08:00
lbl8603 17d1ad3716 支持websocket协议 2024-06-30 22:16:30 +08:00
lbl8603 728598ba8e 处理遗留问题 2024-06-29 18:44:36 +08:00
lbl8603 218483f431 支持upnp 2024-06-29 17:29:37 +08:00
lbl8603 1f80e06d9d 服务端流量过滤 2024-06-29 17:28:30 +08:00
lbl8603 5e56064ec6 ipv6转ipv4判断 2024-06-29 17:26:52 +08:00
lbl8603 31f0213fc5 优化停止逻辑 2024-06-27 22:09:25 +08:00
lbl8603 54b1fef164 增加日志 2024-06-27 22:09:13 +08:00
lbl8603 7c18df0a6f 参考std判断公网IP 2024-06-27 21:59:57 +08:00
lbl8603 916fd8254b 修改stun 2024-06-27 21:58:17 +08:00
lbl8603 9e249862dc 排除虚拟网段 2024-06-26 21:53:59 +08:00
lbl8603 3773c09f57 调整任务停止逻辑 2024-06-25 22:48:06 +08:00
lbl8603 6798c652f9 减少socket数目,降低网络压力 2024-06-25 22:47:38 +08:00
lbl8603 382d5b61ba 调整退避速度 2024-06-25 22:47:19 +08:00
lbl8603 ac162eba66 调整打洞频率 2024-06-20 22:22:01 +08:00
lbl8603 05a60b3cb7 增加包长度上限 2024-06-17 23:46:34 +08:00
lbl8603 7f210e9319 调整获取地址频率 2024-06-17 23:44:11 +08:00
lbl8603 481c00629a 更新地址成功则返回true 2024-06-17 23:43:54 +08:00
lbl8603 bf55ec9512 修改文件名 2024-06-17 22:41:08 +08:00
lbl8603 073fa6ac6b 修改tun占用内存的大小 2024-06-15 22:18:51 +08:00
lbl8603 51315efb52 修改依赖 2024-06-15 21:16:04 +08:00
lbl8603 30cbf39e7f 排除生成的文件 2024-06-15 21:15:52 +08:00
lbl8603 2973d88df3 修改描述 2024-06-15 21:03:41 +08:00
lbl8603 8b2d7293fe 增加程序说明 2024-06-15 20:55:14 +08:00
lbl8603 215d9aa362 编译vn-link-cli 2024-06-15 20:55:05 +08:00
lbl8603 c3f27e8727 调整依赖 2024-06-15 20:54:49 +08:00
lbl8603 755339189d 去除重复代码 2024-06-15 20:54:35 +08:00
lbl8603 71cf0aa66d 初始化无tun的命令行程序 2024-06-15 20:54:18 +08:00
lbl8603 d695efd787 抽离公共逻辑 2024-06-15 20:53:32 +08:00
lbl8603 66129c2a24 支持无tun模式 2024-06-15 20:52:35 +08:00
lbl8603 0bc7115102 支持外置网卡 2024-06-15 20:51:00 +08:00
lbl8603 dca33a3ec1 fmt 2024-06-15 20:50:35 +08:00
lbl8603 9acbae2554 调整模块 2024-06-15 20:49:53 +08:00
lbl8603 ccfff7b1f6 调整参数格式 2024-06-15 20:49:34 +08:00
lbl8603 39ae4c5dd6 inline 2024-06-15 20:48:20 +08:00
lbl8603 350357076d 抽离发送IP包的逻辑 2024-06-15 20:47:37 +08:00
lbl8603 1e5e15f1f5 不再需要jni 2024-06-15 20:44:20 +08:00
lbl8603 1a385a777e 修改feature名称 2024-06-09 00:16:48 +08:00
lbl8603 16278dae62 优化代码结构 2024-06-09 00:11:33 +08:00
lbl8603 8294e46003 去除多余参数 2024-06-09 00:03:47 +08:00
lbl8603 dedc66875b 支持排除tun 2024-06-09 00:03:28 +08:00
lbl8603 3045e239ff Merge branch 'refs/heads/1.2.x' into 2024-05-31 2024-06-07 22:42:29 +08:00
lbl8603 5363558118 忽略io interrupted 2024-06-06 21:42:14 +08:00
lbl8603 13ba7cf0b8 优化提示 2024-06-06 21:41:56 +08:00
lbl8603 a5a43e762d 修改命令处理 2024-06-06 21:39:46 +08:00
lbl8603 cb6101a835 修改tap模式说明 2024-06-05 23:11:56 +08:00
lbl8603 255f84c969 提示创建tun失败的信息 2024-06-05 22:58:56 +08:00
lbl8603 7617d6c409 修改加密描述 2024-06-05 22:39:02 +08:00
lbl8603 f75691b483 过滤非vnt包 2024-06-05 22:38:51 +08:00
lbl8603 c5dbb79ed2 完善代码说明 2024-06-04 23:41:56 +08:00
lbl8603 60ba3c64b2 调整加密逻辑 2024-06-04 23:12:51 +08:00
lbl8603 080e58eeb7 调整代码 2024-06-04 23:00:27 +08:00
lbl8603 5d228b9c27 使用fnv 2024-06-04 22:58:35 +08:00
lbl8603 e36e45054c Merge pull request #49 from lbl8603/1.2.x
1.2.x
2024-06-01 17:00:51 +08:00
lbl8603 ba87a2780d Update README.md 2024-06-01 16:59:54 +08:00
lbl8603 fccc3478c8 增加GUI程序 2024-06-01 16:38:17 +08:00
lbl8603 a76280c53b 调整代码结构 2024-05-29 22:47:48 +08:00
lbl8603 90ff03e27c 计算密码hash 2024-05-28 23:21:43 +08:00
lbl8603 5eed05989f 增加ChaCah20和signal-hook 2024-05-28 22:49:06 +08:00
lbl8603 ac939746bd 增加ChaCah20 2024-05-28 22:48:46 +08:00
lbl8603 b7532e89c2 简化条件编译 2024-05-28 22:48:09 +08:00
lbl8603 74f44d6961 增加ChaCah20 2024-05-28 22:47:50 +08:00
lbl8603 86fc27c233 使用anyhow调整错误处理 2024-05-28 22:45:15 +08:00
lbl8603 4b6bb0e5f7 调整加密 2024-05-28 22:42:58 +08:00
lbl8603 215337fd8a 支持数据压缩 2024-05-25 17:30:27 +08:00
lbl8603 e54341567a 去除安卓上的icmp代理 2024-05-23 22:35:37 +08:00
lbl8603 1ce3e9ff2e 停止时回收连接通道 2024-05-23 22:35:24 +08:00
lbl8603 ffdb5ceb6b 去除无用状态 2024-05-23 22:35:06 +08:00
lbl8603 faa78445b1 返回具体错误信息 2024-05-21 20:37:55 +08:00
lbl8603 39170b74ca Merge pull request #43 from lbl8603/1.2.x
调整条件编译
2024-05-15 20:32:05 +08:00
lbl8603 440a832ace 调整条件编译 2024-05-15 20:30:42 +08:00
lbl8603 0e3f06429b Merge pull request #42 from lbl8603/1.2.x
处理unix去掉所有模块的编译问题
2024-05-14 09:25:52 +08:00
lubeilin 01cf8806e2 处理unix去掉所有模块的编译问题 2024-05-14 09:25:16 +08:00
lbl8603 ee7c597bd8 Merge pull request #41 from lbl8603/1.2.x
1.2.x
2024-05-13 22:53:51 +08:00
lbl8603 a85e0c6d06 修改图片地址 2024-05-13 22:53:22 +08:00
lbl8603 fee3b24df4 减少无用日志 2024-05-13 22:07:05 +08:00
lbl8603 7f500d72c7 Merge pull request #39 from lbl8603/1.2.x
支持--no-default-features编译
2024-05-11 13:31:04 +08:00
lubeilin d8a9ec5732 支持--no-default-features编译 2024-05-11 13:30:16 +08:00
lbl8603 4dca4c4f22 Merge pull request #38 from lbl8603/1.2.x
1.2.x
2024-05-11 09:21:57 +08:00
lbl8603 0e4294415e 增加日志输出内容 2024-05-10 23:19:52 +08:00
lbl8603 9df324f402 增加输出端口映射和ip代理内容 2024-05-10 23:18:45 +08:00
lbl8603 3b1236cfd0 通过stun探测端口,解决使用ipv6或tcp服务时端口错误的问题 2024-05-10 23:18:24 +08:00
lbl8603 699262d79e rustup set auto-self-update disable 2024-05-10 23:16:37 +08:00
lbl8603 f6cfa1b84c 调整安卓端tun逻辑 2024-05-09 22:51:27 +08:00
lbl8603 1338bcbbc5 消除代码警告 2024-05-09 21:25:55 +08:00
lbl8603 4215488cb6 调整中继模式的逻辑 2024-05-09 20:16:02 +08:00
lbl8603 ccd6e44c2b 使用异步tun 2024-05-09 20:14:01 +08:00
lbl8603 b12b0b95a0 去除linux上的tap 2024-05-09 20:13:44 +08:00
lbl8603 948f3d5d24 遍历txt记录值 2024-05-09 20:12:26 +08:00
lbl8603 cf4fc54dfd 端口映射列表为空时不启动 2024-05-09 20:11:43 +08:00
lbl8603 5a9e31bded 支持异步tun 2024-05-09 20:11:08 +08:00
lbl8603 b61c140c24 去除linux上tap的支持 2024-05-09 20:10:17 +08:00
lbl8603 035ae46ee5 过滤/etc/machine-id为空的情况 2024-05-09 20:07:46 +08:00
lbl8603 b57a38e81e 增加超时方法 2024-05-08 23:58:38 +08:00
lbl8603 be767ae300 增加提示 2024-05-08 23:58:03 +08:00
lbl8603 607c8c2e9f 增加端口映射功能 2024-05-08 21:31:21 +08:00
lbl8603 d3dce7a3cc 调整stun处理 2024-05-08 20:20:09 +08:00
lbl8603 ff2bbdd837 改回用tokio处理代理,简化代码 2024-05-08 20:19:33 +08:00
lbl8603 b771b6c074 增加set_ttl方法 2024-05-08 20:16:36 +08:00
lubeilin ff389647c0 修改帮助描述 2024-05-06 21:00:12 +08:00
lubeilin 49bec2d6aa 使用anyhow替换部分处理 2024-05-06 20:02:40 +08:00
lbl8603 ee359dc0d8 调整子网路由逻辑 2024-05-01 15:30:31 +08:00
lbl8603 d4fc28dca3 收到PunchRequest时不添加路由 2024-05-01 15:21:25 +08:00
lbl8603 1130f69a82 修改默认rt 2024-05-01 14:50:00 +08:00
lbl8603 e73ed238cd 去除无用代码 2024-04-30 23:34:17 +08:00
lbl8603 c6c1d297af 去除多余代码 2024-04-30 23:28:45 +08:00
lbl8603 f78f8de15e 修改结构体名称 2024-04-30 23:25:40 +08:00
lbl8603 fcbc52dc2e 增加icmp代理日志 2024-04-30 23:24:12 +08:00
lbl8603 bbfa255dd3 优化环路检测逻辑 2024-04-29 23:23:28 +08:00
lbl8603 18fcee52db 兼容安卓icmp 2024-04-28 23:17:45 +08:00
lbl8603 d36d0b9491 增加编译目标 2024-04-28 23:17:33 +08:00
lbl8603 fb8d7ec5b8 调整解析地址选择 2024-04-25 23:34:44 +08:00
lbl8603 24341808ef Merge remote-tracking branch 'origin/2024-04-19' into 2024-04-19 2024-04-25 23:17:55 +08:00
lbl8603 353809f91c 优化路由添加逻辑 2024-04-25 20:04:32 +08:00
lbl8603 d2b2dfbf1e 去除多余校验 2024-04-25 19:44:20 +08:00
lbl8603 e027cf9d98 提交Cargo.lock锁定依赖版本 2024-04-24 23:18:05 +08:00
lbl8603 484b1657e8 修改dns参数描述 2024-04-24 23:09:20 +08:00
lbl8603 7dc4161f2f 避免打洞环路 2024-04-24 23:04:01 +08:00
lbl8603 18eb4af016 修改默认版本设置 2024-04-24 23:03:50 +08:00
lbl8603 635ec315f7 增加udp代理日志 2024-04-24 23:02:34 +08:00
lbl8603 37eb9ec045 优化dns查询 2024-04-24 23:02:01 +08:00
lbl8603 7c799589df 调整stun逻辑 2024-04-24 23:01:40 +08:00
lubeilin 4f52d58d6e 调整错误输出 2024-04-23 14:04:32 +08:00
lubeilin 441e374563 jni设置dns 2024-04-23 13:56:25 +08:00
lubeilin 3272f3cdca 设置dns 2024-04-23 12:40:33 +08:00
lbl8603 5b40a9f147 支持指定dns 2024-04-23 08:21:01 +08:00
lubeilin 12d4fc8e98 dns解析 2024-04-22 19:00:05 +08:00
lbl8603 ca74827aaf 拒绝服务端密钥对变化 2024-04-21 22:14:56 +08:00
lbl8603 0b570130e8 处理tcp加密注册失败的问题 2024-04-21 18:48:18 +08:00
lbl8603 5bdc514606 增加转发日志 2024-04-21 18:30:33 +08:00
lbl8603 137efe20b8 增加密钥hash,方便客户端判断加密是否一致 2024-04-20 21:27:35 +08:00
lbl8603 0cbc3e0f63 双重探测NAT类型,用于检测某些对称网络 2024-04-20 16:15:45 +08:00
lbl8603 7fbcf0a832 Merge remote-tracking branch 'origin/2024-04-19' into 2024-04-19 2024-04-20 14:49:57 +08:00
lbl8603 7283863fe8 增加路由回调 2024-04-20 14:48:58 +08:00
lbl8603 198f82fb2c 修改ip转u32方式 2024-04-19 20:05:19 +08:00
lubeilin c219af4f4b 增加额外路由 2024-04-19 18:44:22 +08:00
lubeilin 0352982c14 回调增加网络路由 2024-04-19 18:35:37 +08:00
lubeilin cc6cd6dc37 去除多余代码 2024-04-19 18:07:41 +08:00
lubeilin 9155471c26 去除多余代码 2024-04-19 16:28:49 +08:00
lubeilin d916fd7573 去除多余字段 2024-04-19 16:17:39 +08:00
lubeilin 5528557964 去除多余字段 2024-04-19 16:16:49 +08:00
lbl8603 8dfc3b8c43 修改安卓端逻辑 2024-04-19 10:49:47 +08:00
lubeilin 84824731a7 优化nat探测 2024-04-16 23:20:24 +08:00
lubeilin 7131937d06 fmt 2024-04-13 17:45:46 +08:00
lubeilin a657eae599 优化加密状态下的重连 2024-04-13 11:13:37 +08:00
lubeilin e9ec6e8903 增加丢包日志 2024-04-13 10:24:29 +08:00
lubeilin c4a9e79dc2 修复域名转换ip的问题 2024-04-09 22:48:14 +08:00
lubeilin aeebf45390 去除proto生成代码 2024-04-08 23:34:36 +08:00
lubeilin b818851b38 修改rand版本 2024-04-07 22:47:50 +08:00
lubeilin f47db0ab1b 处理通道空置的问题 2024-04-07 21:40:31 +08:00
lubeilin 256a2adc3e 安卓不设置路由 2024-04-07 21:38:09 +08:00
lubeilin 28522f2f07 完善jni调用 2024-04-07 20:57:10 +08:00
lubeilin a33ffd96fd 通道设置为0 2024-04-06 17:30:32 +08:00
lubeilin 61f7352312 测试阻塞udp的性能 2024-04-06 17:30:12 +08:00
lubeilin def33382e3 日志输出版本信息 2024-04-06 11:23:09 +08:00
lubeilin 1d0d64bd30 Merge remote-tracking branch 'origin/main' 2024-04-05 23:21:21 +08:00
lubeilin e7e8f6b771 调整tcp模式打洞 2024-04-05 23:14:45 +08:00
lubeilin 4317b06428 公网端口默认设置为0 2024-04-05 23:14:12 +08:00
lubeilin e9c695f4ac 注册成功后先更新地址 2024-04-05 23:13:36 +08:00
lubeilin da714e97e8 修改公网地址探测频率 2024-04-05 23:10:07 +08:00
lubeilin ee2186aba5 解决tcp掉线问题 2024-04-05 23:01:34 +08:00
lubeilin ca91f97c27 调整序列号 2024-04-05 23:00:34 +08:00
lubeilin 284cf38f0b 优化广播 2024-03-30 00:24:19 +08:00
lbl8603 dc3e4c8253 Merge pull request #32 from ziyouwa/main
优化从linux或wsl系统获取id
2024-03-25 15:46:01 +08:00
ziyouwa c7b9f61bd2 1、优化从linux或wsl系统获取id
2、增加.gitignore文件
2024-03-25 15:36:18 +08:00
lubeilin b26c4b97b2 上报状态 2024-03-24 21:50:47 +08:00
lubeilin 5569c67ba4 增加日志 2024-03-24 21:50:39 +08:00
lubeilin 69da6de1ed 离线时才检测服务器地址 2024-03-24 09:18:12 +08:00
lubeilin ae983f014b 降低地址探测频率 2024-03-23 12:46:20 +08:00
lubeilin 499e3bbfdf 去除重复逻辑 2024-03-21 23:08:48 +08:00
lubeilin 01f6890fd3 减少离线时发包 2024-03-21 21:25:24 +08:00
lubeilin 9badbe180c 不转发来源和目的相同的数据 2024-03-20 12:21:19 +08:00
lubeilin 30b1e71aa1 fmt 2024-03-19 23:54:33 +08:00
lubeilin b36cc352d5 兼容纯ipv4 2024-03-19 23:53:16 +08:00
lubeilin 12d888cefc 忽略跃点设置失败的异常 2024-03-18 21:35:22 +08:00
lubeilin ad1df41029 调整打洞 2024-03-17 15:41:25 +08:00
lubeilin 67498dfc82 增加序列号 2024-03-17 14:28:03 +08:00
lubeilin cf52fdde57 修改线程名称 2024-03-17 14:27:49 +08:00
lubeilin a20082d40b 已支持ipv6 2024-03-14 23:31:40 +08:00
lubeilin 8e29b020e0 Merge branch 'main' into mio 2024-03-14 12:06:47 +08:00
lubeilin 20132861e9 完善日志,修复已知问题 2024-03-14 12:05:35 +08:00
lubeilin a1e9b3c133 增加参数说明 2024-03-13 23:24:36 +08:00
lubeilin 8a4d21849e [mio] 调整代码 2024-03-13 21:23:32 +08:00
lubeilin d2c5aac178 [mio] 完善提示信息 2024-03-13 21:17:01 +08:00
lu 066e655c30 修复macos上已知问题 2024-03-12 23:52:53 +08:00
lubeilin 8627046deb [mio] 兼容mac 2024-03-12 21:30:06 +08:00
lubeilin 76b86bbd0f [mio] 调整打洞策略 2024-03-11 22:12:41 +08:00
lubeilin e78994ceb4 [mio] 修复重连问题 2024-03-11 21:59:13 +08:00
lubeilin c63813e590 [mio] 减少中继探测数据包 2024-03-10 21:07:06 +08:00
lubeilin 39becec2e5 [mio] 调整重连 2024-03-10 16:08:17 +08:00
lubeilin 44e379df82 [mio] 增加日志 2024-03-10 16:07:59 +08:00
lubeilin b2b58a23c1 [mio] 主通道改为异步 2024-03-10 14:11:06 +08:00
lubeilin 03529bc339 [mio] 返回详细路由 2024-03-10 14:10:49 +08:00
lubeilin 996a80e710 [mio] 调整打洞处理 2024-03-10 14:09:47 +08:00
lubeilin ac837077f6 [mio] 简化代码 2024-03-10 14:09:00 +08:00
lubeilin b25f330b0c [mio] 调整打洞处理 2024-03-10 14:08:30 +08:00
lubeilin c783a96e13 [mio] 打印移除路由日志 2024-03-10 14:07:04 +08:00
lubeilin 999a5b9117 [mio] 减少日志 2024-03-10 14:06:37 +08:00
lubeilin 6850681352 [mio] 增加缓冲区长度 2024-03-10 14:05:51 +08:00
lubeilin f2b21c9e4a [mio] 增加队列长度 2024-03-10 14:05:28 +08:00
lubeilin d27f065507 [mio] 调整通道使用策略 2024-03-07 21:48:54 +08:00
lubeilin 5e913568a3 优化通道选择 2024-03-07 13:11:45 +08:00
lubeilin 97b6db3485 输出增加颜色 2024-03-07 12:25:52 +08:00
lubeilin ae63ce80b5 [mio] 启动成功输出 2024-03-06 22:53:56 +08:00
lubeilin c0e930a3ef [mio] 简化代理 2024-03-06 22:46:17 +08:00
lubeilin f8ee8e242f [mio] fmt 2024-03-06 22:40:11 +08:00
lubeilin 20e3ae4d58 完善代理 2024-03-06 12:55:26 +08:00
lubeilin a910d7a673 修复打洞端口为0的问题 2024-03-06 12:55:07 +08:00
lubeilin a4b5b2a028 [mio] fmt 2024-03-05 21:36:21 +08:00
lubeilin a8a1346c98 [mio] 使用实际长度判断 2024-03-05 21:36:12 +08:00
lubeilin 734e613452 [mio] 完善错误处理 2024-03-05 21:35:49 +08:00
lubeilin e2003d8dea [mio] 优化代理 2024-03-05 21:34:51 +08:00
lubeilin f0a2edadfa [mio] 输出ip冲突错误 2024-03-05 21:33:25 +08:00
lubeilin 4c174c3aac [mio] 增加参数说明 2024-03-05 21:32:39 +08:00
lubeilin 147156d96d [mio] 增加模拟弱网参数 2024-03-05 21:32:20 +08:00
lubeilin 64d62272a6 [mio] 完善说明 2024-03-04 21:43:07 +08:00
lubeilin 91713f0fed [mio] fmt 2024-03-04 21:14:05 +08:00
lubeilin e7f2571469 [mio] device_id获取不到时默认值取空 2024-03-04 21:13:05 +08:00
lubeilin 48b41c4b22 [mio] 修复ip代理相关问题 2024-03-04 21:11:41 +08:00
lubeilin 042cc7c797 [mio] 增加开源许可 2024-03-04 21:10:49 +08:00
lubeilin 24c1edff8c [mio] 完善说明 2024-03-03 22:05:58 +08:00
lubeilin ec2a74d06f [mio] 完善说明 2024-03-03 21:35:49 +08:00
lubeilin 63316ee23e [mio] 优化显示 2024-03-03 19:23:43 +08:00
lubeilin f0139c68f7 [mio] 路由为空则回收数据 2024-03-03 19:23:34 +08:00
lubeilin 590721a4da [mio] 默认网卡名称改为vnt-tun/tap 2024-03-03 18:31:19 +08:00
lubeilin 521280537a [mio] 兼容padavan路由器找不到程序当前路径的问题 2024-03-03 09:45:52 +08:00
lubeilin 1eb2a0f5cf [mio] 减少读更新 2024-03-03 00:35:02 +08:00
lubeilin f0ac70c995 [mio] 修复relay参数问题 2024-03-02 23:47:56 +08:00
lubeilin 2570a9ead2 [mio] 修复ip tcp代理问题 2024-03-02 23:39:51 +08:00
lubeilin 54da8ac71f [mio] 判断停止状态 2024-03-02 23:14:16 +08:00
lubeilin bd73ed213e [mio] stop阻塞的问题 2024-03-02 23:13:59 +08:00
lubeilin 20fd060821 [mio] 改为阻塞连接 2024-03-02 15:50:04 +08:00
lubeilin e8df40925a [mio] 增加连接超时时间 2024-03-02 15:49:23 +08:00
lubeilin 2c0a5d459a [mio] 修改连接超时时间 2024-03-02 15:49:06 +08:00
lubeilin b67814f125 [mio] 去除临时输出 2024-03-02 14:15:18 +08:00
lubeilin e3f328cc9b [mio] 修复指定通道类型无效的问题 2024-03-02 14:03:19 +08:00
lubeilin b6aba36510 [mio] fmt 2024-03-02 14:02:20 +08:00
lubeilin 22720a8f8f [mio] fmt 2024-03-02 14:02:01 +08:00
lubeilin cc717a4060 [mio] 去除获取目录失败的unwrap 2024-03-02 11:57:00 +08:00
lubeilin 5264455eba [mio] 回收计数器 2024-03-02 11:50:18 +08:00
lubeilin b0d6b1f884 [mio] 简化流量统计,兼容32位系统 2024-03-01 23:40:14 +08:00
lubeilin 7cd8647c0c [mio] fmt 2024-03-01 23:34:37 +08:00
lubeilin 19187aff47 [mio] 设置广播掩码 2024-03-01 23:34:14 +08:00
lubeilin 23b536f771 [mio] 修复停止命令失效问题 2024-03-01 23:33:47 +08:00
lubeilin 119f719a9f [mio] 支持仅使用p2p模式 2024-03-01 21:48:07 +08:00
lubeilin 9673eaab05 [mio] 添加组播和广播路由 2024-03-01 21:47:49 +08:00
lubeilin 390b8dd242 [mio] 确认离线才调用disconnect 2024-03-01 21:47:14 +08:00
lubeilin 1b1663c87e [mio] 简化校验 2024-03-01 21:46:48 +08:00
lubeilin 7bc4203deb [mio] 网卡名称使用String 2024-03-01 21:44:27 +08:00
lubeilin c798c36914 [mio] 删除多余文件 2024-03-01 21:36:46 +08:00
lubeilin 14e7b518b5 [mio] 更改版本为 1.2.9 2024-02-29 22:40:01 +08:00
lubeilin 88eb3f6514 [mio] fmt 2024-02-29 22:39:35 +08:00
lubeilin d78e48211d [mio] 处理server_encrypt代码逻辑 2024-02-29 22:39:20 +08:00
lubeilin 55a510aa34 [mio] 去除多余代码 2024-02-29 22:31:08 +08:00
lubeilin 8d7eb5ba1b [mio] 适配新版vnt,增加jni java端代码 2024-02-29 22:29:39 +08:00
lubeilin ceadadee68 [mio] 适配新版vnt 2024-02-29 22:29:14 +08:00
lubeilin e3e2a262e4 [mio] 增加依赖 2024-02-29 22:28:52 +08:00
lubeilin 5850a59ec9 [mio] 去除多余代码 2024-02-29 22:28:20 +08:00
lubeilin 318c267c34 [mio] 简化vnt创建 2024-02-29 22:27:47 +08:00
lubeilin 98bd713a91 [mio] 整理数据处理逻辑 2024-02-29 22:27:15 +08:00
lubeilin fe499d0476 [mio] 使用mio改写ip代理 2024-02-29 22:26:53 +08:00
lubeilin ff665d7dcf [mio] 更新protobuf 2024-02-29 22:25:30 +08:00
lubeilin 573537fbfb [mio] 使用io结果简化异常处理 2024-02-29 22:25:11 +08:00
lubeilin 9e1bd28e2d [mio] 创建tun/tap设备 2024-02-29 22:22:25 +08:00
lubeilin 27a6c686a7 [mio] 合并各平台的tun/tap处理 2024-02-29 22:20:30 +08:00
lubeilin 2f6d743931 [mio] 加入端口组,去除tokio 2024-02-29 22:19:41 +08:00
lubeilin ec3aa01b9c [mio] 增加统计、监听器、定时器 2024-02-29 22:17:27 +08:00
lubeilin 02b988e898 [mio] 去除Arc 2024-02-29 22:15:06 +08:00
lubeilin b57cd2ed45 [mio] 简化feature 2024-02-29 22:12:40 +08:00
lubeilin af5d284470 [mio] 支持多通道传输,使用mio代替tokio 2024-02-29 22:06:20 +08:00
lubeilin e7f1165287 [mio] 增加打洞端口组 2024-02-29 22:05:17 +08:00
lubeilin e64e17267d 增加tcp通道处理逻辑 2024-01-07 21:09:26 +08:00
lubeilin 7098111ad1 增加tcp通道,去除并行逻辑 2024-01-06 12:06:21 +08:00
lubeilin 57b903ea29 命令默认使用39271端口 2024-01-06 11:30:05 +08:00
lubeilin d06082fc7a 默认输出到当前路径 2024-01-06 11:26:09 +08:00
lubeilin 77a78daac1 版本号使用环境变量 2024-01-06 11:22:14 +08:00
lubeilin d72fab04a2 mips平台使用1.71.1版本rust 2024-01-06 11:21:59 +08:00
lubeilin 76eaead0b9 修改对称网络处理逻辑 2023-12-31 13:42:32 +08:00
lubeilin 858ca9bbe7 去除udp通道的arc包装 2023-12-30 22:15:48 +08:00
lubeilin cbc4a7378c 优化tcp通道 2023-12-30 21:39:53 +08:00
lubeilin ee34f525e6 nat地址判断 2023-12-30 13:07:54 +08:00
lubeilin 698e2531e8 增加参数校验 2023-12-30 12:00:47 +08:00
lubeilin 16f833ec72 优化延迟优先参数 2023-12-28 23:18:57 +08:00
lubeilin 94d6caef7e 修复连接tcp地址的问题 2023-12-26 22:27:16 +08:00
lubeilin 364012f9dd fmt 2023-12-26 21:47:01 +08:00
lubeilin cf4b1f418f 忽略地址校验 2023-12-26 21:46:41 +08:00
lubeilin c6465977ef 将ipv4转换成ipv6 2023-12-26 21:46:23 +08:00
lubeilin c577e6381f 将ipv4转换成ipv6 2023-12-25 23:06:47 +08:00
lubeilin 134e31f563 fmt 2023-12-24 13:42:41 +08:00
lubeilin 0580b89f48 升级版本号 2023-12-24 12:22:34 +08:00
lubeilin 37080af275 支持ipv6服务端 2023-12-24 12:00:44 +08:00
lubeilin 26d68ac059 版本改为1.2.7 2023-10-31 20:49:25 +08:00
lubeilin 6292c1c381 去除溢出检查 2023-10-31 20:49:16 +08:00
lubeilin b0c3f25a29 使用读写锁简化操作,增加延迟优先选项 2023-10-31 20:48:38 +08:00
lubeilin d2e09d3da5 增加配置文件的参数说明 2023-10-11 20:46:39 +08:00
lbl8603 293c5b90a4 Merge pull request #22 from taotieren/contrib
Update README.md
2023-10-11 09:19:37 +08:00
taotieren e2323361f9 Update README.md 2023-10-10 23:23:25 +08:00
lbl8603 d05cff99ee Merge pull request #21 from taotieren/aur
Update README.md
2023-10-10 22:52:30 +08:00
taotieren e9b1b2ef3b Update README.md 2023-10-10 22:31:38 +08:00
lbl8603 a8ea2c14fc Merge pull request #20 from taotieren/aur
Add AUR vnt-git
2023-10-10 22:22:10 +08:00
taotieren 0688cb4515 Add AUR vnt-git 2023-10-10 22:18:37 +08:00
lubeilin c3261d7a57 修改版本为1.2.6 2023-10-09 21:20:10 +08:00
lubeilin ef8d13f61b 编译features增加ring 2023-10-09 20:56:58 +08:00
lubeilin fc104d5dee 增加参数关闭ip代理 2023-10-09 20:56:04 +08:00
lubeilin 9ad0525216 减少复制 2023-10-08 20:52:12 +08:00
lubeilin 3ea1250b53 完善编译和参数说明 2023-10-08 20:03:14 +08:00
lubeilin 9df34207f7 解决不同features编译时告警的问题 2023-10-08 17:46:31 +08:00
lubeilin dacce892ff 可选ip代理,关闭后可使用外部命令来进行ip转发 2023-10-08 17:16:02 +08:00
lubeilin a9495f1d30 升级tap相关依赖 2023-10-06 22:44:02 +08:00
lubeilin 0892c6eee9 修复进程异常退出的问题 2023-10-06 22:25:30 +08:00
lubeilin f746eadcf1 fmt 2023-10-06 22:22:35 +08:00
lubeilin da2371541c 修改依赖版本 2023-10-06 22:22:22 +08:00
lubeilin cca91d4331 tcp改为使用同步方法 2023-10-06 22:21:29 +08:00
lubeilin 11aa3b1d2c 修改features说明 2023-09-28 10:35:50 +08:00
lubeilin d0b0c61a03 Merge remote-tracking branch 'origin/main' 2023-09-28 10:27:10 +08:00
lubeilin b0501db4e5 修改features说明 2023-09-28 10:26:38 +08:00
lubeilin 465a5c75ae 修改默认features 2023-09-28 10:05:52 +08:00
lbl8603 978460aca8 更新 README.md 2023-09-27 23:51:52 +08:00
lubeilin 4b86752c79 去除不必要的features 2023-09-27 19:59:50 +08:00
lubeilin bd61cad7b6 加密算法可选 2023-09-27 19:51:23 +08:00
lubeilin 40cbd2e26b 更新参数说明 2023-09-26 21:56:04 +08:00
lubeilin 2cab580b4e 更新版本 2023-09-26 21:53:55 +08:00
lubeilin 3d243fb01d 支持读取配置文件和自定义端口 2023-09-26 21:53:41 +08:00
lubeilin ca4e8d14f0 支持sm4-cbc加密 2023-09-26 21:53:07 +08:00
lubeilin 9c098c55c9 调整长度判断 2023-09-26 21:51:56 +08:00
lubeilin 707b07b8d3 ipv6改为完整地址 2023-09-24 15:42:19 +08:00
lubeilin de5a6971f0 更新读取时间不需要再插入 2023-09-24 14:58:03 +08:00
lubeilin 056036c4d2 减少注册和探测nat的频率 2023-09-24 14:53:37 +08:00
lubeilin 1cfb188845 去除多余依赖 2023-09-24 13:45:47 +08:00
lubeilin 73a2c31854 连接通过关闭同时关闭tap 2023-09-24 13:42:09 +08:00
lubeilin 92eea536f8 删除多余依赖 2023-09-24 12:57:49 +08:00
lubeilin 00936a923e 避免直接关闭网卡 2023-09-24 12:55:42 +08:00
lubeilin ba69ba78af 增加线程名称 2023-09-23 23:09:37 +08:00
lubeilin 17b206bace 1.2.4.3 2023-09-23 21:38:31 +08:00
lubeilin 58d5a4f5da 增加wintun日志 2023-09-23 21:38:20 +08:00
lubeilin 2438d14175 避免短时间重复上传服务端密钥 2023-09-23 21:33:00 +08:00
lubeilin 99f8526799 去除tap广播路由 2023-09-22 22:49:05 +08:00
lubeilin 56fcbd64ed 增加日志 2023-09-22 22:43:23 +08:00
lubeilin 3766b2b7c1 修改命令超时时间 2023-09-22 22:43:02 +08:00
lubeilin baf0698fe4 去除广播路由 2023-09-22 22:17:13 +08:00
lubeilin 301938b9fc 增加小版本 2023-09-22 18:19:13 +08:00
lubeilin 16a37c713a 增加日志 2023-09-22 18:18:29 +08:00
lubeilin d412a769dd fmt 2023-09-22 18:18:10 +08:00
lubeilin c8eecc87fd 调整心跳间隔,服务端和客户端心跳分离 2023-09-22 18:17:12 +08:00
lubeilin 6a11db70c8 调整代理超时时间 2023-09-22 18:16:06 +08:00
lubeilin d7c121a756 commit:
1.去除缓冲池
2.数据处理改为同步方法
3.fmt
2023-09-20 19:54:49 +08:00
lubeilin 3429ee8bd6 增加提示 2023-09-20 16:03:25 +08:00
lubeilin 4422f9f8b7 Merge remote-tracking branch 'origin/main'
# Conflicts:
#	vnt/src/ip_proxy/tcp_proxy.rs
2023-09-20 15:39:54 +08:00
lubeilin 57ed454c93 修复内网ip断线问题 2023-09-20 11:11:31 +08:00
lubeilin 236205c0f3 修复内网ip断线问题 2023-09-19 18:25:14 +08:00
lubeilin 99b4bf0041 Merge remote-tracking branch 'origin/main' 2023-09-18 21:51:17 +08:00
lubeilin 9495e39700 优化nat校验 2023-09-18 21:51:08 +08:00
lbl8603 75e244e3a8 Update README.md 2023-09-18 11:17:21 +08:00
lubeilin bf8397e6e3 修复代理不正常关闭的问题 2023-09-17 20:28:59 +08:00
lubeilin 8cee974866 Merge remote-tracking branch 'origin/main' 2023-09-17 19:53:06 +08:00
lubeilin 5e018801db 增加代理超时时间 2023-09-17 19:52:54 +08:00
lbl8603 c4f7f4c19e Update README.md 2023-09-17 17:14:42 +08:00
lubeilin 29945eb3da 1.2.3
1.同步处理UDP,提升性能
2.加密引入openssl,提升性能
3.安卓支持ip代理
2023-09-17 12:54:15 +08:00
lubeilin 7db415d243 Merge remote-tracking branch 'origin/main' 2023-09-12 23:34:08 +08:00
lubeilin 808dcec795 Merge branch 'dev' 2023-09-12 23:28:49 +08:00
lubeilin ad8fecc319 去除命令sudo 2023-09-04 21:32:38 +08:00
lubeilin 27ae9a89da 避免路由切换时的抖动 2023-09-04 20:35:56 +08:00
lubeilin b3a4a4de5e 完善日志输出等 2023-09-04 20:35:39 +08:00
lubeilin ca76c35f6a linux固定网卡名称,启动时删除网卡 2023-09-04 20:35:06 +08:00
lubeilin 073c820da6 增加打洞选项 2023-09-04 20:34:43 +08:00
lubeilin 954f0d2d05 完善1.2.2 2023-09-03 20:58:48 +08:00
lubeilin a943f5bffc 延迟切换NAT类型 2023-09-03 17:24:25 +08:00
lubeilin c3cff7c5b5 cargo fmt 2023-09-03 11:39:30 +08:00
lubeilin acb5a8a325 优化启动速度 2023-09-03 11:28:40 +08:00
lubeilin 1a4e375dbf 支持ipv6 2023-09-02 23:57:32 +08:00
lubeilin eec7d73ebe 可选数据指纹校验、支持ecb算法 2023-09-01 23:36:04 +08:00
lbl8603 f4dda173c2 Update README.md 2023-08-30 22:31:20 +08:00
lubeilin 96fb8c881d 修改jni模块 2023-08-30 21:54:30 +08:00
lubeilin aeebbd18fd 增加异常日志 2023-08-30 21:53:49 +08:00
lubeilin baa71a51eb 修复广播问题 2023-08-30 21:53:31 +08:00
lubeilin 84e70149a1 增加参数说明 2023-08-29 22:02:55 +08:00
lubeilin 959f2aa783 修改版本 2023-08-29 21:15:48 +08:00
lubeilin 99b2aa9522 修改linux上kill不退出的问题 2023-08-29 21:15:39 +08:00
lubeilin 561fa9f8fe Merge remote-tracking branch 'origin/dev' into dev 2023-08-29 21:03:36 +08:00
lubeilin ad9dd6a7f7 增加tun创建失败的说明 2023-08-29 21:03:27 +08:00
lbl8603 65758eb94c Update README.md 2023-08-29 10:39:03 +08:00
lubeilin fb7ccf4d11 增加参数说明 2023-08-29 00:21:25 +08:00
lubeilin b9fe5e0fe2 支持aes_cbc加密算法 2023-08-28 23:13:46 +08:00
lubeilin c3368481ad 1.修改默认任务数
2.修改指纹生成方式
2023-08-28 20:34:23 +08:00
lubeilin cdf5c3a508 parallel为1时不另起任务 2023-08-27 22:55:40 +08:00
lubeilin f9217625e1 增加参数说明 2023-08-27 21:40:03 +08:00
lubeilin 323f6c9221 1.增加缓冲池
2.线程数、任务数配置
2023-08-27 21:15:34 +08:00
lubeilin 2e507f1a9d 修复不加密不能打洞的问题 2023-08-27 17:51:42 +08:00
lubeilin e611d69a25 调整任务数 2023-08-27 17:05:24 +08:00
lubeilin 45d8c6717d 弃用SkipMap 2023-08-27 16:29:44 +08:00
lubeilin 756539c3bd 修改版本号 2023-08-27 15:11:47 +08:00
lubeilin 3d4fcdbc96 支持新版本 2023-08-27 15:11:08 +08:00
lubeilin 3cd19dbc1f 调整mtu 2023-08-27 14:09:09 +08:00
lubeilin 7b770a1bb0 完善tcp通道 2023-08-27 14:07:35 +08:00
lubeilin 0472b1590e 增加服务端加密、完善客户端加密 2023-08-27 11:58:32 +08:00
lbl8603 81764433d8 Merge pull request #12 from Droid-MAX/main
Fixed an output issue when displaying all devices
2023-08-24 20:18:17 +08:00
Droid-MAX 9ee6e101c4 Fixed an output issue when displaying all devices 2023-08-10 11:36:35 +08:00
lbl8603 565e80b994 Update README.md 2023-08-03 23:35:42 +08:00
lbl8603 7ccc4535e4 Update README.md 2023-08-03 21:27:11 +08:00
lubeilin d1bde297d6 支持stun协议 2023-08-03 21:05:15 +08:00
lubeilin c5398a51e8 调整路由超时时间 2023-07-31 23:20:29 +08:00
lubeilin fddb59992b 处理默认路由 2023-07-31 19:15:58 +08:00
lbl8603 665e25b1a3 Update README.md 2023-07-31 19:03:59 +08:00
lbl8603 fdb4bb1155 Update README.md 2023-07-31 19:02:05 +08:00
lubeilin f104e191ed 修复route next显示问题 2023-07-30 10:54:37 +08:00
lubeilin 6311d75ac0 去除-o的ip指定 2023-07-30 10:52:07 +08:00
lubeilin c3f134e332 -o 使用默认网卡 2023-07-29 23:25:39 +08:00
lubeilin 5bccdf3bbe 修改版本,修改帮助信息 2023-07-29 23:03:52 +08:00
lubeilin 9e881eeecd 增加-o默认ip 2023-07-29 21:31:34 +08:00
lubeilin fbe01d8cbf 修复--route命令显示异常的问题 2023-07-29 21:14:31 +08:00
lbl8603 d336d938a9 Update README.md 2023-07-29 20:31:39 +08:00
lbl8603 598923c95a Update README.md 2023-07-29 20:29:38 +08:00
lbl8603 0afe4c4417 Update README.md 2023-07-29 20:02:44 +08:00
lubeilin dff51caf35 增加条件编译 2023-07-29 18:19:53 +08:00
lubeilin 7c30f2691a 增加条件编译 2023-07-29 18:09:15 +08:00
lubeilin c6aca2c2dc 添加编译依赖 2023-07-28 00:28:44 +08:00
lubeilin 28ac8cf88e 优化加解密速度 2023-07-26 21:32:10 +08:00
lubeilin 62b2af54a2 展示客户端中继状态 2023-07-26 21:31:34 +08:00
lubeilin a2d45da44d 修复校验和计算异常 2023-07-26 21:31:07 +08:00
lubeilin d937f392d3 修复代理icmp异常 2023-07-26 21:30:19 +08:00
lbl8603 2c9abf314a Update README.md 2023-07-25 23:15:09 +08:00
lbl8603 ab7abd0c1f Update README.md 2023-07-25 21:56:52 +08:00
lubeilin 71a2e3c592 去除控制台输出 2023-07-24 23:52:27 +08:00
lubeilin aa6d3a6843 修复tcp模式下的p2p问题 2023-07-24 21:37:19 +08:00
lubeilin 9b42c5d092 支持自定义ip、服务端tcp通道、可选择禁止p2p 2023-07-24 00:42:27 +08:00
lbl8603 2f7817ce5b Update README.md 2023-07-19 21:24:15 +08:00
lbl8603 e7c6bcf9a9 Update README.md 2023-07-19 17:42:42 +08:00
lbl8603 b6b8971b12 Update README.md 2023-07-19 11:35:42 +08:00
lbl8603 ba4a0e008b Update README.md 2023-07-19 10:08:06 +08:00
lbl8603 4baccc5047 Update README.md 2023-07-19 09:52:53 +08:00
lubeilin 6cf71c8068 Merge remote-tracking branch 'origin/main' 2023-07-18 23:32:34 +08:00
lubeilin 8e556a20ee 打包排除源码 2023-07-18 23:32:10 +08:00
lbl8603 5f28a5044a Update README.md 2023-07-18 22:27:22 +08:00
lbl8603 16d295bf32 Update README.md 2023-07-18 20:42:09 +08:00
lbl8603 32163e077e Update README.md 2023-07-18 17:37:17 +08:00
lbl8603 6a5a40a7d3 Update README.md 2023-07-18 17:19:34 +08:00
lbl8603 161ecc865c Update README.md 2023-07-18 17:17:30 +08:00
lubeilin 1a5d40ff2d 修复mac端问题 2023-07-17 23:04:21 +08:00
lubeilin 6b988e0612 1.1.0 2023-07-17 22:56:37 +08:00
lubeilin 8d44934382 修改后台命令 2023-07-17 08:42:42 +08:00
lubeilin 6b140b0f71 增加桌面端 2023-07-17 01:32:34 +08:00
lubeilin c2b7b02f3f 调整jni模块、优化cmd模块展示 2023-07-17 01:31:05 +08:00
lubeilin 24140c2145 增加精简客户端模块,适用于服务端、路由器 2023-07-05 23:38:02 +08:00
lubeilin a6050e5f59 增加jni模块 2023-07-05 23:37:30 +08:00
lubeilin 50e97fd95f 增加安卓端支持、优化广播、增加停止监听 2023-07-05 23:36:41 +08:00
lubeilin 890e5f7391 默认使用广播代替组播、精简依赖 2023-06-29 23:14:59 +08:00
lubeilin be3bf82e35 支持客户端加密 2023-06-26 22:38:15 +08:00
lubeilin e8af503130 支持p2p组播/广播 2023-06-23 15:38:13 +08:00
lubeilin 466174ef88 增加国际化输出 2023-06-23 15:33:08 +08:00
lubeilin ba2c792e2a 使用tokio改写处理逻辑 2023-06-23 15:32:49 +08:00
lubeilin 17f3fcf9b0 优化tun\tap处理 2023-06-23 15:31:44 +08:00
lubeilin 11d1ff4b42 igmp解析 2023-06-23 15:30:07 +08:00
lubeilin b640bc50ef 合并tun、tap配置,减少重复代码 2023-06-23 15:29:03 +08:00
lubeilin 4bbd5282ee 优化tun设置 2023-06-23 15:28:11 +08:00
lubeilin d7fd504f8f 添加igmp、调整返回值 2023-06-23 15:25:00 +08:00
lubeilin cf4375b405 使用tokio改写网络通道 2023-06-23 15:17:46 +08:00
lbl8603 45da060c1a Update README.md 2023-06-03 20:48:29 +08:00
lbl8603 4dc84914dd Update README.md 2023-06-03 20:47:55 +08:00
lbl8603 c1ee7ac11c Update README.md 2023-06-03 09:38:46 +08:00
lubeilin 409b57c171 update 2023-06-03 09:37:51 +08:00
lubeilin ff4580b9bf 优化重连逻辑和路径探测 2023-06-02 18:28:33 +08:00
lubeilin 6daa75d2f2 优化网卡配置,增加metric设置 2023-06-02 18:28:02 +08:00
lubeilin 59f07f2d75 修复windows服务不能自启动的问题 2023-06-02 18:26:38 +08:00
lbl8603 44035685c8 Update README.md 2023-06-01 12:42:01 +08:00
lubeilin c8d0f3850f 修改ttl 2023-05-29 22:49:34 +08:00
lubeilin b846f54d79 update 2023-05-29 19:14:11 +08:00
lubeilin bb7b1d23af tun增加重试 2023-05-29 19:05:11 +08:00
lubeilin 9e700b3094 替换数据文件名称 2023-05-29 12:46:06 +08:00
lubeilin 38f28e313c windows服务 增加异常输出 2023-05-28 21:40:23 +08:00
lubeilin 63f03fb11f windows服务配置 2023-05-28 21:29:37 +08:00
lubeilin cf7854906f windows服务配置 2023-05-28 21:11:47 +08:00
lubeilin 78ae5f3036 读取配置文件 2023-05-28 20:33:59 +08:00
lubeilin c412a256b8 支持点对网 2023-05-28 20:28:58 +08:00
Droid-MAX 94def558fc Merge pull request #3 from lbl8603/main
update from upstream
2023-05-12 17:01:32 +08:00
lubeilin 25e9c523d3 修复已知问题 2023-05-12 16:54:44 +08:00
Droid-MAX a009d06000 Update rust.yml 2023-05-11 21:10:09 +08:00
lubeilin 675e0c52ff 调整代码顺序 2023-05-11 20:56:55 +08:00
Droid-MAX dc4219753a Update rust.yml 2023-05-11 20:53:01 +08:00
Droid-MAX c003b9acbb Update rust.yml 2023-05-10 12:25:36 +08:00
Droid-MAX f4869ebd1d Update rust.yml 2023-05-10 11:57:46 +08:00
Droid-MAX de4f42537d Update rust.yml 2023-05-10 11:47:52 +08:00
Droid-MAX b9ebe80ef3 Update rust.yml 2023-05-10 11:41:16 +08:00
lubeilin ea5ba750d4 sync 2023-05-09 22:47:59 +08:00
lubeilin b594afce16 readme 2023-05-07 20:28:35 +08:00
lubeilin ed3c44d6cf 保存参数到配置文件 2023-05-07 19:05:15 +08:00
lubeilin 35ed7f7e45 支持tap网卡,优化tun网卡配置 2023-05-07 18:32:11 +08:00
lubeilin 068580e036 Merge remote-tracking branch 'upstream/main' 2023-05-07 13:34:28 +08:00
Droid-MAX ea61b06e58 Update rust.yml 2023-04-26 11:27:02 +08:00
Droid-MAX f260c26e4f Update rust.yml 2023-04-26 11:11:03 +08:00
lubeilin 9cea433a8b 修复不加日志启动失败的问题 2023-04-25 23:17:31 +08:00
Droid-MAX 38dce9c13b Keep log file names in the same format 2023-04-25 12:51:03 +08:00
lubeilin 04731f1ce5 解决部分linux CPU飙升问题 2023-04-24 23:11:10 +08:00
lubeilin 3ddcb629c0 修改版本 2023-04-23 18:45:35 +08:00
lubeilin 12bd058152 增加日志 2023-04-23 18:44:01 +08:00
lubeilin c9b1bf5a5e 完善错误提示 2023-04-23 17:41:16 +08:00
lubeilin 18df3c2c92 解决win7不能启动的问题 2023-04-23 15:54:15 +08:00
Droid-MAX c08b9cefe9 Update rust.yml 2023-04-22 19:31:50 +08:00
Droid-MAX 258a35740f Merge pull request #1 from lbl8603/main
Sync update from upstream repo
2023-04-20 10:09:15 +08:00
lubeilin 32cfe9a3a8 commit 2023-04-20 09:46:06 +08:00
lubeilin 930a4fcf29 Merge branch 'dev' 2023-04-20 09:45:12 +08:00
Droid-MAX 5a778d5fc3 fix build issue 2023-04-20 09:17:07 +08:00
Droid-MAX 1d64cfc930 Update rust.yml 2023-04-20 00:43:42 +08:00
Droid-MAX 3eab02bc68 rename output filename and remove unfinished part of code 2023-04-20 00:28:30 +08:00
Droid-MAX dcdd03b746 fix submodules init issue 2023-04-20 00:12:08 +08:00
Droid-MAX 51acb2da9b fix github action sytanx issue 2023-04-19 23:44:17 +08:00
Droid-MAX 0d2c107e20 add submodule init and change output filename 2023-04-19 23:34:09 +08:00
Droid-MAX 2089ba7997 use github action for multi targets cross-compilation 2023-04-19 23:04:30 +08:00
lubeilin 8a032f86d8 修复udp广播丢失的问题 2023-04-08 21:44:31 +08:00
lubeilin 5dcda4d088 增加线程名称 2023-03-30 12:09:12 +08:00
lubeilin 8d76214193 传递本地ip、修复延迟问题 2023-03-20 21:13:38 +08:00
lubeilin 24ddf04d7f Merge branch 'dev'
# Conflicts:
#	README.md
#	switch-desktop/src/windows/service.rs
#	switch/src/handle/punch_handler.rs
2023-03-19 22:04:15 +08:00
lubeilin 79fa2a0823 去除多余配置 2023-03-19 22:03:10 +08:00
lbl8603 ddfb5ac943 Update README.md 2023-03-19 20:37:36 +08:00
lbl8603 ed622409b0 Update README.md 2023-03-19 20:22:38 +08:00
lbl8603 b6beaa09ec Update README.md 2023-03-19 20:20:26 +08:00
lubeilin 7840f30b64 优化代码 2023-03-19 20:11:51 +08:00
lubeilin d7226d8522 优化代码 2023-03-14 21:28:11 +08:00
lubeilin 8d5c9cedb3 readme 2023-03-13 22:58:27 +08:00
lubeilin 6f7992ea9f v1.1 2023-03-13 22:11:52 +08:00
lubeilin 5b2c2435d5 v1.1 2023-03-10 23:01:57 +08:00
lbl8603 ebf84db204 Update mod.rs 2023-02-14 09:51:04 +08:00
lubeilin 94e83d80a6 初始化模块 2023-02-13 22:49:01 +08:00
lbl8603 bc80d5f641 Update punch_handler.rs 2023-02-08 18:00:14 +08:00
lbl8603 81a5480250 Update service.rs 2023-02-08 16:39:30 +08:00
lbl8603 e69d416dc4 Update service.rs 2023-02-08 14:05:49 +08:00
lbl8603 15370203a1 修改概率计算结果 2023-02-08 11:52:17 +08:00
lbl8603 8ca09d72bd Update punch_handler.rs 2023-02-08 11:51:24 +08:00
lbl8603 d16cbe4ac6 Update README.md 2023-02-07 22:11:23 +08:00
lbl8603 49ac8827cd Update README.md 2023-02-07 22:06:38 +08:00
lubeilin f2f686d31f update README.md 2023-02-07 22:02:01 +08:00
lubeilin 6872ec3618 完善配置 2023-02-07 21:31:42 +08:00
lbl8603 efb7053931 未安装服务时在当前进程运行 2023-02-07 18:11:45 +08:00
lbl8603 80109fd8aa Update tun_handler.rs 2023-02-07 17:43:38 +08:00
lbl8603 b3abb175c1 调整打洞策略 2023-02-07 17:39:34 +08:00
lbl8603 f05577c927 Update linux.rs 2023-02-07 17:37:51 +08:00
lbl8603 c631f2bb0d Update tun_handler.rs 2023-02-07 17:37:03 +08:00
lbl8603 ad5aac7ef5 Update lib.rs 2023-02-07 15:42:15 +08:00
lubeilin 5cd9179ce7 解决掉线问题 2023-02-06 22:12:04 +08:00
lubeilin af58c3990d 1.更新对称NAT的打洞方式;2.支持windows服务;3.更新协议内容 2023-02-05 18:35:27 +08:00
lubeilin 206c543e8c 1.增加错误枚举 2023-01-15 19:19:04 +08:00
lubeilin 9820c56be6 1.增加设备名称和状态
2.测试windows服务
2023-01-15 19:15:41 +08:00
lubeilin 2232965a1d 格式化代码 2023-01-09 20:31:42 +08:00
lubeilin 6bfadfe5b8 格式化代码 2023-01-09 20:30:35 +08:00
lubeilin c655d9650b 格式化代码 2023-01-09 20:26:25 +08:00
lbl8603 61086089b3 Update README.md 2023-01-09 15:19:20 +08:00
lbl8603 c89d0124a1 Update README.md 2023-01-09 14:20:36 +08:00
lbl8603 a1a5839b20 Update tun_handler.rs 2023-01-09 14:01:15 +08:00
lbl8603 c9e3dc83ab Update tun_handler.rs 2023-01-09 11:23:08 +08:00
lbl8603 c179670b14 Update README.md 2023-01-08 17:31:15 +08:00
lubeilin 292893e9dd 调整项目结构、尝试支持安卓 2023-01-08 15:38:45 +08:00
lbl8603 d956e493af Update README.md 2023-01-06 12:37:16 +08:00
lubeilin 05c85db52a 修复已知问题 2023-01-05 21:40:42 +08:00
lbl8603 49350933c3 Update heartbeat_handler.rs 2023-01-05 11:44:38 +08:00
lbl8603 fa3fed3fbd Update README.md 2023-01-05 10:28:48 +08:00
lbl8603 5df9a357ec Update README.md 2023-01-05 10:08:20 +08:00
lbl8603 bce9c57e60 Update README.md 2023-01-05 10:07:57 +08:00
lbl8603 f39de52c65 Update README.md 2023-01-05 10:03:20 +08:00
lbl8603 4001527a87 Update README.md 2023-01-05 09:52:56 +08:00
lbl8603 85be89ddf0 Update unix.rs 2023-01-05 09:39:52 +08:00
174 changed files with 20356 additions and 8994 deletions
+22
View File
@@ -0,0 +1,22 @@
[build]
# target = ["x86_64-unknown-linux-musl"]
# target = ["x86_64-unknown-linux-gnu"]
# target = ["aarch64-linux-android"]
# target = ["x86_64-linux-android"]
# target = ["aarch64-apple-ios"]
# target = ["x86_64-apple-ios"]
# target = ["x86_64-pc-windows-msvc"]
# target = ["x86_64-apple-darwin"]
# target = ["x86_64-unknown-freebsd"]
[target.aarch64-unknown-linux-musl]
linker = "aarch64-linux-musl-gcc"
rustflags = ["-C", "target-feature=+crt-static", "-C", "strip=symbols"]
[target.mipsel-unknown-linux-musl]
linker = "mipsel-linux-muslsf-gcc"
rustflags = ["-C", "target-feature=+crt-static","-C", "strip=symbols","-C", "link-arg=-static",
"-C", "relocation-model=static","-C", "link-arg=-no-pie","--cfg", "compiler_builtins_no_debug",
"-L", "/opt/musl_gcc/mipsel-linux-muslsf-cross/mipsel-linux-muslsf/lib",
"-L", "/opt/musl_gcc/mipsel-linux-muslsf-cross/lib/gcc/mipsel-linux-muslsf/11.2.1"]
+282
View File
@@ -0,0 +1,282 @@
name: Rust
on:
push:
tags:
- '*'
env:
CARGO_TERM_COLOR: always
defaults:
run:
# necessary for windows
shell: bash
permissions:
contents: write
packages: write
jobs:
build:
name: Build ${{ matrix.TARGET }}
strategy:
fail-fast: false
matrix:
include:
- TARGET: x86_64-unknown-linux-musl
OS: ubuntu-latest
- TARGET: aarch64-unknown-linux-musl
OS: ubuntu-latest
- TARGET: armv7-unknown-linux-musleabihf
OS: ubuntu-latest
- TARGET: armv7-unknown-linux-musleabi
OS: ubuntu-latest
- TARGET: arm-unknown-linux-musleabihf
OS: ubuntu-latest
- TARGET: arm-unknown-linux-musleabi
OS: ubuntu-latest
- TARGET: x86_64-apple-darwin
OS: macos-latest
- TARGET: aarch64-apple-darwin
OS: macos-latest
- TARGET: i686-pc-windows-msvc
OS: windows-latest
- TARGET: x86_64-pc-windows-msvc
OS: windows-latest
- TARGET: aarch64-pc-windows-msvc
OS: windows-latest
- TARGET: mipsel-unknown-linux-musl
OS: ubuntu-latest
- TARGET: mips-unknown-linux-musl
OS: ubuntu-latest
- TARGET: x86_64-unknown-freebsd
OS: ubuntu-latest
ARTIFACT_NAME: freebsd-13.2-x86_64
BSD_VERSION: 13.2
runs-on: ${{ matrix.OS }}
env:
NAME: Vnt
TARGET: ${{ matrix.TARGET }}
OS: ${{ matrix.OS }}
FEATURES: ${{ matrix.FEATURES }}
steps:
- uses: actions/checkout@v4
- name: Init submodules
run: git submodule update --init --recursive --remote && git submodule status
- name: Cargo cache
uses: actions/cache@v4
with:
path: |
~/.cargo/registry
./target
key: build-cargo-registry-${{matrix.TARGET}}
- name: Install Protobuf (MacOS)
if: runner.os == 'macOS'
run: brew install protobuf
- name: Install Protobuf (Windows)
if: runner.os == 'Windows'
run: choco install protoc
- name: Use strawberry perl
if: startsWith(matrix.os, 'windows')
run: echo OPENSSL_SRC_PERL=C:/Strawberry/perl/bin/perl >> $GITHUB_ENV
- name: List
run: find ./
- name: Build Vnt X86_64-FreeBSD
uses: cross-platform-actions/[email protected]
if: ${{ endsWith(matrix.TARGET, 'freebsd') }}
env:
TARGET: ${{ matrix.TARGET }}
with:
operating_system: freebsd
environment_variables: TARGET
architecture: x86-64
version: ${{ matrix.BSD_VERSION }}
shell: bash
memory: 5G
cpu_count: 4
run: |
uname -a
echo $SHELL
pwd
ls -lah
whoami
env | sort
sudo pkg install -y git protobuf llvm15
curl --proto 'https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
source $HOME/.cargo/env
export CC=clang
export CXX=clang++
export CARGO_TERM_COLOR=always
cargo build --release --verbose --bin vnt2_web --features vnt-web --target $TARGET
cargo build --release --verbose --bin vnt2_cli --features vnt-ipc --target $TARGET
cargo build --release --verbose --bin vnt2_ctrl --features vnt-ipc --target $TARGET
- name: Install and configure dependencies
if: ${{ ! endsWith(matrix.TARGET, 'freebsd') }}
run: |
# dependencies are only needed on ubuntu as that's the only place where
# we make cross-compilation
if [[ $OS =~ ^ubuntu.*$ ]]; then
sudo apt-get update && sudo apt-get install protobuf-compiler clang llvm libc6-dev-i386 crossbuild-essential-arm64 crossbuild-essential-armhf musl-tools libboost-all-dev libc6-dev -y
# curl -s musl.cc | grep mipsel
case $TARGET in
mipsel-unknown-linux-musl)
MUSL_URI=mipsel-linux-muslsf-cross
URL=mipsel-linux-muslsf
;;
aarch64-unknown-linux-musl)
MUSL_URI=aarch64-linux-musl-cross
;;
armv7-unknown-linux-musleabihf)
MUSL_URI=armv7l-linux-musleabihf-cross
;;
armv7-unknown-linux-musleabi)
MUSL_URI=armv7m-linux-musleabi-cross
;;
arm-unknown-linux-musleabihf)
MUSL_URI=arm-linux-musleabihf-cross
;;
arm-unknown-linux-musleabi)
MUSL_URI=arm-linux-musleabi-cross
;;
mips-unknown-linux-musl)
MUSL_URI=mips-linux-muslsf-cross
URL=mips-linux-muslsf
;;
esac
if [ -n "$MUSL_URI" ]; then
mkdir -p ./musl_gcc
wget -c https://github.com/rustp2p/musl-cross/releases/download/0.0.1/$MUSL_URI.tgz -P /opt/musl_gcc/
tar zxf /opt/musl_gcc/$MUSL_URI.tgz -C /opt/musl_gcc/
sudo ln -s /opt/musl_gcc/$MUSL_URI/bin/*gcc /usr/bin/
fi
fi
if [[ $TARGET =~ ^mips.*$ ]]; then
cd /opt/musl_gcc/${URL}-cross/lib/gcc/${URL}/11.2.1
cp libgcc_eh.a libunwind.a
rustup toolchain install nightly-x86_64-unknown-linux-gnu
rustup component add rust-src --toolchain nightly-x86_64-unknown-linux-gnu
RUST_LIB_SRC=$HOME/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/
if [[ -f $RUST_LIB_SRC/library/Cargo.lock && ! -f $RUST_LIB_SRC/Cargo.lock ]]; then
cp -f $RUST_LIB_SRC/library/Cargo.lock $RUST_LIB_SRC/Cargo.lock
fi
fi
rustup -V
# some additional configuration for cross-compilation on linux
cat >>~/.cargo/config.toml <<EOF
[target.x86_64-unknown-linux-musl]
rustflags = ["-C", "target-feature=+crt-static","-C", "strip=symbols"]
[target.aarch64-unknown-linux-musl]
linker = "aarch64-linux-musl-gcc"
rustflags = ["-C", "target-feature=+crt-static","-C", "strip=symbols"]
[target.armv7-unknown-linux-musleabihf]
linker = "armv7l-linux-musleabihf-gcc"
rustflags = ["-C", "target-feature=+crt-static","-C", "strip=symbols"]
[target.armv7-unknown-linux-musleabi]
linker = "armv7m-linux-musleabi-gcc"
rustflags = ["-C", "target-feature=+crt-static","-C", "strip=symbols"]
[target.arm-unknown-linux-musleabihf]
linker = "arm-linux-musleabihf-gcc"
rustflags = ["-C", "target-feature=+crt-static","-C", "strip=symbols"]
[target.arm-unknown-linux-musleabi]
linker = "arm-linux-musleabi-gcc"
rustflags = ["-C", "target-feature=+crt-static","-C", "strip=symbols"]
[target.mipsel-unknown-linux-musl]
linker = "mipsel-linux-muslsf-gcc"
rustflags = ["-C", "target-feature=+crt-static","-C", "strip=symbols","-C", "link-arg=-static",
"-C", "relocation-model=static","-C", "link-arg=-no-pie","--cfg", "compiler_builtins_no_debug",
"-L", "/opt/musl_gcc/mipsel-linux-muslsf-cross/mipsel-linux-muslsf/lib",
"-L", "/opt/musl_gcc/mipsel-linux-muslsf-cross/lib/gcc/mipsel-linux-muslsf/11.2.1"]
[target.mips-unknown-linux-musl]
linker = "mips-linux-muslsf-gcc"
rustflags = ["-C", "target-feature=+crt-static","-C", "strip=symbols","-C", "link-arg=-static",
"-C", "relocation-model=static","-C", "link-arg=-no-pie","--cfg", "compiler_builtins_no_debug",
"-L", "/opt/musl_gcc/mips-linux-muslsf-cross/mips-linux-muslsf/lib",
"-L", "/opt/musl_gcc/mips-linux-muslsf-cross/lib/gcc/mips-linux-muslsf/11.2.1"]
[target.x86_64-pc-windows-msvc]
rustflags = ["-C", "target-feature=+crt-static","-C", "strip=symbols"]
[target.i686-pc-windows-msvc]
rustflags = ["-C", "target-feature=+crt-static","-C", "strip=symbols"]
[target.x86_64-apple-darwin]
rustflags = ["-C", "target-feature=+crt-static","-C", "strip=symbols"]
[target.aarch64-apple-darwin]
rustflags = ["-C", "target-feature=+crt-static","-C", "strip=symbols"]
[target.i686-unknown-linux-musl]
rustflags = ["-C", "target-feature=+crt-static","-C", "strip=symbols"]
[target.i686-unknown-linux-gnu]
rustflags = ["-C", "target-feature=+crt-static","-C", "strip=symbols"]
EOF
- name: Install rust target
if: ${{ ! endsWith(matrix.TARGET, 'freebsd') && ! startsWith(matrix.TARGET, 'mips') }}
run: rustup target add $TARGET
- name: Run build Vnt
if: ${{ ! endsWith(matrix.TARGET, 'freebsd') }}
run: |
if [[ $TARGET =~ ^mips.*$ ]]; then
cargo +nightly build --release --verbose --target $TARGET -Z build-std=std,panic_abort --bin vnt2_web --features vnt-web
cargo +nightly build --release --verbose --target $TARGET -Z build-std=std,panic_abort --bin vnt2_cli --features vnt-ipc
cargo +nightly build --release --verbose --target $TARGET -Z build-std=std,panic_abort --bin vnt2_ctrl --features vnt-ipc
else
cargo build --release --verbose --target $TARGET --bin vnt2_web --features vnt-web
cargo build --release --verbose --target $TARGET --bin vnt2_cli --features vnt-ipc
cargo build --release --verbose --target $TARGET --bin vnt2_ctrl --features vnt-ipc
fi
ldd ./target/$TARGET/release/vnt2_web || true
file ./target/$TARGET/release/vnt2_web || true
- name: List target
run: find ./target
- name: Package
run: |
mkdir -p ./artifacts
mkdir -p ./package
if [[ $GITHUB_REF_TYPE =~ ^tag$ ]]; then
TAG=$GITHUB_REF_NAME
else
TAG=$GITHUB_SHA
fi
ZIP_NAME="vnt2-${TARGET}-${TAG}.zip"
if [[ $OS =~ ^windows.*$ ]]; then
cp ./target/$TARGET/release/vnt2_web.exe ./package/
cp ./target/$TARGET/release/vnt2_cli.exe ./package/
cp ./target/$TARGET/release/vnt2_ctrl.exe ./package/
cd ./package
7z a -tzip "../artifacts/$ZIP_NAME" ./*
else
cp ./target/$TARGET/release/vnt2_web ./package/
cp ./target/$TARGET/release/vnt2_cli ./package/
cp ./target/$TARGET/release/vnt2_ctrl ./package/
cd ./package
zip "../artifacts/$ZIP_NAME" ./*
fi
- name: List target Vnt
run: find ./artifacts
- name: Archive artifact
uses: actions/upload-artifact@v4
with:
name: Vnt-${{ matrix.TARGET }}
path: |
./artifacts/*
# deploys to github releases on tag
deploy:
if: startsWith(github.ref, 'refs/tags/')
needs: build
runs-on: ubuntu-latest
steps:
- name: Download artifacts
uses: actions/download-artifact@v4
with:
path: ./artifacts
- name: List
run: find ./artifacts
- name: Release
uses: svenstaro/upload-release-action@v2
with:
repo_token: ${{ secrets.GITHUB_TOKEN }}
file: ./artifacts/*/*.zip
tag: ${{ github.ref }}
overwrite: true
file_glob: true
+5
View File
@@ -0,0 +1,5 @@
/target
./wintun.dll
vnt_config
vnt_current_config.txt
logs/*
Generated
+3844
View File
@@ -0,0 +1,3844 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "ahash"
version = "0.7.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9"
dependencies = [
"getrandom 0.2.17",
"once_cell",
"version_check",
]
[[package]]
name = "aho-corasick"
version = "1.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301"
dependencies = [
"memchr",
]
[[package]]
name = "android_system_properties"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
dependencies = [
"libc",
]
[[package]]
name = "anstream"
version = "0.6.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a"
dependencies = [
"anstyle",
"anstyle-parse",
"anstyle-query",
"anstyle-wincon",
"colorchoice",
"is_terminal_polyfill",
"utf8parse",
]
[[package]]
name = "anstyle"
version = "1.0.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78"
[[package]]
name = "anstyle-parse"
version = "0.2.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2"
dependencies = [
"utf8parse",
]
[[package]]
name = "anstyle-query"
version = "1.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc"
dependencies = [
"windows-sys 0.61.2",
]
[[package]]
name = "anstyle-wincon"
version = "3.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d"
dependencies = [
"anstyle",
"once_cell_polyfill",
"windows-sys 0.61.2",
]
[[package]]
name = "anyhow"
version = "1.0.101"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f0e0fee31ef5ed1ba1316088939cea399010ed7731dba877ed44aeb407a75ea"
[[package]]
name = "arc-swap"
version = "1.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ded5f9a03ac8f24d1b8a25101ee812cd32cdc8c50a4c50237de2c4915850e73"
dependencies = [
"rustversion",
]
[[package]]
name = "asn1-rs"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56624a96882bb8c26d61312ae18cb45868e5a9992ea73c58e45c3101e56a1e60"
dependencies = [
"asn1-rs-derive",
"asn1-rs-impl",
"displaydoc",
"nom",
"num-traits",
"rusticata-macros",
"thiserror 2.0.18",
"time",
]
[[package]]
name = "asn1-rs-derive"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3109e49b1e4909e9db6515a30c633684d68cdeaa252f215214cb4fa1a5bfee2c"
dependencies = [
"proc-macro2",
"quote",
"syn",
"synstructure",
]
[[package]]
name = "asn1-rs-impl"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "async-broadcast"
version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "435a87a52755b8f27fcf321ac4f04b2802e337c8c4872923137471ec39c37532"
dependencies = [
"event-listener",
"event-listener-strategy",
"futures-core",
"pin-project-lite",
]
[[package]]
name = "async-channel"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "924ed96dd52d1b75e9c1a3e6275715fd320f5f9439fb5a4a11fa51f4221158d2"
dependencies = [
"concurrent-queue",
"event-listener-strategy",
"futures-core",
"pin-project-lite",
]
[[package]]
name = "async-event"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c1222afd3d2bce3995035054046a279ae7aa154d70d0766cea050073f3fd7ddf"
dependencies = [
"loom",
"pin-project-lite",
]
[[package]]
name = "async-lock"
version = "3.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "290f7f2596bd5b78a9fec8088ccd89180d7f9f55b94b0576823bbbdc72ee8311"
dependencies = [
"event-listener",
"event-listener-strategy",
"pin-project-lite",
]
[[package]]
name = "async-scoped"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4042078ea593edffc452eef14e99fdb2b120caa4ad9618bcdeabc4a023b98740"
dependencies = [
"futures",
"pin-project",
"tokio",
]
[[package]]
name = "async-task"
version = "4.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de"
[[package]]
name = "async-trait"
version = "0.1.89"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "atomic-waker"
version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"
[[package]]
name = "autocfg"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
[[package]]
name = "axum"
version = "0.8.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b52af3cb4058c895d37317bb27508dccc8e5f2d39454016b297bf4a400597b8"
dependencies = [
"axum-core",
"bytes",
"form_urlencoded",
"futures-util",
"http",
"http-body",
"http-body-util",
"hyper",
"hyper-util",
"itoa",
"matchit",
"memchr",
"mime",
"percent-encoding",
"pin-project-lite",
"serde_core",
"serde_json",
"serde_path_to_error",
"serde_urlencoded",
"sync_wrapper",
"tokio",
"tower",
"tower-layer",
"tower-service",
"tracing",
]
[[package]]
name = "axum-core"
version = "0.5.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08c78f31d7b1291f7ee735c1c6780ccde7785daae9a9206026862dab7d8792d1"
dependencies = [
"bytes",
"futures-core",
"http",
"http-body",
"http-body-util",
"mime",
"pin-project-lite",
"sync_wrapper",
"tower-layer",
"tower-service",
"tracing",
]
[[package]]
name = "base64"
version = "0.22.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bitflags"
version = "2.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3"
[[package]]
name = "block-buffer"
version = "0.10.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
dependencies = [
"generic-array",
]
[[package]]
name = "blocking"
version = "1.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e83f8d02be6967315521be875afa792a316e28d57b5a2d401897e2a7921b7f21"
dependencies = [
"async-channel",
"async-task",
"futures-io",
"futures-lite",
"piper",
]
[[package]]
name = "bumpalo"
version = "3.19.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510"
[[package]]
name = "byteorder"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
[[package]]
name = "bytes"
version = "1.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33"
[[package]]
name = "c2rust-bitfields"
version = "0.21.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dcee50917f9de1a018e3f4f9a8f2ff3d030a288cffa4b18d9b391e97c12e4cfb"
dependencies = [
"c2rust-bitfields-derive",
]
[[package]]
name = "c2rust-bitfields-derive"
version = "0.21.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b457277798202ccd365b9c112ebee08ddd57f1033916c8b8ea52f222e5b715d"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "cc"
version = "1.2.55"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "47b26a0954ae34af09b50f0de26458fa95369a0d478d8236d3f93082b219bd29"
dependencies = [
"find-msvc-tools",
"shlex",
]
[[package]]
name = "cfg-if"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
[[package]]
name = "cfg_aliases"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
[[package]]
name = "chrono"
version = "0.4.43"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fac4744fb15ae8337dc853fee7fb3f4e48c0fbaa23d0afe49c447b4fab126118"
dependencies = [
"iana-time-zone",
"num-traits",
"windows-link 0.2.1",
]
[[package]]
name = "clap"
version = "4.5.57"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6899ea499e3fb9305a65d5ebf6e3d2248c5fab291f300ad0a704fbe142eae31a"
dependencies = [
"clap_builder",
"clap_derive",
]
[[package]]
name = "clap_builder"
version = "4.5.57"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b12c8b680195a62a8364d16b8447b01b6c2c8f9aaf68bee653be34d4245e238"
dependencies = [
"anstream",
"anstyle",
"clap_lex",
"strsim",
]
[[package]]
name = "clap_derive"
version = "4.5.55"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a92793da1a46a5f2a02a6f4c46c6496b28c43638adea8306fcb0caa1634f24e5"
dependencies = [
"heck",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "clap_lex"
version = "0.7.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3e64b0cc0439b12df2fa678eae89a1c56a529fd067a9115f7827f1fffd22b32"
[[package]]
name = "cli-table"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "14da8d951cef7cc4f13ccc9b744d736963d57863c7e6fc33c070ea274546082c"
dependencies = [
"cli-table-derive",
"csv",
"termcolor",
"unicode-width",
]
[[package]]
name = "cli-table-derive"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f7c1b60bae2c3d45228dfb096046aa51ef6c300de70b658d7a13fcb0c4f832e"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "colorchoice"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75"
[[package]]
name = "concurrent-queue"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973"
dependencies = [
"crossbeam-utils",
]
[[package]]
name = "console"
version = "0.16.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "03e45a4a8926227e4197636ba97a9fc9b00477e9f4bd711395687c5f0734bec4"
dependencies = [
"encode_unicode",
"libc",
"once_cell",
"unicode-width",
"windows-sys 0.61.2",
]
[[package]]
name = "core-foundation"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f"
dependencies = [
"core-foundation-sys",
"libc",
]
[[package]]
name = "core-foundation"
version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6"
dependencies = [
"core-foundation-sys",
"libc",
]
[[package]]
name = "core-foundation-sys"
version = "0.8.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
[[package]]
name = "cpufeatures"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280"
dependencies = [
"libc",
]
[[package]]
name = "crossbeam-queue"
version = "0.3.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115"
dependencies = [
"crossbeam-utils",
]
[[package]]
name = "crossbeam-utils"
version = "0.8.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
[[package]]
name = "crypto-common"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a"
dependencies = [
"generic-array",
"typenum",
]
[[package]]
name = "csv"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "52cd9d68cf7efc6ddfaaee42e7288d3a99d613d4b50f76ce9827ae0c6e14f938"
dependencies = [
"csv-core",
"itoa",
"ryu",
"serde_core",
]
[[package]]
name = "csv-core"
version = "0.1.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "704a3c26996a80471189265814dbc2c257598b96b8a7feae2d31ace646bb9782"
dependencies = [
"memchr",
]
[[package]]
name = "dashmap"
version = "6.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf"
dependencies = [
"cfg-if",
"crossbeam-utils",
"hashbrown 0.14.5",
"lock_api",
"once_cell",
"parking_lot_core 0.9.12",
]
[[package]]
name = "data-encoding"
version = "2.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d7a1e2f27636f116493b8b860f5546edb47c8d8f8ea73e1d2a20be88e28d1fea"
[[package]]
name = "der-parser"
version = "10.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07da5016415d5a3c4dd39b11ed26f915f52fc4e0dc197d87908bc916e51bc1a6"
dependencies = [
"asn1-rs",
"displaydoc",
"nom",
"num-bigint",
"num-traits",
"rusticata-macros",
]
[[package]]
name = "deranged"
version = "0.5.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ececcb659e7ba858fb4f10388c250a7252eb0a27373f1a72b8748afdd248e587"
dependencies = [
"powerfmt",
]
[[package]]
name = "derive_more"
version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134"
dependencies = [
"derive_more-impl",
]
[[package]]
name = "derive_more-impl"
version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb"
dependencies = [
"proc-macro2",
"quote",
"rustc_version",
"syn",
"unicode-xid",
]
[[package]]
name = "destructure_traitobject"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c877555693c14d2f84191cfd3ad8582790fc52b5e2274b40b59cf5f5cea25c7"
[[package]]
name = "diatomic-waker"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28025fb55a9d815acf7b0877555f437254f373036eec6ed265116c7a5c0825e9"
dependencies = [
"loom",
"waker-fn",
]
[[package]]
name = "digest"
version = "0.10.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
dependencies = [
"block-buffer",
"crypto-common",
]
[[package]]
name = "displaydoc"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "dns-parser"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4d33be9473d06f75f58220f71f7a9317aca647dc061dbd3c361b0bef505fbea"
dependencies = [
"byteorder",
"quick-error",
]
[[package]]
name = "dyn-clone"
version = "1.0.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555"
[[package]]
name = "either"
version = "1.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
[[package]]
name = "encode_unicode"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0"
[[package]]
name = "encoding_rs"
version = "0.8.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3"
dependencies = [
"cfg-if",
]
[[package]]
name = "equivalent"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
[[package]]
name = "errno"
version = "0.3.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
dependencies = [
"libc",
"windows-sys 0.61.2",
]
[[package]]
name = "event-listener"
version = "5.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab"
dependencies = [
"concurrent-queue",
"parking",
"pin-project-lite",
]
[[package]]
name = "event-listener-strategy"
version = "0.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93"
dependencies = [
"event-listener",
"pin-project-lite",
]
[[package]]
name = "fastrand"
version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
dependencies = [
"getrandom 0.2.17",
]
[[package]]
name = "find-msvc-tools"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
[[package]]
name = "fixedbitset"
version = "0.5.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99"
[[package]]
name = "flume"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da0e4dd2a88388a1f4ccc7c9ce104604dab68d9f408dc34cd45823d5a9069095"
dependencies = [
"futures-core",
"futures-sink",
"nanorand",
"spin",
]
[[package]]
name = "flume"
version = "0.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e139bc46ca777eb5efaf62df0ab8cc5fd400866427e56c68b22e414e53bd3be"
dependencies = [
"fastrand",
"futures-core",
"futures-sink",
"spin",
]
[[package]]
name = "fnv"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
[[package]]
name = "foldhash"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
[[package]]
name = "form_urlencoded"
version = "1.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf"
dependencies = [
"percent-encoding",
]
[[package]]
name = "futures"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876"
dependencies = [
"futures-channel",
"futures-core",
"futures-executor",
"futures-io",
"futures-sink",
"futures-task",
"futures-util",
]
[[package]]
name = "futures-channel"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10"
dependencies = [
"futures-core",
"futures-sink",
]
[[package]]
name = "futures-core"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e"
[[package]]
name = "futures-executor"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f"
dependencies = [
"futures-core",
"futures-task",
"futures-util",
]
[[package]]
name = "futures-io"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6"
[[package]]
name = "futures-lite"
version = "2.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad"
dependencies = [
"futures-core",
"pin-project-lite",
]
[[package]]
name = "futures-macro"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "futures-sink"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7"
[[package]]
name = "futures-task"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988"
[[package]]
name = "futures-util"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81"
dependencies = [
"futures-channel",
"futures-core",
"futures-io",
"futures-macro",
"futures-sink",
"futures-task",
"memchr",
"pin-project-lite",
"pin-utils",
"slab",
]
[[package]]
name = "generator"
version = "0.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5cc16584ff22b460a382b7feec54b23d2908d858152e5739a120b949293bd74e"
dependencies = [
"cc",
"libc",
"log",
"rustversion",
"windows 0.48.0",
]
[[package]]
name = "generic-array"
version = "0.14.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
dependencies = [
"typenum",
"version_check",
]
[[package]]
name = "getifaddrs"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06f770241660efeeedb30b48ccbe0a8e6f649322148237539e71f3ccd14a4461"
dependencies = [
"bitflags 2.10.0",
"libc",
"windows-sys 0.59.0",
]
[[package]]
name = "getrandom"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0"
dependencies = [
"cfg-if",
"js-sys",
"libc",
"wasi",
"wasm-bindgen",
]
[[package]]
name = "getrandom"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd"
dependencies = [
"cfg-if",
"js-sys",
"libc",
"r-efi",
"wasip2",
"wasm-bindgen",
]
[[package]]
name = "glob"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280"
[[package]]
name = "hashbrown"
version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
dependencies = [
"ahash",
]
[[package]]
name = "hashbrown"
version = "0.14.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
[[package]]
name = "hashbrown"
version = "0.15.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
dependencies = [
"foldhash",
]
[[package]]
name = "hashbrown"
version = "0.16.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
[[package]]
name = "heck"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
[[package]]
name = "hex"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
[[package]]
name = "hostname"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "617aaa3557aef3810a6369d0a99fac8a080891b68bd9f9812a1eeda0c0730cbd"
dependencies = [
"cfg-if",
"libc",
"windows-link 0.2.1",
]
[[package]]
name = "http"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a"
dependencies = [
"bytes",
"itoa",
]
[[package]]
name = "http-body"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184"
dependencies = [
"bytes",
"http",
]
[[package]]
name = "http-body-util"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a"
dependencies = [
"bytes",
"futures-core",
"http",
"http-body",
"pin-project-lite",
]
[[package]]
name = "http-range-header"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9171a2ea8a68358193d15dd5d70c1c10a2afc3e7e4c5bc92bc9f025cebd7359c"
[[package]]
name = "httparse"
version = "1.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87"
[[package]]
name = "httpdate"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
[[package]]
name = "humantime"
version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "135b12329e5e3ce057a9f972339ea52bc954fe1e9358ef27f95e89716fbc5424"
[[package]]
name = "hyper"
version = "1.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2ab2d4f250c3d7b1c9fcdff1cece94ea4e2dfbec68614f7b87cb205f24ca9d11"
dependencies = [
"atomic-waker",
"bytes",
"futures-channel",
"futures-core",
"http",
"http-body",
"httparse",
"httpdate",
"itoa",
"pin-project-lite",
"pin-utils",
"smallvec",
"tokio",
]
[[package]]
name = "hyper-util"
version = "0.1.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0"
dependencies = [
"bytes",
"http",
"http-body",
"hyper",
"pin-project-lite",
"tokio",
"tower-service",
]
[[package]]
name = "iana-time-zone"
version = "0.1.65"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470"
dependencies = [
"android_system_properties",
"core-foundation-sys",
"iana-time-zone-haiku",
"js-sys",
"log",
"wasm-bindgen",
"windows-core 0.62.2",
]
[[package]]
name = "iana-time-zone-haiku"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
dependencies = [
"cc",
]
[[package]]
name = "indexmap"
version = "2.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017"
dependencies = [
"equivalent",
"hashbrown 0.16.1",
]
[[package]]
name = "instant"
version = "0.1.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222"
dependencies = [
"cfg-if",
]
[[package]]
name = "ipnet"
version = "2.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130"
dependencies = [
"serde",
]
[[package]]
name = "is_terminal_polyfill"
version = "1.70.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695"
[[package]]
name = "itertools"
version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285"
dependencies = [
"either",
]
[[package]]
name = "itoa"
version = "1.0.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2"
[[package]]
name = "js-sys"
version = "0.3.85"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8c942ebf8e95485ca0d52d97da7c5a2c387d0e7f0ba4c35e93bfcaee045955b3"
dependencies = [
"once_cell",
"wasm-bindgen",
]
[[package]]
name = "lazy_static"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
[[package]]
name = "libc"
version = "0.2.180"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc"
[[package]]
name = "libloading"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "754ca22de805bb5744484a5b151a9e1a8e837d5dc232c2d7d8c2e3492edc8b60"
dependencies = [
"cfg-if",
"windows-link 0.2.1",
]
[[package]]
name = "libm"
version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981"
[[package]]
name = "linux-raw-sys"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039"
[[package]]
name = "lock_api"
version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965"
dependencies = [
"scopeguard",
]
[[package]]
name = "log"
version = "0.4.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
dependencies = [
"serde_core",
]
[[package]]
name = "log-mdc"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a94d21414c1f4a51209ad204c1776a3d0765002c76c6abcb602a6f09f1e881c7"
[[package]]
name = "log4rs"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3e947bb896e702c711fccc2bf02ab2abb6072910693818d1d6b07ee2b9dfd86c"
dependencies = [
"anyhow",
"arc-swap",
"chrono",
"derive_more",
"fnv",
"humantime",
"libc",
"log",
"log-mdc",
"mock_instant",
"parking_lot 0.12.5",
"rand",
"serde",
"serde-value",
"serde_json",
"serde_yaml",
"thiserror 2.0.18",
"thread-id",
"typemap-ors",
"unicode-segmentation",
"winapi",
]
[[package]]
name = "loom"
version = "0.5.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ff50ecb28bb86013e935fb6683ab1f6d3a20016f123c76fd4c27470076ac30f5"
dependencies = [
"cfg-if",
"generator",
"scoped-tls",
"tracing",
"tracing-subscriber",
]
[[package]]
name = "lru"
version = "0.7.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e999beba7b6e8345721bd280141ed958096a2e4abdf74f67ff4ce49b4b54e47a"
dependencies = [
"hashbrown 0.12.3",
]
[[package]]
name = "lru-slab"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154"
[[package]]
name = "lz4_flex"
version = "0.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab6473172471198271ff72e9379150e9dfd70d8e533e0752a27e515b48dd375e"
dependencies = [
"twox-hash",
]
[[package]]
name = "machine-uid"
version = "0.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d7217d573cdb141d6da43113b098172e057d39915d79c4bdedbc3aacd46bd96"
dependencies = [
"libc",
"windows-registry",
"windows-sys 0.61.2",
]
[[package]]
name = "matchers"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9"
dependencies = [
"regex-automata",
]
[[package]]
name = "matchit"
version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3"
[[package]]
name = "memchr"
version = "2.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273"
[[package]]
name = "memoffset"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a"
dependencies = [
"autocfg",
]
[[package]]
name = "mime"
version = "0.3.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
[[package]]
name = "mime_guess"
version = "2.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e"
dependencies = [
"mime",
"unicase",
]
[[package]]
name = "minimal-lexical"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
[[package]]
name = "mio"
version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc"
dependencies = [
"libc",
"wasi",
"windows-sys 0.61.2",
]
[[package]]
name = "mock_instant"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dce6dd36094cac388f119d2e9dc82dc730ef91c32a6222170d630e5414b956e6"
[[package]]
name = "multimap"
version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d87ecb2933e8aeadb3e3a02b828fed80a7528047e68b4f424523a0981a3a084"
[[package]]
name = "nanorand"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a51313c5820b0b02bd422f4b44776fbf47961755c74ce64afc73bfad10226c3"
dependencies = [
"getrandom 0.2.17",
]
[[package]]
name = "netconfig-rs"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "207561c8758738388c2fd851c3e759f503583d8dd70ddc895baae806753ad4c9"
dependencies = [
"cfg-if",
"core-foundation 0.9.4",
"ipnet",
"libc",
"netlink-packet-core 0.7.0",
"netlink-packet-route 0.24.0",
"netlink-sys",
"nix 0.30.1",
"scopeguard",
"system-configuration-sys",
"thiserror 2.0.18",
"widestring",
"windows 0.61.3",
]
[[package]]
name = "netlink-packet-core"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72724faf704479d67b388da142b186f916188505e7e0b26719019c525882eda4"
dependencies = [
"anyhow",
"byteorder",
"netlink-packet-utils",
]
[[package]]
name = "netlink-packet-core"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3463cbb78394cb0141e2c926b93fc2197e473394b761986eca3b9da2c63ae0f4"
dependencies = [
"paste",
]
[[package]]
name = "netlink-packet-route"
version = "0.24.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56d83370a96813d7c977f8b63054f1162df6e5784f1c598d689236564fb5a6f2"
dependencies = [
"anyhow",
"bitflags 2.10.0",
"byteorder",
"libc",
"log",
"netlink-packet-core 0.7.0",
"netlink-packet-utils",
]
[[package]]
name = "netlink-packet-route"
version = "0.28.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ce3636fa715e988114552619582b530481fd5ef176a1e5c1bf024077c2c9445"
dependencies = [
"bitflags 2.10.0",
"libc",
"log",
"netlink-packet-core 0.8.1",
]
[[package]]
name = "netlink-packet-utils"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ede8a08c71ad5a95cdd0e4e52facd37190977039a4704eb82a283f713747d34"
dependencies = [
"anyhow",
"byteorder",
"paste",
"thiserror 1.0.69",
]
[[package]]
name = "netlink-sys"
version = "0.8.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cd6c30ed10fa69cc491d491b85cc971f6bdeb8e7367b7cde2ee6cc878d583fae"
dependencies = [
"bytes",
"libc",
"log",
]
[[package]]
name = "nix"
version = "0.30.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6"
dependencies = [
"bitflags 2.10.0",
"cfg-if",
"cfg_aliases",
"libc",
"memoffset",
]
[[package]]
name = "nix"
version = "0.31.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "225e7cfe711e0ba79a68baeddb2982723e4235247aefce1482f2f16c27865b66"
dependencies = [
"bitflags 2.10.0",
"cfg-if",
"cfg_aliases",
"libc",
"memoffset",
]
[[package]]
name = "no-std-net"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43794a0ace135be66a25d3ae77d41b91615fb68ae937f904090203e81f755b65"
[[package]]
name = "nom"
version = "7.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
dependencies = [
"memchr",
"minimal-lexical",
]
[[package]]
name = "nu-ansi-term"
version = "0.50.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5"
dependencies = [
"windows-sys 0.61.2",
]
[[package]]
name = "num-bigint"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9"
dependencies = [
"num-integer",
"num-traits",
]
[[package]]
name = "num-conv"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf97ec579c3c42f953ef76dbf8d55ac91fb219dde70e49aa4a6b7d74e9919050"
[[package]]
name = "num-integer"
version = "0.1.46"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f"
dependencies = [
"num-traits",
]
[[package]]
name = "num-traits"
version = "0.2.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
dependencies = [
"autocfg",
]
[[package]]
name = "num_enum"
version = "0.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1207a7e20ad57b847bbddc6776b968420d38292bbfe2089accff5e19e82454c"
dependencies = [
"num_enum_derive",
"rustversion",
]
[[package]]
name = "num_enum_derive"
version = "0.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ff32365de1b6743cb203b710788263c44a03de03802daf96092f2da4fe6ba4d7"
dependencies = [
"proc-macro-crate",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "num_threads"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9"
dependencies = [
"libc",
]
[[package]]
name = "oid-registry"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "12f40cff3dde1b6087cc5d5f5d4d65712f34016a03ed60e9c08dcc392736b5b7"
dependencies = [
"asn1-rs",
]
[[package]]
name = "once_cell"
version = "1.21.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
[[package]]
name = "once_cell_polyfill"
version = "1.70.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
[[package]]
name = "openssl-probe"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe"
[[package]]
name = "ordered-float"
version = "2.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68f19d67e5a2795c94e73e0bb1cc1a7edeb2e28efd39e2e1c9b7a40c1108b11c"
dependencies = [
"num-traits",
]
[[package]]
name = "parking"
version = "2.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba"
[[package]]
name = "parking_lot"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99"
dependencies = [
"instant",
"lock_api",
"parking_lot_core 0.8.6",
]
[[package]]
name = "parking_lot"
version = "0.12.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a"
dependencies = [
"lock_api",
"parking_lot_core 0.9.12",
]
[[package]]
name = "parking_lot_core"
version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "60a2cfe6f0ad2bfc16aefa463b497d5c7a5ecd44a23efa72aa342d90177356dc"
dependencies = [
"cfg-if",
"instant",
"libc",
"redox_syscall 0.2.16",
"smallvec",
"winapi",
]
[[package]]
name = "parking_lot_core"
version = "0.9.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1"
dependencies = [
"cfg-if",
"libc",
"redox_syscall 0.5.18",
"smallvec",
"windows-link 0.2.1",
]
[[package]]
name = "paste"
version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
[[package]]
name = "pem"
version = "3.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d30c53c26bc5b31a98cd02d20f25a7c8567146caf63ed593a9d87b2775291be"
dependencies = [
"base64",
"serde_core",
]
[[package]]
name = "percent-encoding"
version = "2.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
[[package]]
name = "petgraph"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8701b58ea97060d5e5b155d383a69952a60943f0e6dfe30b04c287beb0b27455"
dependencies = [
"fixedbitset",
"hashbrown 0.15.5",
"indexmap",
]
[[package]]
name = "pin-project"
version = "1.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a"
dependencies = [
"pin-project-internal",
]
[[package]]
name = "pin-project-internal"
version = "1.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "pin-project-lite"
version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b"
[[package]]
name = "pin-utils"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
[[package]]
name = "piper"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96c8c490f422ef9a4efd2cb5b42b76c8613d7e7dfc1caf667b8a3350a5acc066"
dependencies = [
"atomic-waker",
"fastrand",
"futures-io",
]
[[package]]
name = "pnet_base"
version = "0.35.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ffc190d4067df16af3aba49b3b74c469e611cad6314676eaf1157f31aa0fb2f7"
dependencies = [
"no-std-net",
]
[[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",
]
[[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]]
name = "powerfmt"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
[[package]]
name = "ppv-lite86"
version = "0.2.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9"
dependencies = [
"zerocopy",
]
[[package]]
name = "prettyplease"
version = "0.2.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b"
dependencies = [
"proc-macro2",
"syn",
]
[[package]]
name = "proc-macro-crate"
version = "3.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "219cb19e96be00ab2e37d6e299658a0cfa83e52429179969b0f0121b4ac46983"
dependencies = [
"toml_edit",
]
[[package]]
name = "proc-macro2"
version = "1.0.106"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
dependencies = [
"unicode-ident",
]
[[package]]
name = "prost"
version = "0.14.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2ea70524a2f82d518bce41317d0fae74151505651af45faf1ffbd6fd33f0568"
dependencies = [
"bytes",
"prost-derive",
]
[[package]]
name = "prost-build"
version = "0.14.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "343d3bd7056eda839b03204e68deff7d1b13aba7af2b2fd16890697274262ee7"
dependencies = [
"heck",
"itertools",
"log",
"multimap",
"petgraph",
"prettyplease",
"prost",
"prost-types",
"regex",
"syn",
"tempfile",
]
[[package]]
name = "prost-derive"
version = "0.14.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "27c6023962132f4b30eb4c172c91ce92d933da334c59c23cddee82358ddafb0b"
dependencies = [
"anyhow",
"itertools",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "prost-types"
version = "0.14.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8991c4cbdb8bc5b11f0b074ffe286c30e523de90fee5ba8132f1399f23cb3dd7"
dependencies = [
"prost",
]
[[package]]
name = "quick-error"
version = "1.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0"
[[package]]
name = "quinn"
version = "0.11.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20"
dependencies = [
"bytes",
"cfg_aliases",
"pin-project-lite",
"quinn-proto",
"quinn-udp",
"rustc-hash",
"rustls",
"socket2 0.6.2",
"thiserror 2.0.18",
"tokio",
"tracing",
"web-time",
]
[[package]]
name = "quinn-proto"
version = "0.11.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1906b49b0c3bc04b5fe5d86a77925ae6524a19b816ae38ce1e426255f1d8a31"
dependencies = [
"bytes",
"getrandom 0.3.4",
"lru-slab",
"rand",
"ring",
"rustc-hash",
"rustls",
"rustls-pki-types",
"slab",
"thiserror 2.0.18",
"tinyvec",
"tracing",
"web-time",
]
[[package]]
name = "quinn-udp"
version = "0.5.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd"
dependencies = [
"cfg_aliases",
"libc",
"once_cell",
"socket2 0.6.2",
"tracing",
"windows-sys 0.60.2",
]
[[package]]
name = "quote"
version = "1.0.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4"
dependencies = [
"proc-macro2",
]
[[package]]
name = "r-efi"
version = "5.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
[[package]]
name = "r-ex"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42776a943a4f890410259860d4c308d32792ecbeaca4932a2b94b04f6d27bb9a"
[[package]]
name = "rand"
version = "0.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1"
dependencies = [
"rand_chacha",
"rand_core",
]
[[package]]
name = "rand_chacha"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
dependencies = [
"ppv-lite86",
"rand_core",
]
[[package]]
name = "rand_core"
version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c"
dependencies = [
"getrandom 0.3.4",
]
[[package]]
name = "rcgen"
version = "0.14.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "10b99e0098aa4082912d4c649628623db6aba77335e4f4569ff5083a6448b32e"
dependencies = [
"pem",
"ring",
"rustls-pki-types",
"time",
"x509-parser",
"yasna",
]
[[package]]
name = "redox_syscall"
version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a"
dependencies = [
"bitflags 1.3.2",
]
[[package]]
name = "redox_syscall"
version = "0.5.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d"
dependencies = [
"bitflags 2.10.0",
]
[[package]]
name = "reed-solomon-erasure"
version = "6.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7263373d500d4d4f505d43a2a662d475a894aa94503a1ee28e9188b5f3960d4f"
dependencies = [
"libm",
"lru",
"parking_lot 0.11.2",
"smallvec",
"spin",
]
[[package]]
name = "regex"
version = "1.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276"
dependencies = [
"aho-corasick",
"memchr",
"regex-automata",
"regex-syntax",
]
[[package]]
name = "regex-automata"
version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.8.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a96887878f22d7bad8a3b6dc5b7440e0ada9a245242924394987b21cf2210a4c"
[[package]]
name = "ring"
version = "0.17.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7"
dependencies = [
"cc",
"cfg-if",
"getrandom 0.2.17",
"libc",
"untrusted",
"windows-sys 0.52.0",
]
[[package]]
name = "route_manager"
version = "0.2.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "319bb478ff9aae1dc7a544fa599e9eb47e2521621dc3921fba6abcaaa312092c"
dependencies = [
"flume 0.12.0",
"libc",
"netlink-packet-core 0.8.1",
"netlink-packet-route 0.28.0",
"netlink-sys",
"windows-sys 0.61.2",
]
[[package]]
name = "rust-embed"
version = "8.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "04113cb9355a377d83f06ef1f0a45b8ab8cd7d8b1288160717d66df5c7988d27"
dependencies = [
"rust-embed-impl",
"rust-embed-utils",
"walkdir",
]
[[package]]
name = "rust-embed-impl"
version = "8.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da0902e4c7c8e997159ab384e6d0fc91c221375f6894346ae107f47dd0f3ccaa"
dependencies = [
"proc-macro2",
"quote",
"rust-embed-utils",
"syn",
"walkdir",
]
[[package]]
name = "rust-embed-utils"
version = "8.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5bcdef0be6fe7f6fa333b1073c949729274b05f123a0ad7efcb8efd878e5c3b1"
dependencies = [
"sha2",
"walkdir",
]
[[package]]
name = "rust-p2p-core"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eccee83a23618a90d50a11cf971e72ce68efee69dbca8c246795ecd791118e32"
dependencies = [
"async-broadcast",
"async-lock",
"async-scoped",
"async-trait",
"bytes",
"crossbeam-queue",
"crossbeam-utils",
"dashmap",
"dyn-clone",
"futures",
"libc",
"log",
"parking_lot 0.12.5",
"rand",
"serde",
"socket2 0.5.10",
"stun-format",
"tachyonix",
"tokio",
"windows-sys 0.60.2",
]
[[package]]
name = "rustc-hash"
version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d"
[[package]]
name = "rustc_version"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92"
dependencies = [
"semver",
]
[[package]]
name = "rusticata-macros"
version = "4.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "faf0c4a6ece9950b9abdb62b1cfcf2a68b3b67a10ba445b3bb85be2a293d0632"
dependencies = [
"nom",
]
[[package]]
name = "rustix"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34"
dependencies = [
"bitflags 2.10.0",
"errno",
"libc",
"linux-raw-sys",
"windows-sys 0.61.2",
]
[[package]]
name = "rustls"
version = "0.23.36"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c665f33d38cea657d9614f766881e4d510e0eda4239891eea56b4cadcf01801b"
dependencies = [
"once_cell",
"ring",
"rustls-pki-types",
"rustls-webpki",
"subtle",
"zeroize",
]
[[package]]
name = "rustls-native-certs"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "612460d5f7bea540c490b2b6395d8e34a953e52b491accd6c86c8164c5932a63"
dependencies = [
"openssl-probe",
"rustls-pki-types",
"schannel",
"security-framework",
]
[[package]]
name = "rustls-pki-types"
version = "1.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd"
dependencies = [
"web-time",
"zeroize",
]
[[package]]
name = "rustls-webpki"
version = "0.103.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d7df23109aa6c1567d1c575b9952556388da57401e4ace1d15f79eedad0d8f53"
dependencies = [
"ring",
"rustls-pki-types",
"untrusted",
]
[[package]]
name = "rustversion"
version = "1.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
[[package]]
name = "ryu"
version = "1.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a50f4cf475b65d88e057964e0e9bb1f0aa9bbb2036dc65c64596b42932536984"
[[package]]
name = "same-file"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
dependencies = [
"winapi-util",
]
[[package]]
name = "schannel"
version = "0.1.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "891d81b926048e76efe18581bf793546b4c0eaf8448d72be8de2bbee5fd166e1"
dependencies = [
"windows-sys 0.61.2",
]
[[package]]
name = "scoped-tls"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294"
[[package]]
name = "scopeguard"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]]
name = "security-framework"
version = "3.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b3297343eaf830f66ede390ea39da1d462b6b0c1b000f420d0a83f898bbbe6ef"
dependencies = [
"bitflags 2.10.0",
"core-foundation 0.10.1",
"core-foundation-sys",
"libc",
"security-framework-sys",
]
[[package]]
name = "security-framework-sys"
version = "2.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cc1f0cbffaac4852523ce30d8bd3c5cdc873501d96ff467ca09b6767bb8cd5c0"
dependencies = [
"core-foundation-sys",
"libc",
]
[[package]]
name = "semver"
version = "1.0.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2"
[[package]]
name = "serde"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
dependencies = [
"serde_core",
"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]]
name = "serde_core"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "serde_json"
version = "1.0.149"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86"
dependencies = [
"itoa",
"memchr",
"serde",
"serde_core",
"zmij",
]
[[package]]
name = "serde_path_to_error"
version = "0.1.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "10a9ff822e371bb5403e391ecd83e182e0e77ba7f6fe0160b795797109d1b457"
dependencies = [
"itoa",
"serde",
"serde_core",
]
[[package]]
name = "serde_spanned"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8bbf91e5a4d6315eee45e704372590b30e260ee83af6639d64557f51b067776"
dependencies = [
"serde_core",
]
[[package]]
name = "serde_urlencoded"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd"
dependencies = [
"form_urlencoded",
"itoa",
"ryu",
"serde",
]
[[package]]
name = "serde_yaml"
version = "0.9.34+deprecated"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47"
dependencies = [
"indexmap",
"itoa",
"ryu",
"serde",
"unsafe-libyaml",
]
[[package]]
name = "sha1"
version = "0.10.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba"
dependencies = [
"cfg-if",
"cpufeatures",
"digest",
]
[[package]]
name = "sha2"
version = "0.10.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283"
dependencies = [
"cfg-if",
"cpufeatures",
"digest",
]
[[package]]
name = "sharded-slab"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6"
dependencies = [
"lazy_static",
]
[[package]]
name = "shlex"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
[[package]]
name = "signal-hook-registry"
version = "1.4.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b"
dependencies = [
"errno",
"libc",
]
[[package]]
name = "slab"
version = "0.4.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5"
[[package]]
name = "smallvec"
version = "1.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
[[package]]
name = "socket2"
version = "0.5.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678"
dependencies = [
"libc",
"windows-sys 0.52.0",
]
[[package]]
name = "socket2"
version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "86f4aa3ad99f2088c990dfa82d367e19cb29268ed67c574d10d0a4bfe71f07e0"
dependencies = [
"libc",
"windows-sys 0.60.2",
]
[[package]]
name = "spin"
version = "0.9.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
dependencies = [
"lock_api",
]
[[package]]
name = "strsim"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
[[package]]
name = "stun-bytes"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3bebd666814c8af1e06e4d489f9c3d03a4aef20bae651550c52e6e95e09f3f12"
dependencies = [
"r-ex",
]
[[package]]
name = "stun-format"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c6ad3c54fde6dfd6fe5048a71515b392d22c29730533663b5300e1b511d78df5"
dependencies = [
"r-ex",
"stun-bytes",
]
[[package]]
name = "subtle"
version = "2.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
[[package]]
name = "syn"
version = "2.0.114"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "sync_wrapper"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263"
[[package]]
name = "synstructure"
version = "0.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "system-configuration-sys"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9"
dependencies = [
"core-foundation-sys",
"libc",
]
[[package]]
name = "tachyonix"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "86c3eafa053bbcc63bb4bfc5eb26362a33ea0bc2e589f28bce00287d1c167d45"
dependencies = [
"async-event",
"crossbeam-utils",
"diatomic-waker",
"futures-core",
"pin-project-lite",
]
[[package]]
name = "tcp_ip"
version = "0.1.10"
source = "git+https://github.com/rustp2p/tcp_ip#09c64c81da70a35248dfef4ef42bb9f5e8b6e05e"
dependencies = [
"bytes",
"dashmap",
"flume 0.11.1",
"log",
"num_enum",
"parking_lot 0.12.5",
"pnet_packet",
"rand",
"tokio",
"tokio-util",
]
[[package]]
name = "tempfile"
version = "3.24.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "655da9c7eb6305c55742045d5a8d2037996d61d8de95806335c7c86ce0f82e9c"
dependencies = [
"fastrand",
"getrandom 0.3.4",
"once_cell",
"rustix",
"windows-sys 0.61.2",
]
[[package]]
name = "termcolor"
version = "1.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755"
dependencies = [
"winapi-util",
]
[[package]]
name = "thiserror"
version = "1.0.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
dependencies = [
"thiserror-impl 1.0.69",
]
[[package]]
name = "thiserror"
version = "2.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4"
dependencies = [
"thiserror-impl 2.0.18",
]
[[package]]
name = "thiserror-impl"
version = "1.0.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "thiserror-impl"
version = "2.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "thread-id"
version = "5.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2010d27add3f3240c1fef7959f46c814487b216baee662af53be645ba7831c07"
dependencies = [
"libc",
"windows-sys 0.61.2",
]
[[package]]
name = "thread_local"
version = "1.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185"
dependencies = [
"cfg-if",
]
[[package]]
name = "time"
version = "0.3.47"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c"
dependencies = [
"deranged",
"itoa",
"libc",
"num-conv",
"num_threads",
"powerfmt",
"serde_core",
"time-core",
"time-macros",
]
[[package]]
name = "time-core"
version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca"
[[package]]
name = "time-macros"
version = "0.2.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215"
dependencies = [
"num-conv",
"time-core",
]
[[package]]
name = "tinyvec"
version = "1.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bfa5fdc3bce6191a1dbc8c02d5c8bffcf557bafa17c124c5264a458f1b0613fa"
dependencies = [
"tinyvec_macros",
]
[[package]]
name = "tinyvec_macros"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
[[package]]
name = "tokio"
version = "1.49.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86"
dependencies = [
"bytes",
"libc",
"mio",
"parking_lot 0.12.5",
"pin-project-lite",
"signal-hook-registry",
"socket2 0.6.2",
"tokio-macros",
"windows-sys 0.61.2",
]
[[package]]
name = "tokio-macros"
version = "2.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "tokio-rustls"
version = "0.26.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61"
dependencies = [
"rustls",
"tokio",
]
[[package]]
name = "tokio-tungstenite"
version = "0.28.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d25a406cddcc431a75d3d9afc6a7c0f7428d4891dd973e4d54c56b46127bf857"
dependencies = [
"futures-util",
"log",
"tokio",
"tungstenite",
]
[[package]]
name = "tokio-util"
version = "0.7.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098"
dependencies = [
"bytes",
"futures-core",
"futures-sink",
"pin-project-lite",
"tokio",
]
[[package]]
name = "toml"
version = "0.9.11+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f3afc9a848309fe1aaffaed6e1546a7a14de1f935dc9d89d32afd9a44bab7c46"
dependencies = [
"indexmap",
"serde_core",
"serde_spanned",
"toml_datetime",
"toml_parser",
"toml_writer",
"winnow",
]
[[package]]
name = "toml_datetime"
version = "0.7.5+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347"
dependencies = [
"serde_core",
]
[[package]]
name = "toml_edit"
version = "0.23.10+spec-1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "84c8b9f757e028cee9fa244aea147aab2a9ec09d5325a9b01e0a49730c2b5269"
dependencies = [
"indexmap",
"toml_datetime",
"toml_parser",
"winnow",
]
[[package]]
name = "toml_parser"
version = "1.0.6+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a3198b4b0a8e11f09dd03e133c0280504d0801269e9afa46362ffde1cbeebf44"
dependencies = [
"winnow",
]
[[package]]
name = "toml_writer"
version = "1.0.6+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab16f14aed21ee8bfd8ec22513f7287cd4a91aa92e44edfe2c17ddd004e92607"
[[package]]
name = "tower"
version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4"
dependencies = [
"futures-core",
"futures-util",
"pin-project-lite",
"sync_wrapper",
"tokio",
"tower-layer",
"tower-service",
"tracing",
]
[[package]]
name = "tower-http"
version = "0.6.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8"
dependencies = [
"bitflags 2.10.0",
"bytes",
"futures-core",
"futures-util",
"http",
"http-body",
"http-body-util",
"http-range-header",
"httpdate",
"mime",
"mime_guess",
"percent-encoding",
"pin-project-lite",
"tokio",
"tokio-util",
"tower-layer",
"tower-service",
"tracing",
]
[[package]]
name = "tower-layer"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e"
[[package]]
name = "tower-service"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3"
[[package]]
name = "tracing"
version = "0.1.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100"
dependencies = [
"log",
"pin-project-lite",
"tracing-attributes",
"tracing-core",
]
[[package]]
name = "tracing-attributes"
version = "0.1.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "tracing-core"
version = "0.1.36"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a"
dependencies = [
"once_cell",
"valuable",
]
[[package]]
name = "tracing-log"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3"
dependencies = [
"log",
"once_cell",
"tracing-core",
]
[[package]]
name = "tracing-subscriber"
version = "0.3.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2f30143827ddab0d256fd843b7a66d164e9f271cfa0dde49142c5ca0ca291f1e"
dependencies = [
"matchers",
"nu-ansi-term",
"once_cell",
"regex-automata",
"sharded-slab",
"smallvec",
"thread_local",
"tracing",
"tracing-core",
"tracing-log",
]
[[package]]
name = "tun-rs"
version = "2.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a88c6aea62111e48fb67b733d6bbdc2019272e6d6f7108c9b0f4cd25d715d38a"
dependencies = [
"blocking",
"byteorder",
"bytes",
"c2rust-bitfields",
"encoding_rs",
"futures",
"futures-core",
"getifaddrs",
"ipnet",
"libc",
"libloading",
"log",
"netconfig-rs",
"nix 0.31.1",
"route_manager",
"scopeguard",
"tokio",
"widestring",
"windows-sys 0.61.2",
"winreg",
]
[[package]]
name = "tungstenite"
version = "0.28.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8628dcc84e5a09eb3d8423d6cb682965dea9133204e8fb3efee74c2a0c259442"
dependencies = [
"bytes",
"data-encoding",
"http",
"httparse",
"log",
"rand",
"sha1",
"thiserror 2.0.18",
"utf-8",
]
[[package]]
name = "twox-hash"
version = "2.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ea3136b675547379c4bd395ca6b938e5ad3c3d20fad76e7fe85f9e0d011419c"
[[package]]
name = "typemap-ors"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a68c24b707f02dd18f1e4ccceb9d49f2058c2fb86384ef9972592904d7a28867"
dependencies = [
"unsafe-any-ors",
]
[[package]]
name = "typenum"
version = "1.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb"
[[package]]
name = "unicase"
version = "2.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142"
[[package]]
name = "unicode-ident"
version = "1.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5"
[[package]]
name = "unicode-segmentation"
version = "1.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493"
[[package]]
name = "unicode-width"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254"
[[package]]
name = "unicode-xid"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
[[package]]
name = "unsafe-any-ors"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e0a303d30665362d9680d7d91d78b23f5f899504d4f08b3c4cf08d055d87c0ad"
dependencies = [
"destructure_traitobject",
]
[[package]]
name = "unsafe-libyaml"
version = "0.2.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861"
[[package]]
name = "untrusted"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
[[package]]
name = "utf-8"
version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9"
[[package]]
name = "utf8parse"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
[[package]]
name = "uuid"
version = "1.20.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee48d38b119b0cd71fe4141b30f5ba9c7c5d9f4e7a3a8b4a674e4b6ef789976f"
dependencies = [
"getrandom 0.3.4",
"js-sys",
"wasm-bindgen",
]
[[package]]
name = "valuable"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65"
[[package]]
name = "version_check"
version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
[[package]]
name = "vnt-core"
version = "2.0.0"
dependencies = [
"anyhow",
"bytes",
"dns-parser",
"futures",
"getifaddrs",
"hex",
"ipnet",
"log",
"lz4_flex",
"machine-uid",
"parking_lot 0.12.5",
"pnet_packet",
"prost",
"prost-build",
"quinn",
"rand",
"rcgen",
"reed-solomon-erasure",
"ring",
"rust-p2p-core",
"rustls",
"rustls-native-certs",
"serde",
"sha2",
"socket2 0.6.2",
"tcp_ip",
"time",
"tokio",
"tokio-rustls",
"tokio-tungstenite",
"tokio-util",
"tun-rs",
"tungstenite",
"uuid",
"widestring",
"winapi",
"zerocopy",
]
[[package]]
name = "vnt-ipc"
version = "2.0.0"
dependencies = [
"anyhow",
"cli-table",
"console",
"futures",
"log",
"prost",
"prost-build",
"time",
"tokio",
"tokio-util",
"vnt-core",
]
[[package]]
name = "vnt-web"
version = "2.0.0"
dependencies = [
"anyhow",
"axum",
"hostname",
"ipnet",
"log",
"mime_guess",
"parking_lot 0.12.5",
"route_manager",
"rust-embed",
"serde",
"serde_json",
"time",
"tokio",
"toml",
"tower-http",
"vnt-core",
]
[[package]]
name = "vnt2"
version = "2.0.0"
dependencies = [
"anyhow",
"clap",
"hostname",
"ipnet",
"log",
"log4rs",
"route_manager",
"serde",
"tokio",
"toml",
"vnt-ipc",
"vnt-web",
]
[[package]]
name = "waker-fn"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "317211a0dc0ceedd78fb2ca9a44aed3d7b9b26f81870d485c07122b4350673b7"
[[package]]
name = "walkdir"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b"
dependencies = [
"same-file",
"winapi-util",
]
[[package]]
name = "wasi"
version = "0.11.1+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
[[package]]
name = "wasip2"
version = "1.0.2+wasi-0.2.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5"
dependencies = [
"wit-bindgen",
]
[[package]]
name = "wasm-bindgen"
version = "0.2.108"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "64024a30ec1e37399cf85a7ffefebdb72205ca1c972291c51512360d90bd8566"
dependencies = [
"cfg-if",
"once_cell",
"rustversion",
"wasm-bindgen-macro",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.108"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "008b239d9c740232e71bd39e8ef6429d27097518b6b30bdf9086833bd5b6d608"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
]
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.108"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5256bae2d58f54820e6490f9839c49780dff84c65aeab9e772f15d5f0e913a55"
dependencies = [
"bumpalo",
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.108"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f01b580c9ac74c8d8f0c0e4afb04eeef2acf145458e52c03845ee9cd23e3d12"
dependencies = [
"unicode-ident",
]
[[package]]
name = "web-time"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb"
dependencies = [
"js-sys",
"wasm-bindgen",
]
[[package]]
name = "widestring"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72069c3113ab32ab29e5584db3c6ec55d416895e60715417b5b883a357c3e471"
[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-util"
version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
dependencies = [
"windows-sys 0.61.2",
]
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f"
dependencies = [
"windows-targets 0.48.5",
]
[[package]]
name = "windows"
version = "0.61.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893"
dependencies = [
"windows-collections",
"windows-core 0.61.2",
"windows-future",
"windows-link 0.1.3",
"windows-numerics",
]
[[package]]
name = "windows-collections"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8"
dependencies = [
"windows-core 0.61.2",
]
[[package]]
name = "windows-core"
version = "0.61.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3"
dependencies = [
"windows-implement",
"windows-interface",
"windows-link 0.1.3",
"windows-result 0.3.4",
"windows-strings 0.4.2",
]
[[package]]
name = "windows-core"
version = "0.62.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb"
dependencies = [
"windows-implement",
"windows-interface",
"windows-link 0.2.1",
"windows-result 0.4.1",
"windows-strings 0.5.1",
]
[[package]]
name = "windows-future"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e"
dependencies = [
"windows-core 0.61.2",
"windows-link 0.1.3",
"windows-threading",
]
[[package]]
name = "windows-implement"
version = "0.60.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "windows-interface"
version = "0.59.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "windows-link"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a"
[[package]]
name = "windows-link"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
[[package]]
name = "windows-numerics"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1"
dependencies = [
"windows-core 0.61.2",
"windows-link 0.1.3",
]
[[package]]
name = "windows-registry"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720"
dependencies = [
"windows-link 0.2.1",
"windows-result 0.4.1",
"windows-strings 0.5.1",
]
[[package]]
name = "windows-result"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6"
dependencies = [
"windows-link 0.1.3",
]
[[package]]
name = "windows-result"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5"
dependencies = [
"windows-link 0.2.1",
]
[[package]]
name = "windows-strings"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57"
dependencies = [
"windows-link 0.1.3",
]
[[package]]
name = "windows-strings"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091"
dependencies = [
"windows-link 0.2.1",
]
[[package]]
name = "windows-sys"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
dependencies = [
"windows-targets 0.52.6",
]
[[package]]
name = "windows-sys"
version = "0.59.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
dependencies = [
"windows-targets 0.52.6",
]
[[package]]
name = "windows-sys"
version = "0.60.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb"
dependencies = [
"windows-targets 0.53.5",
]
[[package]]
name = "windows-sys"
version = "0.61.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
dependencies = [
"windows-link 0.2.1",
]
[[package]]
name = "windows-targets"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
dependencies = [
"windows_aarch64_gnullvm 0.48.5",
"windows_aarch64_msvc 0.48.5",
"windows_i686_gnu 0.48.5",
"windows_i686_msvc 0.48.5",
"windows_x86_64_gnu 0.48.5",
"windows_x86_64_gnullvm 0.48.5",
"windows_x86_64_msvc 0.48.5",
]
[[package]]
name = "windows-targets"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
dependencies = [
"windows_aarch64_gnullvm 0.52.6",
"windows_aarch64_msvc 0.52.6",
"windows_i686_gnu 0.52.6",
"windows_i686_gnullvm 0.52.6",
"windows_i686_msvc 0.52.6",
"windows_x86_64_gnu 0.52.6",
"windows_x86_64_gnullvm 0.52.6",
"windows_x86_64_msvc 0.52.6",
]
[[package]]
name = "windows-targets"
version = "0.53.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3"
dependencies = [
"windows-link 0.2.1",
"windows_aarch64_gnullvm 0.53.1",
"windows_aarch64_msvc 0.53.1",
"windows_i686_gnu 0.53.1",
"windows_i686_gnullvm 0.53.1",
"windows_i686_msvc 0.53.1",
"windows_x86_64_gnu 0.53.1",
"windows_x86_64_gnullvm 0.53.1",
"windows_x86_64_msvc 0.53.1",
]
[[package]]
name = "windows-threading"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6"
dependencies = [
"windows-link 0.1.3",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53"
[[package]]
name = "windows_aarch64_msvc"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
[[package]]
name = "windows_aarch64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
[[package]]
name = "windows_aarch64_msvc"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006"
[[package]]
name = "windows_i686_gnu"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
[[package]]
name = "windows_i686_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
[[package]]
name = "windows_i686_gnu"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3"
[[package]]
name = "windows_i686_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
[[package]]
name = "windows_i686_gnullvm"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c"
[[package]]
name = "windows_i686_msvc"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
[[package]]
name = "windows_i686_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
[[package]]
name = "windows_i686_msvc"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2"
[[package]]
name = "windows_x86_64_gnu"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
[[package]]
name = "windows_x86_64_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
[[package]]
name = "windows_x86_64_gnu"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1"
[[package]]
name = "windows_x86_64_msvc"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
[[package]]
name = "windows_x86_64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
[[package]]
name = "windows_x86_64_msvc"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650"
[[package]]
name = "winnow"
version = "0.7.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829"
dependencies = [
"memchr",
]
[[package]]
name = "winreg"
version = "0.55.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cb5a765337c50e9ec252c2069be9bf91c7df47afb103b642ba3a53bf8101be97"
dependencies = [
"cfg-if",
"windows-sys 0.59.0",
]
[[package]]
name = "wit-bindgen"
version = "0.51.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5"
[[package]]
name = "x509-parser"
version = "0.18.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d43b0f71ce057da06bc0851b23ee24f3f86190b07203dd8f567d0b706a185202"
dependencies = [
"asn1-rs",
"data-encoding",
"der-parser",
"lazy_static",
"nom",
"oid-registry",
"ring",
"rusticata-macros",
"thiserror 2.0.18",
"time",
]
[[package]]
name = "yasna"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e17bb3549cc1321ae1296b9cdc2698e2b6cb1992adfa19a8c72e5b7a738f44cd"
dependencies = [
"time",
]
[[package]]
name = "zerocopy"
version = "0.8.39"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "db6d35d663eadb6c932438e763b262fe1a70987f9ae936e60158176d710cae4a"
dependencies = [
"zerocopy-derive",
]
[[package]]
name = "zerocopy-derive"
version = "0.8.39"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4122cd3169e94605190e77839c9a40d40ed048d305bfdc146e7df40ab0f3e517"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "zeroize"
version = "1.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0"
[[package]]
name = "zmij"
version = "1.0.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3ff05f8caa9038894637571ae6b9e29466c1f4f829d26c9b28f869a29cbe3445"
+47 -34
View File
@@ -1,44 +1,57 @@
[package]
name = "switch"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
name = "vnt2"
version = "2.0.0"
edition = "2024"
[dependencies]
packet = { path = "./packet" }
bytes = "1.3.0"
vnt-ipc = { path = "vnt-ipc", optional = true }
vnt-web = { path = "vnt-web", optional = true }
clap = { version = "4.5", features = ["derive"] }
libc = "0.2.137"
tokio = { version = "1", features = ["full"] }
dashmap = "5.4.0"
crossbeam = "0.8.2"
parking_lot = "0.12.1"
rsa = "0.7.2"
rand = "0.8.5"
sha2 = { version = "0.10.6", features = ["oid"] }
#colored = "2.0.0"
log = "0.4"
log4rs = "1.4"
thiserror = "1.0.37"
chrono = "0.4.23"
lazy_static = "1.4.0"
moka = "0.9.6"
protobuf = "3.2.0"
serde = { version = "1.0.228", features = ["derive"] }
anyhow = "1.0.100"
toml = "0.9.8"
hostname = "0.4.2"
ipnet = { version = "2.11", features = ["serde"] }
console = "0.15.2"
mac_address = "1.1.4"
clap = { version = "4.0.32", features = ["derive"] }
[target.'cfg(any(unix))'.dependencies]
tun = { path = "./rust-tun" }
sudo = "0.6.0"
route_manager = "0.2.11"
[target.'cfg(target_os = "windows")'.dependencies]
winapi = { version = "0.3.9", features = ["handleapi", "processthreadsapi", "winnt", "securitybaseapi", "impl-default"] }
wintun = "0.2.1"
libloading = "0.7.4"
runas = "0.2.1"
[features]
default = []
vnt-ipc = ["dep:vnt-ipc"]
vnt-web = ["dep:vnt-web"]
[build-dependencies]
protobuf-codegen = "3.2.0"
protoc-bin-vendored = "3.0.0"
[[bin]]
name = "vnt2_cli"
path = "src/main_cli.rs"
required-features = ["vnt-ipc"]
[[bin]]
name = "vnt2_ctrl"
path = "src/main_ctrl.rs"
required-features = ["vnt-ipc"]
[[bin]]
name = "vnt2_web"
path = "src/main_web.rs"
required-features = ["vnt-web"]
[workspace]
members = ["vnt-web", "vnt-ipc", "vnt-core"]
[profile.release]
opt-level = 'z'
debug = 0
debug-assertions = false
strip = true
lto = true
panic = 'abort'
incremental = false
codegen-units = 1
rpath = false
+14
View File
@@ -0,0 +1,14 @@
一、程序说明
1. vnt-cli vnt的命令行程序
2. vn-link-cli 功能和vnt-cli基本一致,但是不依赖tun、不改变本地路由、不需要管理员/root权限
二、使用说明
使用-k参数构建虚拟网络
1. Program Description
a. vnt-cli: Command-line program for VNT.
b. vn-link-cli: Functions similarly to vnt-cli, but does not depend on TUN, does not change local routing, and does not require administrator/root permissions.
2. Instructions for Use
Use the -k parameter to create a virtual network.
+51 -26
View File
@@ -1,38 +1,63 @@
# switch
Virtual Network Tools
# VNT
将不同网络下的设备虚拟到一个局域网下
一个简单、高效、能快速组建虚拟局域网的工具
# 快速开始
### 简单说明
1. vnt2_cli 是一个纯命令行组网工具,可以从命令行参数或配置文件快速启动组网
2. vnt2_ctrl 和vnt2_cli搭配使用,vnt2_cli后台运行时,可以用vnt2_ctrl来获取组网状态
3. vnt2_web 是一个集成web服务的组网工具,带web页面,可以在页面上操作组网
## 使用vnt2_cli组网
使用方式和vnt1.0一样,只是增减了一些功能,具体参数请查看 -h
```
# 启动程序 服务端可以使用101.35.230.139:6660
./vnt2_cli -k 123456 -s 101.35.230.139:6660
```
```
# 查看组网信息
./vnt2_ctrl info
```
## 使用vnt2_web组网
1. 启动程序
```
# 启动程序
./vnt2_web
```
2. 浏览器打开 http://127.0.0.1:19099
3. 在页面上添加组网配置,再启动组网
### 示例:
# VNT2.0新特性
- 在一台mac设备上运行,获取到ip 10.13.0.2
1. 提升安全性,支持tcp-tls、quic、wss协议连接服务器,和服务端强制使用tls加密,并支持证书绑定,防止伪造服务端攻击
2. 提升流量稳定性,支持使用quic代理流量,支持FEC冗余传输
3. 简化操作,去除了大量vnt1.0的重复和无用的配置参数
4. vnt-link、vnt合二为一
5. 支持有tun模式、无tun模式、端口映射
6. 全功能的情况下,减少程序体积
7. 性能提升,支持linux-offload
8. 更规范的api接入,可以轻松自定义客户端
9. 支持同时连接多个服务端,可以容灾和负载均衡
<img width="506" alt="图片" src="https://user-images.githubusercontent.com/49143209/210379090-a3f21007-5a12-44d3-81d6-a69495209ea7.png">
# 说明
- 在另一台windows上运行,获取到ip 10.13.0.3
vnt2.0整体重构了一遍,和1.0不兼容,同时也可能引入新的bug,欢迎反馈
![图片](https://user-images.githubusercontent.com/49143209/210380063-d02c5b46-8fef-4e21-aa9b-6c2defcb1412.png)
其他平台后续再推出
- 此时这两个设备之间就能用ip相互访问了
### 相关库
1. tun虚拟网卡(https://github.com/tun-rs/tun-rs)
2. 路由设置(https://github.com/tun-rs/route_manager)
3. 用户态协议栈(用于quic代理和无tun模式出口)(https://github.com/rustp2p/tcp_ip)
4. 打洞通道处理(https://github.com/rustp2p/rustp2p/tree/master/rustp2p-core)
<img width="437" alt="图片" src="https://user-images.githubusercontent.com/49143209/210380969-4a7c0f23-1e88-4ab6-9cc2-0c0f086848ac.png">
- token的作用是标识一个虚拟局域网,当使用公共服务器时,建议使用一个唯一值当token(比如uuid),否则有可能连接到其他人创建的虚拟局域网中
- 公共服务器目前的配置是2核4G 4Mbps,有需要再扩展~
### 支持平台
- Mac
- Linux
- Windows
- 依赖 wintun.dll(https://www.wintun.net/)
### 特性
- IP层数据转发
- tun虚拟网卡
- NAT穿透
- 点对点穿透
- 服务端中继转发
### Todo
- 数据加密
- 客户端中继转发
-14
View File
@@ -1,14 +0,0 @@
fn main() {
std::fs::create_dir_all("src/proto").unwrap();
protobuf_codegen::Codegen::new()
.pure()
.out_dir("src/proto")
.inputs(&["proto/message.proto"])
.include("proto")
// .customize(
// protobuf_codegen::Customize::default()
// .tokio_bytes(true)
// )
.run()
.expect("Codegen failed.");
}
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
-10
View File
@@ -1,10 +0,0 @@
[package]
name = "packet"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
byteorder = "1.4.3"
thiserror = "1.0.37"
-25
View File
@@ -1,25 +0,0 @@
use thiserror::Error;
#[derive(Error, Debug)]
pub enum Error {
#[error("the buffer is too small")]
SmallBuffer,
#[error("the packet is invalid")]
InvalidPacket,
#[error("Unimplemented")]
Unimplemented,
// #[error("the vaue is invalid for the field")]
// InvalidValue,
//
// #[error("the value has already been defined")]
// AlreadyDefined,
//
// #[error(transparent)]
// Io(#[from] io::Error),
//
// #[error(transparent)]
// Nul(#[from] ffi::NulError),
}
pub type Result<T> = ::std::result::Result<T, Error>;
-174
View File
@@ -1,174 +0,0 @@
use std::fmt;
use byteorder::{BigEndian, ReadBytesExt};
use crate::cal_checksum;
use crate::error::*;
/// icmp 协议
/* https://www.rfc-editor.org/rfc/rfc792
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Type | Code | Checksum |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 不同Type和Code有不同含义 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 数据体 不同Type和Code有不同含义 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
*/
use crate::icmp::{Code, Kind};
use crate::ip::ipv4::packet::IpV4Packet;
pub struct IcmpPacket<B> {
buffer: B,
}
impl<B: AsRef<[u8]>> IcmpPacket<B> {
pub fn unchecked(buffer: B) -> Self {
Self { buffer }
}
pub fn new(buffer: B) -> Result<Self> {
if buffer.as_ref().len() < 8 {
Err(Error::SmallBuffer)?
}
let packet = Self::unchecked(buffer);
Ok(packet)
}
}
impl<B: AsRef<[u8]> + AsMut<[u8]>> IcmpPacket<B> {
pub fn set_kind(&mut self, kind: Kind) {
self.buffer.as_mut()[0] = kind.into();
}
pub fn update_checksum(&mut self) {
self.buffer.as_mut()[2..4].copy_from_slice(&[0, 0]);
let checksum = cal_checksum(self.buffer.as_ref());
self.buffer.as_mut()[2..4].copy_from_slice(&checksum.to_be_bytes());
}
}
impl<B: AsRef<[u8]>> IcmpPacket<B> {
pub fn kind(&self) -> Kind {
Kind::from(self.buffer.as_ref()[0])
}
pub fn code(&self) -> Code {
Code::from(self.kind(), self.buffer.as_ref()[1])
}
pub fn checksum(&self) -> u16 {
(&self.buffer.as_ref()[2..])
.read_u16::<BigEndian>()
.unwrap()
}
pub fn is_valid(&self) -> bool {
self.checksum() == 0 || cal_checksum(self.buffer.as_ref()) == 0
}
pub fn header_other(&self) -> HeaderOther {
match self.kind() {
Kind::EchoReply
| Kind::EchoRequest
| Kind::TimestampRequest
| Kind::TimestampReply
| Kind::InformationRequest
| Kind::InformationReply => {
let ide = (&self.buffer.as_ref()[4..])
.read_u16::<BigEndian>()
.unwrap();
let seq = (&self.buffer.as_ref()[6..])
.read_u16::<BigEndian>()
.unwrap();
HeaderOther::Identifier(ide, seq)
}
Kind::DestinationUnreachable | Kind::TimeExceeded | Kind::SourceQuench => {
let bytes = self.buffer.as_ref();
HeaderOther::Unused(bytes[4], bytes[5], bytes[6], bytes[7])
}
Kind::Redirect => {
let bytes = self.buffer.as_ref();
HeaderOther::Address(bytes[4], bytes[5], bytes[6], bytes[7])
}
Kind::ParameterProblem => HeaderOther::Pointer(self.buffer.as_ref()[4]),
_ => {
let bytes = self.buffer.as_ref();
HeaderOther::UnKnown(bytes[4], bytes[5], bytes[6], bytes[7])
}
}
}
pub fn payload(&self) -> &[u8] {
&self.buffer.as_ref()[8..]
}
pub fn description(&self) -> Description<&[u8]> {
use std::io::Cursor;
match self.kind() {
Kind::DestinationUnreachable
| Kind::TimeExceeded
| Kind::ParameterProblem
| Kind::SourceQuench
| Kind::Redirect => match IpV4Packet::new(self.payload()) {
Ok(d) => Description::Ip(d),
Err(_) => Description::Other(self.payload()),
},
Kind::TimestampRequest | Kind::TimestampReply => {
let mut buffer = Cursor::new(self.payload());
Description::Timestamp(
buffer.read_u32::<BigEndian>().unwrap(),
buffer.read_u32::<BigEndian>().unwrap(),
buffer.read_u32::<BigEndian>().unwrap(),
)
}
_ => Description::Other(self.payload()),
}
}
}
impl<B: AsRef<[u8]>> fmt::Debug for IcmpPacket<B> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct(if self.is_valid() {
"icmp::Packet"
} else {
"icmp::Packet!"
})
.field("kind", &self.kind())
.field("code", &self.code())
.field("checksum", &self.checksum())
.field("payload", &self.payload())
.finish()
}
}
#[derive(Debug)]
pub enum HeaderOther {
/// 全零
Unused(u8, u8, u8, u8),
/// If code = 0, identifies the octet where an error was detected.
Pointer(u8),
/// Address of the gateway to which traffic for the network specified
/// in the internet destination network field of the original
/// datagram's data should be sent.
Address(u8, u8, u8, u8),
/// Identifier | Sequence Number
Identifier(u16, u16),
UnKnown(u8, u8, u8, u8),
}
pub enum Description<B> {
Ip(IpV4Packet<B>),
///时间戳 Originate Timestamp,Receive Timestamp,Transmit Timestamp
Timestamp(u32, u32, u32),
Other(B),
}
impl<B: AsRef<[u8]> + std::fmt::Debug> fmt::Debug for Description<B> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Description::Ip(packet) => f.debug_struct(&format!("{:?}", packet)).finish(),
Description::Timestamp(originate, receive, transmit) => f
.debug_struct("")
.field("originate", originate)
.field("receive", receive)
.field("transmit", transmit)
.finish(),
Description::Other(bytes) => f.debug_struct(&format!("{:?}", bytes)).finish(),
}
}
}
-414
View File
@@ -1,414 +0,0 @@
pub mod icmp;
#[derive(Eq, PartialEq, Copy, Clone, Debug)]
pub enum Kind {
/// ping应答,type=0
/*
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Type | Code | Checksum |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Identifier | Sequence Number |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Data ...
+-+-+-+-+-
*/
EchoReply,
/// 目的地不可达,差错报文的一种,路由器收到一个不能转发的数据报,会向源地址返回这个报文,type=3
/*
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Type | Code | Checksum |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| unused |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Internet Header + 64 bits of Original Data Datagram |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
*/
DestinationUnreachable,
/// 源抑制报文,用于防止接收端缓存溢出,接收设备发送这个来请求源设备降低发送速度,type=4
/*
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Type | Code | Checksum |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| unused |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Internet Header + 64 bits of Original Data Datagram |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
*/
SourceQuench,
/// 重定向报文,当路由器接收包的接口正好是去往目的地的出口时,会向源地址发送重定向报文,告知源直接将数据发往自己的下一跳,type=5
/*
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Type | Code | Checksum |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Gateway Internet Address |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Internet Header + 64 bits of Original Data Datagram |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
*/
Redirect,
/// ping请求,type=8
EchoRequest,
/// 路由器通告,type=9,
RouterAdvertisement,
/// 路由器请求,type=10
RouterSolicitation,
/// 报文ttl为0后,路由器会向源发送此报文,type=11
/// Tracert工作原理:
/// 首先向目的地发送ttl=1的包,下一跳路由器收到后ttl-1,此时ttl=0,将向源发送 ICMP time exceeded
/// 再发送ttl=2的包,以此类推,直到目标主机接收到改包,此时不会回复ICMP time exceeded,代表已经探测到目的地
/*
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Type | Code | Checksum |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| unused |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Internet Header + 64 bits of Original Data Datagram |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
*/
TimeExceeded,
/// 参数错误,数据有误、校验和不对等,type=12
/*
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Type | Code | Checksum |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Pointer | unused |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Internet Header + 64 bits of Original Data Datagram |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
注:Pointer指示错误的位置
*/
ParameterProblem,
/// 时间戳请求,type=13
/*
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Type | Code | Checksum |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Identifier | Sequence Number |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Originate Timestamp |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Receive Timestamp |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Transmit Timestamp |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
*/
TimestampRequest,
/// 时间戳响应,type=14
TimestampReply,
/// 信息请求,type=15
/*
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Type | Code | Checksum |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Identifier | Sequence Number |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
*/
InformationRequest,
/// 信息响应,type=16
InformationReply,
/// 地址掩码请求,type=17
AddressMaskRequest,
/// 地址掩码应答,type=18
AddressMaskReply,
///
TraceRoute,
///
Unknown(u8),
}
impl From<u8> for Kind {
fn from(value: u8) -> Kind {
use self::Kind::*;
match value {
0 => EchoReply,
3 => DestinationUnreachable,
4 => SourceQuench,
5 => Redirect,
8 => EchoRequest,
9 => RouterAdvertisement,
10 => RouterSolicitation,
11 => TimeExceeded,
12 => ParameterProblem,
13 => TimestampRequest,
14 => TimestampReply,
15 => InformationRequest,
16 => InformationReply,
17 => AddressMaskRequest,
18 => AddressMaskReply,
30 => TraceRoute,
v => Unknown(v),
}
}
}
impl Into<u8> for Kind {
fn into(self) -> u8 {
use self::Kind::*;
match self {
EchoReply => 0,
DestinationUnreachable => 3,
SourceQuench => 4,
Redirect => 5,
EchoRequest => 8,
RouterAdvertisement => 9,
RouterSolicitation => 10,
TimeExceeded => 11,
ParameterProblem => 12,
TimestampRequest => 13,
TimestampReply => 14,
InformationRequest => 15,
InformationReply => 16,
AddressMaskRequest => 17,
AddressMaskReply => 18,
TraceRoute => 30,
Unknown(v) => v,
}
}
}
#[derive(Eq, PartialEq, Copy, Clone, Debug)]
pub enum Code {
DestinationUnreachable(DestinationUnreachable),
Redirect(Redirect),
ParameterProblem(ParameterProblem),
Other(u8),
}
impl Code {
pub fn from(kind: Kind, code: u8) -> Code {
match kind {
Kind::DestinationUnreachable => {
Code::DestinationUnreachable(DestinationUnreachable::from(code))
}
Kind::Redirect => Code::Redirect(Redirect::from(code)),
Kind::ParameterProblem => Code::ParameterProblem(ParameterProblem::from(code)),
_ => Code::Other(code),
}
}
}
#[derive(Eq, PartialEq, Copy, Clone, Debug)]
pub enum DestinationUnreachable {
/// 网络不可达
DestinationNetworkUnreachable,
/// 主机不可达
DestinationHostUnreachable,
/// 协议不可达
DestinationProtocolUnreachable,
/// 端口不可达
DestinationPortUnreachable,
/// 需要进行分片但设置不分片比特
FragmentationRequired,
/// 源站选路失败
SourceRouteFailed,
/// 目的网络未知
DestinationNetworkUnknown,
/// 目的主机未知
DestinationHostUnknown,
/// 源主机被隔离(作废不用)
SourceHostIsolated,
/// 目的网络被强制禁止
NetworkAdministrativelyProhibited,
/// 目的主机被强制禁止
HostAdministrativelyProhibited,
/// 由于服务类型TOS,网络不可达
NetworkUnreachableForTos,
/// 由于服务类型TOS,主机不可达
HostUnreachableForTos,
/// 由于过滤,通信被强制禁止
CommunicationAdministrativelyProhibited,
/// 主机越权
HostPrecedenceViolation,
/// 优先中止生效
PrecedentCutoffInEffect,
///
Unknown(u8),
}
/// Codes for Redirect Message packets.
#[derive(Eq, PartialEq, Copy, Clone, Debug)]
pub enum Redirect {
/// 对网络重定向
RedirectDatagramForNetwork,
/// 对主机重定向
RedirectDatagramForHost,
/// 对服务类型和网络重定向
RedirectDatagramForTosAndNetwork,
/// 对服务类型和主机重定向
RedirectDatagramForTosAndHost,
///
Unknown(u8),
}
/// Codes for TimeExceeded Message packets.
#[derive(Eq, PartialEq, Copy, Clone, Debug)]
pub enum TimeExceeded {
/// TTL超时报文
Transit,
/// 分片重组超时报文
Reassembly,
///
Unknown(u8),
}
/// Codes for Parameter Problem packets.
#[derive(Eq, PartialEq, Copy, Clone, Debug)]
pub enum ParameterProblem {
/// 坏的IP首部(包括各种差错)
PointerIndicatesError,
/// 缺少必需的选项
MissingRequiredData,
/// 长度错误
BadLength,
///
Unknown(u8),
}
impl From<u8> for DestinationUnreachable {
fn from(value: u8) -> Self {
use self::DestinationUnreachable::*;
match value {
0 => DestinationNetworkUnreachable,
1 => DestinationHostUnreachable,
2 => DestinationProtocolUnreachable,
3 => DestinationPortUnreachable,
4 => FragmentationRequired,
5 => SourceRouteFailed,
6 => DestinationNetworkUnknown,
7 => DestinationHostUnknown,
8 => SourceHostIsolated,
9 => NetworkAdministrativelyProhibited,
10 => HostAdministrativelyProhibited,
11 => NetworkUnreachableForTos,
12 => HostUnreachableForTos,
13 => CommunicationAdministrativelyProhibited,
14 => HostPrecedenceViolation,
15 => PrecedentCutoffInEffect,
v => Unknown(v),
}
}
}
impl Into<u8> for DestinationUnreachable {
fn into(self) -> u8 {
use self::DestinationUnreachable::*;
match self {
DestinationNetworkUnreachable => 0,
DestinationHostUnreachable => 1,
DestinationProtocolUnreachable => 2,
DestinationPortUnreachable => 3,
FragmentationRequired => 4,
SourceRouteFailed => 5,
DestinationNetworkUnknown => 6,
DestinationHostUnknown => 7,
SourceHostIsolated => 8,
NetworkAdministrativelyProhibited => 9,
HostAdministrativelyProhibited => 10,
NetworkUnreachableForTos => 11,
HostUnreachableForTos => 12,
CommunicationAdministrativelyProhibited => 13,
HostPrecedenceViolation => 14,
PrecedentCutoffInEffect => 15,
Unknown(v) => v,
}
}
}
impl From<u8> for Redirect {
fn from(value: u8) -> Self {
use self::Redirect::*;
match value {
0 => RedirectDatagramForNetwork,
1 => RedirectDatagramForHost,
2 => RedirectDatagramForTosAndNetwork,
3 => RedirectDatagramForTosAndHost,
v => Unknown(v),
}
}
}
impl Into<u8> for Redirect {
fn into(self) -> u8 {
use self::Redirect::*;
match self {
RedirectDatagramForNetwork => 0,
RedirectDatagramForHost => 1,
RedirectDatagramForTosAndNetwork => 2,
RedirectDatagramForTosAndHost => 3,
Unknown(v) => v,
}
}
}
impl From<u8> for TimeExceeded {
fn from(value: u8) -> Self {
use self::TimeExceeded::*;
match value {
0 => Transit,
1 => Reassembly,
v => Unknown(v),
}
}
}
impl Into<u8> for TimeExceeded {
fn into(self) -> u8 {
use self::TimeExceeded::*;
match self {
Transit => 0,
Reassembly => 1,
Unknown(v) => v,
}
}
}
impl From<u8> for ParameterProblem {
fn from(value: u8) -> Self {
use self::ParameterProblem::*;
match value {
0 => PointerIndicatesError,
1 => MissingRequiredData,
2 => BadLength,
v => Unknown(v),
}
}
}
impl Into<u8> for ParameterProblem {
fn into(self) -> u8 {
use self::ParameterProblem::*;
match self {
PointerIndicatesError => 0,
MissingRequiredData => 1,
BadLength => 2,
Unknown(v) => v,
}
}
}
-2
View File
@@ -1,2 +0,0 @@
pub mod packet;
pub mod protocol;
-250
View File
@@ -1,250 +0,0 @@
use std::fmt;
use std::net::Ipv4Addr;
use byteorder::{BigEndian, ReadBytesExt};
use crate::cal_checksum;
use crate::error::*;
use crate::ip::ipv4::protocol::Protocol;
/// ip协议
/*
RFC: 791 https://www.ietf.org/rfc/rfc791.txt
0 15 31
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 版本(4) | 头部长度(4) | 服务类型(8) | 总字节数(16) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 标识(16) | 标志(3) | 片偏移(13) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 生存时间(8) | 协议(8) | 头部校验和(16) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 源ip地址(32) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 目的ip地址(32) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 选项 + 填充 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
数据体
注:头部长度单位是4字节,所以ip头最长60字节,选项最长40字节,选项填充按4字节对齐
*/
pub struct IpV4Packet<B> {
pub buffer: B,
}
impl<B: AsRef<[u8]>> IpV4Packet<B> {
pub fn unchecked(buffer: B) -> Self {
Self { buffer }
}
pub fn new(buffer: B) -> Result<Self> {
if buffer.as_ref()[0] >> 4 != 4 {
Err(Error::Unimplemented)?
}
if buffer.as_ref().len() < 20 {
Err(Error::SmallBuffer)?
}
let packet = Self::unchecked(buffer);
if packet.buffer.as_ref().len() < packet.header_len() as usize * 4 {
Err(Error::SmallBuffer)?
}
Ok(packet)
}
}
impl<B: AsRef<[u8]>> IpV4Packet<B> {
pub fn header(&self) -> &[u8] {
&self.buffer.as_ref()[..(self.header_len() as usize * 4)]
}
pub fn payload(&self) -> &[u8] {
&self.buffer.as_ref()[(self.header_len() as usize * 4)..]
// match self.protocol() {
// Protocol::Udp => {
// let udp = UdpPacket::new(IpAddr::V4(self.source_ip()),
// IpAddr::V4(self.destination_ip()),
// &self.buffer.as_ref()[(self.header_len() as usize * 4)..])?;
// Ok(crate::IpUpperLayer::UDP(udp))
// }
// _ => {
// Ok(crate::IpUpperLayer::Unknown(self.buffer.as_ref()));
// }
// }
}
}
impl<B: AsRef<[u8]> + AsMut<[u8]>> IpV4Packet<B> {
pub fn header_mut(&mut self) -> &mut [u8] {
let len = self.header_len() as usize * 4;
&mut self.buffer.as_mut()[..len]
}
pub fn payload_mut(&mut self) -> &mut [u8] {
let len = self.header_len() as usize * 4;
&mut self.buffer.as_mut()[len..]
}
pub fn set_source_ip(&mut self, value: Ipv4Addr) {
self.header_mut()[12..16].copy_from_slice(&value.octets());
}
pub fn set_destination_ip(&mut self, value: Ipv4Addr) {
self.header_mut()[16..20].copy_from_slice(&value.octets());
}
fn set_checksum(&mut self, value: u16) {
self.header_mut()[10..12].copy_from_slice(&value.to_be_bytes())
}
/// 更新校验和
pub fn update_checksum(&mut self) {
//先将校验和置0
self.set_checksum(0);
self.set_checksum(cal_checksum(self.header()))
}
}
impl<B: AsRef<[u8]>> IpV4Packet<B> {
/// 版本号,ipv4的为4
pub fn version(&self) -> u8 {
self.buffer.as_ref()[0] >> 4
}
/// 头部长度,以4字节为单位
pub fn header_len(&self) -> u8 {
self.buffer.as_ref()[0] & 0b1111
}
/// 差异化服务编码点
///
/// 类别(3)+丢失概率(2)+用途(1)
///
///
/// 类别子字段值 | 名称
/// ---|:---
/// 000 | 常规(Routine)
/// 001 | 优先(Priority)
/// 010 | 立即(Immediate)
/// 011 | 瞬间(Flash)
/// 100 | 瞬间覆盖(Flash Override)
/// 101 | 严重(CRITIC/ECP)
/// 110 | 网间控制(Internetwork Control)
/// 111 | 网络控制(Network Control)
///
///
/// 参考:https://www.modb.pro/db/477116
pub fn dscp(&self) -> u8 {
self.buffer.as_ref()[1] >> 2
}
/// 显示拥塞 00:发送主机不支持ECN 01或者10:发送主机支持ECN 11:路由器正在经历拥塞
pub fn ecn(&self) -> u8 {
self.buffer.as_ref()[1] & 0b11
}
/// ip报总字节数
pub fn length(&self) -> u16 {
(&self.buffer.as_ref()[2..])
.read_u16::<BigEndian>()
.unwrap()
}
/// 标识. ip报文在数据链路层可能会被拆分,同一报文的不同分组标识字段相同
pub fn id(&self) -> u16 {
(&self.buffer.as_ref()[4..])
.read_u16::<BigEndian>()
.unwrap()
}
/// 标志 3位.
/// 第1位没有使用
/// 第2位表示不分段位(DF
/// 0:允许数据报分段
/// 1:数据报不能分段
/// 置1之后路由器不能对其分段处理,如果超过MTU值则路由器不能对其转发将其丢弃,并向源点发送错误消息
/// 第3位表示更多段位
/// 0:数据包后面没有包,该包为最后的包
/// 1:数据包后面有更多的包
pub fn flags(&self) -> u8 {
self.buffer.as_ref()[6] >> 5
}
/// 片偏移 13位.
/// 以字节为单位,用于指明分段起始点相对于包头起始点的偏移量
/// 由于分段到达时可能错序,所以分段的偏移字段可以使接收者按照正确的顺序重组数据包
pub fn offset(&self) -> u16 {
(&self.buffer.as_ref()[6..])
.read_u16::<BigEndian>()
.unwrap()
& 0x1fff
}
/// 生存时间.
/// 每一跳 减1 到0了则会被丢弃
pub fn ttl(&self) -> u8 {
self.buffer.as_ref()[8]
}
/// 协议.
pub fn protocol(&self) -> Protocol {
self.buffer.as_ref()[9].into()
}
/// 首部校验和
pub fn checksum(&self) -> u16 {
(&self.buffer.as_ref()[10..])
.read_u16::<BigEndian>()
.unwrap()
}
/// 验证校验和
///
/// TCP/IP协议栈不会自己计算校验和,而是简单地将一个空的校验和字段(零或随机填充)交给网卡硬件。
/// 所以抓到发出去的包校验和可能是错误的
pub fn is_valid(&self) -> bool {
self.checksum() == 0 || cal_checksum(self.header()) == 0
}
/// 源ip.
pub fn source_ip(&self) -> Ipv4Addr {
Ipv4Addr::new(
self.buffer.as_ref()[12],
self.buffer.as_ref()[13],
self.buffer.as_ref()[14],
self.buffer.as_ref()[15],
)
}
/// 目标ip.
pub fn destination_ip(&self) -> Ipv4Addr {
Ipv4Addr::new(
self.buffer.as_ref()[16],
self.buffer.as_ref()[17],
self.buffer.as_ref()[18],
self.buffer.as_ref()[19],
)
}
/// 选项.
pub fn options(&self) -> &[u8] {
&self.buffer.as_ref()[20..(self.header_len() as usize * 4)]
}
}
impl<B: AsRef<[u8]>> fmt::Debug for IpV4Packet<B> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("ip::v4::Packet")
.field("version", &self.version())
.field("header_len", &self.header_len())
.field("dscp", &self.dscp())
.field("ecn", &self.ecn())
.field("length", &self.length())
.field("id", &self.id())
.field("flags", &self.flags())
.field("offset", &self.offset())
.field("ttl", &self.ttl())
.field("protocol", &self.protocol())
.field("checksum", &self.checksum())
.field("is_valid", &self.is_valid())
.field("source", &self.source_ip())
.field("destination", &self.destination_ip())
.field("options", &self.options())
.field("payload", &self.payload())
.finish()
}
}
-742
View File
@@ -1,742 +0,0 @@
#[derive(Eq, PartialEq, Copy, Clone, Debug)]
pub enum Protocol {
///
Hopopt,
///
Icmp,
///
Igmp,
///
Ggp,
///
Ipv4,
///
St,
///
Tcp,
///
Cbt,
///
Egp,
///
Igp,
///
BbnRccMon,
///
NvpII,
///
Pup,
///
Argus,
///
Emcon,
///
Xnet,
///
Chaos,
///
Udp,
///
Mux,
///
DcnMeas,
///
Hmp,
///
Prm,
///
XnsIdp,
///
Trunk1,
///
Trunk2,
///
Leaf1,
///
Leaf2,
///
Rdp,
///
Irtp,
///
IsoTp4,
///
Netblt,
///
MfeNsp,
///
MeritInp,
///
Dccp,
///
ThreePc,
///
Idpr,
///
Xtp,
///
Ddp,
///
IdprCmtp,
///
TpPlusPlus,
///
Il,
///
Ipv6,
///
Sdrp,
///
Ipv6Route,
///
Ipv6Frag,
///
Idrp,
///
Rsvp,
///
Gre,
///
Dsr,
///
Bna,
///
Esp,
///
Ah,
///
INlsp,
///
Swipe,
///
Narp,
///
Mobile,
///
Tlsp,
///
Skip,
///
Ipv6Icmp,
///
Ipv6NoNxt,
///
Ipv6Opts,
///
HostInternal,
///
Cftp,
///
LocalNetwork,
///
SatExpak,
///
Kryptolan,
///
Rvd,
///
Ippc,
///
DistributedFs,
///
SatMon,
///
Visa,
///
Ipcv,
///
Cpnx,
///
Cphb,
///
Wsn,
///
Pvp,
///
BrSatMon,
///
SunNd,
///
WbMon,
///
WbExpak,
///
IsoIp,
///
Vmtp,
///
SecureVmtp,
///
Vines,
///
TtpOrIptm,
///
NsfnetIgp,
///
Dgp,
///
Tcf,
///
Eigrp,
///
OspfigP,
///
SpriteRpc,
///
Larp,
///
Mtp,
///
Ax25,
///
IpIp,
///
Micp,
///
SccSp,
///
Etherip,
///
Encap,
///
PrivEncryption,
///
Gmtp,
///
Ifmp,
///
Pnni,
///
Pim,
///
Aris,
///
Scps,
///
Qnx,
///
AN,
///
IpComp,
///
Snp,
///
CompaqPeer,
///
IpxInIp,
///
Vrrp,
///
Pgm,
///
ZeroHop,
///
L2tp,
///
Ddx,
///
Iatp,
///
Stp,
///
Srp,
///
Uti,
///
Smp,
///
Sm,
///
Ptp,
///
IsisOverIpv4,
///
Fire,
///
Crtp,
///
Crudp,
///
Sscopmce,
///
Iplt,
///
Sps,
///
Pipe,
///
Sctp,
///
Fc,
///
RsvpE2eIgnore,
///
MobilityHeader,
///
UdpLite,
///
MplsInIp,
///
Manet,
///
Hip,
///
Shim6,
///
Wesp,
Rohc,
Test1,
Test2,
Unknown(u8),
}
impl From<u8> for Protocol {
fn from(value: u8) -> Protocol {
use self::Protocol::*;
match value {
0 => Hopopt,
1 => Icmp,
2 => Igmp,
3 => Ggp,
4 => Ipv4,
5 => St,
6 => Tcp,
7 => Cbt,
8 => Egp,
9 => Igp,
10 => BbnRccMon,
11 => NvpII,
12 => Pup,
13 => Argus,
14 => Emcon,
15 => Xnet,
16 => Chaos,
17 => Udp,
18 => Mux,
19 => DcnMeas,
20 => Hmp,
21 => Prm,
22 => XnsIdp,
23 => Trunk1,
24 => Trunk2,
25 => Leaf1,
26 => Leaf2,
27 => Rdp,
28 => Irtp,
29 => IsoTp4,
30 => Netblt,
31 => MfeNsp,
32 => MeritInp,
33 => Dccp,
34 => ThreePc,
35 => Idpr,
36 => Xtp,
37 => Ddp,
38 => IdprCmtp,
39 => TpPlusPlus,
40 => Il,
41 => Ipv6,
42 => Sdrp,
43 => Ipv6Route,
44 => Ipv6Frag,
45 => Idrp,
46 => Rsvp,
47 => Gre,
48 => Dsr,
49 => Bna,
50 => Esp,
51 => Ah,
52 => INlsp,
53 => Swipe,
54 => Narp,
55 => Mobile,
56 => Tlsp,
57 => Skip,
58 => Ipv6Icmp,
59 => Ipv6NoNxt,
60 => Ipv6Opts,
61 => HostInternal,
62 => Cftp,
63 => LocalNetwork,
64 => SatExpak,
65 => Kryptolan,
66 => Rvd,
67 => Ippc,
68 => DistributedFs,
69 => SatMon,
70 => Visa,
71 => Ipcv,
72 => Cpnx,
73 => Cphb,
74 => Wsn,
75 => Pvp,
76 => BrSatMon,
77 => SunNd,
78 => WbMon,
79 => WbExpak,
80 => IsoIp,
81 => Vmtp,
82 => SecureVmtp,
83 => Vines,
84 => TtpOrIptm,
85 => NsfnetIgp,
86 => Dgp,
87 => Tcf,
88 => Eigrp,
89 => OspfigP,
90 => SpriteRpc,
91 => Larp,
92 => Mtp,
93 => Ax25,
94 => IpIp,
95 => Micp,
96 => SccSp,
97 => Etherip,
98 => Encap,
99 => PrivEncryption,
100 => Gmtp,
101 => Ifmp,
102 => Pnni,
103 => Pim,
104 => Aris,
105 => Scps,
106 => Qnx,
107 => AN,
108 => IpComp,
109 => Snp,
110 => CompaqPeer,
111 => IpxInIp,
112 => Vrrp,
113 => Pgm,
114 => ZeroHop,
115 => L2tp,
116 => Ddx,
117 => Iatp,
118 => Stp,
119 => Srp,
120 => Uti,
121 => Smp,
122 => Sm,
123 => Ptp,
124 => IsisOverIpv4,
125 => Fire,
126 => Crtp,
127 => Crudp,
128 => Sscopmce,
129 => Iplt,
130 => Sps,
131 => Pipe,
132 => Sctp,
133 => Fc,
134 => RsvpE2eIgnore,
135 => MobilityHeader,
136 => UdpLite,
137 => MplsInIp,
138 => Manet,
139 => Hip,
140 => Shim6,
141 => Wesp,
142 => Rohc,
253 => Test1,
254 => Test2,
p => Unknown(p),
}
}
}
impl Into<u8> for Protocol {
fn into(self) -> u8 {
use self::Protocol::*;
match self {
Hopopt => 0,
Icmp => 1,
Igmp => 2,
Ggp => 3,
Ipv4 => 4,
St => 5,
Tcp => 6,
Cbt => 7,
Egp => 8,
Igp => 9,
BbnRccMon => 10,
NvpII => 11,
Pup => 12,
Argus => 13,
Emcon => 14,
Xnet => 15,
Chaos => 16,
Udp => 17,
Mux => 18,
DcnMeas => 19,
Hmp => 20,
Prm => 21,
XnsIdp => 22,
Trunk1 => 23,
Trunk2 => 24,
Leaf1 => 25,
Leaf2 => 26,
Rdp => 27,
Irtp => 28,
IsoTp4 => 29,
Netblt => 30,
MfeNsp => 31,
MeritInp => 32,
Dccp => 33,
ThreePc => 34,
Idpr => 35,
Xtp => 36,
Ddp => 37,
IdprCmtp => 38,
TpPlusPlus => 39,
Il => 40,
Ipv6 => 41,
Sdrp => 42,
Ipv6Route => 43,
Ipv6Frag => 44,
Idrp => 45,
Rsvp => 46,
Gre => 47,
Dsr => 48,
Bna => 49,
Esp => 50,
Ah => 51,
INlsp => 52,
Swipe => 53,
Narp => 54,
Mobile => 55,
Tlsp => 56,
Skip => 57,
Ipv6Icmp => 58,
Ipv6NoNxt => 59,
Ipv6Opts => 60,
HostInternal => 61,
Cftp => 62,
LocalNetwork => 63,
SatExpak => 64,
Kryptolan => 65,
Rvd => 66,
Ippc => 67,
DistributedFs => 68,
SatMon => 69,
Visa => 70,
Ipcv => 71,
Cpnx => 72,
Cphb => 73,
Wsn => 74,
Pvp => 75,
BrSatMon => 76,
SunNd => 77,
WbMon => 78,
WbExpak => 79,
IsoIp => 80,
Vmtp => 81,
SecureVmtp => 82,
Vines => 83,
TtpOrIptm => 84,
NsfnetIgp => 85,
Dgp => 86,
Tcf => 87,
Eigrp => 88,
OspfigP => 89,
SpriteRpc => 90,
Larp => 91,
Mtp => 92,
Ax25 => 93,
IpIp => 94,
Micp => 95,
SccSp => 96,
Etherip => 97,
Encap => 98,
PrivEncryption => 99,
Gmtp => 100,
Ifmp => 101,
Pnni => 102,
Pim => 103,
Aris => 104,
Scps => 105,
Qnx => 106,
AN => 107,
IpComp => 108,
Snp => 109,
CompaqPeer => 110,
IpxInIp => 111,
Vrrp => 112,
Pgm => 113,
ZeroHop => 114,
L2tp => 115,
Ddx => 116,
Iatp => 117,
Stp => 118,
Srp => 119,
Uti => 120,
Smp => 121,
Sm => 122,
Ptp => 123,
IsisOverIpv4 => 124,
Fire => 125,
Crtp => 126,
Crudp => 127,
Sscopmce => 128,
Iplt => 129,
Sps => 130,
Pipe => 131,
Sctp => 132,
Fc => 133,
RsvpE2eIgnore => 134,
MobilityHeader => 135,
UdpLite => 136,
MplsInIp => 137,
Manet => 138,
Hip => 139,
Shim6 => 140,
Wesp => 141,
Rohc => 142,
Test1 => 253,
Test2 => 254,
Unknown(p) => p,
}
}
}
-18
View File
@@ -1,18 +0,0 @@
use ipv4::packet::IpV4Packet;
use crate::error::*;
pub mod ipv4;
pub enum IpPacket<B> {
V4(IpV4Packet<B>),
}
impl<B: AsRef<[u8]>> IpPacket<B> {
pub fn new(buffer: B) -> Result<Self> {
match buffer.as_ref()[0] >> 4 {
4 => Ok(IpPacket::V4(IpV4Packet::new(buffer)?)),
_ => Err(Error::InvalidPacket),
}
}
}
-146
View File
@@ -1,146 +0,0 @@
use std::net::Ipv4Addr;
use byteorder::BigEndian;
use byteorder::ReadBytesExt;
pub mod error;
pub mod icmp;
pub mod ip;
pub mod tcp;
pub mod udp;
// pub enum IpUpperLayer<B> {
// UDP(UdpPacket<B>),
// Unknown(B),
// }
//
// impl<B: AsRef<[u8]>> fmt::Debug for IpUpperLayer<B> {
// fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
// match self {
// IpUpperLayer::UDP(p) => {
// f.debug_struct("udp::Packet")
// .field("data", p).finish()
// }
// IpUpperLayer::Unknown(p) => {
// f.debug_struct("Unknown")
// .field("data", &p.as_ref()).finish()
// }
// }
// }
// }
/// https://datatracker.ietf.org/doc/html/rfc1071 4.1节
///
/// 计算校验和,各协议都是通用的
/// 计算:
/// 首先将校验和置0,然后对首部每个16位数进行二进制反码求和,
/// 得到校验和之后,持续取高16位加到低16位,直到高16位全为0
/// 最后取反
///
/// 校验:
/// 在已有校验和的情况下,再计算校验和,正确的数据计算得到的值为0
/*
unsigned short getChecksum(unsigned short * iphead, int count)
{
unsigned long int sum = 0;
unsigned short checksum = 0;
printf("\nStarting adress: %p\n", iphead);
while(count > 1) {
sum += * (unsigned short *) (iphead);
count -=2;
printf("a: %p, content is: %d, new sum: %ld\n", iphead, (unsigned short) *(iphead), sum);
iphead++;
}
if(count > 0) {
sum += * (unsigned short *) (iphead);
}
while(sum >> 16) {
sum = (sum & 0xffff) + (sum >> 16);
}
checksum = ~sum;
return checksum;
}
*/
pub fn cal_checksum(buffer: &[u8]) -> u16 {
use std::io::Cursor;
let mut sum = 0;
let length = buffer.len();
let mut buffer = Cursor::new(buffer);
while let Ok(value) = buffer.read_u16::<BigEndian>() {
sum += u32::from(value);
}
if length & 1 == 1 {
//奇数,说明还有一位,不足的补0
sum += u32c(buffer.read_u8().unwrap(), 0);
}
while sum >> 16 != 0 {
sum = (sum & 0xffff) + (sum >> 16);
}
!sum as u16
}
/// ipv4上层协议校验和计算方式
/// ipv4 udp伪首部 用于参与计算首部校验和
/*
0 7 8 15 16 23 24 31
+--------+--------+--------+--------+
| source address |
+--------+--------+--------+--------+
| destination address |
+--------+--------+--------+--------+
| zero |protocol| length |
+--------+--------+--------+--------+
*/
pub fn ipv4_cal_checksum(
buffer: &[u8],
src_ip: &Ipv4Addr,
dest_ip: &Ipv4Addr,
protocol: u8,
length: u16,
) -> u16 {
use std::io::Cursor;
let mut sum = 0;
let src_ip = src_ip.octets();
sum += u32c(src_ip[0], src_ip[1]);
sum += u32c(src_ip[2], src_ip[3]);
let dest_ip = dest_ip.octets();
sum += u32c(dest_ip[0], dest_ip[1]);
sum += u32c(dest_ip[2], dest_ip[3]);
sum += u32c(0, protocol);
sum += length as u32;
let mut buffer = Cursor::new(buffer);
while let Ok(value) = buffer.read_u16::<BigEndian>() {
sum += u32::from(value);
}
if length & 1 == 1 {
//奇数,说明还有一位
sum += u32c(buffer.read_u8().unwrap(), 0);
}
while sum >> 16 != 0 {
sum = (sum & 0xffff) + (sum >> 16);
}
!sum as u16
}
#[inline]
fn u32c(x: u8, y: u8) -> u32 {
((x as u32) << 8) | y as u32
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn it_works() {
let sum = cal_checksum(&[255, 255]);
println!("{:?}", sum);
}
}
-42
View File
@@ -1,42 +0,0 @@
use std::fmt;
pub mod tcp;
pub struct Flags(u8);
pub const FIN: u8 = 0b0000_0001;
pub const SYN: u8 = 0b0000_0010;
pub const RST: u8 = 0b0000_0100;
pub const PSH: u8 = 0b0000_1000;
pub const ACK: u8 = 0b0001_0000;
pub const URG: u8 = 0b0010_0000;
impl fmt::Debug for Flags {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut str = String::with_capacity(22);
if self.0 & URG != 0 {
str.push_str("URG|");
}
if self.0 & ACK != 0 {
str.push_str("ACK|");
}
if self.0 & PSH != 0 {
str.push_str("PSH|");
}
if self.0 & RST != 0 {
str.push_str("RST|");
}
if self.0 & SYN != 0 {
str.push_str("SYN|");
}
if self.0 & FIN != 0 {
str.push_str("FIN|");
}
if str.is_empty() {
f.debug_struct("NULL").finish()
} else {
let len = str.len() - 1;
f.debug_struct(&str[..len]).finish()
}
}
}
-175
View File
@@ -1,175 +0,0 @@
use std::fmt;
use std::net::IpAddr;
use byteorder::{BigEndian, ReadBytesExt};
use crate::error::*;
use crate::tcp::Flags;
/// tcp
/*
https://www.rfc-editor.org/rfc/rfc793
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Source Port | Destination Port |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Sequence Number |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Acknowledgment Number |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Data | |U|A|P|R|S|F| |
| Offset| Reserved |R|C|S|S|Y|I| Window |
| | |G|K|H|T|N|N| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Checksum | Urgent Pointer |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Options | Padding |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| data |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Source Port: 16位 源端口
Destination Port:16位 目的端口
Sequence Number:32位 序列号,如果存在syn标志,则为初始序列号
Acknowledgment Number:32位 如果设置了ack标志,这个表示确认收到的序号
Data Offset:4位 数据的开始偏移位,单位是4字节
Reserved:6位 未使用,全零
控制位:6位 从左到右
URG: 紧急指针 表示数据要优先处理
ACK: 确认位
PSH: 推送 要求把数据尽快的交给应用层,不做处理
RST: 重置连接
SYN: 同步序列号
FIN: 结束发送
Window: 16位 能接收的数据大小
Checksum:16位 校验和,需要加入伪首部
Urgent Pointer:16位 紧急指针
Options+Padding:32位整数倍,最多40个字节
*/
pub struct TcpPacket<B> {
source_ip: IpAddr,
destination_ip: IpAddr,
buffer: B,
}
impl<B: AsRef<[u8]>> TcpPacket<B> {
pub fn unchecked(source_ip: IpAddr, destination_ip: IpAddr, buffer: B) -> TcpPacket<B> {
TcpPacket {
source_ip,
destination_ip,
buffer,
}
}
pub fn new(source_ip: IpAddr, destination_ip: IpAddr, buffer: B) -> Result<TcpPacket<B>> {
let packet = TcpPacket::unchecked(source_ip, destination_ip, buffer);
if packet.buffer.as_ref().len() < 20 {
Err(Error::SmallBuffer)?
}
if packet.buffer.as_ref().len() < packet.data_offset() as usize * 4 {
Err(Error::SmallBuffer)?
}
Ok(packet)
}
}
impl<B: AsRef<[u8]>> TcpPacket<B> {
/// 源端口
pub fn source_port(&self) -> u16 {
(&self.buffer.as_ref()[0..])
.read_u16::<BigEndian>()
.unwrap()
}
/// 目标端口
pub fn destination_port(&self) -> u16 {
(&self.buffer.as_ref()[2..])
.read_u16::<BigEndian>()
.unwrap()
}
/// 序列号
pub fn sequence(&self) -> u32 {
(&self.buffer.as_ref()[4..])
.read_u32::<BigEndian>()
.unwrap()
}
/// 确认号
pub fn acknowledgment(&self) -> u32 {
(&self.buffer.as_ref()[8..])
.read_u32::<BigEndian>()
.unwrap()
}
/// 数据偏移 4字节为单位
pub fn data_offset(&self) -> u8 {
self.buffer.as_ref()[12] >> 4
}
pub fn flags(&self) -> Flags {
Flags(self.buffer.as_ref()[13])
}
pub fn window(&self) -> u16 {
(&self.buffer.as_ref()[14..])
.read_u16::<BigEndian>()
.unwrap()
}
pub fn checksum(&self) -> u16 {
(&self.buffer.as_ref()[16..])
.read_u16::<BigEndian>()
.unwrap()
}
/// 验证校验和,ipv4中为0表示不使用校验和,ipv6校验和不能为0
/// TCP/IP协议栈不会自己计算校验和,而是简单地将一个空的校验和字段(零或随机填充)交给网卡硬件。
/// 所以抓到发出去的包校验和可能是错误的
pub fn is_valid(&self) -> bool {
self.checksum() == 0 || self.cal_checksum() == 0
}
fn cal_checksum(&self) -> u16 {
match self.source_ip {
IpAddr::V4(src) => {
if let IpAddr::V4(dest) = self.destination_ip {
return crate::ipv4_cal_checksum(
self.buffer.as_ref(),
&src,
&dest,
6,
self.buffer.as_ref().len() as u16,
);
}
}
IpAddr::V6(_src) => {}
}
unimplemented!()
}
pub fn urgent_pointer(&self) -> u16 {
(&self.buffer.as_ref()[18..])
.read_u16::<BigEndian>()
.unwrap()
}
pub fn options(&self) -> &[u8] {
&self.buffer.as_ref()[20..(self.data_offset() as usize * 4)]
}
pub fn payload(&self) -> &[u8] {
&self.buffer.as_ref()[(self.data_offset() as usize * 4)..]
}
}
impl<B: AsRef<[u8]>> fmt::Debug for TcpPacket<B> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("tcp::Packet")
.field("source", &self.source_port())
.field("destination", &self.destination_port())
.field("sequence", &self.sequence())
.field("acknowledgment", &self.acknowledgment())
.field("offset", &self.data_offset())
.field("flags", &self.flags())
.field("window", &self.window())
.field("checksum", &self.checksum())
.field("is_valid", &self.is_valid())
.field("pointer", &self.urgent_pointer())
.field("options", &self.options())
.field("payload", &self.payload())
.finish()
}
}
-1
View File
@@ -1 +0,0 @@
pub mod udp;
-177
View File
@@ -1,177 +0,0 @@
use std::fmt;
use std::io::Cursor;
use std::net::IpAddr;
use byteorder::WriteBytesExt;
use byteorder::{BigEndian, ReadBytesExt};
use crate::error::*;
/// udp协议
///
/*
RFC 768 https://www.ietf.org/rfc/rfc768.txt
0 7 8 15 16 23 24 31
+--------+--------+--------+--------+
| 源端口(16) | 目的端口(16) |
+--------+--------+--------+--------+
| 长度(16) | 校验和(16) |
+--------+--------+--------+--------+
|
| 载荷 ...
+---------------- ...
注:1.长度包含标头和数据体,以字节为单位
2.伪首部和载荷参与校验和的计算,位数不够则补0
*/
/// ipv6 udp伪首部
/* https://datatracker.ietf.org/doc/html/rfc2460
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
+ +
| |
+ Source Address +
| |
+ +
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
+ +
| |
+ Destination Address +
| |
+ +
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Upper-Layer Packet Length |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| zero | Next Header |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
*/
pub struct UdpPacket<B> {
source_ip: IpAddr,
destination_ip: IpAddr,
buffer: B,
}
impl<B: AsRef<[u8]>> UdpPacket<B> {
pub fn unchecked(source_ip: IpAddr, destination_ip: IpAddr, buffer: B) -> UdpPacket<B> {
UdpPacket {
source_ip,
destination_ip,
buffer,
}
}
pub fn new(source_ip: IpAddr, destination_ip: IpAddr, buffer: B) -> Result<UdpPacket<B>> {
if buffer.as_ref().len() < 8 {
Err(Error::SmallBuffer)?
}
let packet = Self::unchecked(source_ip, destination_ip, buffer);
Ok(packet)
}
}
impl<B: AsRef<[u8]>> UdpPacket<B> {
/// 源端口
pub fn source_port(&self) -> u16 {
(&self.buffer.as_ref()[0..])
.read_u16::<BigEndian>()
.unwrap()
}
/// 目标端口
pub fn destination_port(&self) -> u16 {
(&self.buffer.as_ref()[2..])
.read_u16::<BigEndian>()
.unwrap()
}
/// 总字节数
pub fn length(&self) -> u16 {
(&self.buffer.as_ref()[4..])
.read_u16::<BigEndian>()
.unwrap()
}
/// Checksum of the packet.
pub fn checksum(&self) -> u16 {
(&self.buffer.as_ref()[6..])
.read_u16::<BigEndian>()
.unwrap()
}
/// 验证校验和,ipv4中为0表示不使用校验和,ipv6校验和不能为0
pub fn is_valid(&self) -> bool {
self.checksum() == 0 || self.cal_checksum() == 0
}
pub fn payload(&self) -> &[u8] {
&self.buffer.as_ref()[8..]
}
fn cal_checksum(&self) -> u16 {
match self.source_ip {
IpAddr::V4(src) => {
if let IpAddr::V4(dest) = self.destination_ip {
return crate::ipv4_cal_checksum(
self.buffer.as_ref(),
&src,
&dest,
17,
self.length(),
);
}
}
IpAddr::V6(_src) => {}
}
unimplemented!()
}
}
impl<B: AsRef<[u8]> + AsMut<[u8]>> UdpPacket<B> {
fn header_mut(&mut self) -> &mut [u8] {
&mut self.buffer.as_mut()[..8]
}
}
impl<B: AsRef<[u8]> + AsMut<[u8]>> UdpPacket<B> {
/// 设置源端口
pub fn set_source_port(&mut self, value: u16) -> &mut Self {
Cursor::new(&mut self.header_mut()[0..])
.write_u16::<BigEndian>(value)
.unwrap();
self
}
/// 设置目的端口
pub fn set_destination_port(&mut self, value: u16) -> &mut Self {
Cursor::new(&mut self.header_mut()[2..])
.write_u16::<BigEndian>(value)
.unwrap();
self
}
fn set_checknum(&mut self, value: u16) {
Cursor::new(&mut self.header_mut()[6..])
.write_u16::<BigEndian>(value)
.unwrap();
}
pub fn update_checknum(&mut self) {
//先写0
self.set_checknum(0);
self.set_checknum(self.cal_checksum());
}
}
impl<B: AsRef<[u8]>> fmt::Debug for UdpPacket<B> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("udp::Packet")
.field("source", &self.source_port())
.field("destination", &self.destination_port())
.field("length", &self.length())
.field("checksum", &self.checksum())
.field("is_valid", &self.is_valid())
.field("payload", &self.payload())
.finish()
}
}
-40
View File
@@ -1,40 +0,0 @@
syntax = "proto3";
message RegistrationRequest{
string token = 1;
string mac_address = 2;
}
message RegistrationResponse{
fixed32 virtual_ip = 1;
fixed32 virtual_gateway = 2;
fixed32 virtual_netmask = 3;
uint32 epoch = 4;
repeated fixed32 virtual_ip_list = 5;
fixed32 public_ip = 6;
uint32 public_port = 7;
}
message DeviceList{
uint32 epoch = 1;
repeated fixed32 virtual_ip_list = 2;
}
message Punch{
fixed32 virtual_ip = 1;
repeated fixed32 public_ip_list = 2;
uint32 public_port = 3;
uint32 public_port_range = 4;
NatType nat_type = 5;
bool reply = 6;
Step step = 7;
}
enum NatType{
Symmetric = 0;
Cone = 1;
}
enum Step{
Step1 = 0;
Step2 = 1;
Step3 = 2;
Step4 = 3;
}
-45
View File
@@ -1,45 +0,0 @@
[package]
name = "tun"
version = "0.5.4"
edition = "2018"
authors = ["meh. <[email protected]>"]
license = "WTFPL"
description = "TUN device creation and handling."
repository = "https://github.com/meh/rust-tun"
keywords = ["tun", "network", "tunnel", "bindings"]
[dependencies]
libc = "0.2"
thiserror = "1"
[target.'cfg(any(target_os = "linux", target_os = "macos", target_os = "ios", target_os = "android"))'.dependencies]
tokio = { version = "1", features = ["net", "macros"], optional = true }
tokio-util = { version = "0.6", features = ["codec"], optional = true }
bytes = { version = "1", optional = true }
byteorder = { version = "1", optional = true }
# This is only for the `ready` macro.
futures-core = { version = "0.3", optional = true }
[target.'cfg(any(target_os = "linux", target_os = "macos"))'.dependencies]
ioctl = { version = "0.6", package = "ioctl-sys" }
[dev-dependencies]
packet = "0.1"
futures = "0.3"
[features]
async = ["tokio", "tokio-util", "bytes", "byteorder", "futures-core"]
[[example]]
name = "read-async"
required-features = [ "async", "tokio/rt-multi-thread" ]
[[example]]
name = "read-async-codec"
required-features = [ "async", "tokio/rt-multi-thread" ]
[[example]]
name = "ping-tun"
required-features = [ "async", "tokio/rt-multi-thread" ]
-106
View File
@@ -1,106 +0,0 @@
TUN interfaces [![Crates.io](https://img.shields.io/crates/v/tun.svg)](https://crates.io/crates/tun) ![tun](https://docs.rs/tun/badge.svg) ![WTFPL](http://img.shields.io/badge/license-WTFPL-blue.svg)
==============
This crate allows the creation and usage of TUN interfaces, the aim is to make this cross-platform.
Usage
-----
First, add the following to your `Cargo.toml`:
```toml
[dependencies]
tun = "0.5"
```
Next, add this to your crate root:
```rust
extern crate tun;
```
If you want to use the TUN interface with mio/tokio, you need to enable the `async` feature:
```toml
[dependencies]
tun = { version = "0.5", features = ["async"] }
```
Example
-------
The following example creates and configures a TUN interface and starts reading
packets from it.
```rust
use std::io::Read;
extern crate tun;
fn main() {
let mut config = tun::Configuration::default();
config.address((10, 0, 0, 1))
.netmask((255, 255, 255, 0))
.up();
#[cfg(target_os = "linux")]
config.platform(|config| {
config.packet_information(true);
});
let mut dev = tun::create(&config).unwrap();
let mut buf = [0; 4096];
loop {
let amount = dev.read(&mut buf).unwrap();
println!("{:?}", &buf[0 .. amount]);
}
}
```
Platforms
=========
Not every platform is supported.
Linux
-----
You will need the `tun` module to be loaded and root is required to create
interfaces.
macOS
-----
It just werks, but you have to set up routing manually.
iOS
----
You can pass the file descriptor of the TUN device to `rust-tun` to create the interface.
Here is an example to create the TUN device on iOS and pass the `fd` to `rust-tun`:
```swift
// Swift
class PacketTunnelProvider: NEPacketTunnelProvider {
override func startTunnel(options: [String : NSObject]?, completionHandler: @escaping (Error?) -> Void) {
let tunnelNetworkSettings = createTunnelSettings() // Configure TUN address, DNS, mtu, routing...
setTunnelNetworkSettings(tunnelNetworkSettings) { [weak self] error in
let tunFd = self?.packetFlow.value(forKeyPath: "socket.fileDescriptor") as! Int32
DispatchQueue.global(qos: .default).async {
start_tun(tunFd)
}
completionHandler(nil)
}
}
}
```
```rust
#[no_mangle]
pub extern "C" fn start_tun(fd: std::os::raw::c_int) {
let mut rt = tokio::runtime::Runtime::new().unwrap();
rt.block_on(async {
let mut cfg = tun::Configuration::default();
cfg.raw_fd(fd);
let mut tun = tun::create_as_async(&cfg).unwrap();
let mut framed = tun.into_framed();
while let Some(packet) = framed.next().await {
...
}
});
}
```
-78
View File
@@ -1,78 +0,0 @@
// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
// Version 2, December 2004
//
// Copyleft (ↄ) meh. <[email protected]> | http://meh.schizofreni.co
//
// Everyone is permitted to copy and distribute verbatim or modified
// copies of this license document, and changing it is allowed as long
// as the name is changed.
//
// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
// TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
//
// 0. You just DO WHAT THE FUCK YOU WANT TO.
use futures::{SinkExt, StreamExt};
use packet::{builder::Builder, icmp, ip, Packet};
use tun::{self, Configuration, TunPacket};
#[tokio::main]
async fn main() {
let mut config = Configuration::default();
config
.address((10, 0, 0, 1))
.netmask((255, 255, 255, 0))
.up();
#[cfg(target_os = "linux")]
config.platform(|config| {
config.packet_information(true);
});
let dev = tun::create_as_async(&config).unwrap();
let mut framed = dev.into_framed();
while let Some(packet) = framed.next().await {
match packet {
Ok(pkt) => match ip::Packet::new(pkt.get_bytes()) {
Ok(ip::Packet::V4(pkt)) => match icmp::Packet::new(pkt.payload()) {
Ok(icmp) => match icmp.echo() {
Ok(icmp) => {
let reply = ip::v4::Builder::default()
.id(0x42)
.unwrap()
.ttl(64)
.unwrap()
.source(pkt.destination())
.unwrap()
.destination(pkt.source())
.unwrap()
.icmp()
.unwrap()
.echo()
.unwrap()
.reply()
.unwrap()
.identifier(icmp.identifier())
.unwrap()
.sequence(icmp.sequence())
.unwrap()
.payload(icmp.payload())
.unwrap()
.build()
.unwrap();
framed.send(TunPacket::new(reply)).await.unwrap();
}
_ => {}
},
_ => {}
},
Err(err) => println!("Received an invalid packet: {:?}", err),
_ => {}
},
Err(err) => panic!("Error: {:?}", err),
}
}
}
-61
View File
@@ -1,61 +0,0 @@
// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
// Version 2, December 2004
//
// Copyleft (ↄ) meh. <[email protected]> | http://meh.schizofreni.co
//
// Everyone is permitted to copy and distribute verbatim or modified
// copies of this license document, and changing it is allowed as long
// as the name is changed.
//
// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
// TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
//
// 0. You just DO WHAT THE FUCK YOU WANT TO.
use bytes::BytesMut;
use futures::StreamExt;
use packet::{ip::Packet, Error};
use tokio_util::codec::{Decoder, FramedRead};
pub struct IPPacketCodec;
impl Decoder for IPPacketCodec {
type Item = Packet<BytesMut>;
type Error = Error;
fn decode(&mut self, buf: &mut BytesMut) -> Result<Option<Self::Item>, Self::Error> {
if buf.is_empty() {
return Ok(None);
}
let buf = buf.split_to(buf.len());
Ok(match Packet::no_payload(buf) {
Ok(pkt) => Some(pkt),
Err(err) => {
println!("error {:?}", err);
None
}
})
}
}
#[tokio::main]
async fn main() {
let mut config = tun::Configuration::default();
config
.address((10, 0, 0, 1))
.netmask((255, 255, 255, 0))
.up();
let dev = tun::create_as_async(&config).unwrap();
let mut stream = FramedRead::new(dev, IPPacketCodec);
while let Some(packet) = stream.next().await {
match packet {
Ok(pkt) => println!("pkt: {:#?}", pkt),
Err(err) => panic!("Error: {:?}", err),
}
}
}
-42
View File
@@ -1,42 +0,0 @@
// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
// Version 2, December 2004
//
// Copyleft (ↄ) meh. <[email protected]> | http://meh.schizofreni.co
//
// Everyone is permitted to copy and distribute verbatim or modified
// copies of this license document, and changing it is allowed as long
// as the name is changed.
//
// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
// TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
//
// 0. You just DO WHAT THE FUCK YOU WANT TO.
use futures::StreamExt;
use packet::ip::Packet;
#[tokio::main]
async fn main() {
let mut config = tun::Configuration::default();
config
.address((10, 0, 0, 1))
.netmask((255, 255, 255, 0))
.up();
#[cfg(target_os = "linux")]
config.platform(|config| {
config.packet_information(true);
});
let dev = tun::create_as_async(&config).unwrap();
let mut stream = dev.into_framed();
while let Some(packet) = stream.next().await {
match packet {
Ok(pkt) => println!("pkt: {:#?}", Packet::unchecked(pkt.get_bytes())),
Err(err) => panic!("Error: {:?}", err),
}
}
}
-37
View File
@@ -1,37 +0,0 @@
// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
// Version 2, December 2004
//
// Copyleft (ↄ) meh. <[email protected]> | http://meh.schizofreni.co
//
// Everyone is permitted to copy and distribute verbatim or modified
// copies of this license document, and changing it is allowed as long
// as the name is changed.
//
// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
// TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
//
// 0. You just DO WHAT THE FUCK YOU WANT TO.
use std::io::Read;
fn main() {
let mut config = tun::Configuration::default();
config
.address((10, 0, 0, 1))
.netmask((255, 255, 255, 0))
.up();
#[cfg(target_os = "linux")]
config.platform(|config| {
config.packet_information(true);
});
let mut dev = tun::create(&config).unwrap();
let mut buf = [0; 4096];
loop {
let amount = dev.read(&mut buf).unwrap();
println!("{:?}", &buf[0..amount]);
}
}
-128
View File
@@ -1,128 +0,0 @@
// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
// Version 2, December 2004
//
// Copyleft (ↄ) meh. <[email protected]> | http://meh.schizofreni.co
//
// Everyone is permitted to copy and distribute verbatim or modified
// copies of this license document, and changing it is allowed as long
// as the name is changed.
//
// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
// TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
//
// 0. You just DO WHAT THE FUCK YOU WANT TO.
use std::net::{IpAddr, Ipv4Addr};
use std::net::{SocketAddr, SocketAddrV4};
use crate::error::*;
/// Helper trait to convert things into IPv4 addresses.
#[allow(clippy::wrong_self_convention)]
pub trait IntoAddress {
/// Convert the type to an `Ipv4Addr`.
fn into_address(&self) -> Result<Ipv4Addr>;
}
impl IntoAddress for u32 {
fn into_address(&self) -> Result<Ipv4Addr> {
Ok(Ipv4Addr::new(
((*self) & 0xff) as u8,
((*self >> 8) & 0xff) as u8,
((*self >> 16) & 0xff) as u8,
((*self >> 24) & 0xff) as u8,
))
}
}
impl IntoAddress for i32 {
fn into_address(&self) -> Result<Ipv4Addr> {
(*self as u32).into_address()
}
}
impl IntoAddress for (u8, u8, u8, u8) {
fn into_address(&self) -> Result<Ipv4Addr> {
Ok(Ipv4Addr::new(self.0, self.1, self.2, self.3))
}
}
impl IntoAddress for str {
fn into_address(&self) -> Result<Ipv4Addr> {
self.parse().map_err(|_| Error::InvalidAddress)
}
}
impl<'a> IntoAddress for &'a str {
fn into_address(&self) -> Result<Ipv4Addr> {
(*self).into_address()
}
}
impl IntoAddress for String {
fn into_address(&self) -> Result<Ipv4Addr> {
(&**self).into_address()
}
}
impl<'a> IntoAddress for &'a String {
fn into_address(&self) -> Result<Ipv4Addr> {
(&**self).into_address()
}
}
impl IntoAddress for Ipv4Addr {
fn into_address(&self) -> Result<Ipv4Addr> {
Ok(*self)
}
}
impl<'a> IntoAddress for &'a Ipv4Addr {
fn into_address(&self) -> Result<Ipv4Addr> {
(&**self).into_address()
}
}
impl IntoAddress for IpAddr {
fn into_address(&self) -> Result<Ipv4Addr> {
match *self {
IpAddr::V4(ref value) => Ok(*value),
IpAddr::V6(_) => Err(Error::InvalidAddress),
}
}
}
impl<'a> IntoAddress for &'a IpAddr {
fn into_address(&self) -> Result<Ipv4Addr> {
(&**self).into_address()
}
}
impl IntoAddress for SocketAddrV4 {
fn into_address(&self) -> Result<Ipv4Addr> {
Ok(*self.ip())
}
}
impl<'a> IntoAddress for &'a SocketAddrV4 {
fn into_address(&self) -> Result<Ipv4Addr> {
(&**self).into_address()
}
}
impl IntoAddress for SocketAddr {
fn into_address(&self) -> Result<Ipv4Addr> {
match *self {
SocketAddr::V4(ref value) => Ok(*value.ip()),
SocketAddr::V6(_) => Err(Error::InvalidAddress),
}
}
}
impl<'a> IntoAddress for &'a SocketAddr {
fn into_address(&self) -> Result<Ipv4Addr> {
(&**self).into_address()
}
}
-149
View File
@@ -1,149 +0,0 @@
// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
// Version 2, December 2004
//
// Copyleft (ↄ) meh. <[email protected]> | http://meh.schizofreni.co
//
// Everyone is permitted to copy and distribute verbatim or modified
// copies of this license document, and changing it is allowed as long
// as the name is changed.
//
// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
// TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
//
// 0. You just DO WHAT THE FUCK YOU WANT TO.
use std::io;
use byteorder::{NativeEndian, NetworkEndian, WriteBytesExt};
use bytes::{BufMut, Bytes, BytesMut};
use tokio_util::codec::{Decoder, Encoder};
/// A packet protocol IP version
#[derive(Debug)]
enum PacketProtocol {
IPv4,
IPv6,
Other(u8),
}
// Note: the protocol in the packet information header is platform dependent.
impl PacketProtocol {
#[cfg(any(target_os = "linux", target_os = "android"))]
fn into_pi_field(&self) -> Result<u16, io::Error> {
match self {
PacketProtocol::IPv4 => Ok(libc::ETH_P_IP as u16),
PacketProtocol::IPv6 => Ok(libc::ETH_P_IPV6 as u16),
PacketProtocol::Other(_) => Err(io::Error::new(
io::ErrorKind::Other,
"neither an IPv4 or IPv6 packet",
)),
}
}
#[cfg(any(target_os = "macos", target_os = "ios"))]
fn into_pi_field(&self) -> Result<u16, io::Error> {
match self {
PacketProtocol::IPv4 => Ok(libc::PF_INET as u16),
PacketProtocol::IPv6 => Ok(libc::PF_INET6 as u16),
PacketProtocol::Other(_) => Err(io::Error::new(
io::ErrorKind::Other,
"neither an IPv4 or IPv6 packet",
)),
}
}
}
/// A Tun Packet to be sent or received on the TUN interface.
#[derive(Debug)]
pub struct TunPacket(PacketProtocol, Bytes);
/// Infer the protocol based on the first nibble in the packet buffer.
fn infer_proto(buf: &[u8]) -> PacketProtocol {
match buf[0] >> 4 {
4 => PacketProtocol::IPv4,
6 => PacketProtocol::IPv6,
p => PacketProtocol::Other(p),
}
}
impl TunPacket {
/// Create a new `TunPacket` based on a byte slice.
pub fn new(bytes: Vec<u8>) -> TunPacket {
let proto = infer_proto(&bytes);
TunPacket(proto, Bytes::from(bytes))
}
/// Return this packet's bytes.
pub fn get_bytes(&self) -> &[u8] {
&self.1
}
pub fn into_bytes(self) -> Bytes {
self.1
}
}
/// A TunPacket Encoder/Decoder.
pub struct TunPacketCodec(bool, i32);
impl TunPacketCodec {
/// Create a new `TunPacketCodec` specifying whether the underlying
/// tunnel Device has enabled the packet information header.
pub fn new(pi: bool, mtu: i32) -> TunPacketCodec {
TunPacketCodec(pi, mtu)
}
}
impl Decoder for TunPacketCodec {
type Item = TunPacket;
type Error = io::Error;
fn decode(&mut self, buf: &mut BytesMut) -> Result<Option<Self::Item>, Self::Error> {
if buf.is_empty() {
return Ok(None);
}
let mut pkt = buf.split_to(buf.len());
// reserve enough space for the next packet
if self.0 {
buf.reserve(self.1 as usize + 4);
} else {
buf.reserve(self.1 as usize);
}
// if the packet information is enabled we have to ignore the first 4 bytes
if self.0 {
let _ = pkt.split_to(4);
}
let proto = infer_proto(pkt.as_ref());
Ok(Some(TunPacket(proto, pkt.freeze())))
}
}
impl Encoder<TunPacket> for TunPacketCodec {
type Error = io::Error;
fn encode(&mut self, item: TunPacket, dst: &mut BytesMut) -> Result<(), Self::Error> {
dst.reserve(item.get_bytes().len() + 4);
match item {
TunPacket(proto, bytes) if self.0 => {
// build the packet information header comprising of 2 u16
// fields: flags and protocol.
let mut buf = Vec::<u8>::with_capacity(4);
// flags is always 0
buf.write_u16::<NativeEndian>(0).unwrap();
// write the protocol as network byte order
buf.write_u16::<NetworkEndian>(proto.into_pi_field()?)
.unwrap();
dst.put_slice(&buf);
dst.put(bytes);
}
TunPacket(_, bytes) => dst.put(bytes),
}
Ok(())
}
}
-201
View File
@@ -1,201 +0,0 @@
// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
// Version 2, December 2004
//
// Copyleft (ↄ) meh. <[email protected]> | http://meh.schizofreni.co
//
// Everyone is permitted to copy and distribute verbatim or modified
// copies of this license document, and changing it is allowed as long
// as the name is changed.
//
// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
// TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
//
// 0. You just DO WHAT THE FUCK YOU WANT TO.
use std::io;
use std::io::{IoSlice, Read, Write};
use core::pin::Pin;
use core::task::{Context, Poll};
use futures_core::ready;
use tokio::io::unix::AsyncFd;
use tokio::io::{AsyncRead, AsyncWrite, ReadBuf};
use tokio_util::codec::Framed;
use crate::device::Device as D;
use crate::platform::{Device, Queue};
use crate::r#async::codec::*;
/// An async TUN device wrapper around a TUN device.
pub struct AsyncDevice {
inner: AsyncFd<Device>,
}
impl AsyncDevice {
/// Create a new `AsyncDevice` wrapping around a `Device`.
pub fn new(device: Device) -> io::Result<AsyncDevice> {
device.set_nonblock()?;
Ok(AsyncDevice {
inner: AsyncFd::new(device)?,
})
}
/// Returns a shared reference to the underlying Device object
pub fn get_ref(&self) -> &Device {
self.inner.get_ref()
}
/// Returns a mutable reference to the underlying Device object
pub fn get_mut(&mut self) -> &mut Device {
self.inner.get_mut()
}
/// Consumes this AsyncDevice and return a Framed object (unified Stream and Sink interface)
pub fn into_framed(mut self) -> Framed<Self, TunPacketCodec> {
let pi = self.get_mut().has_packet_information();
let codec = TunPacketCodec::new(pi, self.inner.get_ref().mtu().unwrap_or(1504));
Framed::new(self, codec)
}
}
impl AsyncRead for AsyncDevice {
fn poll_read(
mut self: Pin<&mut Self>,
cx: &mut Context<'_>,
buf: &mut ReadBuf,
) -> Poll<io::Result<()>> {
loop {
let mut guard = ready!(self.inner.poll_read_ready_mut(cx))?;
let rbuf = buf.initialize_unfilled();
match guard.try_io(|inner| inner.get_mut().read(rbuf)) {
Ok(res) => return Poll::Ready(res.map(|n| buf.advance(n))),
Err(_wb) => continue,
}
}
}
}
impl AsyncWrite for AsyncDevice {
fn poll_write(
mut self: Pin<&mut Self>,
cx: &mut Context<'_>,
buf: &[u8],
) -> Poll<io::Result<usize>> {
loop {
let mut guard = ready!(self.inner.poll_write_ready_mut(cx))?;
match guard.try_io(|inner| inner.get_mut().write(buf)) {
Ok(res) => return Poll::Ready(res),
Err(_wb) => continue,
}
}
}
fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
loop {
let mut guard = ready!(self.inner.poll_write_ready_mut(cx))?;
match guard.try_io(|inner| inner.get_mut().flush()) {
Ok(res) => return Poll::Ready(res),
Err(_wb) => continue,
}
}
}
fn poll_shutdown(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<io::Result<()>> {
Poll::Ready(Ok(()))
}
fn poll_write_vectored(
mut self: Pin<&mut Self>,
cx: &mut Context<'_>,
bufs: &[IoSlice<'_>],
) -> Poll<Result<usize, io::Error>> {
loop {
let mut guard = ready!(self.inner.poll_write_ready_mut(cx))?;
match guard.try_io(|inner| inner.get_mut().write_vectored(bufs)) {
Ok(res) => return Poll::Ready(res),
Err(_wb) => continue,
}
}
}
fn is_write_vectored(&self) -> bool {
true
}
}
/// An async TUN device queue wrapper around a TUN device queue.
pub struct AsyncQueue {
inner: AsyncFd<Queue>,
}
impl AsyncQueue {
/// Create a new `AsyncQueue` wrapping around a `Queue`.
pub fn new(queue: Queue) -> io::Result<AsyncQueue> {
queue.set_nonblock()?;
Ok(AsyncQueue {
inner: AsyncFd::new(queue)?,
})
}
/// Returns a shared reference to the underlying Queue object
pub fn get_ref(&self) -> &Queue {
self.inner.get_ref()
}
/// Returns a mutable reference to the underlying Queue object
pub fn get_mut(&mut self) -> &mut Queue {
self.inner.get_mut()
}
/// Consumes this AsyncQueue and return a Framed object (unified Stream and Sink interface)
pub fn into_framed(mut self) -> Framed<Self, TunPacketCodec> {
let pi = self.get_mut().has_packet_information();
let codec = TunPacketCodec::new(pi, 1504);
Framed::new(self, codec)
}
}
impl AsyncRead for AsyncQueue {
fn poll_read(
mut self: Pin<&mut Self>,
cx: &mut Context<'_>,
buf: &mut ReadBuf,
) -> Poll<io::Result<()>> {
loop {
let mut guard = ready!(self.inner.poll_read_ready_mut(cx))?;
let rbuf = buf.initialize_unfilled();
match guard.try_io(|inner| inner.get_mut().read(rbuf)) {
Ok(res) => return Poll::Ready(res.map(|n| buf.advance(n))),
Err(_wb) => continue,
}
}
}
}
impl AsyncWrite for AsyncQueue {
fn poll_write(
mut self: Pin<&mut Self>,
cx: &mut Context<'_>,
buf: &[u8],
) -> Poll<io::Result<usize>> {
loop {
let mut guard = ready!(self.inner.poll_write_ready_mut(cx))?;
match guard.try_io(|inner| inner.get_mut().write(buf)) {
Ok(res) => return Poll::Ready(res),
Err(_wb) => continue,
}
}
}
fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
loop {
let mut guard = ready!(self.inner.poll_write_ready_mut(cx))?;
match guard.try_io(|inner| inner.get_mut().flush()) {
Ok(res) => return Poll::Ready(res),
Err(_wb) => continue,
}
}
}
fn poll_shutdown(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<io::Result<()>> {
Poll::Ready(Ok(()))
}
}
-32
View File
@@ -1,32 +0,0 @@
// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
// Version 2, December 2004
//
// Copyleft (ↄ) meh. <[email protected]> | http://meh.schizofreni.co
//
// Everyone is permitted to copy and distribute verbatim or modified
// copies of this license document, and changing it is allowed as long
// as the name is changed.
//
// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
// TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
//
// 0. You just DO WHAT THE FUCK YOU WANT TO.
//! Async specific modules.
use crate::error;
use crate::configuration::Configuration;
use crate::platform::create;
mod device;
pub use self::device::{AsyncDevice, AsyncQueue};
mod codec;
pub use self::codec::{TunPacket, TunPacketCodec};
/// Create a TUN device with the given name.
pub fn create_as_async(configuration: &Configuration) -> Result<AsyncDevice, error::Error> {
let device = create(&configuration)?;
AsyncDevice::new(device).map_err(|err| err.into())
}
-126
View File
@@ -1,126 +0,0 @@
// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
// Version 2, December 2004
//
// Copyleft (ↄ) meh. <[email protected]> | http://meh.schizofreni.co
//
// Everyone is permitted to copy and distribute verbatim or modified
// copies of this license document, and changing it is allowed as long
// as the name is changed.
//
// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
// TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
//
// 0. You just DO WHAT THE FUCK YOU WANT TO.
use std::net::Ipv4Addr;
use std::os::unix::io::RawFd;
use crate::address::IntoAddress;
use crate::platform;
/// TUN interface OSI layer of operation.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum Layer {
L2,
L3,
}
impl Default for Layer {
fn default() -> Self {
Layer::L3
}
}
/// Configuration builder for a TUN interface.
#[derive(Clone, Default, Debug)]
pub struct Configuration {
pub(crate) name: Option<String>,
pub(crate) platform: platform::Configuration,
pub(crate) address: Option<Ipv4Addr>,
pub(crate) destination: Option<Ipv4Addr>,
pub(crate) broadcast: Option<Ipv4Addr>,
pub(crate) netmask: Option<Ipv4Addr>,
pub(crate) mtu: Option<i32>,
pub(crate) enabled: Option<bool>,
pub(crate) layer: Option<Layer>,
pub(crate) queues: Option<usize>,
pub(crate) raw_fd: Option<RawFd>,
}
impl Configuration {
/// Access the platform dependant configuration.
pub fn platform<F>(&mut self, f: F) -> &mut Self
where
F: FnOnce(&mut platform::Configuration),
{
f(&mut self.platform);
self
}
/// Set the name.
pub fn name<S: AsRef<str>>(&mut self, name: S) -> &mut Self {
self.name = Some(name.as_ref().into());
self
}
/// Set the address.
pub fn address<A: IntoAddress>(&mut self, value: A) -> &mut Self {
self.address = Some(value.into_address().unwrap());
self
}
/// Set the destination address.
pub fn destination<A: IntoAddress>(&mut self, value: A) -> &mut Self {
self.destination = Some(value.into_address().unwrap());
self
}
/// Set the broadcast address.
pub fn broadcast<A: IntoAddress>(&mut self, value: A) -> &mut Self {
self.broadcast = Some(value.into_address().unwrap());
self
}
/// Set the netmask.
pub fn netmask<A: IntoAddress>(&mut self, value: A) -> &mut Self {
self.netmask = Some(value.into_address().unwrap());
self
}
/// Set the MTU.
pub fn mtu(&mut self, value: i32) -> &mut Self {
self.mtu = Some(value);
self
}
/// Set the interface to be enabled once created.
pub fn up(&mut self) -> &mut Self {
self.enabled = Some(true);
self
}
/// Set the interface to be disabled once created.
pub fn down(&mut self) -> &mut Self {
self.enabled = Some(false);
self
}
/// Set the OSI layer of operation.
pub fn layer(&mut self, value: Layer) -> &mut Self {
self.layer = Some(value);
self
}
/// Set the number of queues.
pub fn queues(&mut self, value: usize) -> &mut Self {
self.queues = Some(value);
self
}
/// Set the raw fd.
pub fn raw_fd(&mut self, fd: RawFd) -> &mut Self {
self.raw_fd = Some(fd);
self
}
}
-95
View File
@@ -1,95 +0,0 @@
// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
// Version 2, December 2004
//
// Copyleft (ↄ) meh. <[email protected]> | http://meh.schizofreni.co
//
// Everyone is permitted to copy and distribute verbatim or modified
// copies of this license document, and changing it is allowed as long
// as the name is changed.
//
// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
// TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
//
// 0. You just DO WHAT THE FUCK YOU WANT TO.
use std::io::{Read, Write};
use std::net::Ipv4Addr;
use crate::configuration::Configuration;
use crate::error::*;
/// A TUN device.
pub trait Device: Read + Write {
type Queue: Read + Write;
/// Reconfigure the device.
fn configure(&mut self, config: &Configuration) -> Result<()> {
if let Some(ip) = config.address {
self.set_address(ip)?;
}
if let Some(ip) = config.destination {
self.set_destination(ip)?;
}
if let Some(ip) = config.broadcast {
self.set_broadcast(ip)?;
}
if let Some(ip) = config.netmask {
self.set_netmask(ip)?;
}
if let Some(mtu) = config.mtu {
self.set_mtu(mtu)?;
}
if let Some(enabled) = config.enabled {
self.enabled(enabled)?;
}
Ok(())
}
/// Get the device name.
fn name(&self) -> &str;
/// Set the device name.
fn set_name(&mut self, name: &str) -> Result<()>;
/// Turn on or off the interface.
fn enabled(&mut self, value: bool) -> Result<()>;
/// Get the address.
fn address(&self) -> Result<Ipv4Addr>;
/// Set the address.
fn set_address(&mut self, value: Ipv4Addr) -> Result<()>;
/// Get the destination address.
fn destination(&self) -> Result<Ipv4Addr>;
/// Set the destination address.
fn set_destination(&mut self, value: Ipv4Addr) -> Result<()>;
/// Get the broadcast address.
fn broadcast(&self) -> Result<Ipv4Addr>;
/// Set the broadcast address.
fn set_broadcast(&mut self, value: Ipv4Addr) -> Result<()>;
/// Get the netmask.
fn netmask(&self) -> Result<Ipv4Addr>;
/// Set the netmask.
fn set_netmask(&mut self, value: Ipv4Addr) -> Result<()>;
/// Get the MTU.
fn mtu(&self) -> Result<i32>;
/// Set the MTU.
fn set_mtu(&mut self, value: i32) -> Result<()>;
/// Get a device queue.
fn queue(&mut self, index: usize) -> Option<&mut Self::Queue>;
}
-54
View File
@@ -1,54 +0,0 @@
// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
// Version 2, December 2004
//
// Copyleft (ↄ) meh. <[email protected]> | http://meh.schizofreni.co
//
// Everyone is permitted to copy and distribute verbatim or modified
// copies of this license document, and changing it is allowed as long
// as the name is changed.
//
// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
// TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
//
// 0. You just DO WHAT THE FUCK YOU WANT TO.
use std::{ffi, io, num};
use thiserror::Error;
#[derive(Error, Debug)]
pub enum Error {
#[error("invalid configuration")]
InvalidConfig,
#[error("not implementated")]
NotImplemented,
#[error("device name too long")]
NameTooLong,
#[error("invalid device name")]
InvalidName,
#[error("invalid address")]
InvalidAddress,
#[error("invalid file descriptor")]
InvalidDescriptor,
#[error("unsuported network layer of operation")]
UnsupportedLayer,
#[error("invalid queues number")]
InvalidQueuesNumber,
#[error(transparent)]
Io(#[from] io::Error),
#[error(transparent)]
Nul(#[from] ffi::NulError),
#[error(transparent)]
ParseNum(#[from] num::ParseIntError),
}
pub type Result<T> = ::std::result::Result<T, Error>;
-53
View File
@@ -1,53 +0,0 @@
// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
// Version 2, December 2004
//
// Copyleft (ↄ) meh. <[email protected]> | http://meh.schizofreni.co
//
// Everyone is permitted to copy and distribute verbatim or modified
// copies of this license document, and changing it is allowed as long
// as the name is changed.
//
// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
// TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
//
// 0. You just DO WHAT THE FUCK YOU WANT TO.
mod error;
pub use crate::error::*;
mod address;
pub use crate::address::IntoAddress;
mod device;
pub use crate::device::Device;
mod configuration;
pub use crate::configuration::{Configuration, Layer};
pub mod platform;
pub use crate::platform::create;
#[cfg(all(
feature = "async",
any(
target_os = "linux",
target_os = "macos",
target_os = "ios",
target_os = "android"
)
))]
pub mod r#async;
#[cfg(all(
feature = "async",
any(
target_os = "linux",
target_os = "macos",
target_os = "ios",
target_os = "android"
)
))]
pub use r#async::*;
pub fn configure() -> Configuration {
Configuration::default()
}
-214
View File
@@ -1,214 +0,0 @@
// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
// Version 2, December 2004
//
// Copyleft (ↄ) meh. <[email protected]> | http://meh.schizofreni.co
//
// Everyone is permitted to copy and distribute verbatim or modified
// copies of this license document, and changing it is allowed as long
// as the name is changed.
//
// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
// TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
//
// 0. You just DO WHAT THE FUCK YOU WANT TO.
#![allow(unused_variables)]
use std::io::{self, Read, Write};
use std::net::Ipv4Addr;
use std::os::unix::io::{AsRawFd, IntoRawFd, RawFd};
use std::sync::Arc;
use crate::configuration::Configuration;
use crate::device::Device as D;
use crate::error::*;
use crate::platform::posix::{self, Fd};
/// A TUN device for Android.
pub struct Device {
queue: Queue,
}
impl Device {
/// Create a new `Device` for the given `Configuration`.
pub fn new(config: &Configuration) -> Result<Self> {
let fd = match config.raw_fd {
Some(raw_fd) => raw_fd,
_ => return Err(Error::InvalidConfig),
};
let device = {
let tun = Fd::new(fd).map_err(|_| io::Error::last_os_error())?;
Device {
queue: Queue { tun: tun },
}
};
Ok(device)
}
/// Split the interface into a `Reader` and `Writer`.
pub fn split(self) -> (posix::Reader, posix::Writer) {
let fd = Arc::new(self.queue.tun);
(posix::Reader(fd.clone()), posix::Writer(fd.clone()))
}
/// Return whether the device has packet information
pub fn has_packet_information(&self) -> bool {
self.queue.has_packet_information()
}
/// Set non-blocking mode
pub fn set_nonblock(&self) -> io::Result<()> {
self.queue.set_nonblock()
}
}
impl Read for Device {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
self.queue.tun.read(buf)
}
fn read_vectored(&mut self, bufs: &mut [io::IoSliceMut<'_>]) -> io::Result<usize> {
self.queue.tun.read_vectored(bufs)
}
}
impl Write for Device {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.queue.tun.write(buf)
}
fn flush(&mut self) -> io::Result<()> {
self.queue.tun.flush()
}
fn write_vectored(&mut self, bufs: &[io::IoSlice<'_>]) -> io::Result<usize> {
self.queue.tun.write_vectored(bufs)
}
}
impl D for Device {
type Queue = Queue;
fn name(&self) -> &str {
return "";
}
fn set_name(&mut self, value: &str) -> Result<()> {
Err(Error::NotImplemented)
}
fn enabled(&mut self, value: bool) -> Result<()> {
Ok(())
}
fn address(&self) -> Result<Ipv4Addr> {
Err(Error::NotImplemented)
}
fn set_address(&mut self, value: Ipv4Addr) -> Result<()> {
Ok(())
}
fn destination(&self) -> Result<Ipv4Addr> {
Err(Error::NotImplemented)
}
fn set_destination(&mut self, value: Ipv4Addr) -> Result<()> {
Ok(())
}
fn broadcast(&self) -> Result<Ipv4Addr> {
Err(Error::NotImplemented)
}
fn set_broadcast(&mut self, value: Ipv4Addr) -> Result<()> {
Ok(())
}
fn netmask(&self) -> Result<Ipv4Addr> {
Err(Error::NotImplemented)
}
fn set_netmask(&mut self, value: Ipv4Addr) -> Result<()> {
Ok(())
}
fn mtu(&self) -> Result<i32> {
Err(Error::NotImplemented)
}
fn set_mtu(&mut self, value: i32) -> Result<()> {
Ok(())
}
fn queue(&mut self, index: usize) -> Option<&mut Self::Queue> {
if index > 0 {
return None;
}
Some(&mut self.queue)
}
}
impl AsRawFd for Device {
fn as_raw_fd(&self) -> RawFd {
self.queue.as_raw_fd()
}
}
impl IntoRawFd for Device {
fn into_raw_fd(self) -> RawFd {
self.queue.into_raw_fd()
}
}
pub struct Queue {
tun: Fd,
}
impl Queue {
pub fn has_packet_information(&self) -> bool {
// on Android this is always the case
false
}
pub fn set_nonblock(&self) -> io::Result<()> {
self.tun.set_nonblock()
}
}
impl AsRawFd for Queue {
fn as_raw_fd(&self) -> RawFd {
self.tun.as_raw_fd()
}
}
impl IntoRawFd for Queue {
fn into_raw_fd(self) -> RawFd {
self.tun.into_raw_fd()
}
}
impl Read for Queue {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
self.tun.read(buf)
}
fn read_vectored(&mut self, bufs: &mut [io::IoSliceMut<'_>]) -> io::Result<usize> {
self.tun.read_vectored(bufs)
}
}
impl Write for Queue {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.tun.write(buf)
}
fn flush(&mut self) -> io::Result<()> {
self.tun.flush()
}
fn write_vectored(&mut self, bufs: &[io::IoSlice<'_>]) -> io::Result<usize> {
self.tun.write_vectored(bufs)
}
}
-30
View File
@@ -1,30 +0,0 @@
// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
// Version 2, December 2004
//
// Copyleft (ↄ) meh. <[email protected]> | http://meh.schizofreni.co
//
// Everyone is permitted to copy and distribute verbatim or modified
// copies of this license document, and changing it is allowed as long
// as the name is changed.
//
// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
// TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
//
// 0. You just DO WHAT THE FUCK YOU WANT TO.
//! Android specific functionality.
mod device;
pub use self::device::{Device, Queue};
use crate::configuration::Configuration as C;
use crate::error::*;
/// Android-only interface configuration.
#[derive(Copy, Clone, Default, Debug)]
pub struct Configuration {}
/// Create a TUN device with the given name.
pub fn create(configuration: &C) -> Result<Device> {
Device::new(&configuration)
}
-214
View File
@@ -1,214 +0,0 @@
// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
// Version 2, December 2004
//
// Copyleft (ↄ) meh. <[email protected]> | http://meh.schizofreni.co
//
// Everyone is permitted to copy and distribute verbatim or modified
// copies of this license document, and changing it is allowed as long
// as the name is changed.
//
// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
// TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
//
// 0. You just DO WHAT THE FUCK YOU WANT TO.
#![allow(unused_variables)]
use std::io::{self, Read, Write};
use std::net::Ipv4Addr;
use std::os::unix::io::{AsRawFd, IntoRawFd, RawFd};
use std::sync::Arc;
use crate::configuration::Configuration;
use crate::device::Device as D;
use crate::error::*;
use crate::platform::posix::{self, Fd};
/// A TUN device for iOS.
pub struct Device {
queue: Queue,
}
impl Device {
/// Create a new `Device` for the given `Configuration`.
pub fn new(config: &Configuration) -> Result<Self> {
let fd = match config.raw_fd {
Some(raw_fd) => raw_fd,
_ => return Err(Error::InvalidConfig),
};
let mut device = unsafe {
let tun = Fd::new(fd).map_err(|_| io::Error::last_os_error())?;
Device {
queue: Queue { tun: tun },
}
};
Ok(device)
}
/// Split the interface into a `Reader` and `Writer`.
pub fn split(self) -> (posix::Reader, posix::Writer) {
let fd = Arc::new(self.queue.tun);
(posix::Reader(fd.clone()), posix::Writer(fd.clone()))
}
/// Return whether the device has packet information
pub fn has_packet_information(&self) -> bool {
self.queue.has_packet_information()
}
/// Set non-blocking mode
pub fn set_nonblock(&self) -> io::Result<()> {
self.queue.set_nonblock()
}
}
impl Read for Device {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
self.queue.tun.read(buf)
}
fn read_vectored(&mut self, bufs: &mut [io::IoSliceMut<'_>]) -> io::Result<usize> {
self.queue.tun.read_vectored(bufs)
}
}
impl Write for Device {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.queue.tun.write(buf)
}
fn flush(&mut self) -> io::Result<()> {
self.queue.tun.flush()
}
fn write_vectored(&mut self, bufs: &[io::IoSlice<'_>]) -> io::Result<usize> {
self.queue.tun.write_vectored(bufs)
}
}
impl D for Device {
type Queue = Queue;
fn name(&self) -> &str {
return "";
}
fn set_name(&mut self, value: &str) -> Result<()> {
Err(Error::NotImplemented)
}
fn enabled(&mut self, value: bool) -> Result<()> {
Ok(())
}
fn address(&self) -> Result<Ipv4Addr> {
Err(Error::NotImplemented)
}
fn set_address(&mut self, value: Ipv4Addr) -> Result<()> {
Ok(())
}
fn destination(&self) -> Result<Ipv4Addr> {
Err(Error::NotImplemented)
}
fn set_destination(&mut self, value: Ipv4Addr) -> Result<()> {
Ok(())
}
fn broadcast(&self) -> Result<Ipv4Addr> {
Err(Error::NotImplemented)
}
fn set_broadcast(&mut self, value: Ipv4Addr) -> Result<()> {
Ok(())
}
fn netmask(&self) -> Result<Ipv4Addr> {
Err(Error::NotImplemented)
}
fn set_netmask(&mut self, value: Ipv4Addr) -> Result<()> {
Ok(())
}
fn mtu(&self) -> Result<i32> {
Err(Error::NotImplemented)
}
fn set_mtu(&mut self, value: i32) -> Result<()> {
Ok(())
}
fn queue(&mut self, index: usize) -> Option<&mut Self::Queue> {
if index > 0 {
return None;
}
Some(&mut self.queue)
}
}
impl AsRawFd for Device {
fn as_raw_fd(&self) -> RawFd {
self.queue.as_raw_fd()
}
}
impl IntoRawFd for Device {
fn into_raw_fd(self) -> RawFd {
self.queue.into_raw_fd()
}
}
pub struct Queue {
tun: Fd,
}
impl Queue {
pub fn has_packet_information(&self) -> bool {
// on ios this is always the case
true
}
pub fn set_nonblock(&self) -> io::Result<()> {
self.tun.set_nonblock()
}
}
impl AsRawFd for Queue {
fn as_raw_fd(&self) -> RawFd {
self.tun.as_raw_fd()
}
}
impl IntoRawFd for Queue {
fn into_raw_fd(self) -> RawFd {
self.tun.into_raw_fd()
}
}
impl Read for Queue {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
self.tun.read(buf)
}
fn read_vectored(&mut self, bufs: &mut [io::IoSliceMut<'_>]) -> io::Result<usize> {
self.tun.read_vectored(bufs)
}
}
impl Write for Queue {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.tun.write(buf)
}
fn flush(&mut self) -> io::Result<()> {
self.tun.flush()
}
fn write_vectored(&mut self, bufs: &[io::IoSlice<'_>]) -> io::Result<usize> {
self.tun.write_vectored(bufs)
}
}
-30
View File
@@ -1,30 +0,0 @@
// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
// Version 2, December 2004
//
// Copyleft (ↄ) meh. <[email protected]> | http://meh.schizofreni.co
//
// Everyone is permitted to copy and distribute verbatim or modified
// copies of this license document, and changing it is allowed as long
// as the name is changed.
//
// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
// TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
//
// 0. You just DO WHAT THE FUCK YOU WANT TO.
//! iOS specific functionality.
mod device;
pub use self::device::{Device, Queue};
use crate::configuration::Configuration as C;
use crate::error::*;
/// iOS-only interface configuration.
#[derive(Copy, Clone, Default, Debug)]
pub struct Configuration {}
/// Create a TUN device with the given name.
pub fn create(configuration: &C) -> Result<Device> {
Device::new(&configuration)
}
-457
View File
@@ -1,457 +0,0 @@
// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
// Version 2, December 2004
//
// Copyleft (ↄ) meh. <[email protected]> | http://meh.schizofreni.co
//
// Everyone is permitted to copy and distribute verbatim or modified
// copies of this license document, and changing it is allowed as long
// as the name is changed.
//
// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
// TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
//
// 0. You just DO WHAT THE FUCK YOU WANT TO.
use std::ffi::{CStr, CString};
use std::io::{self, Read, Write};
use std::mem;
use std::net::Ipv4Addr;
use std::os::unix::io::{AsRawFd, IntoRawFd, RawFd};
use std::ptr;
use std::sync::Arc;
use std::vec::Vec;
use libc;
use libc::{c_char, c_short};
use libc::{AF_INET, O_RDWR, SOCK_DGRAM};
use crate::configuration::{Configuration, Layer};
use crate::device::Device as D;
use crate::error::*;
use crate::platform::linux::sys::*;
use crate::platform::posix::{self, Fd, SockAddr};
/// A TUN device using the TUN/TAP Linux driver.
pub struct Device {
name: String,
queues: Vec<Queue>,
ctl: Fd,
}
impl Device {
/// Create a new `Device` for the given `Configuration`.
pub fn new(config: &Configuration) -> Result<Self> {
let mut device = unsafe {
let dev = match config.name.as_ref() {
Some(name) => {
let name = CString::new(name.clone())?;
if name.as_bytes_with_nul().len() > IFNAMSIZ {
return Err(Error::NameTooLong);
}
Some(name)
}
None => None,
};
let mut queues = Vec::new();
let mut req: ifreq = mem::zeroed();
if let Some(dev) = dev.as_ref() {
ptr::copy_nonoverlapping(
dev.as_ptr() as *const c_char,
req.ifrn.name.as_mut_ptr(),
dev.as_bytes().len(),
);
}
let device_type: c_short = config.layer.unwrap_or(Layer::L3).into();
let queues_num = config.queues.unwrap_or(1);
if queues_num < 1 {
return Err(Error::InvalidQueuesNumber);
}
req.ifru.flags = device_type
| if config.platform.packet_information {
0
} else {
IFF_NO_PI
}
| if queues_num > 1 { IFF_MULTI_QUEUE } else { 0 };
for _ in 0..queues_num {
let tun = Fd::new(libc::open(b"/dev/net/tun\0".as_ptr() as *const _, O_RDWR))
.map_err(|_| io::Error::last_os_error())?;
if tunsetiff(tun.0, &mut req as *mut _ as *mut _) < 0 {
return Err(io::Error::last_os_error().into());
}
queues.push(Queue {
tun,
pi_enabled: config.platform.packet_information,
});
}
let ctl = Fd::new(libc::socket(AF_INET, SOCK_DGRAM, 0))
.map_err(|_| io::Error::last_os_error())?;
Device {
name: CStr::from_ptr(req.ifrn.name.as_ptr())
.to_string_lossy()
.into(),
queues,
ctl,
}
};
device.configure(config)?;
Ok(device)
}
/// Prepare a new request.
unsafe fn request(&self) -> ifreq {
let mut req: ifreq = mem::zeroed();
ptr::copy_nonoverlapping(
self.name.as_ptr() as *const c_char,
req.ifrn.name.as_mut_ptr(),
self.name.len(),
);
req
}
/// Make the device persistent.
pub fn persist(&mut self) -> Result<()> {
unsafe {
if tunsetpersist(self.as_raw_fd(), &1) < 0 {
Err(io::Error::last_os_error().into())
} else {
Ok(())
}
}
}
/// Set the owner of the device.
pub fn user(&mut self, value: i32) -> Result<()> {
unsafe {
if tunsetowner(self.as_raw_fd(), &value) < 0 {
Err(io::Error::last_os_error().into())
} else {
Ok(())
}
}
}
/// Set the group of the device.
pub fn group(&mut self, value: i32) -> Result<()> {
unsafe {
if tunsetgroup(self.as_raw_fd(), &value) < 0 {
Err(io::Error::last_os_error().into())
} else {
Ok(())
}
}
}
pub fn split(mut self) -> (posix::Reader, posix::Writer) {
let queue = self.queues.swap_remove(0);
let fd = Arc::new(queue.tun);
(posix::Reader(fd.clone()), posix::Writer(fd.clone()))
}
/// Return whether the device has packet information
pub fn has_packet_information(&mut self) -> bool {
self.queues[0].has_packet_information()
}
/// Set non-blocking mode
pub fn set_nonblock(&self) -> io::Result<()> {
self.queues[0].set_nonblock()
}
}
impl Read for Device {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
self.queues[0].read(buf)
}
fn read_vectored(&mut self, bufs: &mut [io::IoSliceMut<'_>]) -> io::Result<usize> {
self.queues[0].read_vectored(bufs)
}
}
impl Write for Device {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.queues[0].write(buf)
}
fn flush(&mut self) -> io::Result<()> {
self.queues[0].flush()
}
fn write_vectored(&mut self, bufs: &[io::IoSlice<'_>]) -> io::Result<usize> {
self.queues[0].write_vectored(bufs)
}
}
impl D for Device {
type Queue = Queue;
fn name(&self) -> &str {
&self.name
}
fn set_name(&mut self, value: &str) -> Result<()> {
unsafe {
let name = CString::new(value)?;
if name.as_bytes_with_nul().len() > IFNAMSIZ {
return Err(Error::NameTooLong);
}
let mut req = self.request();
ptr::copy_nonoverlapping(
name.as_ptr() as *const c_char,
req.ifru.newname.as_mut_ptr(),
value.len(),
);
if siocsifname(self.ctl.as_raw_fd(), &req) < 0 {
return Err(io::Error::last_os_error().into());
}
self.name = value.into();
Ok(())
}
}
fn enabled(&mut self, value: bool) -> Result<()> {
unsafe {
let mut req = self.request();
if siocgifflags(self.ctl.as_raw_fd(), &mut req) < 0 {
return Err(io::Error::last_os_error().into());
}
if value {
req.ifru.flags |= IFF_UP | IFF_RUNNING;
} else {
req.ifru.flags &= !IFF_UP;
}
if siocsifflags(self.ctl.as_raw_fd(), &req) < 0 {
return Err(io::Error::last_os_error().into());
}
Ok(())
}
}
fn address(&self) -> Result<Ipv4Addr> {
unsafe {
let mut req = self.request();
if siocgifaddr(self.ctl.as_raw_fd(), &mut req) < 0 {
return Err(io::Error::last_os_error().into());
}
SockAddr::new(&req.ifru.addr).map(Into::into)
}
}
fn set_address(&mut self, value: Ipv4Addr) -> Result<()> {
unsafe {
let mut req = self.request();
req.ifru.addr = SockAddr::from(value).into();
if siocsifaddr(self.ctl.as_raw_fd(), &req) < 0 {
return Err(io::Error::last_os_error().into());
}
Ok(())
}
}
fn destination(&self) -> Result<Ipv4Addr> {
unsafe {
let mut req = self.request();
if siocgifdstaddr(self.ctl.as_raw_fd(), &mut req) < 0 {
return Err(io::Error::last_os_error().into());
}
SockAddr::new(&req.ifru.dstaddr).map(Into::into)
}
}
fn set_destination(&mut self, value: Ipv4Addr) -> Result<()> {
unsafe {
let mut req = self.request();
req.ifru.dstaddr = SockAddr::from(value).into();
if siocsifdstaddr(self.ctl.as_raw_fd(), &req) < 0 {
return Err(io::Error::last_os_error().into());
}
Ok(())
}
}
fn broadcast(&self) -> Result<Ipv4Addr> {
unsafe {
let mut req = self.request();
if siocgifbrdaddr(self.ctl.as_raw_fd(), &mut req) < 0 {
return Err(io::Error::last_os_error().into());
}
SockAddr::new(&req.ifru.broadaddr).map(Into::into)
}
}
fn set_broadcast(&mut self, value: Ipv4Addr) -> Result<()> {
unsafe {
let mut req = self.request();
req.ifru.broadaddr = SockAddr::from(value).into();
if siocsifbrdaddr(self.ctl.as_raw_fd(), &req) < 0 {
return Err(io::Error::last_os_error().into());
}
Ok(())
}
}
fn netmask(&self) -> Result<Ipv4Addr> {
unsafe {
let mut req = self.request();
if siocgifnetmask(self.ctl.as_raw_fd(), &mut req) < 0 {
return Err(io::Error::last_os_error().into());
}
SockAddr::new(&req.ifru.netmask).map(Into::into)
}
}
fn set_netmask(&mut self, value: Ipv4Addr) -> Result<()> {
unsafe {
let mut req = self.request();
req.ifru.netmask = SockAddr::from(value).into();
if siocsifnetmask(self.ctl.as_raw_fd(), &req) < 0 {
return Err(io::Error::last_os_error().into());
}
Ok(())
}
}
fn mtu(&self) -> Result<i32> {
unsafe {
let mut req = self.request();
if siocgifmtu(self.ctl.as_raw_fd(), &mut req) < 0 {
return Err(io::Error::last_os_error().into());
}
Ok(req.ifru.mtu)
}
}
fn set_mtu(&mut self, value: i32) -> Result<()> {
unsafe {
let mut req = self.request();
req.ifru.mtu = value;
if siocsifmtu(self.ctl.as_raw_fd(), &req) < 0 {
return Err(io::Error::last_os_error().into());
}
Ok(())
}
}
fn queue(&mut self, index: usize) -> Option<&mut Self::Queue> {
self.queues.get_mut(index)
}
}
impl AsRawFd for Device {
fn as_raw_fd(&self) -> RawFd {
self.queues[0].as_raw_fd()
}
}
impl IntoRawFd for Device {
fn into_raw_fd(mut self) -> RawFd {
// It is Ok to swap the first queue with the last one, because the self will be dropped afterwards
let queue = self.queues.swap_remove(0);
queue.into_raw_fd()
}
}
pub struct Queue {
tun: Fd,
pi_enabled: bool,
}
impl Queue {
pub fn has_packet_information(&mut self) -> bool {
self.pi_enabled
}
pub fn set_nonblock(&self) -> io::Result<()> {
self.tun.set_nonblock()
}
}
impl Read for Queue {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
self.tun.read(buf)
}
fn read_vectored(&mut self, bufs: &mut [io::IoSliceMut<'_>]) -> io::Result<usize> {
self.tun.read_vectored(bufs)
}
}
impl Write for Queue {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.tun.write(buf)
}
fn flush(&mut self) -> io::Result<()> {
self.tun.flush()
}
fn write_vectored(&mut self, bufs: &[io::IoSlice<'_>]) -> io::Result<usize> {
self.tun.write_vectored(bufs)
}
}
impl AsRawFd for Queue {
fn as_raw_fd(&self) -> RawFd {
self.tun.as_raw_fd()
}
}
impl IntoRawFd for Queue {
fn into_raw_fd(self) -> RawFd {
self.tun.into_raw_fd()
}
}
impl From<Layer> for c_short {
fn from(layer: Layer) -> Self {
match layer {
Layer::L2 => IFF_TAP,
Layer::L3 => IFF_TUN,
}
}
}
-43
View File
@@ -1,43 +0,0 @@
// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
// Version 2, December 2004
//
// Copyleft (ↄ) meh. <[email protected]> | http://meh.schizofreni.co
//
// Everyone is permitted to copy and distribute verbatim or modified
// copies of this license document, and changing it is allowed as long
// as the name is changed.
//
// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
// TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
//
// 0. You just DO WHAT THE FUCK YOU WANT TO.
//! Linux specific functionality.
pub mod sys;
mod device;
pub use self::device::{Device, Queue};
use crate::configuration::Configuration as C;
use crate::error::*;
/// Linux-only interface configuration.
#[derive(Copy, Clone, Default, Debug)]
pub struct Configuration {
pub(crate) packet_information: bool,
}
impl Configuration {
/// Enable or disable packet information, when enabled the first 4 bytes of
/// each packet is a header with flags and protocol type.
pub fn packet_information(&mut self, value: bool) -> &mut Self {
self.packet_information = value;
self
}
}
/// Create a TUN device with the given name.
pub fn create(configuration: &C) -> Result<Device> {
Device::new(configuration)
}
-111
View File
@@ -1,111 +0,0 @@
// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
// Version 2, December 2004
//
// Copyleft (ↄ) meh. <[email protected]> | http://meh.schizofreni.co
//
// Everyone is permitted to copy and distribute verbatim or modified
// copies of this license document, and changing it is allowed as long
// as the name is changed.
//
// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
// TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
//
// 0. You just DO WHAT THE FUCK YOU WANT TO.
//! Bindings to internal Linux stuff.
use ioctl::*;
use libc::sockaddr;
use libc::{c_char, c_int, c_short, c_uchar, c_uint, c_ulong, c_ushort, c_void};
pub const IFNAMSIZ: usize = 16;
pub const IFF_UP: c_short = 0x1;
pub const IFF_RUNNING: c_short = 0x40;
pub const IFF_TUN: c_short = 0x0001;
pub const IFF_TAP: c_short = 0x0002;
pub const IFF_NO_PI: c_short = 0x1000;
pub const IFF_MULTI_QUEUE: c_short = 0x0100;
#[repr(C)]
#[derive(Copy, Clone)]
pub struct ifmap {
pub mem_start: c_ulong,
pub mem_end: c_ulong,
pub base_addr: c_ushort,
pub irq: c_uchar,
pub dma: c_uchar,
pub port: c_uchar,
}
#[repr(C)]
#[derive(Copy, Clone)]
pub union ifsu {
pub raw_hdlc_proto: *mut c_void,
pub cisco: *mut c_void,
pub fr: *mut c_void,
pub fr_pvc: *mut c_void,
pub fr_pvc_info: *mut c_void,
pub sync: *mut c_void,
pub te1: *mut c_void,
}
#[repr(C)]
#[derive(Copy, Clone)]
pub struct if_settings {
pub type_: c_uint,
pub size: c_uint,
pub ifsu: ifsu,
}
#[repr(C)]
#[derive(Copy, Clone)]
pub union ifrn {
pub name: [c_char; IFNAMSIZ],
}
#[repr(C)]
#[derive(Copy, Clone)]
pub union ifru {
pub addr: sockaddr,
pub dstaddr: sockaddr,
pub broadaddr: sockaddr,
pub netmask: sockaddr,
pub hwaddr: sockaddr,
pub flags: c_short,
pub ivalue: c_int,
pub mtu: c_int,
pub map: ifmap,
pub slave: [c_char; IFNAMSIZ],
pub newname: [c_char; IFNAMSIZ],
pub data: *mut c_void,
pub settings: if_settings,
}
#[repr(C)]
#[derive(Copy, Clone)]
pub struct ifreq {
pub ifrn: ifrn,
pub ifru: ifru,
}
ioctl!(bad read siocgifflags with 0x8913; ifreq);
ioctl!(bad write siocsifflags with 0x8914; ifreq);
ioctl!(bad read siocgifaddr with 0x8915; ifreq);
ioctl!(bad write siocsifaddr with 0x8916; ifreq);
ioctl!(bad read siocgifdstaddr with 0x8917; ifreq);
ioctl!(bad write siocsifdstaddr with 0x8918; ifreq);
ioctl!(bad read siocgifbrdaddr with 0x8919; ifreq);
ioctl!(bad write siocsifbrdaddr with 0x891a; ifreq);
ioctl!(bad read siocgifnetmask with 0x891b; ifreq);
ioctl!(bad write siocsifnetmask with 0x891c; ifreq);
ioctl!(bad read siocgifmtu with 0x8921; ifreq);
ioctl!(bad write siocsifmtu with 0x8922; ifreq);
ioctl!(bad write siocsifname with 0x8923; ifreq);
ioctl!(write tunsetiff with b'T', 202; c_int);
ioctl!(write tunsetpersist with b'T', 203; c_int);
ioctl!(write tunsetowner with b'T', 204; c_int);
ioctl!(write tunsetgroup with b'T', 206; c_int);
-438
View File
@@ -1,438 +0,0 @@
// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
// Version 2, December 2004
//
// Copyleft (ↄ) meh. <[email protected]> | http://meh.schizofreni.co
//
// Everyone is permitted to copy and distribute verbatim or modified
// copies of this license document, and changing it is allowed as long
// as the name is changed.
//
// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
// TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
//
// 0. You just DO WHAT THE FUCK YOU WANT TO.
#![allow(unused_variables)]
use std::ffi::CStr;
use std::io::{self, Read, Write};
use std::mem;
use std::net::Ipv4Addr;
use std::os::unix::io::{AsRawFd, IntoRawFd, RawFd};
use std::ptr;
use std::sync::Arc;
use libc;
use libc::{c_char, c_uint, c_void, sockaddr, socklen_t, AF_INET, SOCK_DGRAM};
use crate::configuration::{Configuration, Layer};
use crate::device::Device as D;
use crate::error::*;
use crate::platform::macos::sys::*;
use crate::platform::posix::{self, Fd, SockAddr};
/// A TUN device using the TUN macOS driver.
pub struct Device {
name: String,
queue: Queue,
ctl: Fd,
}
impl Device {
/// Create a new `Device` for the given `Configuration`.
pub fn new(config: &Configuration) -> Result<Self> {
let id = if let Some(name) = config.name.as_ref() {
if name.len() > IFNAMSIZ {
return Err(Error::NameTooLong);
}
if !name.starts_with("utun") {
return Err(Error::InvalidName);
}
name[4..].parse()?
} else {
0
};
if config.layer.filter(|l| *l != Layer::L3).is_some() {
return Err(Error::UnsupportedLayer);
}
let queues_number = config.queues.unwrap_or(1);
if queues_number != 1 {
return Err(Error::InvalidQueuesNumber);
}
let mut device = unsafe {
let tun = Fd::new(libc::socket(PF_SYSTEM, SOCK_DGRAM, SYSPROTO_CONTROL))
.map_err(|_| io::Error::last_os_error())?;
let mut info = ctl_info {
ctl_id: 0,
ctl_name: {
let mut buffer = [0; 96];
for (i, o) in UTUN_CONTROL_NAME.as_bytes().iter().zip(buffer.iter_mut()) {
*o = *i as _;
}
buffer
},
};
if ctliocginfo(tun.0, &mut info as *mut _ as *mut _) < 0 {
return Err(io::Error::last_os_error().into());
}
let addr = sockaddr_ctl {
sc_id: info.ctl_id,
sc_len: mem::size_of::<sockaddr_ctl>() as _,
sc_family: AF_SYSTEM,
ss_sysaddr: AF_SYS_CONTROL,
sc_unit: id as c_uint,
sc_reserved: [0; 5],
};
if libc::connect(
tun.0,
&addr as *const sockaddr_ctl as *const sockaddr,
mem::size_of_val(&addr) as socklen_t,
) < 0
{
return Err(io::Error::last_os_error().into());
}
let mut name = [0u8; 64];
let mut name_len: socklen_t = 64;
if libc::getsockopt(
tun.0,
SYSPROTO_CONTROL,
UTUN_OPT_IFNAME,
&mut name as *mut _ as *mut c_void,
&mut name_len as *mut socklen_t,
) < 0
{
return Err(io::Error::last_os_error().into());
}
let ctl = Fd::new(libc::socket(AF_INET, SOCK_DGRAM, 0))
.map_err(|_| io::Error::last_os_error())?;
Device {
name: CStr::from_ptr(name.as_ptr() as *const c_char)
.to_string_lossy()
.into(),
queue: Queue { tun: tun },
ctl: ctl,
}
};
device.configure(&config)?;
Ok(device)
}
/// Prepare a new request.
pub unsafe fn request(&self) -> ifreq {
let mut req: ifreq = mem::zeroed();
ptr::copy_nonoverlapping(
self.name.as_ptr() as *const c_char,
req.ifrn.name.as_mut_ptr(),
self.name.len(),
);
req
}
/// Set the IPv4 alias of the device.
pub fn set_alias(&mut self, addr: Ipv4Addr, broadaddr: Ipv4Addr, mask: Ipv4Addr) -> Result<()> {
unsafe {
let mut req: ifaliasreq = mem::zeroed();
ptr::copy_nonoverlapping(
self.name.as_ptr() as *const c_char,
req.ifran.as_mut_ptr(),
self.name.len(),
);
req.addr = SockAddr::from(addr).into();
req.broadaddr = SockAddr::from(broadaddr).into();
req.mask = SockAddr::from(mask).into();
if siocaifaddr(self.ctl.as_raw_fd(), &req) < 0 {
return Err(io::Error::last_os_error().into());
}
Ok(())
}
}
/// Split the interface into a `Reader` and `Writer`.
pub fn split(self) -> (posix::Reader, posix::Writer) {
let fd = Arc::new(self.queue.tun);
(posix::Reader(fd.clone()), posix::Writer(fd.clone()))
}
/// Return whether the device has packet information
pub fn has_packet_information(&self) -> bool {
self.queue.has_packet_information()
}
/// Set non-blocking mode
pub fn set_nonblock(&self) -> io::Result<()> {
self.queue.set_nonblock()
}
}
impl Read for Device {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
self.queue.tun.read(buf)
}
fn read_vectored(&mut self, bufs: &mut [io::IoSliceMut<'_>]) -> io::Result<usize> {
self.queue.tun.read_vectored(bufs)
}
}
impl Write for Device {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.queue.tun.write(buf)
}
fn flush(&mut self) -> io::Result<()> {
self.queue.tun.flush()
}
fn write_vectored(&mut self, bufs: &[io::IoSlice<'_>]) -> io::Result<usize> {
self.queue.tun.write_vectored(bufs)
}
}
impl D for Device {
type Queue = Queue;
fn name(&self) -> &str {
&self.name
}
// XXX: Cannot set interface name on Darwin.
fn set_name(&mut self, value: &str) -> Result<()> {
Err(Error::InvalidName)
}
fn enabled(&mut self, value: bool) -> Result<()> {
unsafe {
let mut req = self.request();
if siocgifflags(self.ctl.as_raw_fd(), &mut req) < 0 {
return Err(io::Error::last_os_error().into());
}
if value {
req.ifru.flags |= IFF_UP | IFF_RUNNING;
} else {
req.ifru.flags &= !IFF_UP;
}
if siocsifflags(self.ctl.as_raw_fd(), &req) < 0 {
return Err(io::Error::last_os_error().into());
}
Ok(())
}
}
fn address(&self) -> Result<Ipv4Addr> {
unsafe {
let mut req = self.request();
if siocgifaddr(self.ctl.as_raw_fd(), &mut req) < 0 {
return Err(io::Error::last_os_error().into());
}
SockAddr::new(&req.ifru.addr).map(Into::into)
}
}
fn set_address(&mut self, value: Ipv4Addr) -> Result<()> {
unsafe {
let mut req = self.request();
req.ifru.addr = SockAddr::from(value).into();
if siocsifaddr(self.ctl.as_raw_fd(), &req) < 0 {
return Err(io::Error::last_os_error().into());
}
Ok(())
}
}
fn destination(&self) -> Result<Ipv4Addr> {
unsafe {
let mut req = self.request();
if siocgifdstaddr(self.ctl.as_raw_fd(), &mut req) < 0 {
return Err(io::Error::last_os_error().into());
}
SockAddr::new(&req.ifru.dstaddr).map(Into::into)
}
}
fn set_destination(&mut self, value: Ipv4Addr) -> Result<()> {
unsafe {
let mut req = self.request();
req.ifru.dstaddr = SockAddr::from(value).into();
if siocsifdstaddr(self.ctl.as_raw_fd(), &req) < 0 {
return Err(io::Error::last_os_error().into());
}
Ok(())
}
}
fn broadcast(&self) -> Result<Ipv4Addr> {
unsafe {
let mut req = self.request();
if siocgifbrdaddr(self.ctl.as_raw_fd(), &mut req) < 0 {
return Err(io::Error::last_os_error().into());
}
SockAddr::new(&req.ifru.broadaddr).map(Into::into)
}
}
fn set_broadcast(&mut self, value: Ipv4Addr) -> Result<()> {
unsafe {
let mut req = self.request();
req.ifru.broadaddr = SockAddr::from(value).into();
if siocsifbrdaddr(self.ctl.as_raw_fd(), &req) < 0 {
return Err(io::Error::last_os_error().into());
}
Ok(())
}
}
fn netmask(&self) -> Result<Ipv4Addr> {
unsafe {
let mut req = self.request();
if siocgifnetmask(self.ctl.as_raw_fd(), &mut req) < 0 {
return Err(io::Error::last_os_error().into());
}
SockAddr::unchecked(&req.ifru.addr).map(Into::into)
}
}
fn set_netmask(&mut self, value: Ipv4Addr) -> Result<()> {
unsafe {
let mut req = self.request();
req.ifru.addr = SockAddr::from(value).into();
if siocsifnetmask(self.ctl.as_raw_fd(), &req) < 0 {
return Err(io::Error::last_os_error().into());
}
Ok(())
}
}
fn mtu(&self) -> Result<i32> {
unsafe {
let mut req = self.request();
if siocgifmtu(self.ctl.as_raw_fd(), &mut req) < 0 {
return Err(io::Error::last_os_error().into());
}
Ok(req.ifru.mtu)
}
}
fn set_mtu(&mut self, value: i32) -> Result<()> {
unsafe {
let mut req = self.request();
req.ifru.mtu = value;
if siocsifmtu(self.ctl.as_raw_fd(), &req) < 0 {
return Err(io::Error::last_os_error().into());
}
Ok(())
}
}
fn queue(&mut self, index: usize) -> Option<&mut Self::Queue> {
if index > 0 {
return None;
}
Some(&mut self.queue)
}
}
impl AsRawFd for Device {
fn as_raw_fd(&self) -> RawFd {
self.queue.as_raw_fd()
}
}
impl IntoRawFd for Device {
fn into_raw_fd(self) -> RawFd {
self.queue.into_raw_fd()
}
}
pub struct Queue {
tun: Fd,
}
impl Queue {
pub fn has_packet_information(&self) -> bool {
// on macos this is always the case
true
}
pub fn set_nonblock(&self) -> io::Result<()> {
self.tun.set_nonblock()
}
}
impl AsRawFd for Queue {
fn as_raw_fd(&self) -> RawFd {
self.tun.as_raw_fd()
}
}
impl IntoRawFd for Queue {
fn into_raw_fd(self) -> RawFd {
self.tun.into_raw_fd()
}
}
impl Read for Queue {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
self.tun.read(buf)
}
fn read_vectored(&mut self, bufs: &mut [io::IoSliceMut<'_>]) -> io::Result<usize> {
self.tun.read_vectored(bufs)
}
}
impl Write for Queue {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.tun.write(buf)
}
fn flush(&mut self) -> io::Result<()> {
self.tun.flush()
}
fn write_vectored(&mut self, bufs: &[io::IoSlice<'_>]) -> io::Result<usize> {
self.tun.write_vectored(bufs)
}
}
-32
View File
@@ -1,32 +0,0 @@
// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
// Version 2, December 2004
//
// Copyleft (ↄ) meh. <[email protected]> | http://meh.schizofreni.co
//
// Everyone is permitted to copy and distribute verbatim or modified
// copies of this license document, and changing it is allowed as long
// as the name is changed.
//
// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
// TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
//
// 0. You just DO WHAT THE FUCK YOU WANT TO.
//! macOS specific functionality.
pub mod sys;
mod device;
pub use self::device::{Device, Queue};
use crate::configuration::Configuration as C;
use crate::error::*;
/// macOS-only interface configuration.
#[derive(Copy, Clone, Default, Debug)]
pub struct Configuration {}
/// Create a TUN device with the given name.
pub fn create(configuration: &C) -> Result<Device> {
Device::new(&configuration)
}
-138
View File
@@ -1,138 +0,0 @@
// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
// Version 2, December 2004
//
// Copyleft (ↄ) meh. <[email protected]> | http://meh.schizofreni.co
//
// Everyone is permitted to copy and distribute verbatim or modified
// copies of this license document, and changing it is allowed as long
// as the name is changed.
//
// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
// TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
//
// 0. You just DO WHAT THE FUCK YOU WANT TO.
//! Bindings to internal macOS stuff.
use ioctl::*;
use libc::sockaddr;
use libc::{c_char, c_int, c_short, c_uint, c_ushort, c_void};
pub const IFNAMSIZ: usize = 16;
pub const IFF_UP: c_short = 0x1;
pub const IFF_RUNNING: c_short = 0x40;
pub const AF_SYS_CONTROL: c_ushort = 2;
pub const AF_SYSTEM: c_char = 32;
pub const PF_SYSTEM: c_int = AF_SYSTEM as c_int;
pub const SYSPROTO_CONTROL: c_int = 2;
pub const UTUN_OPT_IFNAME: c_int = 2;
pub const UTUN_CONTROL_NAME: &str = "com.apple.net.utun_control";
#[repr(C)]
#[derive(Copy, Clone)]
pub struct ctl_info {
pub ctl_id: c_uint,
pub ctl_name: [c_char; 96],
}
#[repr(C)]
#[derive(Copy, Clone)]
pub struct sockaddr_ctl {
pub sc_len: c_char,
pub sc_family: c_char,
pub ss_sysaddr: c_ushort,
pub sc_id: c_uint,
pub sc_unit: c_uint,
pub sc_reserved: [c_uint; 5],
}
#[repr(C)]
#[derive(Copy, Clone)]
pub union ifrn {
pub name: [c_char; IFNAMSIZ],
}
#[repr(C)]
#[derive(Copy, Clone)]
pub struct ifdevmtu {
pub current: c_int,
pub min: c_int,
pub max: c_int,
}
#[repr(C)]
#[derive(Copy, Clone)]
pub union ifku {
pub ptr: *mut c_void,
pub value: c_int,
}
#[repr(C)]
#[derive(Copy, Clone)]
pub struct ifkpi {
pub module_id: c_uint,
pub type_: c_uint,
pub ifku: ifku,
}
#[repr(C)]
#[derive(Copy, Clone)]
pub union ifru {
pub addr: sockaddr,
pub dstaddr: sockaddr,
pub broadaddr: sockaddr,
pub flags: c_short,
pub metric: c_int,
pub mtu: c_int,
pub phys: c_int,
pub media: c_int,
pub intval: c_int,
pub data: *mut c_void,
pub devmtu: ifdevmtu,
pub wake_flags: c_uint,
pub route_refcnt: c_uint,
pub cap: [c_int; 2],
pub functional_type: c_uint,
}
#[repr(C)]
#[derive(Copy, Clone)]
pub struct ifreq {
pub ifrn: ifrn,
pub ifru: ifru,
}
#[repr(C)]
#[derive(Copy, Clone)]
pub struct ifaliasreq {
pub ifran: [c_char; IFNAMSIZ],
pub addr: sockaddr,
pub broadaddr: sockaddr,
pub mask: sockaddr,
}
ioctl!(readwrite ctliocginfo with 'N', 3; ctl_info);
ioctl!(write siocsifflags with 'i', 16; ifreq);
ioctl!(readwrite siocgifflags with 'i', 17; ifreq);
ioctl!(write siocsifaddr with 'i', 12; ifreq);
ioctl!(readwrite siocgifaddr with 'i', 33; ifreq);
ioctl!(write siocsifdstaddr with 'i', 14; ifreq);
ioctl!(readwrite siocgifdstaddr with 'i', 34; ifreq);
ioctl!(write siocsifbrdaddr with 'i', 19; ifreq);
ioctl!(readwrite siocgifbrdaddr with 'i', 35; ifreq);
ioctl!(write siocsifnetmask with 'i', 22; ifreq);
ioctl!(readwrite siocgifnetmask with 'i', 37; ifreq);
ioctl!(write siocsifmtu with 'i', 52; ifreq);
ioctl!(readwrite siocgifmtu with 'i', 51; ifreq);
ioctl!(write siocaifaddr with 'i', 26; ifaliasreq);
ioctl!(write siocdifaddr with 'i', 25; ifreq);
-70
View File
@@ -1,70 +0,0 @@
// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
// Version 2, December 2004
//
// Copyleft (ↄ) meh. <[email protected]> | http://meh.schizofreni.co
//
// Everyone is permitted to copy and distribute verbatim or modified
// copies of this license document, and changing it is allowed as long
// as the name is changed.
//
// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
// TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
//
// 0. You just DO WHAT THE FUCK YOU WANT TO.
//! Platform specific modules.
#[cfg(unix)]
pub mod posix;
#[cfg(target_os = "linux")]
pub mod linux;
#[cfg(target_os = "linux")]
pub use self::linux::{create, Configuration, Device, Queue};
#[cfg(target_os = "macos")]
pub mod macos;
#[cfg(target_os = "macos")]
pub use self::macos::{create, Configuration, Device, Queue};
#[cfg(target_os = "ios")]
pub mod ios;
#[cfg(target_os = "ios")]
pub use self::ios::{create, Configuration, Device, Queue};
#[cfg(target_os = "android")]
pub mod android;
#[cfg(target_os = "android")]
pub use self::android::{create, Configuration, Device, Queue};
#[cfg(test)]
mod test {
use crate::configuration::Configuration;
use crate::device::Device;
use std::net::Ipv4Addr;
#[test]
fn create() {
let dev = super::create(
Configuration::default()
.name("utun6")
.address("192.168.50.1")
.netmask("255.255.0.0")
.mtu(1400)
.up(),
)
.unwrap();
assert_eq!(
"192.168.50.1".parse::<Ipv4Addr>().unwrap(),
dev.address().unwrap()
);
assert_eq!(
"255.255.0.0".parse::<Ipv4Addr>().unwrap(),
dev.netmask().unwrap()
);
assert_eq!(1400, dev.mtu().unwrap());
}
}
-124
View File
@@ -1,124 +0,0 @@
// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
// Version 2, December 2004
//
// Copyleft (ↄ) meh. <[email protected]> | http://meh.schizofreni.co
//
// Everyone is permitted to copy and distribute verbatim or modified
// copies of this license document, and changing it is allowed as long
// as the name is changed.
//
// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
// TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
//
// 0. You just DO WHAT THE FUCK YOU WANT TO.
use std::io::{self, Read, Write};
use std::os::unix::io::{AsRawFd, IntoRawFd, RawFd};
use crate::error::*;
use libc::{self, fcntl, F_GETFL, F_SETFL, O_NONBLOCK};
/// POSIX file descriptor support for `io` traits.
pub struct Fd(pub RawFd);
impl Fd {
pub fn new(value: RawFd) -> Result<Self> {
if value < 0 {
return Err(Error::InvalidDescriptor);
}
Ok(Fd(value))
}
/// Enable non-blocking mode
pub fn set_nonblock(&self) -> io::Result<()> {
match unsafe { fcntl(self.0, F_SETFL, fcntl(self.0, F_GETFL) | O_NONBLOCK) } {
0 => Ok(()),
_ => Err(io::Error::last_os_error()),
}
}
}
impl Read for Fd {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
unsafe {
let amount = libc::read(self.0, buf.as_mut_ptr() as *mut _, buf.len());
if amount < 0 {
return Err(io::Error::last_os_error());
}
Ok(amount as usize)
}
}
fn read_vectored(&mut self, bufs: &mut [io::IoSliceMut<'_>]) -> io::Result<usize> {
unsafe {
let iov = bufs.as_ptr().cast();
let iovcnt = bufs.len().min(libc::c_int::MAX as usize) as _;
let n = libc::readv(self.0, iov, iovcnt);
if n < 0 {
return Err(io::Error::last_os_error());
}
Ok(n as usize)
}
}
}
impl Write for Fd {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
unsafe {
let amount = libc::write(self.0, buf.as_ptr() as *const _, buf.len());
if amount < 0 {
return Err(io::Error::last_os_error());
}
Ok(amount as usize)
}
}
fn flush(&mut self) -> io::Result<()> {
Ok(())
}
fn write_vectored(&mut self, bufs: &[io::IoSlice<'_>]) -> io::Result<usize> {
unsafe {
let iov = bufs.as_ptr().cast();
let iovcnt = bufs.len().min(libc::c_int::MAX as usize) as _;
let n = libc::writev(self.0, iov, iovcnt);
if n < 0 {
return Err(io::Error::last_os_error());
}
Ok(n as usize)
}
}
}
impl AsRawFd for Fd {
fn as_raw_fd(&self) -> RawFd {
self.0
}
}
impl IntoRawFd for Fd {
fn into_raw_fd(mut self) -> RawFd {
let fd = self.0;
self.0 = -1;
fd
}
}
impl Drop for Fd {
fn drop(&mut self) {
unsafe {
if self.0 >= 0 {
libc::close(self.0);
}
}
}
}
-24
View File
@@ -1,24 +0,0 @@
// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
// Version 2, December 2004
//
// Copyleft (ↄ) meh. <[email protected]> | http://meh.schizofreni.co
//
// Everyone is permitted to copy and distribute verbatim or modified
// copies of this license document, and changing it is allowed as long
// as the name is changed.
//
// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
// TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
//
// 0. You just DO WHAT THE FUCK YOU WANT TO.
//! POSIX compliant support.
mod sockaddr;
pub use self::sockaddr::SockAddr;
mod fd;
pub use self::fd::Fd;
mod split;
pub use self::split::{Reader, Writer};
-96
View File
@@ -1,96 +0,0 @@
// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
// Version 2, December 2004
//
// Copyleft (ↄ) meh. <[email protected]> | http://meh.schizofreni.co
//
// Everyone is permitted to copy and distribute verbatim or modified
// copies of this license document, and changing it is allowed as long
// as the name is changed.
//
// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
// TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
//
// 0. You just DO WHAT THE FUCK YOU WANT TO.
use std::mem;
use std::net::Ipv4Addr;
use std::ptr;
#[cfg(any(target_os = "macos", target_os = "ios"))]
use libc::c_uchar;
#[cfg(any(target_os = "linux", target_os = "android"))]
use libc::c_ushort;
use libc::AF_INET as _AF_INET;
use libc::{in_addr, sockaddr, sockaddr_in};
use crate::error::*;
/// A wrapper for `sockaddr_in`.
#[derive(Copy, Clone)]
pub struct SockAddr(sockaddr_in);
#[cfg(any(target_os = "linux", target_os = "android"))]
const AF_INET: c_ushort = _AF_INET as c_ushort;
#[cfg(any(target_os = "macos", target_os = "ios"))]
const AF_INET: c_uchar = _AF_INET as c_uchar;
impl SockAddr {
/// Create a new `SockAddr` from a generic `sockaddr`.
pub fn new(value: &sockaddr) -> Result<Self> {
if value.sa_family != AF_INET {
return Err(Error::InvalidAddress);
}
unsafe { Self::unchecked(value) }
}
/// # Safety
/// Create a new `SockAddr` and not check the source.
pub unsafe fn unchecked(value: &sockaddr) -> Result<Self> {
Ok(SockAddr(ptr::read(value as *const _ as *const _)))
}
/// # Safety
/// Get a generic pointer to the `SockAddr`.
pub unsafe fn as_ptr(&self) -> *const sockaddr {
&self.0 as *const _ as *const sockaddr
}
}
impl From<Ipv4Addr> for SockAddr {
fn from(ip: Ipv4Addr) -> SockAddr {
let octets = ip.octets();
let mut addr = unsafe { mem::zeroed::<sockaddr_in>() };
addr.sin_family = AF_INET;
addr.sin_port = 0;
addr.sin_addr = in_addr {
s_addr: u32::from_ne_bytes(octets),
};
SockAddr(addr)
}
}
impl From<SockAddr> for Ipv4Addr {
fn from(addr: SockAddr) -> Ipv4Addr {
let ip = addr.0.sin_addr.s_addr;
let [a, b, c, d] = ip.to_ne_bytes();
Ipv4Addr::new(a, b, c, d)
}
}
impl From<SockAddr> for sockaddr {
fn from(addr: SockAddr) -> sockaddr {
unsafe { mem::transmute(addr.0) }
}
}
impl From<SockAddr> for sockaddr_in {
fn from(addr: SockAddr) -> sockaddr_in {
addr.0
}
}
-105
View File
@@ -1,105 +0,0 @@
// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
// Version 2, December 2004
//
// Copyleft (ↄ) meh. <[email protected]> | http://meh.schizofreni.co
//
// Everyone is permitted to copy and distribute verbatim or modified
// copies of this license document, and changing it is allowed as long
// as the name is changed.
//
// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
// TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
//
// 0. You just DO WHAT THE FUCK YOU WANT TO.
use std::io::{self, Read, Write};
use std::mem;
use std::os::unix::io::{AsRawFd, RawFd};
use std::sync::Arc;
use crate::platform::posix::Fd;
use libc;
/// Read-only end for a file descriptor.
pub struct Reader(pub(crate) Arc<Fd>);
/// Write-only end for a file descriptor.
pub struct Writer(pub(crate) Arc<Fd>);
impl Read for Reader {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
unsafe {
let amount = libc::read(self.0.as_raw_fd(), buf.as_mut_ptr() as *mut _, buf.len());
if amount < 0 {
return Err(io::Error::last_os_error());
}
Ok(amount as usize)
}
}
fn read_vectored(&mut self, bufs: &mut [io::IoSliceMut<'_>]) -> io::Result<usize> {
unsafe {
let mut msg: libc::msghdr = mem::zeroed();
// msg.msg_name: NULL
// msg.msg_namelen: 0
msg.msg_iov = bufs.as_mut_ptr().cast();
msg.msg_iovlen = bufs.len().min(libc::c_int::MAX as usize) as _;
let n = libc::recvmsg(self.0.as_raw_fd(), &mut msg, 0);
if n < 0 {
return Err(io::Error::last_os_error());
}
Ok(n as usize)
}
}
}
impl Write for Writer {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
unsafe {
let amount = libc::write(self.0.as_raw_fd(), buf.as_ptr() as *const _, buf.len());
if amount < 0 {
return Err(io::Error::last_os_error());
}
Ok(amount as usize)
}
}
fn flush(&mut self) -> io::Result<()> {
Ok(())
}
fn write_vectored(&mut self, bufs: &[io::IoSlice<'_>]) -> io::Result<usize> {
unsafe {
let mut msg: libc::msghdr = mem::zeroed();
// msg.msg_name = NULL
// msg.msg_namelen = 0
msg.msg_iov = bufs.as_ptr() as *mut _;
msg.msg_iovlen = bufs.len().min(libc::c_int::MAX as usize) as _;
let n = libc::sendmsg(self.0.as_raw_fd(), &msg, 0);
if n < 0 {
return Err(io::Error::last_os_error());
}
Ok(n as usize)
}
}
}
impl AsRawFd for Reader {
fn as_raw_fd(&self) -> RawFd {
self.0.as_raw_fd()
}
}
impl AsRawFd for Writer {
fn as_raw_fd(&self) -> RawFd {
self.0.as_raw_fd()
}
}
-76
View File
@@ -1,76 +0,0 @@
/// 使用 https://github.com/spa5k/is_sudo/blob/main/src/window.rs
use std::io::Error;
use std::ptr;
use winapi::um::handleapi::CloseHandle;
use winapi::um::processthreadsapi::{GetCurrentProcess, OpenProcessToken};
use winapi::um::securitybaseapi::GetTokenInformation;
use winapi::um::winnt::{HANDLE, TOKEN_ELEVATION, TOKEN_QUERY, TokenElevation};
// Use std::io::Error::last_os_error for errors.
// NOTE: For this example I'm simple passing on the OS error.
// However, customising the error could provide more context
/// Returns true if the current process has admin rights, otherwise false.
pub fn is_app_elevated() -> bool {
_is_app_elevated().unwrap_or(false)
}
/// On success returns a bool indicating if the current process has admin rights.
/// Otherwise returns an OS error.
///
/// This is unlikely to fail but if it does it's even more unlikely that you have admin permissions anyway.
/// Therefore the public function above simply eats the error and returns a bool.
fn _is_app_elevated() -> Result<bool, Error> {
let token = QueryAccessToken::from_current_process()?;
token.is_elevated()
}
/// A safe wrapper around querying Windows access tokens.
pub struct QueryAccessToken(HANDLE);
impl QueryAccessToken {
pub fn from_current_process() -> Result<Self, Error> {
unsafe {
let mut handle: HANDLE = ptr::null_mut();
let result = OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &mut handle);
if result != 0 {
Ok(Self(handle))
} else {
Err(Error::last_os_error())
}
}
}
/// On success returns a bool indicating if the access token has elevated privilidges.
/// Otherwise returns an OS error.
pub fn is_elevated(&self) -> Result<bool, Error> {
unsafe {
let mut elevation = TOKEN_ELEVATION::default();
let size = std::mem::size_of::<TOKEN_ELEVATION>() as u32;
let mut ret_size = size;
// The weird looking repetition of `as *mut _` is casting the reference to a c_void pointer.
if GetTokenInformation(
self.0,
TokenElevation,
&mut elevation as *mut _ as *mut _,
size,
&mut ret_size,
) != 0
{
Ok(elevation.TokenIsElevated != 0)
} else {
Err(Error::last_os_error())
}
}
}
}
impl Drop for QueryAccessToken {
fn drop(&mut self) {
if !self.0.is_null() {
unsafe { CloseHandle(self.0) };
}
}
}
+468
View File
@@ -0,0 +1,468 @@
use anyhow::anyhow;
use clap::Parser;
use ipnet::Ipv4Net;
use serde::{Deserialize, Serialize};
use std::net::Ipv4Addr;
use std::path::{Path, PathBuf};
use vnt_core::context::config::Config;
use vnt_core::nat::NetInput;
use vnt_core::tls::verifier::CertValidationMode;
use vnt_core::tunnel_core::server::transport::config::ProtocolAddress;
use vnt_ipc as vnt_core;
use vnt_ipc::port_mapping::PortMapping;
#[derive(Debug, Deserialize, Serialize, Default)]
pub struct FileConfig {
pub server: Option<Vec<String>>,
pub network_code: Option<String>,
pub ip: Option<Ipv4Addr>,
pub no_punch: Option<bool>,
pub rtx: Option<bool>,
pub compress: Option<bool>,
pub fec: Option<bool>,
pub input: Option<Vec<NetInput>>,
pub output: Option<Vec<Ipv4Net>>,
pub no_nat: Option<bool>,
pub no_tun: Option<bool>,
pub mtu: Option<u16>,
pub ctrl_port: Option<u16>,
pub port_mapping: Option<Vec<String>>,
pub allow_mapping: Option<bool>,
pub device_id: Option<String>,
pub device_name: Option<String>,
pub tun_name: Option<String>,
pub password: Option<String>,
pub cert_mode: Option<String>,
pub udp_stun: Option<Vec<String>>,
pub tcp_stun: Option<Vec<String>>,
}
impl FileConfig {
pub fn load(path: &Path) -> anyhow::Result<Self> {
let content = std::fs::read_to_string(path)?;
Ok(toml::from_str(&content)?)
}
pub fn save(&self, path: &Path) -> anyhow::Result<()> {
let content = toml::to_string_pretty(self)?;
std::fs::write(path, content)?;
Ok(())
}
pub fn to_server_addr(&self) -> anyhow::Result<Vec<ProtocolAddress>> {
if let Some(server_raw) = &self.server {
let mut server_addr = Vec::with_capacity(server_raw.len());
for x in server_raw {
server_addr.push(
x.parse::<ProtocolAddress>()
.map_err(|e| anyhow!("invalid server address '{}': {}", x, e))?,
)
}
Ok(server_addr)
} else {
Ok(Vec::new())
}
}
pub fn to_port_mapping(&self) -> anyhow::Result<Vec<PortMapping>> {
if let Some(port_mapping_raw) = &self.port_mapping {
let mut port_mapping = Vec::with_capacity(port_mapping_raw.len());
for x in port_mapping_raw {
port_mapping.push(
x.parse::<PortMapping>()
.map_err(|e| anyhow!("invalid port_mapping '{}': {}", x, e))?,
)
}
Ok(port_mapping)
} else {
Ok(Vec::new())
}
}
}
#[derive(Parser, Debug)]
#[command(author, version, about, long_about = None)]
pub struct Args {
/// 服务器地址 例如 `-s quic://127.0.0.1:29872`, 支持quic/tcp/wss/dynamic
#[clap(short, long)]
pub server: Vec<ProtocolAddress>,
/// 网络编号,相同编号的会组同一个局域网
#[clap(short, long)]
pub network_code: Option<String>,
#[clap(short = 'k', long, hide = true)]
pub token: Option<String>,
/// 自定义虚拟IP
#[clap(long)]
pub ip: Option<Ipv4Addr>,
/// 启用加密,设置加密密码
#[clap(short, long)]
pub password: Option<String>,
/// 启用quic优化传输
#[clap(long)]
pub rtx: bool,
/// 启用压缩 (LZ4)
#[clap(short = 'z', long)]
pub compress: bool,
/// 启用 FEC 前向纠错,损失一定带宽来提升网络稳定性
#[clap(long)]
pub fec: bool,
/// 入栈监听网段
#[clap(short, long)]
pub input: Vec<NetInput>,
/// 出栈允许网段
#[clap(short, long)]
pub output: Vec<Ipv4Net>,
/// 自定义设备名称
#[clap(long, alias = "name")]
pub device_name: Option<String>,
/// 设备id
#[clap(long, alias = "id")]
pub device_id: Option<String>,
/// 关闭打洞
#[clap(long)]
pub no_punch: bool,
/// 服务端证书验证
#[clap(long)]
pub cert_mode: Option<CertValidationMode>,
/// 虚拟网卡名称
#[clap(long)]
pub tun_name: Option<String>,
/// 关闭内置子网NAT
#[clap(long)]
pub no_nat: bool,
/// 禁用tun,禁用后只能充当流量出口或者进行端口映射,无需管理员权限
#[clap(long)]
pub no_tun: bool,
/// 端口映射,格式为:协议://本地监听地址-目标虚拟IP-目标映射地址
#[clap(long)]
pub port_mapping: Vec<PortMapping>,
/// 是否允许作为端口映射出口,开启后其他设备才可使用本设备的ip为"目标虚拟IP"
#[clap(long)]
pub allow_mapping: bool,
/// 设置mtu
#[clap(long)]
pub mtu: Option<u16>,
/// 控制端口,设置0时禁用控制服务
#[clap(long)]
pub ctrl_port: Option<u16>,
/// 读取配置文件
#[arg(long)]
pub conf: Option<PathBuf>,
/// 输出配置文件示例
#[clap(long)]
pub conf_example: bool,
}
impl Args {
pub fn parse_compatible() -> Self {
let mut args = Args::parse();
if args.network_code.is_none() {
args.network_code = args.token.clone();
}
args
}
}
pub struct CtrlConfig {
pub ctrl_port: Option<u16>,
}
pub fn build_config_from_args_and_file(
args: Option<Args>,
file: Option<FileConfig>,
) -> anyhow::Result<(Config, CtrlConfig)> {
match (args, file) {
(Some(args), Some(file)) => build_from_args_and_file(args, file),
(Some(args), None) => build_from_args_only(args),
(None, Some(file)) => build_from_file_only(file),
(None, None) => Err(anyhow!("neither args nor config file provided")),
}
}
fn build_from_args_and_file(args: Args, file: FileConfig) -> anyhow::Result<(Config, CtrlConfig)> {
let server_addr = if args.server.is_empty() {
file.to_server_addr()?
} else {
args.server
};
let port_mapping = if args.port_mapping.is_empty() {
file.to_port_mapping()?
} else {
args.port_mapping
};
let network_code = args
.network_code
.or_else(|| file.network_code.clone())
.ok_or_else(|| anyhow!("network_code is required"))?;
let cert_mode = args
.cert_mode
.or_else(|| file.cert_mode.as_deref().and_then(|s| s.parse().ok()))
.unwrap_or(CertValidationMode::InsecureSkipVerification);
let device_id = match args.device_id.or_else(|| file.device_id.clone()) {
Some(id) => id,
None => vnt_core::utils::device_id::get_device_id()?,
};
let input = if args.input.is_empty() {
file.input.unwrap_or_default()
} else {
args.input
};
let output = if args.output.is_empty() {
file.output.unwrap_or_default()
} else {
args.output
};
let mut udp_stun = file.udp_stun.unwrap_or_default();
for x in udp_stun.iter_mut() {
if !x.contains(':') {
x.push_str(":3478");
}
}
let mut tcp_stun = file.tcp_stun.unwrap_or_default();
for x in tcp_stun.iter_mut() {
if !x.contains(':') {
x.push_str(":3478");
}
}
let config = Config {
server_addr,
network_code,
ip: args.ip.or(file.ip),
no_punch: args.no_punch || file.no_punch.unwrap_or(false),
rtx: args.rtx || file.rtx.unwrap_or(false),
compress: args.compress || file.compress.unwrap_or(false),
fec: args.fec || file.fec.unwrap_or(false),
device_id,
device_name: args
.device_name
.or_else(|| file.device_name.clone())
.unwrap_or_else(default_hostname),
tun_name: args.tun_name.or_else(|| file.tun_name.clone()),
password: args.password.or_else(|| file.password.clone()),
cert_mode,
input,
output,
no_nat: args.no_nat || file.no_nat.unwrap_or(false),
no_tun: args.no_tun || file.no_tun.unwrap_or(false),
mtu: args.mtu.or(file.mtu),
port_mapping,
allow_port_mapping: args.allow_mapping || file.allow_mapping.unwrap_or(false),
udp_stun,
tcp_stun,
};
let ctrl_config = CtrlConfig {
ctrl_port: args.ctrl_port.or(file.ctrl_port),
};
Ok((config, ctrl_config))
}
fn build_from_args_only(args: Args) -> anyhow::Result<(Config, CtrlConfig)> {
let device_id = match args.device_id {
Some(id) => id,
None => vnt_core::utils::device_id::get_device_id()?,
};
let config = Config {
server_addr: args.server,
network_code: args
.network_code
.ok_or_else(|| anyhow!("network_code is required"))?,
ip: args.ip,
no_punch: args.no_punch,
rtx: args.rtx,
input: args.input,
compress: args.compress,
fec: args.fec,
device_id,
device_name: args.device_name.unwrap_or_else(default_hostname),
tun_name: args.tun_name,
password: args.password,
cert_mode: args
.cert_mode
.unwrap_or(CertValidationMode::InsecureSkipVerification),
output: args.output,
no_nat: args.no_nat,
no_tun: args.no_tun,
mtu: args.mtu,
port_mapping: args.port_mapping,
allow_port_mapping: args.allow_mapping,
..Default::default()
};
let ctrl_config = CtrlConfig {
ctrl_port: args.ctrl_port,
};
Ok((config, ctrl_config))
}
fn build_from_file_only(file: FileConfig) -> anyhow::Result<(Config, CtrlConfig)> {
let server_addr = file.to_server_addr()?;
let port_mapping = file.to_port_mapping()?;
let cert_mode = file
.cert_mode
.as_deref()
.and_then(|s| s.parse().ok())
.unwrap_or(CertValidationMode::InsecureSkipVerification);
let device_id = match file.device_id.clone() {
Some(id) => id,
None => vnt_core::utils::device_id::get_device_id()?,
};
let mut udp_stun = file.udp_stun.unwrap_or_default();
for x in udp_stun.iter_mut() {
if !x.contains(':') {
x.push_str(":3478");
}
}
let mut tcp_stun = file.tcp_stun.unwrap_or_default();
for x in tcp_stun.iter_mut() {
if !x.contains(':') {
x.push_str(":3478");
}
}
let config = Config {
server_addr,
network_code: file
.network_code
.ok_or_else(|| anyhow!("network_code is required"))?,
ip: file.ip,
no_punch: file.no_punch.unwrap_or(false),
rtx: file.rtx.unwrap_or(false),
input: file.input.unwrap_or_default(),
compress: file.compress.unwrap_or(false),
fec: file.fec.unwrap_or(false),
device_id,
device_name: file.device_name.clone().unwrap_or_else(default_hostname),
tun_name: file.tun_name.clone(),
password: file.password.clone(),
cert_mode,
output: file.output.unwrap_or_default(),
no_nat: file.no_nat.unwrap_or(false),
no_tun: file.no_tun.unwrap_or(false),
mtu: file.mtu,
port_mapping,
allow_port_mapping: file.allow_mapping.unwrap_or(false),
udp_stun,
tcp_stun,
};
let ctrl_config = CtrlConfig {
ctrl_port: file.ctrl_port,
};
Ok((config, ctrl_config))
}
fn default_hostname() -> String {
hostname::get()
.ok()
.and_then(|v| v.into_string().ok())
.unwrap_or_default()
}
impl FileConfig {
pub fn print_example(path: Option<&Path>) -> anyhow::Result<()> {
const VERSION: &str = env!("CARGO_PKG_VERSION");
let example = format!(
r#"# ==================================
# VNT 配置文件示例(程序版本 v{version}
# ==================================
# --- 网络配置 ---
# 网络编号,相同网络编号的会组在同一个虚拟网 (必填)
network_code = "your_network_code"
# 服务器地址列表(支持 quic / tcp / wss / dynamic) (必填)
# dynamic 协议使用dns txt解析记录值
server = ["quic://1.2.3.4:29872"]
# ===简单使用以下参数可以不动===
# 自定义虚拟 IP (可选)
# ip = "10.10.0.2"
# 是否启用quic优化传输 (默认 false,设置为true时开启)
# rtx = false
# 是否启用 FEC 前向纠错,损失一定带宽来提升网络稳定性(默认 false,设置为true时开启)
# fec = false
# 是否关闭 P2P 打洞 (默认 false,设置为true时关闭)
# no_punch = false
# 是否启用 LZ4 压缩 (默认 false,设置为true时开启)
# compress = false
# 入栈监听网段 (逗号分隔的 CIDR 和目标 IP),用于点对网,将指定网段的流量发送到目标节点
# input = ["192.168.0.0/24,10.26.0.2", "192.168.1.0/24,10.26.0.3"]
# 出栈允许网段,用于点对网,允许指定网段的转发
# output = ["0.0.0.0/0"]
# 是否关闭内置子网NAT,关闭(设为true)后需要配置网卡转发,否则无法使用点对网。通常关闭内置子网NAT,使用系统的网卡转发,点对网性能会更好
# no_nat = false
# 是否关闭TUN虚拟网卡,关闭(设为true)后只能充当流量出口或者进行端口映射,关闭后无需管理员权限
# no_tun = false
# 端口映射,格式为:协议://本地监听地址-目标虚拟IP-目标映射地址
# 端口映射用于在本地监听指定端口,并将收到的网络流量经由指定虚拟节点转发到目标地址,从而实现跨网络或内网服务访问
# 例如 port_mapping = ["tcp://0.0.0.0:81-10.0.0.2-10.0.0.2:80"]
# tcp://0.0.0.0:81-10.0.0.2-10.0.0.2:80 则表示将本地tcp的81端口的数据转发到10.0.0.2:80
# tcp://0.0.0.0:81-10.0.0.2-192.168.1.10:80 则表示将本地tcp的81端口的数据经过10.0.0.2转到192.168.1.10:80
# tcp://0.0.0.0:81-10.0.0.2-anyonehost:80 则表示将本地tcp的81端口的数据经过10.0.0.2转到anyonehost:80
# port_mapping = []
# 是否允许作为端口映射出口,开启(设置为true)后其他设备才可使用本设备的ip为"目标虚拟IP"
# 开启后虚拟网络其他设备可以使用此设备当跳板访问其他网络
# allow_mapping = false
# 控制服务的 tcp 端口
# ctrl_port = 11233
# MTU 设置
# mtu = 1400
# --- 设备配置 ---
# 设备名称 (可选,默认读取本机 hostname)
# device_name = "my-device"
# 设备 ID (可选,不填自动生成,不同设备ID不能相同)
# device_id = "device-id-xxxx"
# 虚拟网卡名称
# tun_name = "vnt-tun"
# --- 安全配置 ---
# 加密密码 (可选)
# password = "123456"
# 证书校验方式:
# skip 跳过验证(默认)
# standard 使用系统证书验证
# finger 使用证书指纹验证,服务端启动时日志会输出指纹,
# 例如 finger:3bdd8675606837cdf95d5e13445606315762315a78555f9da652940a25feaec1
# cert_mode = "skip"
# --- 其他配置 ---
# 自定义stun地址,分别用于udp打洞和tcp打洞,需要单独配置,不设置则用默认stun
# udp_stun = ["stun.chat.bilibili.com"]
# tcp_stun = ["stun.nextcloud.com:443"]
"#,
version = VERSION
);
println!("--- 示例配置文件内容 ---\n{}", example);
if let Some(p) = path {
std::fs::write(p, &example)?;
println!("示例配置文件已写入 {}", p.display());
}
Ok(())
}
}
-24
View File
@@ -1,24 +0,0 @@
use std::io;
use crossbeam::channel::RecvError;
use thiserror::Error;
#[derive(Error, Debug)]
pub enum Error {
#[error("packet error")]
PacketError(#[from] packet::error::Error),
#[error("Io error")]
Io(#[from] io::Error),
#[error("Channel error")]
Channel(#[from] RecvError),
#[error("Protobuf error")]
Protobuf(#[from] protobuf::Error),
#[error("Invalid packet")]
InvalidPacket,
#[error("Not support")]
NotSupport,
#[error("Stop")]
Stop(String),
}
pub type Result<T> = std::result::Result<T, Error>;
+33
View File
@@ -0,0 +1,33 @@
use std::fs;
use std::io::{self, Write};
use std::path::Path;
#[cfg(target_arch = "x86_64")]
const WINTUN_DLL: &[u8] = include_bytes!("../dll/amd64/wintun.dll");
#[cfg(target_arch = "x86")]
const WINTUN_DLL: &[u8] = include_bytes!("../dll/x86/wintun.dll");
#[cfg(target_arch = "aarch64")]
const WINTUN_DLL: &[u8] = include_bytes!("../dll/arm64/wintun.dll");
#[cfg(target_arch = "arm")]
const WINTUN_DLL: &[u8] = include_bytes!("../dll/arm/wintun.dll");
pub fn extract_wintun() {
if let Err(e) = extract_wintun_impl() {
log::error!("extract wintun.dll {:?}", e);
}
}
fn extract_wintun_impl() -> io::Result<()> {
let path = std::env::current_exe()
.ok()
.and_then(|p| p.parent().map(|d| d.join("wintun.dll")))
.unwrap_or_else(|| Path::new("wintun.dll").to_path_buf());
if !path.exists() {
let mut file = fs::File::create(&path)?;
file.write_all(WINTUN_DLL)?;
}
Ok(())
}
-45
View File
@@ -1,45 +0,0 @@
use std::net::{SocketAddr, UdpSocket};
use std::thread;
use std::time::Duration;
use chrono::Local;
use crate::DEVICE_LIST;
use crate::error::*;
use crate::handle::DIRECT_ROUTE_TABLE;
use crate::protocol::{control_packet, NetPacket, Protocol, Version};
use crate::protocol::control_packet::PingPacket;
pub fn handle_loop(udp: UdpSocket, server_addr: SocketAddr) -> Result<()> {
const INTERVAL: u64 = 3000;
const MAX_INTERVAL: i64 = 3000 * 3;
let mut buf = [0u8; (4 + 8 + 4)];
let mut net_packet = NetPacket::new(&mut buf)?;
net_packet.set_version(Version::V1);
net_packet.set_protocol(Protocol::Control);
net_packet.set_transport_protocol(control_packet::Protocol::Ping.into());
net_packet.set_ttl(255);
loop {
let current_time = Local::now().timestamp_millis();
{
let mut ping = PingPacket::new(net_packet.payload_mut())?;
ping.set_time(current_time);
let epoch = { DEVICE_LIST.lock().0 };
ping.set_epoch(epoch);
}
let _ = udp.send_to(net_packet.buffer(), server_addr);
for x in DIRECT_ROUTE_TABLE.iter() {
let virtual_ip = x.key().clone();
let route = x.value().clone();
drop(x);
if current_time - route.recv_time <= MAX_INTERVAL {
let _ = udp.send_to(net_packet.buffer(), route.address);
} else {
DIRECT_ROUTE_TABLE.remove_if(&virtual_ip, |_, route| {
current_time - route.recv_time <= MAX_INTERVAL
});
}
}
thread::sleep(Duration::from_millis(INTERVAL));
}
}
-132
View File
@@ -1,132 +0,0 @@
use std::net::{ Ipv4Addr, SocketAddr};
use std::sync::atomic::AtomicI64;
use std::time::Duration;
use chrono::Local;
use dashmap::DashMap;
use lazy_static::lazy_static;
use moka::sync::Cache;
use parking_lot::{const_mutex, Mutex};
use crate::proto::message::NatType;
pub mod heartbeat_handler;
pub mod punch_handler;
pub mod registration_handler;
pub mod tun_handler;
pub mod udp_recv_handler;
lazy_static! {
/// 0. 机器纪元,每一次上线或者下线都会增1,由服务端维护,用于感知网络中机器变化
/// 服务端和客户端的不一致,则服务端会推送新的设备列表
/// 1. 网络中的虚拟ip列表
pub static ref DEVICE_LIST:Mutex<(u32,Vec<Ipv4Addr>)> = const_mutex((0,Vec::new()));
/// 服务器延迟
pub static ref SERVER_RT:AtomicI64 = AtomicI64::new(-1);
/// id
pub static ref ID:AtomicI64 = AtomicI64::new(0);
/// 直连路由表
pub static ref DIRECT_ROUTE_TABLE:DashMap<Ipv4Addr,Route> = DashMap::new();
/// 地址映射
pub static ref ADDR_TABLE:Cache<SocketAddr,Ipv4Addr> = Cache::builder()
.time_to_idle(Duration::from_secs(60*5)).build();
/// 当前设备的nat信息
pub static ref NAT_INFO:Mutex<Option<NatInfo>> = const_mutex(None);
}
#[derive(Clone, Debug)]
pub struct NatInfo {
public_ips: Vec<u32>,
public_port: u16,
public_port_range: u16,
nat_type: NatType,
}
impl NatInfo {
pub fn new(public_ips: Vec<u32>,
public_port: u16,
public_port_range: u16,
nat_type: NatType, ) -> Self {
Self {
public_ips,
public_port,
public_port_range,
nat_type,
}
}
}
/// 初始化nat信息
pub fn init_nat_info(public_ip: u32, public_port: u16) {
match crate::nat::check::public_ip_list() {
Ok((nat_type, ips, port_range)) => {
let mut public_ips = Vec::new();
public_ips.push(public_ip);
for ip in ips {
let ip = u32::from_be_bytes(ip.octets());
if ip != public_ip {
public_ips.push(ip);
}
}
let nat_info = NatInfo::new(public_ips,
public_port,
port_range, nat_type);
// println!("nat信息:{:?}",nat_info);
let mut nat_info_lock = NAT_INFO.lock();
nat_info_lock.replace(nat_info);
}
Err(e) => {
println!("获取nat数据失败,将无法进行udp打洞:{:?}", e);
}
}
}
#[derive(Clone, Debug)]
pub struct CurrentDeviceInfo {
pub(crate) virtual_ip: Ipv4Addr,
pub(crate) virtual_gateway: Ipv4Addr,
pub(crate) virtual_netmask: Ipv4Addr,
//网络地址
pub(crate) virtual_network: Ipv4Addr,
//直接广播地址
pub(crate) broadcast_address: Ipv4Addr,
//链接的服务器地址
pub(crate) connect_server: SocketAddr,
}
impl CurrentDeviceInfo {
pub fn new(virtual_ip: Ipv4Addr, virtual_gateway: Ipv4Addr, virtual_netmask: Ipv4Addr, connect_server: SocketAddr) -> Self {
let broadcast_address = (!u32::from_be_bytes(virtual_netmask.octets()))
| u32::from_be_bytes(virtual_gateway.octets());
let broadcast_address = Ipv4Addr::from(broadcast_address);
let virtual_network = u32::from_be_bytes(virtual_netmask.octets())
& u32::from_be_bytes(virtual_gateway.octets());
let virtual_network = Ipv4Addr::from(virtual_network);
Self {
virtual_ip,
virtual_netmask,
virtual_gateway,
virtual_network,
broadcast_address,
connect_server,
}
}
}
#[derive(Clone,Debug)]
pub struct Route {
pub(crate) address: SocketAddr,
//用心跳探测延迟,收包时更新
pub(crate) delay: i64,
//收包时更新,如果太久没有收到消息则剔除
pub(crate) recv_time: i64,
}
impl Route {
pub fn new(address: SocketAddr) -> Self {
Self {
address,
delay: -1,
recv_time: Local::now().timestamp_millis(),
}
}
}
-354
View File
@@ -1,354 +0,0 @@
use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4, UdpSocket};
use std::thread;
use std::time::Duration;
use crossbeam::channel::{Receiver, RecvTimeoutError, Sender, SendError, TrySendError};
use dashmap::DashMap;
use lazy_static::lazy_static;
use protobuf::Message;
use crate::{CurrentDeviceInfo, DEVICE_LIST, NAT_INFO, NatInfo};
use crate::error::*;
use crate::handle::DIRECT_ROUTE_TABLE;
use crate::proto::message::{NatType, Punch, Step};
use crate::protocol::{control_packet, NetPacket, Protocol, turn_packet, Version};
use crate::protocol::control_packet::PunchRequestPacket;
use crate::protocol::turn_packet::TurnPacket;
lazy_static! {
pub static ref STEP_MAP:DashMap<Ipv4Addr,Step> = DashMap::new();
}
/// 每一种类型一个通道,减少相互干扰
pub fn bounded() -> (PunchSender, ConeReceiver, ReqSymmetricReceiver, ResSymmetricReceiver) {
let (cone_sender, cone_receiver) = crossbeam::channel::bounded(3);
let (req_symmetric_sender, req_symmetric_receiver) = crossbeam::channel::bounded(1);
let (res_symmetric_sender, res_symmetric_receiver) = crossbeam::channel::bounded(1);
(PunchSender::new(cone_sender, req_symmetric_sender, res_symmetric_sender),
ConeReceiver(cone_receiver), ReqSymmetricReceiver(req_symmetric_receiver),
ResSymmetricReceiver(res_symmetric_receiver))
}
pub struct ConeReceiver(Receiver<Punch>);
pub struct ReqSymmetricReceiver(Receiver<Punch>);
pub struct ResSymmetricReceiver(Receiver<Punch>);
#[derive(Clone)]
pub struct PunchSender {
cone_sender: Sender<Punch>,
req_symmetric_sender: Sender<Punch>,
res_symmetric_sender: Sender<Punch>,
}
impl PunchSender {
pub fn new(cone_sender: Sender<Punch>,
req_symmetric_sender: Sender<Punch>,
res_symmetric_sender: Sender<Punch>, ) -> Self {
Self {
cone_sender,
req_symmetric_sender,
res_symmetric_sender,
}
}
pub fn send(&self, punch: Punch) -> std::result::Result<(), SendError<Punch>> {
match punch.nat_type.enum_value_or_default() {
NatType::Symmetric => {
if punch.reply {
// 为true表示回应,也就是主动发起的打洞操作
self.res_symmetric_sender.send(punch)
} else {
self.req_symmetric_sender.send(punch)
}
}
NatType::Cone => {
self.cone_sender.send(punch)
}
}
}
pub fn try_send(&self, punch: Punch) -> std::result::Result<(), TrySendError<Punch>> {
match punch.nat_type.enum_value_or_default() {
NatType::Symmetric => {
if punch.reply {
// 为true表示回应,也就是主动发起的打洞操作
self.res_symmetric_sender.try_send(punch)
} else {
self.req_symmetric_sender.try_send(punch)
}
}
NatType::Cone => {
self.cone_sender.try_send(punch)
}
}
}
}
fn handle(udp: &UdpSocket, punch_list: Vec<Punch>, buf: &[u8]) -> Result<()> {
let mut counter = 0u64;
for punch in punch_list {
let dest = Ipv4Addr::from(punch.virtual_ip);
if DIRECT_ROUTE_TABLE.contains_key(&dest) {
continue;
}
// println!("punch {:?}", punch);
match punch.nat_type.enum_value_or_default() {
NatType::Symmetric => {
match punch.step.enum_value_or_default() {
Step::Step1 | Step::Step2 | Step::Step3 => {
//预测范围发送
for pub_ip in punch.public_ip_list {
let pub_ip = Ipv4Addr::from(pub_ip);
for range in 0..punch.public_port_range + 1 {
let right_port = ((punch.public_port + range) & 0xFFFF) as u16;
let left_port = ((0xFFFF + punch.public_port - range) & 0xFFFF) as u16;
if right_port != 0 {
// println!("{:?}", SocketAddr::V4(SocketAddrV4::new(pub_ip, right_port)));
udp.send_to(
buf,
SocketAddr::V4(SocketAddrV4::new(pub_ip, right_port)),
)?;
select_sleep(&mut counter);
}
if left_port != 0 && range != 0 {
// println!("{:?}", SocketAddr::V4(SocketAddrV4::new(pub_ip, right_port)));
if left_port == right_port {
break;
}
udp.send_to(
buf,
SocketAddr::V4(SocketAddrV4::new(pub_ip, left_port)),
)?;
select_sleep(&mut counter);
}
}
}
}
Step::Step4 => {
//全范围发送
for pub_ip in punch.public_ip_list {
let pub_ip = Ipv4Addr::from(pub_ip);
for port in 1..0xFFFF {
udp.send_to(
buf,
SocketAddr::V4(SocketAddrV4::new(pub_ip, port)),
)?;
select_sleep(&mut counter);
}
}
}
}
}
NatType::Cone => {
for pub_ip in punch.public_ip_list {
udp.send_to(
buf,
SocketAddr::V4(SocketAddrV4::new(
Ipv4Addr::from(pub_ip),
punch.public_port as u16,
)),
)?;
select_sleep(&mut counter);
}
}
}
}
Ok(())
}
/// 给对称nat发送打洞数据包
pub fn req_symmetric_handle_loop(
receiver: ReqSymmetricReceiver,
udp: UdpSocket,
cur_info: CurrentDeviceInfo,
) -> Result<()> {
let receiver = receiver.0;
handle_loop(receiver, udp, cur_info)
}
/// 给对称nat发送打洞数据包,处理主动发起的打洞操作
pub fn res_symmetric_handle_loop(
receiver: ResSymmetricReceiver,
udp: UdpSocket,
cur_info: CurrentDeviceInfo,
) -> Result<()> {
let receiver = receiver.0;
let mut buf = [0u8; 12];
let mut packet = NetPacket::new(&mut buf)?;
packet.set_version(Version::V1);
packet.set_ttl(255);
packet.set_protocol(Protocol::Control);
packet.set_transport_protocol(control_packet::Protocol::PunchRequest.into());
{
let mut punch_packet = PunchRequestPacket::new(packet.payload_mut())?;
punch_packet.set_source(cur_info.virtual_ip);
}
loop {
match receiver.recv_timeout(Duration::from_secs(30)) {
Ok(punch) => {
let mut list = Vec::new();
list.push(punch);
loop {
match receiver.try_recv() {
Ok(punch) => {
list.push(punch);
}
Err(_) => {
break;
}
}
}
for punch in &list {
let dest = Ipv4Addr::from(punch.virtual_ip);
match punch.step.enum_value_or_default() {
Step::Step1 => {
STEP_MAP.insert(dest, Step::Step2);
}
Step::Step2 => {
STEP_MAP.insert(dest, Step::Step3);
}
Step::Step3 => {
STEP_MAP.insert(dest, Step::Step4);
}
Step::Step4 => {
STEP_MAP.insert(dest, Step::Step1);
}
}
}
if let Err(e) = handle(&udp, list, packet.buffer()) {
println!("{:?}", e);
}
}
Err(RecvTimeoutError::Timeout) => {
punch_request_handle(&udp, &cur_info)?;
}
Err(_) => {
return Err(Error::Stop("打洞线程通道关闭".to_string()));
}
}
}
}
/// 给锥形nat发送打洞数据包
pub fn cone_handle_loop(
receiver: ConeReceiver,
udp: UdpSocket,
cur_info: CurrentDeviceInfo,
) -> Result<()> {
let receiver = receiver.0;
handle_loop(receiver, udp, cur_info)
}
pub fn handle_loop(
receiver: Receiver<Punch>,
udp: UdpSocket,
cur_info: CurrentDeviceInfo,
) -> Result<()> {
let mut buf = [0u8; 12];
let mut packet = NetPacket::new(&mut buf)?;
packet.set_version(Version::V1);
packet.set_ttl(255);
packet.set_protocol(Protocol::Control);
packet.set_transport_protocol(control_packet::Protocol::PunchRequest.into());
{
let mut punch_packet = PunchRequestPacket::new(packet.payload_mut())?;
punch_packet.set_source(cur_info.virtual_ip);
}
loop {
match receiver.recv() {
Ok(punch) => {
let mut list = Vec::new();
list.push(punch);
loop {
match receiver.try_recv() {
Ok(punch) => {
list.push(punch);
}
Err(_) => {
break;
}
}
}
if let Err(e) = handle(&udp, list, packet.buffer()) {
println!("{:?}", e);
}
}
Err(_) => {
return Err(Error::Stop("打洞线程通道关闭".to_string()));
}
}
}
}
fn select_sleep(counter: &mut u64) {
*counter += 1;
thread::sleep(Duration::from_millis(1));
// if *counter > 1 {
// if cone_nat {
// thread::sleep(Duration::from_millis(2));
// } else {
// if (*counter) & 10 == 10 {
// thread::sleep(Duration::from_millis(1));
// }
// }
// }
}
fn punch_request_handle(udp: &UdpSocket, cur_info: &CurrentDeviceInfo) -> Result<()> {
let nat_info_lock = NAT_INFO.lock();
let nat_info = nat_info_lock.clone();
drop(nat_info_lock);
if let Some(nat_info) = nat_info {
if let Err(e) = send_punch(&udp,
&cur_info,
nat_info) {
println!("发送打洞数据失败 :{:?}", e);
}
Ok(())
} else {
Err(Error::Stop("未初始化nat信息".to_string()))
}
}
fn send_punch(udp: &UdpSocket, cur_info: &CurrentDeviceInfo, nat_info: NatInfo) -> Result<()> {
let lock = DEVICE_LIST.lock();
let list = lock.1.clone();
drop(lock);
for ip in list {
//只向ip比自己大的发起打洞,避免双方同时发起打洞浪费流量
if ip > cur_info.virtual_ip && !DIRECT_ROUTE_TABLE.contains_key(&ip) {
let step = if let Some(step) = STEP_MAP.get(&ip) {
*step
} else {
Step::Step1
};
let bytes = punch_packet(cur_info.virtual_ip,
nat_info.clone(), ip, step)?;
udp.send_to(&bytes, cur_info.connect_server)?;
}
}
Ok(())
}
fn punch_packet(virtual_ip: Ipv4Addr, nat_info: NatInfo, dest: Ipv4Addr, step: Step) -> Result<Vec<u8>> {
let mut punch_reply = Punch::new();
punch_reply.reply = false;
punch_reply.virtual_ip = u32::from_be_bytes(virtual_ip.octets());
punch_reply.step = protobuf::EnumOrUnknown::new(step);
punch_reply.public_ip_list = nat_info.public_ips;
punch_reply.public_port = nat_info.public_port as u32;
punch_reply.public_port_range = nat_info.public_port_range as u32;
punch_reply.nat_type = protobuf::EnumOrUnknown::new(nat_info.nat_type);
let bytes = punch_reply.write_to_bytes()?;
let mut net_packet = NetPacket::new(vec![0u8; 4 + 8 + bytes.len()])?;
net_packet.set_version(Version::V1);
net_packet.set_protocol(Protocol::OtherTurn);
net_packet.set_transport_protocol(turn_packet::Protocol::Punch.into());
net_packet.set_ttl(255);
let mut turn_packet = TurnPacket::new(net_packet.payload_mut())?;
turn_packet.set_source(virtual_ip);
turn_packet.set_destination(dest);
turn_packet.set_payload(&bytes);
Ok(net_packet.into_buffer())
}
-113
View File
@@ -1,113 +0,0 @@
use std::io;
use std::net::{SocketAddr, UdpSocket};
use std::sync::atomic::{AtomicI64, Ordering};
use std::time::Duration;
use chrono::Local;
use parking_lot::RwLock;
use protobuf::Message;
use crate::error::*;
use crate::proto::message::{RegistrationRequest, RegistrationResponse};
use crate::protocol::{error_packet, NetPacket, Protocol, service_packet, Version};
lazy_static::lazy_static! {
static ref REQUEST:RwLock<Option<(String,String)>> = parking_lot::const_rwlock(None);
static ref REGISTRATION_TIME:AtomicI64=AtomicI64::new(0);
}
///向中继服务器注册,token标识一个虚拟网关,mac_address防止多次注册时得到的ip不一致
pub fn registration(
udp: &UdpSocket,
server_address: SocketAddr,
token: String,
mac_address: String,
) -> Result<RegistrationResponse> {
// todo 和服务器通信加密
let request_packet = registration_request_packet(token.clone(), mac_address.clone())?;
let buf = request_packet.buffer();
let mut counter = 0;
let mut recv_buf = [0u8; 10240];
udp.set_read_timeout(Some(Duration::from_millis(500)))?;
loop {
counter += 1;
if counter & 10 == 10 {
return Err(Error::Stop("注册请求超时".to_string()));
}
udp.send_to(buf, server_address)?;
let (len, addr) = match udp.recv_from(&mut recv_buf) {
Ok(ok) => ok,
Err(e) => {
if e.kind() == io::ErrorKind::WouldBlock || e.kind() == io::ErrorKind::TimedOut {
continue;
}
return Err(Error::Io(e));
}
};
if server_address != addr {
continue;
}
let net_packet = NetPacket::new(&recv_buf[..len])?;
match net_packet.protocol() {
Protocol::Service => {
match service_packet::Protocol::from(net_packet.transport_protocol()) {
service_packet::Protocol::RegistrationResponse => {
let response =
RegistrationResponse::parse_from_bytes(net_packet.payload())?;
let _ = REQUEST.write().replace((token, mac_address));
udp.set_read_timeout(None)?;
return Ok(response);
}
_ => {}
}
}
Protocol::Error => {
match error_packet::Protocol::from(net_packet.transport_protocol()) {
error_packet::Protocol::TokenError => {
return Err(Error::Stop("token错误".to_string()));
}
_ => {}
}
}
_ => {}
}
}
}
fn registration_request_packet(token: String, mac_address: String) -> Result<NetPacket<Vec<u8>>> {
let mut request = RegistrationRequest::new();
request.token = token;
request.mac_address = mac_address;
let bytes = request.write_to_bytes()?;
let buf = vec![0u8; 4 + bytes.len()];
let mut net_packet = NetPacket::new(buf)?;
net_packet.set_version(Version::V1);
net_packet.set_protocol(Protocol::Service);
net_packet.set_transport_protocol(service_packet::Protocol::RegistrationRequest.into());
net_packet.set_ttl(255);
net_packet.set_payload(&bytes);
Ok(net_packet)
}
pub fn fast_registration(udp: &UdpSocket, server_address: SocketAddr) -> Result<()> {
let last = REGISTRATION_TIME.load(Ordering::Relaxed);
let new = Local::now().timestamp_millis();
if new - last < 2000
|| REGISTRATION_TIME
.compare_exchange(last, new, Ordering::Relaxed, Ordering::Relaxed)
.is_err()
{
//短时间不重复注册
return Ok(());
}
let lock = REQUEST.read();
let option = lock.clone();
drop(lock);
if let Some((token, mac_address)) = option {
let request_packet = registration_request_packet(token, mac_address)?;
udp.send_to(request_packet.buffer(), server_address)?;
REGISTRATION_TIME.store(Local::now().timestamp_millis(), Ordering::Relaxed);
return Ok(());
}
return Err(Error::Stop("注册信息不存在".to_string()));
}
-127
View File
@@ -1,127 +0,0 @@
/// 接收tun数据,并且转发到udp上
use std::net::{IpAddr, Ipv4Addr, UdpSocket};
use chrono::Local;
use packet::icmp::icmp::IcmpPacket;
use packet::icmp::Kind;
use packet::ip::ipv4;
use packet::ip::ipv4::packet::IpV4Packet;
use crate::error::*;
use crate::handle::{CurrentDeviceInfo, DIRECT_ROUTE_TABLE};
use crate::protocol::{NetPacket, Protocol, Version};
use crate::protocol::turn_packet::TurnPacket;
use crate::tun_device::TunReader;
/// 是否在一个网段
fn check_dest(dest: Ipv4Addr, cur_info: &CurrentDeviceInfo) -> bool {
u32::from_be_bytes(dest.octets()) & u32::from_be_bytes(cur_info.virtual_netmask.octets())
== u32::from_be_bytes(cur_info.virtual_network.octets())
}
fn icmp(udp: &UdpSocket, mut ipv4_packet: IpV4Packet<&mut [u8]>) -> Result<()> {
if ipv4_packet.protocol() == ipv4::protocol::Protocol::Icmp {
let mut icmp = IcmpPacket::new(ipv4_packet.payload_mut())?;
if icmp.kind() == Kind::EchoRequest {
icmp.set_kind(Kind::EchoReply);
icmp.update_checksum();
let src = ipv4_packet.source_ip();
ipv4_packet.set_source_ip(ipv4_packet.destination_ip());
ipv4_packet.set_destination_ip(src);
ipv4_packet.update_checksum();
let mut addr = udp.local_addr()?;
addr.set_ip(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)));
udp.send_to(ipv4_packet.buffer, addr)?;
}
}
Ok(())
}
#[inline]
fn handle(
udp: &UdpSocket,
data: &mut [u8],
cur_info: &CurrentDeviceInfo,
net_packet: &mut NetPacket<Vec<u8>>,
) -> Result<()> {
let data_len = data.len();
let ipv4_packet = match IpV4Packet::new(data) {
Ok(ipv4_packet) => ipv4_packet,
Err(packet::error::Error::Unimplemented) => {
return Ok(());
}
Err(e) => Err(e)?,
};
let src_ip = ipv4_packet.source_ip();
let dest_ip = ipv4_packet.destination_ip();
// if dest_ip == cur_info.broadcast_address {
// // 启动服务后会收到对137端口的广播
// // 137端口是在局域网中提供计算机的名字或IP地址查询服务
// return Ok(());
// }
if src_ip != cur_info.virtual_ip || !check_dest(dest_ip, &cur_info) {
return Ok(());
}
if src_ip == dest_ip {
return icmp(&udp, ipv4_packet);
}
let mut ipv4_turn_packet = TurnPacket::new(net_packet.payload_mut())?;
ipv4_turn_packet.set_source(src_ip);
ipv4_turn_packet.set_destination(dest_ip);
ipv4_turn_packet.set_payload(ipv4_packet.buffer);
//优先发到直连到地址
if let Some(route) = DIRECT_ROUTE_TABLE.get(&dest_ip) {
let current_time = Local::now().timestamp_millis();
if current_time - route.recv_time < 3_000 {
udp.send_to(&net_packet.buffer()[..(4 + 8 + data_len)], route.address)?;
return Ok(());
}
}
udp.send_to(&net_packet.buffer()[..(4 + 8 + data_len)], cur_info.connect_server)?;
return Ok(());
}
#[cfg(target_os = "windows")]
pub fn handle_loop(
udp: UdpSocket,
tun_reader: TunReader,
cur_info: CurrentDeviceInfo,
) -> Result<()> {
let mut net_packet = NetPacket::new(vec![0u8; 4 + 8 + 1500])?;
net_packet.set_version(Version::V1);
net_packet.set_protocol(Protocol::Ipv4Turn);
net_packet.set_transport_protocol(ipv4::protocol::Protocol::Ipv4.into());
net_packet.set_ttl(255);
loop {
let mut data = tun_reader.next()?;
match handle(&udp, data.bytes_mut(), &cur_info, &mut net_packet) {
Ok(_) => {}
Err(e) => {
println!("{:?}", e)
}
}
}
}
#[cfg(any(unix))]
pub fn handle_loop(
udp: UdpSocket,
mut tun_reader: TunReader,
cur_info: CurrentDeviceInfo,
) -> Result<()> {
let mut net_packet = NetPacket::new(vec![0u8; 4 + 8 + 1500])?;
net_packet.set_version(Version::V1);
net_packet.set_protocol(Protocol::Ipv4Turn);
net_packet.set_transport_protocol(0);
net_packet.set_ttl(255);
let mut buf = [0u8; 1500];
loop {
let data = tun_reader.read(&mut buf)?;
match handle(&udp, data, &cur_info, &mut net_packet) {
Ok(_) => {}
Err(e) => {
println!("{:?}", e)
}
}
}
}
-299
View File
@@ -1,299 +0,0 @@
use std::net::{Ipv4Addr, SocketAddr, UdpSocket};
use std::sync::atomic::Ordering;
use chrono::Local;
use crossbeam::channel::{Receiver, Sender, TrySendError};
use packet::icmp::{icmp, Kind};
use packet::ip::ipv4;
use packet::ip::ipv4::packet::IpV4Packet;
use protobuf::Message;
use crate::CurrentDeviceInfo;
use crate::error::*;
use crate::handle::{ADDR_TABLE, DEVICE_LIST, DIRECT_ROUTE_TABLE, NAT_INFO, Route, SERVER_RT};
use crate::handle::punch_handler::PunchSender;
use crate::handle::registration_handler::fast_registration;
use crate::proto::message::{DeviceList, Punch, RegistrationResponse};
use crate::protocol::{control_packet, NetPacket, Protocol, service_packet, turn_packet, Version};
use crate::protocol::control_packet::{ControlPacket, PunchResponsePacket};
use crate::protocol::error_packet::InErrorPacket;
use crate::protocol::turn_packet::TurnPacket;
use crate::tun_device::TunWriter;
pub fn recv_loop(
udp: UdpSocket,
server_addr: SocketAddr,
other_sender: Sender<(SocketAddr, Vec<u8>)>,
mut tun_writer: TunWriter,
current_device: CurrentDeviceInfo,
) -> Result<()> {
let mut buf = [0u8; 65536];
let mut local_addr = udp.local_addr()?;
local_addr.set_ip(std::net::IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)));
loop {
match udp.recv_from(&mut buf) {
Ok((len, addr)) => {
if addr == local_addr {
//本地的包直接再发到网卡,这个主要用于处理当前虚拟ip的icmp ping
if let Ok(ip) = IpV4Packet::new(&buf[..len]) {
if ip.destination_ip() == current_device.virtual_ip {
let _ = tun_writer.write(&buf[..len]);
}
}
continue;
}
match recv_handle(
&udp,
addr,
&mut buf[..len],
&server_addr,
&other_sender,
&mut tun_writer,
&current_device,
) {
Ok(_) => {}
Err(Error::Stop(str)) => {
return Err(Error::Stop(str));
}
Err(_) => {}
}
}
Err(e) => {
println!("{:?}", e);
}
};
}
}
fn recv_handle(
udp: &UdpSocket,
recv_addr: SocketAddr,
buf: &mut [u8],
_server_addr: &SocketAddr,
other_sender: &Sender<(SocketAddr, Vec<u8>)>,
tun_writer: &mut TunWriter,
current_device: &CurrentDeviceInfo,
) -> Result<()> {
let mut net_packet = NetPacket::new(buf)?;
match net_packet.protocol() {
Protocol::Ipv4Turn => {
let mut ipv4_turn_packet = TurnPacket::new(net_packet.payload_mut())?;
let source = ipv4_turn_packet.source();
let destination = ipv4_turn_packet.destination();
let mut ipv4 = IpV4Packet::new(ipv4_turn_packet.payload_mut())?;
if ipv4.source_ip() == source
&& ipv4.destination_ip() == destination
&& current_device.virtual_ip == ipv4.destination_ip()
{
if ipv4.protocol() == ipv4::protocol::Protocol::Icmp {
let mut icmp_packet = icmp::IcmpPacket::new(ipv4.payload_mut())?;
if icmp_packet.kind() == Kind::EchoRequest {
//开启ping
icmp_packet.set_kind(Kind::EchoReply);
icmp_packet.update_checksum();
ipv4.set_source_ip(destination);
ipv4.set_destination_ip(source);
ipv4.update_checksum();
ipv4_turn_packet.set_source(destination);
ipv4_turn_packet.set_destination(source);
udp.send_to(net_packet.buffer(), recv_addr)?;
} else {
tun_writer.write(ipv4_turn_packet.payload())?;
}
} else {
tun_writer.write(ipv4_turn_packet.payload())?;
}
}
}
Protocol::UnKnow(_) => {}
_ => {
//发送到子线程处理
let v = net_packet.buffer().to_vec();
match other_sender.try_send((recv_addr, v)) {
Ok(_) => {}
Err(TrySendError::Disconnected(_)) => {
return Err(Error::Stop("处理线程停止".to_string()));
}
Err(e) => {
println!("子线程处理 :{:?}", e);
}
}
}
}
Ok(())
}
pub fn other_loop(
udp: UdpSocket,
receiver: Receiver<(SocketAddr, Vec<u8>)>,
current_device: CurrentDeviceInfo,
sender: PunchSender,
) -> Result<()> {
loop {
let (peer_addr, buf) = receiver.recv()?;
match other_handle(&udp, buf, peer_addr, &current_device, &sender) {
Ok(_) => {}
Err(Error::Stop(str)) => {
return Err(Error::Stop(str));
}
Err(e) => {
println!("{:?}", e)
}
}
}
}
fn other_handle(
udp: &UdpSocket,
buf: Vec<u8>,
peer_addr: SocketAddr,
current_device: &CurrentDeviceInfo,
sender: &PunchSender,
) -> Result<()> {
let server_addr = current_device.connect_server;
let mut net_packet = NetPacket::new(buf)?;
match net_packet.protocol() {
Protocol::Service => {
if peer_addr != current_device.connect_server {
return Ok(());
}
match service_packet::Protocol::from(net_packet.transport_protocol()) {
service_packet::Protocol::RegistrationRequest => {}
service_packet::Protocol::RegistrationResponse => {
let response = RegistrationResponse::parse_from_bytes(net_packet.payload())?;
crate::handle::init_nat_info(response.public_ip, response.public_port as u16);
//todo 重连之后ip可能会发生改变(目前2分钟内未重连则会释放ip),需要更新本地ip(或者保证重连ip不变)
}
service_packet::Protocol::UpdateDeviceList => {
let device_list = DeviceList::parse_from_bytes(net_packet.payload())?;
let ip_list: Vec<Ipv4Addr> = device_list
.virtual_ip_list
.iter()
.map(|ip| Ipv4Addr::from(*ip))
.collect();
let mut dev = DEVICE_LIST.lock();
if dev.0 < device_list.epoch || device_list.epoch - dev.0 > u32::MAX >> 2 {
dev.0 = device_list.epoch;
dev.1 = ip_list;
}
}
service_packet::Protocol::UnKnow(_) => {}
}
}
Protocol::Error => {
match InErrorPacket::new(net_packet.transport_protocol(), net_packet.payload())? {
InErrorPacket::TokenError => {
if server_addr == peer_addr {
//停止整个应用
return Err(Error::Stop("token无效".to_string()));
}
}
InErrorPacket::Disconnect => {
if server_addr == peer_addr {
fast_registration(&udp, server_addr)?;
}
}
InErrorPacket::OtherError(e) => {
println!("{:?}", e.message());
}
}
}
Protocol::Control => {
match ControlPacket::new(net_packet.transport_protocol(), net_packet.payload())? {
ControlPacket::PingPacket(ping) => {
net_packet.set_transport_protocol(control_packet::Protocol::Pong.into());
udp.send_to(&net_packet.buffer()[..12], peer_addr)?;
}
ControlPacket::PongPacket(pong_packet) => {
let current_time = Local::now().timestamp_millis();
let rt = current_time - pong_packet.time();
if rt >= 0 {
if peer_addr == server_addr {
SERVER_RT.store(rt, Ordering::Relaxed)
} else {
//其他设备
if let Some(virtual_ip) = ADDR_TABLE.get(&peer_addr) {
if let Some(mut info) = DIRECT_ROUTE_TABLE.get_mut(&virtual_ip) {
info.delay = rt;
info.recv_time = current_time;
}
}
}
}
}
ControlPacket::PunchRequest(punch_request) => {
// println!("打洞请求:{:?}", punch_request);
let src = punch_request.source();
drop(punch_request);
//回应
let mut punch_response = PunchResponsePacket::new(net_packet.payload_mut())?;
punch_response.set_source(current_device.virtual_ip);
net_packet.set_transport_protocol(control_packet::Protocol::PunchResponse.into());
udp.send_to(net_packet.buffer(), peer_addr)?;
let route = Route::new(peer_addr);
DIRECT_ROUTE_TABLE.insert(src, route);
ADDR_TABLE.insert(peer_addr, src);
}
ControlPacket::PunchResponse(punch_response) => {
// println!("打洞响应:{:?}", punch_response);
let route = Route::new(peer_addr);
DIRECT_ROUTE_TABLE.insert(punch_response.source(), route);
ADDR_TABLE.insert(peer_addr, punch_response.source());
}
}
}
Protocol::Ipv4Turn => {}
Protocol::OtherTurn => {
let turn_packet = TurnPacket::new(net_packet.payload())?;
// println!("{:?}",turn_packet);
let src = turn_packet.source();
let dest = turn_packet.destination();
if dest == current_device.virtual_ip {
match turn_packet::Protocol::from(net_packet.transport_protocol()) {
turn_packet::Protocol::Punch => {
let punch = Punch::parse_from_bytes(turn_packet.payload())?;
if punch.virtual_ip.to_be_bytes() == src.octets() {
if !punch.reply {
let mut punch_reply = Punch::new();
punch_reply.reply = true;
punch_reply.virtual_ip = u32::from_be_bytes(current_device.virtual_ip.octets());
punch_reply.step = punch.step;
if let Err(_) = sender.try_send(punch) {
return Ok(());
}
let nat_info = NAT_INFO.lock();
if let Some(info) = nat_info.as_ref() {
punch_reply.public_ip_list = info.public_ips.clone();
punch_reply.public_port = info.public_port as u32;
punch_reply.public_port_range = info.public_port_range as u32;
punch_reply.nat_type = protobuf::EnumOrUnknown::new(info.nat_type);
drop(nat_info);
let bytes = punch_reply.write_to_bytes()?;
let mut net_packet = NetPacket::new(vec![0u8; 4 + 8 + bytes.len()])?;
net_packet.set_version(Version::V1);
net_packet.set_protocol(Protocol::OtherTurn);
net_packet.set_transport_protocol(turn_packet::Protocol::Punch.into());
net_packet.set_ttl(255);
let mut turn_packet = TurnPacket::new(net_packet.payload_mut())?;
turn_packet.set_source(current_device.virtual_ip);
turn_packet.set_destination(src);
turn_packet.set_payload(&bytes);
udp.send_to(net_packet.buffer(), peer_addr)?;
}
} else {
let _ = sender.try_send(punch);
}
}
}
turn_packet::Protocol::UnKnow(_) => {}
}
} else {
panic!("ip")
}
}
Protocol::UnKnow(p) => {
println!("未知协议:{}", p)
}
}
Ok(())
}
+1
View File
@@ -0,0 +1 @@
pub mod log;
+61
View File
@@ -0,0 +1,61 @@
use std::io::Write;
use std::path::PathBuf;
pub fn log_init(log_name: &str) {
log_init0(log_name, None);
}
pub fn log_init0(log_name: &str, yaml: Option<&str>) {
let path = PathBuf::from("logs");
if !path.exists() {
let _ = std::fs::create_dir(&path);
}
let log_config = path.join(yaml.unwrap_or("log4rs.yaml"));
if !log_config.exists()
&& let Ok(mut f) = std::fs::File::create(&log_config)
{
let log_file = path.join(format!("{log_name}.log"));
let mut string = String::new();
string.push_str(log_name);
string.push_str(".{}.log");
let log_file_pattern = path.join(&string);
let c = format!(
r#"
refresh_rate: 30 seconds
appenders:
rolling_file:
kind: rolling_file
path: {}
append: true
encoder:
pattern: "{{d}} [{{f}}:{{L}}] {{h({{l}})}} {{M}}:{{m}}{{n}}"
policy:
kind: compound
trigger:
kind: size
limit: 10 mb
roller:
kind: fixed_window
pattern: {}
base: 1
count: 5
console:
kind: console
encoder:
pattern: "{{d}} {{h({{l}})}} {{m}}{{n}}"
root:
level: info
appenders:
- rolling_file
- console
"#,
log_file.display(),
log_file_pattern.display()
);
let _ = f.write_all(c.as_bytes());
}
let _ = log4rs::init_file(log_config, Default::default());
}
-248
View File
@@ -1,248 +0,0 @@
use std::{io, thread};
use std::net::{IpAddr, Ipv4Addr, SocketAddr, SocketAddrV4, UdpSocket};
use std::sync::atomic::Ordering;
use clap::Parser;
use console::style;
use crate::handle::{CurrentDeviceInfo, DEVICE_LIST, DIRECT_ROUTE_TABLE, NAT_INFO, NatInfo, SERVER_RT};
use crate::handle::registration_handler::registration;
use crate::tun_device::create_tun;
pub mod tun_device;
pub mod nat;
pub mod error;
pub mod handle;
pub mod proto;
pub mod protocol;
#[cfg(windows)]
pub mod admin_check;
#[derive(Parser, Debug)]
#[command(author = "Lu Beilin", version, about = "一个虚拟网络工具,启动后会获取一个ip,相同token下的设备之间可以用ip直接通信")]
struct Args {
/// 32位字符
/// 相同token的设备之间才能通信。
/// 建议使用uuid保证唯一性。
/// 32-bit characters.
/// Only devices with the same token can communicate with each other.
/// It is recommended to use uuid to ensure uniqueness
#[arg(short, long)]
token: String,
}
fn main() {
let args = Args::parse();
#[cfg(windows)]
if !admin_check::is_app_elevated() {
let args: Vec<_> = std::env::args().collect();
println!("{}", style("正在启动管理员权限执行...").red());
if let Some(absolute_path) = std::env::current_exe()
.ok()
.and_then(|p| p.to_str().map(|p| p.to_string()))
{
let _ = runas::Command::new(&absolute_path).args(&args[1..]).status()
.expect("failed to execute");
} else {
panic!("failed to execute")
}
return;
}
#[cfg(any(unix))]
if sudo::RunningAs::Root != sudo::check() {
println!("{}", style("需要使用root权限执行...").red());
sudo::escalate_if_needed().unwrap();
}
println!("{}", style("启动服务...").green());
let token = args.token;
// let d = Local::now().timestamp().to_string();
let mac_address = mac_address::get_mac_address().unwrap().unwrap().to_string();
let server_address = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(43, 139, 56, 10)), 29876);
// let server_address = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127,0,0,1)), 29876);
let mut port = 101 as u16;
let udp = loop {
match UdpSocket::bind(SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::from(0), port))) {
Ok(udp) => {
break udp;
}
Err(e) => {
if e.kind() == io::ErrorKind::AddrInUse {
port += 1;
} else {
println!("创建udp失败:{:?}", e);
panic!()
}
}
}
};
//注册
let response = registration(&udp, server_address, token, mac_address).unwrap();
{
let ip_list = response
.virtual_ip_list
.iter()
.map(|ip| Ipv4Addr::from(*ip))
.collect();
let mut dev = DEVICE_LIST.lock();
dev.0 = response.epoch;
dev.1 = ip_list;
}
let virtual_ip = Ipv4Addr::from(response.virtual_ip);
let virtual_gateway = Ipv4Addr::from(response.virtual_gateway);
let virtual_netmask = Ipv4Addr::from(response.virtual_netmask);
println!("virtual_gateway:{:?}", virtual_gateway);
println!("virtual_netmask:{:?}", virtual_netmask);
println!("当前设备ip(virtual_ip):{}", style(virtual_ip).green());
//心跳线程
{
let udp = udp.try_clone().unwrap();
let _ = thread::spawn(move || {
if let Err(e) = handle::heartbeat_handler::handle_loop(udp, server_address) {
println!("心跳线程停止:{:?}", e);
}
std::process::exit(1);
});
}
//初始化nat数据
handle::init_nat_info(response.public_ip, response.public_port as u16);
// tun服务
let (tun_writer, tun_reader) =
create_tun(virtual_ip, virtual_netmask, virtual_gateway).unwrap();
// 打洞数据通道
let (punch_sender, cone_receiver, req_symmetric_receiver, res_symmetric_receiver) = handle::punch_handler::bounded();
//udp数据处理
{
// 低优先级的udp数据通道
let (sender, receiver) = crossbeam::channel::bounded(100);
let udp1 = udp.try_clone().unwrap();
let _ = thread::spawn(move || {
let current_device = CurrentDeviceInfo::new(virtual_ip, virtual_gateway, virtual_netmask, server_address);
if let Err(e) = handle::udp_recv_handler::recv_loop(
udp1,
server_address,
sender,
tun_writer,
current_device,
) {
println!("udp数据处理线程停止:{:?}", e);
}
std::process::exit(1);
});
let udp1 = udp.try_clone().unwrap();
let _ = thread::spawn(move || {
let current_device = CurrentDeviceInfo::new(virtual_ip, virtual_gateway, virtual_netmask, server_address);
if let Err(e) = handle::udp_recv_handler::other_loop(udp1, receiver, current_device, punch_sender) {
println!("udp数据处理线程停止:{:?}", e);
}
std::process::exit(1);
});
}
//打洞处理
{
let udp1 = udp.try_clone().unwrap();
let _ = thread::spawn(move || {
let current_device = CurrentDeviceInfo::new(virtual_ip, virtual_gateway, virtual_netmask, server_address);
if let Err(e) = handle::punch_handler::cone_handle_loop(cone_receiver, udp1, current_device) {
println!("打洞响应线程停止:{:?}", e);
}
});
let udp1 = udp.try_clone().unwrap();
let _ = thread::spawn(move || {
let current_device = CurrentDeviceInfo::new(virtual_ip, virtual_gateway, virtual_netmask, server_address);
if let Err(e) = handle::punch_handler::req_symmetric_handle_loop(req_symmetric_receiver, udp1, current_device) {
println!("打洞触发线程停止:{:?}", e);
}
});
let udp1 = udp.try_clone().unwrap();
let _ = thread::spawn(move || {
let current_device = CurrentDeviceInfo::new(virtual_ip, virtual_gateway, virtual_netmask, server_address);
if let Err(e) = handle::punch_handler::res_symmetric_handle_loop(res_symmetric_receiver, udp1, current_device) {
println!("打洞触发线程停止:{:?}", e);
}
});
}
//tun数据处理
{
let udp = udp.try_clone().unwrap();
let _ = thread::spawn(move || {
let current_device = CurrentDeviceInfo::new(virtual_ip, virtual_gateway, virtual_netmask, server_address);
if let Err(e) = handle::tun_handler::handle_loop(udp, tun_reader, current_device) {
println!("tun数据处理线程停止:{:?}", e);
}
std::process::exit(1);
});
}
use console::Term;
let term = Term::stdout();
let current_device = CurrentDeviceInfo::new(virtual_ip, virtual_gateway, virtual_netmask, server_address);
loop {
println!("{}", style("Please enter the command (Usage: list,status,exit,help):").color256(102));
match term.read_line() {
Ok(cmd) => {
command(cmd.trim(), &current_device);
}
Err(e) => {
println!("read_line:{:?}", e);
std::process::exit(1);
}
}
}
}
fn command(cmd: &str, current_device: &CurrentDeviceInfo) {
match cmd {
"list" => {
let server_delay = SERVER_RT.load(Ordering::Relaxed);
let device_list_lock = DEVICE_LIST.lock();
let (_epoch, device_list) = device_list_lock.clone();
drop(device_list_lock);
if device_list.is_empty() {
println!("No other devices found");
return;
}
for ip in device_list {
if let Some(route_ref) = DIRECT_ROUTE_TABLE.get(&ip) {
let str = if route_ref.value().delay >= 0 {
format!("{}(p2p delay:{}ms)", ip, route_ref.value().delay)
} else {
format!("{}(p2p)", ip)
};
drop(route_ref);
println!("{}", style(str).green());
} else {
let str = if server_delay >= 0 {
format!("{}(relay delay:{}ms)", ip, server_delay * 2)
} else {
format!("{}(relay)", ip)
};
println!("{}", style(str).blue());
}
}
}
"status" => {
let server_delay = SERVER_RT.load(Ordering::Relaxed);
println!("Virtual ip:{}", style(current_device.virtual_ip).green());
println!("Virtual gateway:{}", style(current_device.virtual_gateway).green());
println!("Relay server :{}", style(current_device.connect_server).green());
if server_delay >= 0 {
println!("Delay of relay server :{}", style(server_delay).green());
}
}
"help" | "h" => {
println!("Options: ");
println!("{} , Query the virtual IP of other devices", style("list").green());
println!("{} , View current device status", style("status").green());
println!("{} , Exit the program", style("exit").green());
}
"exit" => {
std::process::exit(1);
}
_ => {
println!("command {} not fount. ", style(cmd).red());
println!("Try to enter: '{}'", style("help").green());
}
}
}
+142
View File
@@ -0,0 +1,142 @@
use anyhow::Context;
use args_config::{build_config_from_args_and_file, Args, FileConfig};
use route_manager::Route;
use std::path::Path;
use vnt_ipc as vnt_core;
use vnt_core::core::NetworkManager;
use vnt_core::utils::task_control::TaskGroupManager;
pub mod args_config;
#[cfg(windows)]
mod extract_wintun_dll;
#[tokio::main]
pub async fn main() {
if let Err(e) = main0().await {
log::error!("{:?}", e);
}
}
async fn main0() -> anyhow::Result<()> {
let args = Args::parse_compatible();
vnt2::log::log_init("vnt2");
log::info!("version: {:?}", env!("CARGO_PKG_VERSION"));
#[cfg(windows)]
extract_wintun_dll::extract_wintun();
if args.conf_example {
FileConfig::print_example(Some(Path::new("example_config.toml")))?;
return Ok(());
}
let file_cfg = if let Some(path) = args.conf.as_ref() {
log::info!("loaded config from {:?}", path);
Some(FileConfig::load(path).context("failed to load config")?)
} else {
None
};
let (config, ctrl_config) =
build_config_from_args_and_file(Some(args), file_cfg).context("invalid configuration")?;
log::info!(
"server: {}",
config
.server_addr
.iter()
.map(|x| x.to_string())
.collect::<Vec<String>>()
.join(", ")
);
log::info!("network code: {}", config.network_code);
log::info!("device id: {}", config.device_id);
log::info!("device name: {}", config.device_name);
log::info!("cert mode: {}", config.cert_mode);
log::info!("compress: {}", config.compress);
log::info!("rtx(quic channel): {}", config.rtx);
let sub_input = config.input.clone();
if !config.input.is_empty() {
let x = config
.input
.iter()
.map(|v| v.to_string())
.collect::<Vec<_>>()
.join(",");
log::info!("Sub network input:{x}");
}
if !config.output.is_empty() {
let x = config
.output
.iter()
.map(|v| v.to_string())
.collect::<Vec<_>>()
.join(",");
log::info!("Sub network output:{x}");
}
if let Some(password_sign) = config.key_sign() {
log::info!("password sign: {:?}", password_sign);
}
let group_manager = TaskGroupManager::new();
let (task_group, task_group_guard) =
group_manager.create_task().context("create task group")?;
let mut network_manager = NetworkManager::create_network(Box::new(config), task_group)
.await
.context("create network")?;
let reg_msg = network_manager.register().await.context("register")?;
if !network_manager.is_no_tun() {
log::info!("启动网络:{}/{}", reg_msg.ip, reg_msg.prefix_len);
network_manager.start_tun().await.context("start tun")?;
network_manager
.set_network_ip(reg_msg.ip, reg_msg.prefix_len)
.await
.context("set network ip")?;
if !sub_input.is_empty() {
let if_index = network_manager
.tun_if_index()
.await
.context("tun_if_index")?;
let mut route_manager = route_manager::RouteManager::new()?;
for x in sub_input {
let route = Route::new(x.net.network().into(), x.net.prefix_len())
.with_gateway(x.target_ip.into())
.with_if_index(if_index);
if let Err(e) = route_manager.add(&route) {
log::error!("add route [{route}] error: {e:?}");
} else {
log::info!("add route [{route}] successful");
}
}
}
} else {
log::info!(
"启动网络:{}/{} (无虚拟网卡)",
reg_msg.ip,
reg_msg.prefix_len
);
}
let vnt_api = network_manager.vnt_api();
if ctrl_config.ctrl_port.is_none_or(|p| p != 0) {
tokio::spawn(async move {
if let Err(e) = vnt_ipc::server::run_server(ctrl_config.ctrl_port, vnt_api).await {
log::error!("ipc:{e:?}");
}
});
}
loop {
tokio::select! {
_ = network_manager.wait_all_stopped() => {
break;
}
_ = tokio::signal::ctrl_c() => {
log::info!("Ctrl+c received!");
break;
}
}
}
drop(task_group_guard);
log::info!("stop network");
Ok(())
}
+42
View File
@@ -0,0 +1,42 @@
use clap::{Parser, Subcommand};
use vnt_ipc::message::ipc_request::IpcCmd;
/// 操作vnt进程
#[derive(Parser, Debug)]
#[command(author, version, about, long_about = None)]
struct Args {
#[command(subcommand)]
command: Commands,
/// 核心进程控制端口
#[clap(short, long)]
port: Option<u16>,
}
#[derive(Subcommand, Debug)]
enum Commands {
/// 输出当前程序信息
Info,
/// 输出客户端ip列表
Ips,
/// 输出客户端信息列表
#[command(alias = "list")]
Clients,
/// 输出IP路由信息
Route,
}
#[tokio::main]
pub async fn main() {
let args = Args::parse();
let port = args.port;
let cmd = match args.command {
Commands::Info => IpcCmd::AppInfo(Default::default()),
Commands::Ips => IpcCmd::ClientIps(Default::default()),
Commands::Clients => IpcCmd::ClientList(Default::default()),
Commands::Route => IpcCmd::AllRoute(Default::default()),
};
if let Err(e) = vnt_ipc::client::run_client(cmd, port).await {
eprintln!("{:?}", e);
}
}
+36
View File
@@ -0,0 +1,36 @@
use clap::Parser;
use std::net::SocketAddr;
use std::path::PathBuf;
#[cfg(windows)]
mod extract_wintun_dll;
/// vnt web服务
#[derive(Parser, Debug)]
#[command(author, version, about, long_about = None)]
struct Args {
/// 本地http服务监听地址
#[clap(long)]
addr: Option<SocketAddr>,
/// 加载vnt配置路径,配置内容参考web端的配置格式
#[clap(long)]
conf: Option<PathBuf>,
}
#[tokio::main]
pub async fn main() {
if let Err(e) = main0().await {
log::error!("{:?}", e);
}
}
async fn main0() -> anyhow::Result<()> {
let args = Args::parse();
vnt2::log::log_init("vnt2");
log::info!("version: {:?}", env!("CARGO_PKG_VERSION"));
#[cfg(windows)]
extract_wintun_dll::extract_wintun();
let addr = args.addr.unwrap_or("127.0.0.1:19099".parse()?);
vnt_web::run_http_server(addr, args.conf).await?;
Ok(())
}
-156
View File
@@ -1,156 +0,0 @@
use std::{io, thread};
use std::collections::HashSet;
use std::net::{IpAddr, Ipv4Addr, SocketAddr, UdpSocket};
use std::time::Duration;
use crate::proto::message::NatType;
// #[derive(Debug, Copy, Clone, PartialEq)]
// pub enum NatType {
// Symmetric,
// Cone,
// }
//
// impl Into<u8> for NatType {
// fn into(self) -> u8 {
// match self {
// NatType::Symmetric => 0,
// NatType::Cone => 1,
// }
// }
// }
/// 返回所有公网ip和端口变化范围
pub fn public_ip_list() -> io::Result<(NatType, Vec<Ipv4Addr>, u16)> {
let mut hash_set = HashSet::new();
let mut max_port_range = 0;
let mut nat_type = NatType::Cone;
let mut port = 88;
for _ in 0..3 {
let udp = loop {
match UdpSocket::bind(SocketAddr::new(IpAddr::from(Ipv4Addr::from(0)), port)) {
Ok(udp) => {
break udp;
}
Err(e) => {
if e.kind() == io::ErrorKind::AddrInUse {
port += 1;
continue;
}
return Err(e);
}
}
};
let (set, min_port, max_port) = public_ip_list_(&udp)?;
drop(udp);
let port_range = max_port - min_port;
//有多个ip或者端口有变化,说明是对称nat
if nat_type == NatType::Cone && (set.len() > 1 || port_range != 0) {
nat_type = NatType::Symmetric;
}
if max_port_range < port_range {
max_port_range = port_range;
}
for x in set {
hash_set.insert(x);
}
thread::sleep(Duration::from_micros(5));
}
Ok((nat_type, hash_set.into_iter().collect(), max_port_range))
}
/// 测试样本较少,可能不对
///
/// - 移动宽带:锥形网络、一个ip、端口和局域网端口不相同
/// - 电信宽带:锥形网络、一个ip,端口和局域网端口不相同
/// - 联调宽带:对称网络、端口不变ip轮流用
/// - 移动4g:对称网络、ip端口都变 使用小的端口变化量小
/// - 联通4g:对称网络、只有一个ip 端口变化大
/// - 电信4g:对称网络只有一个ip 公网端口比较连续
/// - 综上:客户端使用小端口,针对对称网络 尝试所有ip 公网端口+-变化量的范围
/// - 打通概率 移动宽带=电信宽带>联调宽带>电信4g>移动4g>>联调4g
pub fn public_ip_list_(udp: &UdpSocket) -> io::Result<(HashSet<Ipv4Addr>, u16, u16)> {
// println!("local port {:?}", udp.local_addr().unwrap().port());
udp.set_read_timeout(Some(Duration::from_millis(300)))?;
let mut buf = [0u8; 128];
let _ = udp.send_to(b"NatTest", "nat1.wherewego.top:35061")?;
let _ = udp.send_to(b"NatTest", "nat1.wherewego.top:35062")?;
let _ = udp.send_to(b"NatTest", "nat2.wherewego.top:35061")?;
let _ = udp.send_to(b"NatTest", "nat2.wherewego.top:35062")?;
let mut hash_set = HashSet::new();
let mut count = 0;
let mut min_port = 65535;
let mut max_port = 0;
for _ in 0..4 {
if let Ok(len) = udp.recv(&mut buf) {
if len != 16 || &buf[..10] != &b"NatType213"[..] {
continue;
}
let port = u16::from_be_bytes([buf[14], buf[15]]);
if min_port > port {
min_port = port;
}
if max_port < port {
max_port = port;
}
let ip = Ipv4Addr::new(buf[10], buf[11], buf[12], buf[13]);
// println!("pub {:?}:{}", ip, port);
hash_set.insert(ip);
count += 1;
}
}
if count <= 1 {
return Err(io::Error::from(io::ErrorKind::TimedOut));
}
Ok((hash_set, min_port, max_port))
}
/// 返回nat类型
pub fn nat_test() -> io::Result<NatType> {
for _ in 0..3 {
if NatType::Symmetric == nat_test_()? {
return Ok(NatType::Symmetric);
}
thread::sleep(Duration::from_micros(5));
}
Ok(NatType::Cone)
}
pub fn nat_test_() -> io::Result<NatType> {
let udp = UdpSocket::bind("0.0.0.0:0")?;
udp.set_read_timeout(Some(Duration::from_millis(300)))?;
let mut buf = [0u8; 128];
let _ = udp.send_to(b"NatTest", "nat1.wherewego.top:35061")?;
let _ = udp.send_to(b"NatTest", "nat1.wherewego.top:35062")?;
let _ = udp.send_to(b"NatTest", "nat2.wherewego.top:35061")?;
let _ = udp.send_to(b"NatTest", "nat2.wherewego.top:35062")?;
let mut tmp_ip_port: Option<[u8; 6]> = None;
let mut count = 0;
for _ in 0..4 {
if let Ok(len) = udp.recv(&mut buf) {
if len != 16 || &buf[..10] != &b"NatType213"[..] {
continue;
}
count += 1;
let mut ip_port = [0u8; 6];
ip_port.copy_from_slice(&buf[10..16]);
if let Some(tmp_ip_port) = &tmp_ip_port {
if tmp_ip_port != &ip_port {
return Ok(NatType::Symmetric);
}
} else {
tmp_ip_port = Some(ip_port);
}
}
}
if count <= 1 {
return Err(io::Error::from(io::ErrorKind::TimedOut));
}
Ok(NatType::Cone)
}
#[test]
fn nat_test_run(){
let udp = UdpSocket::bind("0.0.0.0:101").unwrap();
let print = public_ip_list_(&udp).unwrap();
println!("{:?}",print);
}
-1
View File
@@ -1 +0,0 @@
pub mod check;
-941
View File
@@ -1,941 +0,0 @@
// This file is generated by rust-protobuf 3.2.0. Do not edit
// .proto file is parsed by pure
// @generated
// https://github.com/rust-lang/rust-clippy/issues/702
#![allow(unknown_lints)]
#![allow(clippy::all)]
#![allow(unused_attributes)]
#![cfg_attr(rustfmt, rustfmt::skip)]
#![allow(box_pointers)]
#![allow(dead_code)]
#![allow(missing_docs)]
#![allow(non_camel_case_types)]
#![allow(non_snake_case)]
#![allow(non_upper_case_globals)]
#![allow(trivial_casts)]
#![allow(unused_results)]
#![allow(unused_mut)]
//! Generated file from `message.proto`
/// Generated files are compatible only with the same version
/// of protobuf runtime.
const _PROTOBUF_VERSION_CHECK: () = ::protobuf::VERSION_3_2_0;
#[derive(PartialEq,Clone,Default,Debug)]
// @@protoc_insertion_point(message:RegistrationRequest)
pub struct RegistrationRequest {
// message fields
// @@protoc_insertion_point(field:RegistrationRequest.token)
pub token: ::std::string::String,
// @@protoc_insertion_point(field:RegistrationRequest.mac_address)
pub mac_address: ::std::string::String,
// special fields
// @@protoc_insertion_point(special_field:RegistrationRequest.special_fields)
pub special_fields: ::protobuf::SpecialFields,
}
impl<'a> ::std::default::Default for &'a RegistrationRequest {
fn default() -> &'a RegistrationRequest {
<RegistrationRequest as ::protobuf::Message>::default_instance()
}
}
impl RegistrationRequest {
pub fn new() -> RegistrationRequest {
::std::default::Default::default()
}
fn generated_message_descriptor_data() -> ::protobuf::reflect::GeneratedMessageDescriptorData {
let mut fields = ::std::vec::Vec::with_capacity(2);
let mut oneofs = ::std::vec::Vec::with_capacity(0);
fields.push(::protobuf::reflect::rt::v2::make_simpler_field_accessor::<_, _>(
"token",
|m: &RegistrationRequest| { &m.token },
|m: &mut RegistrationRequest| { &mut m.token },
));
fields.push(::protobuf::reflect::rt::v2::make_simpler_field_accessor::<_, _>(
"mac_address",
|m: &RegistrationRequest| { &m.mac_address },
|m: &mut RegistrationRequest| { &mut m.mac_address },
));
::protobuf::reflect::GeneratedMessageDescriptorData::new_2::<RegistrationRequest>(
"RegistrationRequest",
fields,
oneofs,
)
}
}
impl ::protobuf::Message for RegistrationRequest {
const NAME: &'static str = "RegistrationRequest";
fn is_initialized(&self) -> bool {
true
}
fn merge_from(&mut self, is: &mut ::protobuf::CodedInputStream<'_>) -> ::protobuf::Result<()> {
while let Some(tag) = is.read_raw_tag_or_eof()? {
match tag {
10 => {
self.token = is.read_string()?;
},
18 => {
self.mac_address = is.read_string()?;
},
tag => {
::protobuf::rt::read_unknown_or_skip_group(tag, is, self.special_fields.mut_unknown_fields())?;
},
};
}
::std::result::Result::Ok(())
}
// Compute sizes of nested messages
#[allow(unused_variables)]
fn compute_size(&self) -> u64 {
let mut my_size = 0;
if !self.token.is_empty() {
my_size += ::protobuf::rt::string_size(1, &self.token);
}
if !self.mac_address.is_empty() {
my_size += ::protobuf::rt::string_size(2, &self.mac_address);
}
my_size += ::protobuf::rt::unknown_fields_size(self.special_fields.unknown_fields());
self.special_fields.cached_size().set(my_size as u32);
my_size
}
fn write_to_with_cached_sizes(&self, os: &mut ::protobuf::CodedOutputStream<'_>) -> ::protobuf::Result<()> {
if !self.token.is_empty() {
os.write_string(1, &self.token)?;
}
if !self.mac_address.is_empty() {
os.write_string(2, &self.mac_address)?;
}
os.write_unknown_fields(self.special_fields.unknown_fields())?;
::std::result::Result::Ok(())
}
fn special_fields(&self) -> &::protobuf::SpecialFields {
&self.special_fields
}
fn mut_special_fields(&mut self) -> &mut ::protobuf::SpecialFields {
&mut self.special_fields
}
fn new() -> RegistrationRequest {
RegistrationRequest::new()
}
fn clear(&mut self) {
self.token.clear();
self.mac_address.clear();
self.special_fields.clear();
}
fn default_instance() -> &'static RegistrationRequest {
static instance: RegistrationRequest = RegistrationRequest {
token: ::std::string::String::new(),
mac_address: ::std::string::String::new(),
special_fields: ::protobuf::SpecialFields::new(),
};
&instance
}
}
impl ::protobuf::MessageFull for RegistrationRequest {
fn descriptor() -> ::protobuf::reflect::MessageDescriptor {
static descriptor: ::protobuf::rt::Lazy<::protobuf::reflect::MessageDescriptor> = ::protobuf::rt::Lazy::new();
descriptor.get(|| file_descriptor().message_by_package_relative_name("RegistrationRequest").unwrap()).clone()
}
}
impl ::std::fmt::Display for RegistrationRequest {
fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
::protobuf::text_format::fmt(self, f)
}
}
impl ::protobuf::reflect::ProtobufValue for RegistrationRequest {
type RuntimeType = ::protobuf::reflect::rt::RuntimeTypeMessage<Self>;
}
#[derive(PartialEq,Clone,Default,Debug)]
// @@protoc_insertion_point(message:RegistrationResponse)
pub struct RegistrationResponse {
// message fields
// @@protoc_insertion_point(field:RegistrationResponse.virtual_ip)
pub virtual_ip: u32,
// @@protoc_insertion_point(field:RegistrationResponse.virtual_gateway)
pub virtual_gateway: u32,
// @@protoc_insertion_point(field:RegistrationResponse.virtual_netmask)
pub virtual_netmask: u32,
// @@protoc_insertion_point(field:RegistrationResponse.epoch)
pub epoch: u32,
// @@protoc_insertion_point(field:RegistrationResponse.virtual_ip_list)
pub virtual_ip_list: ::std::vec::Vec<u32>,
// @@protoc_insertion_point(field:RegistrationResponse.public_ip)
pub public_ip: u32,
// @@protoc_insertion_point(field:RegistrationResponse.public_port)
pub public_port: u32,
// special fields
// @@protoc_insertion_point(special_field:RegistrationResponse.special_fields)
pub special_fields: ::protobuf::SpecialFields,
}
impl<'a> ::std::default::Default for &'a RegistrationResponse {
fn default() -> &'a RegistrationResponse {
<RegistrationResponse as ::protobuf::Message>::default_instance()
}
}
impl RegistrationResponse {
pub fn new() -> RegistrationResponse {
::std::default::Default::default()
}
fn generated_message_descriptor_data() -> ::protobuf::reflect::GeneratedMessageDescriptorData {
let mut fields = ::std::vec::Vec::with_capacity(7);
let mut oneofs = ::std::vec::Vec::with_capacity(0);
fields.push(::protobuf::reflect::rt::v2::make_simpler_field_accessor::<_, _>(
"virtual_ip",
|m: &RegistrationResponse| { &m.virtual_ip },
|m: &mut RegistrationResponse| { &mut m.virtual_ip },
));
fields.push(::protobuf::reflect::rt::v2::make_simpler_field_accessor::<_, _>(
"virtual_gateway",
|m: &RegistrationResponse| { &m.virtual_gateway },
|m: &mut RegistrationResponse| { &mut m.virtual_gateway },
));
fields.push(::protobuf::reflect::rt::v2::make_simpler_field_accessor::<_, _>(
"virtual_netmask",
|m: &RegistrationResponse| { &m.virtual_netmask },
|m: &mut RegistrationResponse| { &mut m.virtual_netmask },
));
fields.push(::protobuf::reflect::rt::v2::make_simpler_field_accessor::<_, _>(
"epoch",
|m: &RegistrationResponse| { &m.epoch },
|m: &mut RegistrationResponse| { &mut m.epoch },
));
fields.push(::protobuf::reflect::rt::v2::make_vec_simpler_accessor::<_, _>(
"virtual_ip_list",
|m: &RegistrationResponse| { &m.virtual_ip_list },
|m: &mut RegistrationResponse| { &mut m.virtual_ip_list },
));
fields.push(::protobuf::reflect::rt::v2::make_simpler_field_accessor::<_, _>(
"public_ip",
|m: &RegistrationResponse| { &m.public_ip },
|m: &mut RegistrationResponse| { &mut m.public_ip },
));
fields.push(::protobuf::reflect::rt::v2::make_simpler_field_accessor::<_, _>(
"public_port",
|m: &RegistrationResponse| { &m.public_port },
|m: &mut RegistrationResponse| { &mut m.public_port },
));
::protobuf::reflect::GeneratedMessageDescriptorData::new_2::<RegistrationResponse>(
"RegistrationResponse",
fields,
oneofs,
)
}
}
impl ::protobuf::Message for RegistrationResponse {
const NAME: &'static str = "RegistrationResponse";
fn is_initialized(&self) -> bool {
true
}
fn merge_from(&mut self, is: &mut ::protobuf::CodedInputStream<'_>) -> ::protobuf::Result<()> {
while let Some(tag) = is.read_raw_tag_or_eof()? {
match tag {
13 => {
self.virtual_ip = is.read_fixed32()?;
},
21 => {
self.virtual_gateway = is.read_fixed32()?;
},
29 => {
self.virtual_netmask = is.read_fixed32()?;
},
32 => {
self.epoch = is.read_uint32()?;
},
42 => {
is.read_repeated_packed_fixed32_into(&mut self.virtual_ip_list)?;
},
45 => {
self.virtual_ip_list.push(is.read_fixed32()?);
},
53 => {
self.public_ip = is.read_fixed32()?;
},
56 => {
self.public_port = is.read_uint32()?;
},
tag => {
::protobuf::rt::read_unknown_or_skip_group(tag, is, self.special_fields.mut_unknown_fields())?;
},
};
}
::std::result::Result::Ok(())
}
// Compute sizes of nested messages
#[allow(unused_variables)]
fn compute_size(&self) -> u64 {
let mut my_size = 0;
if self.virtual_ip != 0 {
my_size += 1 + 4;
}
if self.virtual_gateway != 0 {
my_size += 1 + 4;
}
if self.virtual_netmask != 0 {
my_size += 1 + 4;
}
if self.epoch != 0 {
my_size += ::protobuf::rt::uint32_size(4, self.epoch);
}
my_size += 5 * self.virtual_ip_list.len() as u64;
if self.public_ip != 0 {
my_size += 1 + 4;
}
if self.public_port != 0 {
my_size += ::protobuf::rt::uint32_size(7, self.public_port);
}
my_size += ::protobuf::rt::unknown_fields_size(self.special_fields.unknown_fields());
self.special_fields.cached_size().set(my_size as u32);
my_size
}
fn write_to_with_cached_sizes(&self, os: &mut ::protobuf::CodedOutputStream<'_>) -> ::protobuf::Result<()> {
if self.virtual_ip != 0 {
os.write_fixed32(1, self.virtual_ip)?;
}
if self.virtual_gateway != 0 {
os.write_fixed32(2, self.virtual_gateway)?;
}
if self.virtual_netmask != 0 {
os.write_fixed32(3, self.virtual_netmask)?;
}
if self.epoch != 0 {
os.write_uint32(4, self.epoch)?;
}
for v in &self.virtual_ip_list {
os.write_fixed32(5, *v)?;
};
if self.public_ip != 0 {
os.write_fixed32(6, self.public_ip)?;
}
if self.public_port != 0 {
os.write_uint32(7, self.public_port)?;
}
os.write_unknown_fields(self.special_fields.unknown_fields())?;
::std::result::Result::Ok(())
}
fn special_fields(&self) -> &::protobuf::SpecialFields {
&self.special_fields
}
fn mut_special_fields(&mut self) -> &mut ::protobuf::SpecialFields {
&mut self.special_fields
}
fn new() -> RegistrationResponse {
RegistrationResponse::new()
}
fn clear(&mut self) {
self.virtual_ip = 0;
self.virtual_gateway = 0;
self.virtual_netmask = 0;
self.epoch = 0;
self.virtual_ip_list.clear();
self.public_ip = 0;
self.public_port = 0;
self.special_fields.clear();
}
fn default_instance() -> &'static RegistrationResponse {
static instance: RegistrationResponse = RegistrationResponse {
virtual_ip: 0,
virtual_gateway: 0,
virtual_netmask: 0,
epoch: 0,
virtual_ip_list: ::std::vec::Vec::new(),
public_ip: 0,
public_port: 0,
special_fields: ::protobuf::SpecialFields::new(),
};
&instance
}
}
impl ::protobuf::MessageFull for RegistrationResponse {
fn descriptor() -> ::protobuf::reflect::MessageDescriptor {
static descriptor: ::protobuf::rt::Lazy<::protobuf::reflect::MessageDescriptor> = ::protobuf::rt::Lazy::new();
descriptor.get(|| file_descriptor().message_by_package_relative_name("RegistrationResponse").unwrap()).clone()
}
}
impl ::std::fmt::Display for RegistrationResponse {
fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
::protobuf::text_format::fmt(self, f)
}
}
impl ::protobuf::reflect::ProtobufValue for RegistrationResponse {
type RuntimeType = ::protobuf::reflect::rt::RuntimeTypeMessage<Self>;
}
#[derive(PartialEq,Clone,Default,Debug)]
// @@protoc_insertion_point(message:DeviceList)
pub struct DeviceList {
// message fields
// @@protoc_insertion_point(field:DeviceList.epoch)
pub epoch: u32,
// @@protoc_insertion_point(field:DeviceList.virtual_ip_list)
pub virtual_ip_list: ::std::vec::Vec<u32>,
// special fields
// @@protoc_insertion_point(special_field:DeviceList.special_fields)
pub special_fields: ::protobuf::SpecialFields,
}
impl<'a> ::std::default::Default for &'a DeviceList {
fn default() -> &'a DeviceList {
<DeviceList as ::protobuf::Message>::default_instance()
}
}
impl DeviceList {
pub fn new() -> DeviceList {
::std::default::Default::default()
}
fn generated_message_descriptor_data() -> ::protobuf::reflect::GeneratedMessageDescriptorData {
let mut fields = ::std::vec::Vec::with_capacity(2);
let mut oneofs = ::std::vec::Vec::with_capacity(0);
fields.push(::protobuf::reflect::rt::v2::make_simpler_field_accessor::<_, _>(
"epoch",
|m: &DeviceList| { &m.epoch },
|m: &mut DeviceList| { &mut m.epoch },
));
fields.push(::protobuf::reflect::rt::v2::make_vec_simpler_accessor::<_, _>(
"virtual_ip_list",
|m: &DeviceList| { &m.virtual_ip_list },
|m: &mut DeviceList| { &mut m.virtual_ip_list },
));
::protobuf::reflect::GeneratedMessageDescriptorData::new_2::<DeviceList>(
"DeviceList",
fields,
oneofs,
)
}
}
impl ::protobuf::Message for DeviceList {
const NAME: &'static str = "DeviceList";
fn is_initialized(&self) -> bool {
true
}
fn merge_from(&mut self, is: &mut ::protobuf::CodedInputStream<'_>) -> ::protobuf::Result<()> {
while let Some(tag) = is.read_raw_tag_or_eof()? {
match tag {
8 => {
self.epoch = is.read_uint32()?;
},
18 => {
is.read_repeated_packed_fixed32_into(&mut self.virtual_ip_list)?;
},
21 => {
self.virtual_ip_list.push(is.read_fixed32()?);
},
tag => {
::protobuf::rt::read_unknown_or_skip_group(tag, is, self.special_fields.mut_unknown_fields())?;
},
};
}
::std::result::Result::Ok(())
}
// Compute sizes of nested messages
#[allow(unused_variables)]
fn compute_size(&self) -> u64 {
let mut my_size = 0;
if self.epoch != 0 {
my_size += ::protobuf::rt::uint32_size(1, self.epoch);
}
my_size += 5 * self.virtual_ip_list.len() as u64;
my_size += ::protobuf::rt::unknown_fields_size(self.special_fields.unknown_fields());
self.special_fields.cached_size().set(my_size as u32);
my_size
}
fn write_to_with_cached_sizes(&self, os: &mut ::protobuf::CodedOutputStream<'_>) -> ::protobuf::Result<()> {
if self.epoch != 0 {
os.write_uint32(1, self.epoch)?;
}
for v in &self.virtual_ip_list {
os.write_fixed32(2, *v)?;
};
os.write_unknown_fields(self.special_fields.unknown_fields())?;
::std::result::Result::Ok(())
}
fn special_fields(&self) -> &::protobuf::SpecialFields {
&self.special_fields
}
fn mut_special_fields(&mut self) -> &mut ::protobuf::SpecialFields {
&mut self.special_fields
}
fn new() -> DeviceList {
DeviceList::new()
}
fn clear(&mut self) {
self.epoch = 0;
self.virtual_ip_list.clear();
self.special_fields.clear();
}
fn default_instance() -> &'static DeviceList {
static instance: DeviceList = DeviceList {
epoch: 0,
virtual_ip_list: ::std::vec::Vec::new(),
special_fields: ::protobuf::SpecialFields::new(),
};
&instance
}
}
impl ::protobuf::MessageFull for DeviceList {
fn descriptor() -> ::protobuf::reflect::MessageDescriptor {
static descriptor: ::protobuf::rt::Lazy<::protobuf::reflect::MessageDescriptor> = ::protobuf::rt::Lazy::new();
descriptor.get(|| file_descriptor().message_by_package_relative_name("DeviceList").unwrap()).clone()
}
}
impl ::std::fmt::Display for DeviceList {
fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
::protobuf::text_format::fmt(self, f)
}
}
impl ::protobuf::reflect::ProtobufValue for DeviceList {
type RuntimeType = ::protobuf::reflect::rt::RuntimeTypeMessage<Self>;
}
#[derive(PartialEq,Clone,Default,Debug)]
// @@protoc_insertion_point(message:Punch)
pub struct Punch {
// message fields
// @@protoc_insertion_point(field:Punch.virtual_ip)
pub virtual_ip: u32,
// @@protoc_insertion_point(field:Punch.public_ip_list)
pub public_ip_list: ::std::vec::Vec<u32>,
// @@protoc_insertion_point(field:Punch.public_port)
pub public_port: u32,
// @@protoc_insertion_point(field:Punch.public_port_range)
pub public_port_range: u32,
// @@protoc_insertion_point(field:Punch.nat_type)
pub nat_type: ::protobuf::EnumOrUnknown<NatType>,
// @@protoc_insertion_point(field:Punch.reply)
pub reply: bool,
// @@protoc_insertion_point(field:Punch.step)
pub step: ::protobuf::EnumOrUnknown<Step>,
// special fields
// @@protoc_insertion_point(special_field:Punch.special_fields)
pub special_fields: ::protobuf::SpecialFields,
}
impl<'a> ::std::default::Default for &'a Punch {
fn default() -> &'a Punch {
<Punch as ::protobuf::Message>::default_instance()
}
}
impl Punch {
pub fn new() -> Punch {
::std::default::Default::default()
}
fn generated_message_descriptor_data() -> ::protobuf::reflect::GeneratedMessageDescriptorData {
let mut fields = ::std::vec::Vec::with_capacity(7);
let mut oneofs = ::std::vec::Vec::with_capacity(0);
fields.push(::protobuf::reflect::rt::v2::make_simpler_field_accessor::<_, _>(
"virtual_ip",
|m: &Punch| { &m.virtual_ip },
|m: &mut Punch| { &mut m.virtual_ip },
));
fields.push(::protobuf::reflect::rt::v2::make_vec_simpler_accessor::<_, _>(
"public_ip_list",
|m: &Punch| { &m.public_ip_list },
|m: &mut Punch| { &mut m.public_ip_list },
));
fields.push(::protobuf::reflect::rt::v2::make_simpler_field_accessor::<_, _>(
"public_port",
|m: &Punch| { &m.public_port },
|m: &mut Punch| { &mut m.public_port },
));
fields.push(::protobuf::reflect::rt::v2::make_simpler_field_accessor::<_, _>(
"public_port_range",
|m: &Punch| { &m.public_port_range },
|m: &mut Punch| { &mut m.public_port_range },
));
fields.push(::protobuf::reflect::rt::v2::make_simpler_field_accessor::<_, _>(
"nat_type",
|m: &Punch| { &m.nat_type },
|m: &mut Punch| { &mut m.nat_type },
));
fields.push(::protobuf::reflect::rt::v2::make_simpler_field_accessor::<_, _>(
"reply",
|m: &Punch| { &m.reply },
|m: &mut Punch| { &mut m.reply },
));
fields.push(::protobuf::reflect::rt::v2::make_simpler_field_accessor::<_, _>(
"step",
|m: &Punch| { &m.step },
|m: &mut Punch| { &mut m.step },
));
::protobuf::reflect::GeneratedMessageDescriptorData::new_2::<Punch>(
"Punch",
fields,
oneofs,
)
}
}
impl ::protobuf::Message for Punch {
const NAME: &'static str = "Punch";
fn is_initialized(&self) -> bool {
true
}
fn merge_from(&mut self, is: &mut ::protobuf::CodedInputStream<'_>) -> ::protobuf::Result<()> {
while let Some(tag) = is.read_raw_tag_or_eof()? {
match tag {
13 => {
self.virtual_ip = is.read_fixed32()?;
},
18 => {
is.read_repeated_packed_fixed32_into(&mut self.public_ip_list)?;
},
21 => {
self.public_ip_list.push(is.read_fixed32()?);
},
24 => {
self.public_port = is.read_uint32()?;
},
32 => {
self.public_port_range = is.read_uint32()?;
},
40 => {
self.nat_type = is.read_enum_or_unknown()?;
},
48 => {
self.reply = is.read_bool()?;
},
56 => {
self.step = is.read_enum_or_unknown()?;
},
tag => {
::protobuf::rt::read_unknown_or_skip_group(tag, is, self.special_fields.mut_unknown_fields())?;
},
};
}
::std::result::Result::Ok(())
}
// Compute sizes of nested messages
#[allow(unused_variables)]
fn compute_size(&self) -> u64 {
let mut my_size = 0;
if self.virtual_ip != 0 {
my_size += 1 + 4;
}
my_size += 5 * self.public_ip_list.len() as u64;
if self.public_port != 0 {
my_size += ::protobuf::rt::uint32_size(3, self.public_port);
}
if self.public_port_range != 0 {
my_size += ::protobuf::rt::uint32_size(4, self.public_port_range);
}
if self.nat_type != ::protobuf::EnumOrUnknown::new(NatType::Symmetric) {
my_size += ::protobuf::rt::int32_size(5, self.nat_type.value());
}
if self.reply != false {
my_size += 1 + 1;
}
if self.step != ::protobuf::EnumOrUnknown::new(Step::Step1) {
my_size += ::protobuf::rt::int32_size(7, self.step.value());
}
my_size += ::protobuf::rt::unknown_fields_size(self.special_fields.unknown_fields());
self.special_fields.cached_size().set(my_size as u32);
my_size
}
fn write_to_with_cached_sizes(&self, os: &mut ::protobuf::CodedOutputStream<'_>) -> ::protobuf::Result<()> {
if self.virtual_ip != 0 {
os.write_fixed32(1, self.virtual_ip)?;
}
for v in &self.public_ip_list {
os.write_fixed32(2, *v)?;
};
if self.public_port != 0 {
os.write_uint32(3, self.public_port)?;
}
if self.public_port_range != 0 {
os.write_uint32(4, self.public_port_range)?;
}
if self.nat_type != ::protobuf::EnumOrUnknown::new(NatType::Symmetric) {
os.write_enum(5, ::protobuf::EnumOrUnknown::value(&self.nat_type))?;
}
if self.reply != false {
os.write_bool(6, self.reply)?;
}
if self.step != ::protobuf::EnumOrUnknown::new(Step::Step1) {
os.write_enum(7, ::protobuf::EnumOrUnknown::value(&self.step))?;
}
os.write_unknown_fields(self.special_fields.unknown_fields())?;
::std::result::Result::Ok(())
}
fn special_fields(&self) -> &::protobuf::SpecialFields {
&self.special_fields
}
fn mut_special_fields(&mut self) -> &mut ::protobuf::SpecialFields {
&mut self.special_fields
}
fn new() -> Punch {
Punch::new()
}
fn clear(&mut self) {
self.virtual_ip = 0;
self.public_ip_list.clear();
self.public_port = 0;
self.public_port_range = 0;
self.nat_type = ::protobuf::EnumOrUnknown::new(NatType::Symmetric);
self.reply = false;
self.step = ::protobuf::EnumOrUnknown::new(Step::Step1);
self.special_fields.clear();
}
fn default_instance() -> &'static Punch {
static instance: Punch = Punch {
virtual_ip: 0,
public_ip_list: ::std::vec::Vec::new(),
public_port: 0,
public_port_range: 0,
nat_type: ::protobuf::EnumOrUnknown::from_i32(0),
reply: false,
step: ::protobuf::EnumOrUnknown::from_i32(0),
special_fields: ::protobuf::SpecialFields::new(),
};
&instance
}
}
impl ::protobuf::MessageFull for Punch {
fn descriptor() -> ::protobuf::reflect::MessageDescriptor {
static descriptor: ::protobuf::rt::Lazy<::protobuf::reflect::MessageDescriptor> = ::protobuf::rt::Lazy::new();
descriptor.get(|| file_descriptor().message_by_package_relative_name("Punch").unwrap()).clone()
}
}
impl ::std::fmt::Display for Punch {
fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
::protobuf::text_format::fmt(self, f)
}
}
impl ::protobuf::reflect::ProtobufValue for Punch {
type RuntimeType = ::protobuf::reflect::rt::RuntimeTypeMessage<Self>;
}
#[derive(Clone,Copy,PartialEq,Eq,Debug,Hash)]
// @@protoc_insertion_point(enum:NatType)
pub enum NatType {
// @@protoc_insertion_point(enum_value:NatType.Symmetric)
Symmetric = 0,
// @@protoc_insertion_point(enum_value:NatType.Cone)
Cone = 1,
}
impl ::protobuf::Enum for NatType {
const NAME: &'static str = "NatType";
fn value(&self) -> i32 {
*self as i32
}
fn from_i32(value: i32) -> ::std::option::Option<NatType> {
match value {
0 => ::std::option::Option::Some(NatType::Symmetric),
1 => ::std::option::Option::Some(NatType::Cone),
_ => ::std::option::Option::None
}
}
const VALUES: &'static [NatType] = &[
NatType::Symmetric,
NatType::Cone,
];
}
impl ::protobuf::EnumFull for NatType {
fn enum_descriptor() -> ::protobuf::reflect::EnumDescriptor {
static descriptor: ::protobuf::rt::Lazy<::protobuf::reflect::EnumDescriptor> = ::protobuf::rt::Lazy::new();
descriptor.get(|| file_descriptor().enum_by_package_relative_name("NatType").unwrap()).clone()
}
fn descriptor(&self) -> ::protobuf::reflect::EnumValueDescriptor {
let index = *self as usize;
Self::enum_descriptor().value_by_index(index)
}
}
impl ::std::default::Default for NatType {
fn default() -> Self {
NatType::Symmetric
}
}
impl NatType {
fn generated_enum_descriptor_data() -> ::protobuf::reflect::GeneratedEnumDescriptorData {
::protobuf::reflect::GeneratedEnumDescriptorData::new::<NatType>("NatType")
}
}
#[derive(Clone,Copy,PartialEq,Eq,Debug,Hash)]
// @@protoc_insertion_point(enum:Step)
pub enum Step {
// @@protoc_insertion_point(enum_value:Step.Step1)
Step1 = 0,
// @@protoc_insertion_point(enum_value:Step.Step2)
Step2 = 1,
// @@protoc_insertion_point(enum_value:Step.Step3)
Step3 = 2,
// @@protoc_insertion_point(enum_value:Step.Step4)
Step4 = 3,
}
impl ::protobuf::Enum for Step {
const NAME: &'static str = "Step";
fn value(&self) -> i32 {
*self as i32
}
fn from_i32(value: i32) -> ::std::option::Option<Step> {
match value {
0 => ::std::option::Option::Some(Step::Step1),
1 => ::std::option::Option::Some(Step::Step2),
2 => ::std::option::Option::Some(Step::Step3),
3 => ::std::option::Option::Some(Step::Step4),
_ => ::std::option::Option::None
}
}
const VALUES: &'static [Step] = &[
Step::Step1,
Step::Step2,
Step::Step3,
Step::Step4,
];
}
impl ::protobuf::EnumFull for Step {
fn enum_descriptor() -> ::protobuf::reflect::EnumDescriptor {
static descriptor: ::protobuf::rt::Lazy<::protobuf::reflect::EnumDescriptor> = ::protobuf::rt::Lazy::new();
descriptor.get(|| file_descriptor().enum_by_package_relative_name("Step").unwrap()).clone()
}
fn descriptor(&self) -> ::protobuf::reflect::EnumValueDescriptor {
let index = *self as usize;
Self::enum_descriptor().value_by_index(index)
}
}
impl ::std::default::Default for Step {
fn default() -> Self {
Step::Step1
}
}
impl Step {
fn generated_enum_descriptor_data() -> ::protobuf::reflect::GeneratedEnumDescriptorData {
::protobuf::reflect::GeneratedEnumDescriptorData::new::<Step>("Step")
}
}
static file_descriptor_proto_data: &'static [u8] = b"\
\n\rmessage.proto\"L\n\x13RegistrationRequest\x12\x14\n\x05token\x18\x01\
\x20\x01(\tR\x05token\x12\x1f\n\x0bmac_address\x18\x02\x20\x01(\tR\nmacA\
ddress\"\x83\x02\n\x14RegistrationResponse\x12\x1d\n\nvirtual_ip\x18\x01\
\x20\x01(\x07R\tvirtualIp\x12'\n\x0fvirtual_gateway\x18\x02\x20\x01(\x07\
R\x0evirtualGateway\x12'\n\x0fvirtual_netmask\x18\x03\x20\x01(\x07R\x0ev\
irtualNetmask\x12\x14\n\x05epoch\x18\x04\x20\x01(\rR\x05epoch\x12&\n\x0f\
virtual_ip_list\x18\x05\x20\x03(\x07R\rvirtualIpList\x12\x1b\n\tpublic_i\
p\x18\x06\x20\x01(\x07R\x08publicIp\x12\x1f\n\x0bpublic_port\x18\x07\x20\
\x01(\rR\npublicPort\"J\n\nDeviceList\x12\x14\n\x05epoch\x18\x01\x20\x01\
(\rR\x05epoch\x12&\n\x0fvirtual_ip_list\x18\x02\x20\x03(\x07R\rvirtualIp\
List\"\xef\x01\n\x05Punch\x12\x1d\n\nvirtual_ip\x18\x01\x20\x01(\x07R\tv\
irtualIp\x12$\n\x0epublic_ip_list\x18\x02\x20\x03(\x07R\x0cpublicIpList\
\x12\x1f\n\x0bpublic_port\x18\x03\x20\x01(\rR\npublicPort\x12*\n\x11publ\
ic_port_range\x18\x04\x20\x01(\rR\x0fpublicPortRange\x12#\n\x08nat_type\
\x18\x05\x20\x01(\x0e2\x08.NatTypeR\x07natType\x12\x14\n\x05reply\x18\
\x06\x20\x01(\x08R\x05reply\x12\x19\n\x04step\x18\x07\x20\x01(\x0e2\x05.\
StepR\x04step*\"\n\x07NatType\x12\r\n\tSymmetric\x10\0\x12\x08\n\x04Cone\
\x10\x01*2\n\x04Step\x12\t\n\x05Step1\x10\0\x12\t\n\x05Step2\x10\x01\x12\
\t\n\x05Step3\x10\x02\x12\t\n\x05Step4\x10\x03b\x06proto3\
";
/// `FileDescriptorProto` object which was a source for this generated file
fn file_descriptor_proto() -> &'static ::protobuf::descriptor::FileDescriptorProto {
static file_descriptor_proto_lazy: ::protobuf::rt::Lazy<::protobuf::descriptor::FileDescriptorProto> = ::protobuf::rt::Lazy::new();
file_descriptor_proto_lazy.get(|| {
::protobuf::Message::parse_from_bytes(file_descriptor_proto_data).unwrap()
})
}
/// `FileDescriptor` object which allows dynamic access to files
pub fn file_descriptor() -> &'static ::protobuf::reflect::FileDescriptor {
static generated_file_descriptor_lazy: ::protobuf::rt::Lazy<::protobuf::reflect::GeneratedFileDescriptor> = ::protobuf::rt::Lazy::new();
static file_descriptor: ::protobuf::rt::Lazy<::protobuf::reflect::FileDescriptor> = ::protobuf::rt::Lazy::new();
file_descriptor.get(|| {
let generated_file_descriptor = generated_file_descriptor_lazy.get(|| {
let mut deps = ::std::vec::Vec::with_capacity(0);
let mut messages = ::std::vec::Vec::with_capacity(4);
messages.push(RegistrationRequest::generated_message_descriptor_data());
messages.push(RegistrationResponse::generated_message_descriptor_data());
messages.push(DeviceList::generated_message_descriptor_data());
messages.push(Punch::generated_message_descriptor_data());
let mut enums = ::std::vec::Vec::with_capacity(2);
enums.push(NatType::generated_enum_descriptor_data());
enums.push(Step::generated_enum_descriptor_data());
::protobuf::reflect::GeneratedFileDescriptor::new_generated(
file_descriptor_proto(),
deps,
messages,
enums,
)
});
::protobuf::reflect::FileDescriptor::new_generated_2(generated_file_descriptor)
})
}
-3
View File
@@ -1,3 +0,0 @@
// @generated
pub mod message;
-223
View File
@@ -1,223 +0,0 @@
use std::fmt;
use std::net::Ipv4Addr;
use crate::error::*;
#[derive(Eq, PartialEq, Copy, Clone, Debug)]
pub enum Protocol {
Ping,
Pong,
PunchRequest,
PunchResponse,
UnKnow(u8),
}
impl From<u8> for Protocol {
fn from(value: u8) -> Self {
match value {
1 => Protocol::Ping,
2 => Protocol::Pong,
3 => Protocol::PunchRequest,
4 => Protocol::PunchResponse,
val => Protocol::UnKnow(val),
}
}
}
impl Into<u8> for Protocol {
fn into(self) -> u8 {
match self {
Protocol::Ping => 1,
Protocol::Pong => 2,
Protocol::PunchRequest => 3,
Protocol::PunchResponse => 4,
Protocol::UnKnow(val) => val,
}
}
}
pub enum ControlPacket<B> {
PingPacket(PingPacket<B>),
PongPacket(PongPacket<B>),
PunchRequest(PunchRequestPacket<B>),
PunchResponse(PunchResponsePacket<B>),
}
impl<B: AsRef<[u8]>> ControlPacket<B> {
pub fn new(protocol: u8, buffer: B) -> Result<ControlPacket<B>> {
match Protocol::from(protocol) {
Protocol::Ping => Ok(ControlPacket::PingPacket(PingPacket::new(buffer)?)),
Protocol::Pong => Ok(ControlPacket::PongPacket(PongPacket::new(buffer)?)),
Protocol::PunchRequest => Ok(ControlPacket::PunchRequest(PunchRequestPacket::new(
buffer,
)?)),
Protocol::PunchResponse => Ok(ControlPacket::PunchResponse(PunchResponsePacket::new(
buffer,
)?)),
Protocol::UnKnow(_) => Err(Error::NotSupport),
}
}
}
/// 网络探针
#[derive(Copy, Clone)]
pub struct PingPacket<B> {
buffer: B,
}
#[derive(Copy, Clone)]
pub struct PongPacket<B> {
buffer: B,
}
impl<B: AsRef<[u8]>> PingPacket<B> {
pub fn new(buffer: B) -> Result<PingPacket<B>> {
let len = buffer.as_ref().len();
if len != 8 + 4 {
return Err(Error::InvalidPacket);
}
Ok(PingPacket { buffer })
}
}
impl<B: AsRef<[u8]>> PingPacket<B> {
pub fn time(&self) -> i64 {
i64::from_be_bytes(self.buffer.as_ref()[..8].try_into().unwrap())
}
pub fn epoch(&self) -> u32 {
u32::from_be_bytes(self.buffer.as_ref()[8..12].try_into().unwrap())
}
}
impl<B: AsRef<[u8]> + AsMut<[u8]>> PingPacket<B> {
pub fn set_time(&mut self, time: i64) {
self.buffer.as_mut()[..8].copy_from_slice(&time.to_be_bytes())
}
pub fn set_epoch(&mut self, epoch: u32) {
self.buffer.as_mut()[8..12].copy_from_slice(&epoch.to_be_bytes())
}
}
impl<B: AsRef<[u8]>> fmt::Debug for PingPacket<B> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("PingPacket")
.field("time", &self.time())
.field("epoch", &self.epoch())
.finish()
}
}
impl<B: AsRef<[u8]>> PongPacket<B> {
pub fn new(buffer: B) -> Result<PongPacket<B>> {
let len = buffer.as_ref().len();
if len != 8 {
return Err(Error::InvalidPacket);
}
Ok(PongPacket { buffer })
}
}
impl<B: AsRef<[u8]>> PongPacket<B> {
pub fn time(&self) -> i64 {
i64::from_be_bytes(self.buffer.as_ref()[..8].try_into().unwrap())
}
}
impl<B: AsRef<[u8]> + AsMut<[u8]>> PongPacket<B> {
pub fn set_time(&mut self, time: i64) {
self.buffer.as_mut()[..8].copy_from_slice(&time.to_be_bytes())
}
}
impl<B: AsRef<[u8]>> fmt::Debug for PongPacket<B> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("PongPacket")
.field("time", &self.time())
.finish()
}
}
pub type TurnPongPacket<B> = TurnPingPacket<B>;
/// 探测目标延迟
#[derive(Copy, Clone)]
pub struct TurnPingPacket<B> {
buffer: B,
}
impl<B: AsRef<[u8]>> TurnPingPacket<B> {
pub fn new(buffer: B) -> Result<TurnPingPacket<B>> {
let len = buffer.as_ref().len();
if len != 16 {
return Err(Error::InvalidPacket);
}
Ok(TurnPingPacket { buffer })
}
}
impl<B: AsRef<[u8]>> TurnPingPacket<B> {
// pub fn source(&self) -> Ipv4Addr {
// let tmp:[u8;4] = self.buffer.as_ref()[..4].try_into().unwrap();
// Ipv4Addr::from(tmp)
// }
// pub fn destination(&self) -> Ipv4Addr {
// let tmp:[u8;4] = self.buffer.as_ref()[4..8].try_into().unwrap();
// Ipv4Addr::from(tmp)
// }
pub fn time(&self) -> i64 {
i64::from_be_bytes(self.buffer.as_ref()[8..].try_into().unwrap())
}
}
impl<B: AsRef<[u8]> + AsMut<[u8]>> TurnPingPacket<B> {
pub fn set_source(&mut self, source: Ipv4Addr) {
self.buffer.as_mut()[..4].copy_from_slice(&source.octets());
}
pub fn set_destination(&mut self, destination: Ipv4Addr) {
self.buffer.as_mut()[4..8].copy_from_slice(&destination.octets());
}
pub fn set_time(&mut self, time: i64) {
self.buffer.as_mut()[8..].copy_from_slice(&time.to_be_bytes())
}
}
pub type PunchResponsePacket<B> = PunchPacket<B>;
pub type PunchRequestPacket<B> = PunchPacket<B>;
/// nat穿透
#[derive(Clone)]
pub struct PunchPacket<B> {
buffer: B,
}
impl<B: AsRef<[u8]>> PunchPacket<B> {
pub fn new(buffer: B) -> Result<PunchPacket<B>> {
let len = buffer.as_ref().len();
if len != 8 {
return Err(Error::InvalidPacket);
}
Ok(Self { buffer })
}
}
impl<B: AsRef<[u8]>> PunchPacket<B> {
pub fn source(&self) -> Ipv4Addr {
let tmp: [u8; 4] = self.buffer.as_ref()[..4].try_into().unwrap();
Ipv4Addr::from(tmp)
}
}
impl<B: AsRef<[u8]> + AsMut<[u8]>> PunchPacket<B> {
pub fn set_source(&mut self, source: Ipv4Addr) {
self.buffer.as_mut()[..4].copy_from_slice(&source.octets());
}
}
impl<B: AsRef<[u8]>> fmt::Debug for PunchPacket<B> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("PunchPacket")
.field("source", &self.source())
.finish()
}
}
-69
View File
@@ -1,69 +0,0 @@
use crate::error::*;
#[derive(Eq, PartialEq, Copy, Clone, Debug)]
pub enum Protocol {
TokenError,
Disconnect,
Other(u8),
}
impl From<u8> for Protocol {
fn from(value: u8) -> Self {
match value {
1 => Self::TokenError,
2 => Self::Disconnect,
val => Self::Other(val),
}
}
}
impl Into<u8> for Protocol {
fn into(self) -> u8 {
match self {
Protocol::TokenError => 1,
Protocol::Disconnect => 2,
Protocol::Other(val) => val,
}
}
}
pub enum InErrorPacket<B> {
TokenError,
Disconnect,
OtherError(ErrorPacket<B>),
}
impl<B: AsRef<[u8]>> InErrorPacket<B> {
pub fn new(protocol: u8, buffer: B) -> Result<InErrorPacket<B>> {
match Protocol::from(protocol) {
Protocol::TokenError => Ok(InErrorPacket::TokenError),
Protocol::Disconnect => Ok(InErrorPacket::Disconnect),
Protocol::Other(_) => Ok(InErrorPacket::OtherError(ErrorPacket::new(buffer)?)),
}
}
}
pub struct ErrorPacket<B> {
buffer: B,
}
impl<B: AsRef<[u8]>> ErrorPacket<B> {
pub fn new(buffer: B) -> Result<ErrorPacket<B>> {
Ok(Self { buffer })
}
}
impl<B: AsRef<[u8]>> ErrorPacket<B> {
pub fn message(&self) -> Result<String> {
match String::from_utf8(self.buffer.as_ref().to_vec()) {
Ok(str) => Ok(str),
Err(_) => Err(Error::InvalidPacket),
}
}
}
impl<B: AsRef<[u8]> + AsMut<[u8]>> ErrorPacket<B> {
pub fn set_message(&mut self, message: &str) {
self.buffer.as_mut().copy_from_slice(message.as_bytes())
}
}
-145
View File
@@ -1,145 +0,0 @@
use std::fmt;
use crate::error::*;
pub mod control_packet;
pub mod error_packet;
pub mod service_packet;
pub mod turn_packet;
#[derive(Eq, PartialEq, Copy, Clone, Debug)]
pub enum Version {
V1,
UnKnow(u8),
}
impl From<u8> for Version {
fn from(value: u8) -> Self {
match value {
1 => Version::V1,
val => Version::UnKnow(val),
}
}
}
impl Into<u8> for Version {
fn into(self) -> u8 {
match self {
Version::V1 => 1,
Version::UnKnow(val) => val,
}
}
}
#[derive(Eq, PartialEq, Copy, Clone, Debug)]
pub enum Protocol {
/// 服务包 用于和服务端交互
Service,
/// 响应异常
Error,
/// 控制协议
Control,
/// 转发ipv4数据
Ipv4Turn,
OtherTurn,
UnKnow(u8),
}
impl From<u8> for Protocol {
fn from(value: u8) -> Self {
match value {
1 => Protocol::Service,
2 => Protocol::Error,
3 => Protocol::Control,
4 => Protocol::Ipv4Turn,
5 => Protocol::OtherTurn,
val => Protocol::UnKnow(val),
}
}
}
impl Into<u8> for Protocol {
fn into(self) -> u8 {
match self {
Protocol::Service => 1,
Protocol::Error => 2,
Protocol::Control => 3,
Protocol::Ipv4Turn => 4,
Protocol::OtherTurn => 5,
Protocol::UnKnow(val) => val,
}
}
}
#[derive(Copy, Clone)]
pub struct NetPacket<B> {
buffer: B,
}
impl<B: AsRef<[u8]>> NetPacket<B> {
pub fn new(buffer: B) -> Result<NetPacket<B>> {
let len = buffer.as_ref().len();
// 不能大于udp最大载荷长度
if len < 4 || len > 65535 - 20 - 8 {
return Err(Error::InvalidPacket);
}
Ok(NetPacket { buffer })
}
pub fn buffer(&self) -> &[u8] {
self.buffer.as_ref()
}
pub fn into_buffer(self) -> B {
self.buffer
}
}
impl<B: AsRef<[u8]>> NetPacket<B> {
pub fn version(&self) -> Version {
Version::from(self.buffer.as_ref()[0])
}
pub fn protocol(&self) -> Protocol {
Protocol::from(self.buffer.as_ref()[1])
}
pub fn transport_protocol(&self) -> u8 {
self.buffer.as_ref()[2]
}
pub fn ttl(&self) -> u8 {
self.buffer.as_ref()[3]
}
pub fn payload(&self) -> &[u8] {
&self.buffer.as_ref()[4..]
}
}
impl<B: AsRef<[u8]> + AsMut<[u8]>> NetPacket<B> {
pub fn set_version(&mut self, version: Version) {
self.buffer.as_mut()[0] = version.into();
}
pub fn set_protocol(&mut self, protocol: Protocol) {
self.buffer.as_mut()[1] = protocol.into();
}
pub fn set_transport_protocol(&mut self, transport_protocol: u8) {
self.buffer.as_mut()[2] = transport_protocol;
}
pub fn set_ttl(&mut self, ttl: u8) {
self.buffer.as_mut()[3] = ttl;
}
pub fn set_payload(&mut self, payload: &[u8]) {
self.buffer.as_mut()[4..payload.len() + 4].copy_from_slice(payload);
}
pub fn payload_mut(&mut self) -> &mut [u8] {
&mut self.buffer.as_mut()[4..]
}
}
impl<B: AsRef<[u8]>> fmt::Debug for NetPacket<B> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("NetPacket")
.field("version", &self.version())
.field("protocol", &self.protocol())
.field("transport_protocol", &self.transport_protocol())
.field("ttl", &self.ttl())
.field("payload", &self.payload())
.finish()
}
}
-32
View File
@@ -1,32 +0,0 @@
#[derive(Eq, PartialEq, Copy, Clone, Debug)]
pub enum Protocol {
/// 注册请求
RegistrationRequest,
/// 注册响应
RegistrationResponse,
/// 更新设备列表
UpdateDeviceList,
UnKnow(u8),
}
impl From<u8> for Protocol {
fn from(value: u8) -> Self {
match value {
1 => Self::RegistrationRequest,
2 => Self::RegistrationResponse,
3 => Self::UpdateDeviceList,
val => Self::UnKnow(val),
}
}
}
impl Into<u8> for Protocol {
fn into(self) -> u8 {
match self {
Self::RegistrationRequest => 1,
Self::RegistrationResponse => 2,
Self::UpdateDeviceList => 3,
Self::UnKnow(val) => val,
}
}
}
-81
View File
@@ -1,81 +0,0 @@
use std::fmt;
use std::net::Ipv4Addr;
use crate::error::*;
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
pub enum Protocol {
Punch,
UnKnow(u8),
}
impl From<u8> for Protocol {
fn from(value: u8) -> Self {
match value {
1 => Protocol::Punch,
val => Protocol::UnKnow(val),
}
}
}
impl Into<u8> for Protocol {
fn into(self) -> u8 {
match self {
Protocol::Punch => 1,
Protocol::UnKnow(val) => val,
}
}
}
pub struct TurnPacket<B> {
buffer: B,
}
impl<B: AsRef<[u8]>> TurnPacket<B> {
pub fn new(buffer: B) -> Result<TurnPacket<B>> {
let len = buffer.as_ref().len();
if len <= 8 {
return Err(Error::InvalidPacket);
}
Ok(Self { buffer })
}
}
impl<B: AsRef<[u8]>> TurnPacket<B> {
pub fn source(&self) -> Ipv4Addr {
let tmp: [u8; 4] = self.buffer.as_ref()[..4].try_into().unwrap();
Ipv4Addr::from(tmp)
}
pub fn destination(&self) -> Ipv4Addr {
let tmp: [u8; 4] = self.buffer.as_ref()[4..8].try_into().unwrap();
Ipv4Addr::from(tmp)
}
pub fn payload(&self) -> &[u8] {
&self.buffer.as_ref()[8..]
}
}
impl<B: AsRef<[u8]> + AsMut<[u8]>> TurnPacket<B> {
pub fn payload_mut(&mut self) -> &mut [u8] {
&mut self.buffer.as_mut()[8..]
}
pub fn set_source(&mut self, source: Ipv4Addr) {
self.buffer.as_mut()[..4].copy_from_slice(&source.octets());
}
pub fn set_destination(&mut self, destination: Ipv4Addr) {
self.buffer.as_mut()[4..8].copy_from_slice(&destination.octets());
}
pub fn set_payload(&mut self, payload: &[u8]) {
self.buffer.as_mut()[8..payload.len() + 8].copy_from_slice(payload)
}
}
impl<B: AsRef<[u8]>> fmt::Debug for TurnPacket<B> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("TurnPacket")
.field("source", &self.source())
.field("destination", &self.destination())
.field("payload", &self.payload())
.finish()
}
}
-14
View File
@@ -1,14 +0,0 @@
#[cfg(any(unix))]
pub use unix::create_tun;
#[cfg(any(unix))]
pub use unix::{TunReader, TunWriter};
#[cfg(any(unix))]
pub mod unix;
#[cfg(target_os = "windows")]
pub mod windows;
#[cfg(target_os = "windows")]
pub use windows::create_tun;
#[cfg(target_os = "windows")]
pub use windows::{TunReader, TunWriter};
-156
View File
@@ -1,156 +0,0 @@
use std::io;
use std::io::{Error, Read, Write};
use std::net::Ipv4Addr;
use std::os::fd::AsRawFd;
use std::os::unix::process::CommandExt;
use std::process::Command;
use bytes::BufMut;
use tun::Device;
use tun::platform::posix::{Reader, Writer};
pub fn create_tun(
address: Ipv4Addr,
netmask: Ipv4Addr,
gateway: Ipv4Addr,
) -> crate::error::Result<(TunWriter, TunReader)> {
let mut config = tun::Configuration::default();
config
.destination(gateway)
.address(address)
.netmask(netmask)
.mtu(1420)
.up();
#[cfg(target_os = "linux")]
config.platform(|config| {
config.packet_information(true);
});
let dev = tun::create(&config).unwrap();
// let up_eth_str: String = format!("ifconfig utun3 {:?} {:?} up ", address, gateway);
let route_add_str: String = format!(
"sudo route -n add -net {:?} -netmask {:?} {:?}",
address, netmask, gateway
);
//
// let up_eth_out = Command::new("sh")
// .arg("-c")
// .arg(up_eth_str)
// .output()
// .expect("sh exec error!");
// if !up_eth_out.status.success() {
// return Err(crate::error::Error::Stop(format!("设置地址失败:{:?}", up_eth_out)));
// }
// println!("{:?}", up_eth_out);
let if_config_out = Command::new("sh")
.arg("-c")
.arg(route_add_str)
.output()
.expect("sh exec error!");
if !if_config_out.status.success() {
return Err(crate::error::Error::Stop(format!("设置路由失败:{:?}", if_config_out)));
}
// println!("{:?}", if_config_out);
// let cmd_str: String = " ifconfig|grep flags=8051|awk -F ':' '{print $1}'|tail -1".to_string();
//
// let cmd_str_out = Command::new("sh")
// .arg("-c")
// .arg(cmd_str)
// .output()
// .expect("sh exec error!");
// if !cmd_str_out.status.success(){
// return Err(Error::Stop(format!("设置路由失败:{:?}", cmd_str_out)));
// }
// println!("{:?}", cmd_str_out);
let packet_information = dev.has_packet_information();
let (reader, writer) = dev.split();
Ok((
TunWriter(writer, packet_information),
TunReader(reader, packet_information),
))
}
pub struct TunReader(Reader, bool);
impl TunReader {
pub fn read<'a>(&'a mut self, buf: &'a mut [u8]) -> io::Result<&mut [u8]> {
let len = self.0.read(buf)?;
if self.1 {
Ok(&mut buf[4..len])
} else {
Ok(&mut buf[..len])
}
}
}
pub struct TunWriter(Writer, bool);
impl TunWriter {
pub fn write(&mut self, packet: &[u8]) -> io::Result<()> {
if self.1 {
let mut buf = Vec::<u8>::with_capacity(4 + packet.len());
buf.put_u16(0);
#[cfg(any(target_os = "macos", target_os = "ios"))]
buf.put_u16(libc::PF_INET as u16);
#[cfg(any(target_os = "linux", target_os = "android"))]
buf.put_u16(libc::ETH_P_IP as u16);
buf.extend_from_slice(packet);
self.0.write_all(&buf)
} else {
self.0.write_all(packet)
}
}
}
// pub fn main1() {
// loop {
// let len = reader.read(&mut buffer).unwrap();
// println!("{:?}", &buffer[..len]);
// match ip::Packet::new(&buffer[4..len]) {
// Ok(ip::Packet::V4(pkt)) => {
// match icmp::Packet::new(pkt.payload()) {
// Ok(icmp) => {
// match icmp.echo() {
// Ok(icmp) => {
// println!("{:?}", icmp);
// let reply = ip::v4::Builder::default()
// .id(0x42)
// .unwrap()
// .ttl(64)
// .unwrap()
// .source(pkt.destination())
// .unwrap()
// .destination(pkt.source())
// .unwrap()
// .icmp()
// .unwrap()
// .echo()
// .unwrap()
// .reply()
// .unwrap()
// .identifier(icmp.identifier())
// .unwrap()
// .sequence(icmp.sequence())
// .unwrap()
// .payload(icmp.payload())
// .unwrap()
// .build()
// .unwrap();
// let l = reply.len();
// &mut buffer[4..(l + 4)].copy_from_slice(&reply);
// // writer.write_all(&buffer[..4]).unwrap();
// writer.write_all(&buffer[..(l + 4)]).unwrap();
// }
// Err(_) => {}
// }
// }
// Err(_) => {}
// }
// }
// _ => {}
// }
// }
// }
-132
View File
@@ -1,132 +0,0 @@
use std::io;
use std::net::Ipv4Addr;
use std::sync::Arc;
use libloading::Library;
use wintun::{Adapter, Packet, Session};
use crate::error::*;
pub struct TunWriter(Arc<Session>);
impl TunWriter {
pub fn write(&self, buf: &[u8]) -> io::Result<()> {
match self.0.allocate_send_packet(buf.len() as u16) {
Ok(mut packet) => {
packet.bytes_mut().copy_from_slice(buf);
self.0.send_packet(packet);
return Ok(());
}
Err(_) => {}
}
return Err(io::Error::new(io::ErrorKind::Other, "send err"));
}
}
pub struct TunReader(Arc<Session>);
impl TunReader {
pub fn next(&self) -> io::Result<Packet> {
match self.0.receive_blocking() {
Ok(packet) => {
return Ok(packet);
}
Err(_) => {}
}
return Err(io::Error::new(io::ErrorKind::Other, "read err"));
}
}
pub fn create_tun(
address: Ipv4Addr,
netmask: Ipv4Addr,
gateway: Ipv4Addr,
) -> Result<(TunWriter, TunReader)> {
let win_tun = unsafe {
match Library::new("wintun.dll") {
Ok(library) => match wintun::load_from_library(library) {
Ok(win_tun) => win_tun,
Err(e) => {
return Err(Error::Stop(format!("{:?}", e)));
}
},
Err(e) => {
println!("{}", console::style("wintun.dll not found").red());
return Err(Error::Stop(format!("{:?}", e)));
}
}
};
let adapter = match Adapter::open(&win_tun, "Demo") {
Ok(a) => a,
Err(_) => match Adapter::create(&win_tun, "Example", "Demo", None) {
Ok(adapter) => adapter,
Err(e) => return Err(Error::Stop(format!("{:?}", e))),
},
};
let index = adapter.get_adapter_index().unwrap();
let set_mtu = format!(
"netsh interface ipv4 set subinterface {} mtu=1420 store=persistent",
index
);
let set_metric = format!("netsh interface ip set interface {} metric=1", index);
let set_address = format!(
"netsh interface ip set address {} static {:?} {:?} ", // gateway={:?}
index, address, netmask,
);
// println!("{}", set_mtu);
// println!("{}", set_metric);
// println!("{}", set_address);
// 执行网卡初始化命令
let out = std::process::Command::new("cmd")
.arg("/C")
.arg(set_mtu)
.output()
.unwrap();
if !out.status.success() {
return Err(Error::Stop(format!("设置mtu失败:{:?}", out)));
}
let out = std::process::Command::new("cmd")
.arg("/C")
.arg(set_metric)
.output()
.unwrap();
if !out.status.success() {
return Err(Error::Stop(format!("设置接口跃点失败:{:?}", out)));
}
let out = std::process::Command::new("cmd")
.arg("/C")
.arg(set_address)
.output()
.unwrap();
if !out.status.success() {
return Err(Error::Stop(format!("设置网络地址失败:{:?}", out)));
}
let dest = {
let ip = address.octets();
let mask = netmask.octets();
Ipv4Addr::from([
ip[0] & mask[0],
ip[1] & mask[1],
ip[2] & mask[2],
ip[3] & mask[3],
])
};
let set_route = format!(
"route add {:?} mask {:?} {:?} if {}",
dest, netmask, gateway, index
);
// println!("{}", set_route);
// 执行添加路由命令
let out = std::process::Command::new("cmd")
.arg("/C")
.arg(set_route)
.output()
.unwrap();
if !out.status.success() {
return Err(Error::Stop(format!("添加路由失败:{:?}", out)));
}
let session = Arc::new(adapter.start_session(wintun::MAX_RING_CAPACITY).unwrap());
let reader_session = session.clone();
Ok((TunWriter(session), TunReader(reader_session)))
}
+58
View File
@@ -0,0 +1,58 @@
[package]
name = "vnt-core"
version = "2.0.0"
edition = "2024"
[dependencies]
tokio = { version = "1", features = ["full"] }
tokio-util = { version = "0.7", features = ["codec"] }
futures = "0.3"
tun-rs = { version = "2", features = ["async", "async_framed"] }
rust-p2p-core = { version="0.4" }
tcp_ip = { git = "https://github.com/rustp2p/tcp_ip" }
anyhow = "1"
parking_lot = "0.12"
quinn = { version = "0.11", default-features = false, features = ["rustls", "runtime-tokio"] }
tokio-rustls = { version = "0.26", default-features = false, features = ["ring"] }
rustls = { version = "0.23", default-features = false, features = ["ring"] }
hex = "0.4"
sha2 = "0.10"
rustls-native-certs = "0.8.2"
log = "0.4"
bytes = "1.11.0"
rand = "0.9"
time = { version = "0.3", features = ["macros", "formatting", "local-offset"] }
pnet_packet = "0.35"
ring = "0.17.14"
prost = "0.14"
tokio-tungstenite = "0.28.0"
tungstenite = "0.28.0"
uuid = { version = "1.18.1", features = ["v4"] }
ipnet = { version = "2.11", features = ["serde"] }
getifaddrs = "0.6.0"
dns-parser = "0.8"
lz4_flex = "0.12.0"
reed-solomon-erasure = "6.0"
serde = { version = "1.0.228", features = ["derive"] }
rcgen = "0.14.6"
machine-uid = "0.5.4"
zerocopy = { version = "0.8.31", features = ["derive"] }
socket2 = { version = "0.6.1", features = ["all"] }
[target.'cfg(target_os = "windows")'.dependencies]
winapi = { version = "0.3.9", features = ["winreg"] }
widestring = "1.2"
[build-dependencies]
prost-build = "0.14"
+15
View File
@@ -0,0 +1,15 @@
fn main() {
let mut config = prost_build::Config::new();
config.protoc_arg("--experimental_allow_proto3_optional");
config
.compile_protos(
&[
"proto/control_message.proto",
"proto/rpc.proto",
"proto/client.proto",
"proto/fec.proto",
],
&["proto"],
)
.unwrap();
}
+62
View File
@@ -0,0 +1,62 @@
syntax = "proto3";
package protocol.client;
message QuicProxyHandshake{
oneof handshake{
TcpProxyHandshake tcp = 1;
IpProxyHandshake ip = 2;
PortProxyHandshake tcp_port_mapping = 3;
PortProxyHandshake udp_port_mapping = 4;
}
}
message TcpProxyHandshake{
fixed32 src_ip = 1;
uint32 src_port = 2;
fixed32 dst_ip = 3;
uint32 dst_port = 4;
}
message PortProxyHandshake{
string src_ip = 1;
uint32 src_port = 2;
string dst_host = 3;
uint32 dst_port = 4;
}
message IpProxyHandshake{
uint32 ip_next_header_protocol = 1;
fixed32 src_ip = 2;
fixed32 dst_ip = 3;
}
enum NatType {
NAT_TYPE_CONE = 0;
NAT_TYPE_SYMMETRIC = 1;
}
message NatInfo {
NatType nat_type = 1;
repeated fixed32 public_ips = 2;
repeated uint32 public_udp_ports = 3;
uint32 public_port_range = 4;
repeated fixed32 local_ipv4s = 5;
optional bytes ipv6 = 6;
repeated uint32 local_udp_ports = 7;
uint32 local_tcp_port = 8;
uint32 public_tcp_port = 9;
}
message PunchInfo{
NatInfo nat_info = 1;
}
+72
View File
@@ -0,0 +1,72 @@
syntax = "proto3";
package protocol.control_message;
enum RegistrationMode {
NORMAL = 0;
PRE_REGISTER = 1;
}
message RegRequestMsg {
string network_code = 1;
string device_id = 2;
optional fixed32 ip = 3;
string name = 4;
string version = 5;
optional string key_sign = 6;
bool ip_variable = 7;
fixed32 server_id = 8;
RegistrationMode registration_mode = 9;
}
message RegResponseMsg {
fixed32 ip = 1;
uint32 prefix_len = 2;
fixed32 gateway = 3;
string server_version = 4;
}
message ConfirmRegMsg {
}
message ConfirmRegResponseMsg {
bool success = 1;
}
message ErrorResponseMsg {
uint32 code = 1;
string message = 2;
}
message RequestMessage{
oneof request_payload{
RegRequestMsg reg = 1;
ConfirmRegMsg confirm_reg = 2;
}
}
message ResponseMessage{
oneof response_payload{
RegResponseMsg reg = 1;
ErrorResponseMsg error = 2;
ConfirmRegResponseMsg confirm_reg = 3;
}
}
message SelectiveBroadcast {
repeated fixed32 ips = 1;
bytes data = 2;
}
message ClientSimpleInfo{
fixed32 ip = 1;
bool online = 2;
}
message ClientSimpleInfoList{
uint64 data_version = 1;
repeated ClientSimpleInfo list = 2;
bool is_all = 3;
int64 time = 4;
}
+18
View File
@@ -0,0 +1,18 @@
syntax = "proto3";
package protocol.fec;
message FecPacket {
uint64 group_id = 1; // FEC 组号(按目标IP分组)
uint32 packet_index = 2; // 包在组内的序号(0-based
bytes payload = 3;
optional ParityData parity_data = 4;
}
// 冗余包(携带FEC恢复所需的元数据)
message ParityData {
uint32 data_shards = 1; // 原始包数量
uint32 parity_shards = 2; // 冗余包数量
}
+37
View File
@@ -0,0 +1,37 @@
syntax = "proto3";
package protocol.rpc;
message RpcMessageRequest{
uint64 id = 1;
oneof rpc_req_payload{
ClientListRequest client_list_req = 2;
}
}
message ClientListRequest{
}
message RpcMessageResponse{
uint64 id = 1;
oneof rpc_res_payload{
ClientListResponse client_list_res = 2;
}
}
message ClientInfo{
string name = 1;
string version = 2;
fixed32 ip = 3;
optional string key_sign = 4;
bool online = 5;
int64 last_connected_time = 6;
string id = 7;
}
message ClientListResponse{
repeated ClientInfo list = 1;
}
+94
View File
@@ -0,0 +1,94 @@
use crate::context::config::Config;
use crate::context::{AppState, NetworkAddr, PacketLossInfo, ServerNodeInfo, TrafficInfo};
use crate::protocol::control_message::ClientSimpleInfo;
use crate::tunnel_core::p2p::route_table::Route;
use crate::tunnel_core::server::rpc::ServerRPC;
use rust_p2p_core::nat::NatInfo;
use std::net::Ipv4Addr;
#[derive(Clone)]
pub struct VntApi {
app_state: AppState,
server_rpc: ServerRPC,
}
impl VntApi {
pub(crate) fn new(app_state: AppState, server_rpc: ServerRPC) -> Self {
Self {
app_state,
server_rpc,
}
}
pub fn server_rpc(&self) -> &ServerRPC {
&self.server_rpc
}
/// 获取启动配置
pub fn get_config(&self) -> Option<Box<Config>> {
self.app_state.get_config()
}
/// 获取所有客户端ip
pub fn client_ips(&self) -> Vec<ClientSimpleInfo> {
self.app_state.client_ips()
}
/// 判断目标IP是否直连
pub fn is_direct(&self, ip: &Ipv4Addr) -> bool {
self.app_state.route_table.p2p_num(ip) > 0
}
/// 查找路由
pub fn find_route(&self, ip: &Ipv4Addr) -> Option<Route> {
self.app_state.route_table.get_route_by_id(ip).ok()
}
pub fn get_rtt(&self, ip: &Ipv4Addr) -> Option<u32> {
if let Some(route) = self.find_route(ip) {
Some(route.rtt())
} else {
self.server_node_rtt(ip).map(|v| v * 2)
}
}
/// 获取所有路由
pub fn route_table(&self) -> Vec<(Ipv4Addr, Vec<Route>)> {
self.app_state.route_table.route_table()
}
/// 获取服务器节点
pub fn server_node_list(&self) -> Vec<ServerNodeInfo> {
self.app_state.server_info_collection.server_node_list()
}
pub fn server_node_rtt(&self, ip: &Ipv4Addr) -> Option<u32> {
self.app_state.server_info_collection.get_server_rtt(ip)
}
/// 获取网络配置
pub fn network(&self) -> Option<NetworkAddr> {
self.app_state.get_network()
}
/// 获取当前的nat信息
pub fn nat_info(&self) -> Option<NatInfo> {
self.app_state.get_nat_info()
}
pub fn peer_nat_info(&self, ip: &Ipv4Addr) -> Option<NatInfo> {
self.app_state.get_peer_info(ip).and_then(|v| v.nat_info)
}
pub fn packet_loss_info(&self, ip: &Ipv4Addr) -> Option<PacketLossInfo> {
self.app_state.packet_loss_stats.get_loss_info(ip)
}
pub fn all_packet_loss_info(&self) -> Vec<PacketLossInfo> {
self.app_state.packet_loss_stats.get_all_loss_info()
}
pub fn reset_packet_loss(&self, ip: &Ipv4Addr) {
self.app_state.packet_loss_stats.reset(ip)
}
pub fn reset_all_packet_loss(&self) {
self.app_state.packet_loss_stats.reset_all()
}
pub fn traffic_info(&self, ip: &Ipv4Addr) -> Option<TrafficInfo> {
self.app_state.traffic_stats.get_traffic_info(ip)
}
pub fn all_traffic_info(&self) -> Vec<TrafficInfo> {
self.app_state.traffic_stats.get_all_traffic_info()
}
pub fn reset_traffic(&self, ip: &Ipv4Addr) {
self.app_state.traffic_stats.reset(ip)
}
pub fn reset_all_traffic(&self) {
self.app_state.traffic_stats.reset_all()
}
}
+132
View File
@@ -0,0 +1,132 @@
use crate::protocol::ip_packet_protocol::{HEAD_LENGTH, NetPacket};
use crate::protocol::transmission::TransmissionBytes;
use std::io;
#[derive(Clone)]
pub struct LZ4Compression {
min_size: usize, // 只压缩大于此大小的数据包
}
impl LZ4Compression {
pub fn new() -> Self {
Self::with_min_size(256)
}
pub fn with_min_size(min_size: usize) -> Self {
Self { min_size }
}
/// 压缩数据包,返回新的压缩后的数据包
/// reserve: 尾部预留空间(用于后续加密等操作)
pub fn compress(
&self,
pkt: NetPacket<TransmissionBytes>,
reserve: usize,
) -> io::Result<NetPacket<TransmissionBytes>> {
let payload = pkt.payload();
if payload.len() < self.min_size {
return Ok(pkt);
}
let compressed = lz4_flex::compress_prepend_size(payload);
if compressed.len() >= payload.len() {
return Ok(pkt);
}
let total_len = HEAD_LENGTH + compressed.len();
let mut buf = TransmissionBytes::zeroed_size(total_len, reserve);
buf[..HEAD_LENGTH].copy_from_slice(&pkt.buffer()[..HEAD_LENGTH]);
buf[HEAD_LENGTH..total_len].copy_from_slice(&compressed);
let mut packet = NetPacket::new(buf)?;
packet.set_compressed_flag(true);
Ok(packet)
}
/// 解压缩数据包,返回新的解压后的数据包
/// reserve: 尾部预留空间(用于后续加密等操作)
pub fn decompress(
&self,
pkt: NetPacket<TransmissionBytes>,
) -> io::Result<NetPacket<TransmissionBytes>> {
if !pkt.is_compressed() {
return Ok(pkt);
}
let payload = pkt.payload();
let decompressed = lz4_flex::decompress_size_prepended(payload).map_err(|e| {
io::Error::new(
io::ErrorKind::InvalidData,
format!("decompress failed: {}", e),
)
})?;
let total_len = HEAD_LENGTH + decompressed.len();
let mut buf = TransmissionBytes::zeroed(total_len);
buf[..HEAD_LENGTH].copy_from_slice(&pkt.buffer()[..HEAD_LENGTH]);
buf[HEAD_LENGTH..total_len].copy_from_slice(&decompressed);
let mut packet = NetPacket::new(buf)?;
packet.set_compressed_flag(false);
Ok(packet)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::protocol::ip_packet_protocol::{HEAD_LENGTH, NetPacket};
use crate::protocol::transmission::TransmissionBytes;
fn make_packet(data: &[u8]) -> NetPacket<TransmissionBytes> {
let mut buf = TransmissionBytes::zeroed(HEAD_LENGTH + data.len());
buf[HEAD_LENGTH..HEAD_LENGTH + data.len()].copy_from_slice(data);
NetPacket::new(buf).unwrap()
}
#[test]
fn test_lz4_compress_and_decompress() {
let lz = LZ4Compression::with_min_size(10);
// --- 构造原始包 ---
let payload = vec![1u8; 200];
let original = make_packet(&payload);
// --- 压缩 ---
let compressed = lz.compress(original, 0).unwrap();
assert!(compressed.is_compressed());
// 压缩后的 payload 应变小
assert!(
compressed.payload().len() < payload.len(),
"压缩后 payload 应该更小"
);
// --- 解压 ---
let decompressed = lz.decompress(compressed).unwrap();
// 标志应清除
assert!(!decompressed.is_compressed());
// HEAD 不变
assert_eq!(
&decompressed.buffer()[..HEAD_LENGTH],
&[0u8; HEAD_LENGTH][..],
"HEAD 必须保持不变"
);
// payload 必须等于原始 payload
assert_eq!(decompressed.payload(), &payload[..]);
}
#[test]
fn test_no_compress_when_small() {
let lz = LZ4Compression::with_min_size(100);
let pkt = make_packet(&[7; 20]);
let compressed = lz.compress(pkt, 0).unwrap();
assert!(!compressed.is_compressed(), "小包不应该被压缩");
assert_eq!(compressed.payload(), &[7; 20][..]);
}
}
+45
View File
@@ -0,0 +1,45 @@
use crate::compression::lz4_compression::LZ4Compression;
use crate::protocol::ip_packet_protocol::NetPacket;
use crate::protocol::transmission::TransmissionBytes;
use std::io;
mod lz4_compression;
#[derive(Clone)]
pub(crate) struct PacketCompression {
compression: Option<LZ4Compression>,
}
impl PacketCompression {
pub(crate) fn new(enabled: bool) -> Self {
Self {
compression: if enabled {
Some(LZ4Compression::new())
} else {
None
},
}
}
pub(crate) fn compress(
&self,
pkt: NetPacket<TransmissionBytes>,
reserve: usize,
) -> io::Result<NetPacket<TransmissionBytes>> {
if let Some(compression) = self.compression.as_ref() {
return compression.compress(pkt, reserve);
}
Ok(pkt)
}
pub(crate) fn decompress(
&self,
pkt: NetPacket<TransmissionBytes>,
) -> io::Result<NetPacket<TransmissionBytes>> {
if let Some(compression) = self.compression.as_ref() {
return compression.decompress(pkt);
}
Ok(pkt)
}
}
+101
View File
@@ -0,0 +1,101 @@
use crate::crypto::PacketCrypto;
use crate::nat::NetInput;
use crate::port_mapping::PortMapping;
use crate::tls::verifier::CertValidationMode;
use crate::tunnel_core::server::transport::config::{ConnectRegConfig, ProtocolAddress};
use anyhow::bail;
use ipnet::Ipv4Net;
use std::collections::HashSet;
use std::net::Ipv4Addr;
pub const MAX_NETWORK_CODE_LEN: usize = 32;
pub const MAX_DEVICE_ID_LEN: usize = 64;
pub const MAX_NAME_LEN: usize = 128;
pub const MAX_VERSION_LEN: usize = 32;
pub const MAX_MTU: u16 = 1500;
#[derive(Debug, Clone, Default)]
pub struct Config {
pub server_addr: Vec<ProtocolAddress>,
pub cert_mode: CertValidationMode,
pub network_code: String,
pub device_id: String,
pub device_name: String,
pub tun_name: Option<String>,
pub ip: Option<Ipv4Addr>,
pub password: Option<String>,
pub no_punch: bool,
pub compress: bool,
pub rtx: bool,
pub fec: bool,
pub input: Vec<NetInput>,
pub output: Vec<Ipv4Net>,
pub no_nat: bool,
pub no_tun: bool,
pub mtu: Option<u16>,
pub port_mapping: Vec<PortMapping>,
pub allow_port_mapping: bool,
pub udp_stun: Vec<String>,
pub tcp_stun: Vec<String>,
}
impl Config {
pub fn check(&self) -> anyhow::Result<()> {
if self.server_addr.is_empty() {
bail!("服务器地址不能为空");
}
if self.server_addr.len() > 1 {
let mut set = HashSet::new();
for a in self.server_addr.iter() {
if !set.insert(a.address.as_str()) {
bail!("服务器地址不能相同")
}
}
}
if self.network_code.len() > MAX_NETWORK_CODE_LEN {
bail!(
"network_code length exceeds {} characters (current: {})",
MAX_NETWORK_CODE_LEN,
self.network_code.len()
)
}
if self.device_id.len() > MAX_DEVICE_ID_LEN {
bail!(
"device_id length exceeds {} characters (current: {})",
MAX_DEVICE_ID_LEN,
self.device_id.len()
)
}
if self.device_name.len() > MAX_NAME_LEN {
bail!(
"name length exceeds {} characters (current: {})",
MAX_NAME_LEN,
self.device_name.len()
)
}
if let Some(mtu) = self.mtu
&& mtu > MAX_MTU
{
bail!("MTU is too large (Maximum mtu: {MAX_MTU})",)
}
Ok(())
}
pub fn key_sign(&self) -> Option<String> {
self.password.as_ref().map(|p| PacketCrypto::key_sign(p))
}
pub(crate) fn to_connect_config(&self, index: usize) -> ConnectRegConfig {
ConnectRegConfig {
server_addr: self.server_addr[index].clone(),
cert_mode: self.cert_mode.clone(),
network_code: self.network_code.clone(),
device_id: self.device_id.clone(),
device_name: self.device_name.clone(),
ip: self.ip,
key_sign: self.key_sign(),
ip_variable: self.ip.is_none(),
}
}
}
+632
View File
@@ -0,0 +1,632 @@
use crate::context::config::Config;
use crate::context::nat::{MyNatInfo, PunchBackoff};
use crate::nat::SubnetExternalRoute;
use crate::protocol::client_message::PunchInfo;
use crate::protocol::control_message::{ClientSimpleInfo, ClientSimpleInfoList};
use crate::tunnel_core::p2p::route_table::RouteTable;
use crate::tunnel_core::server::transport::config::ProtocolAddress;
use ipnet::Ipv4Net;
use parking_lot::{Mutex, RwLock};
use rust_p2p_core::nat::NatInfo;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::net::Ipv4Addr;
use std::sync::Arc;
#[derive(Default)]
struct PingStats {
sent: u64,
received: u64,
}
#[derive(Default)]
struct TrafficCounter {
tx_bytes: u64,
rx_bytes: u64,
}
#[derive(Clone, Default)]
pub struct TrafficStats {
inner: Arc<RwLock<HashMap<Ipv4Addr, Arc<Mutex<TrafficCounter>>>>>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct TrafficInfo {
pub ip: Ipv4Addr,
pub tx_bytes: u64,
pub rx_bytes: u64,
}
impl TrafficStats {
fn get_or_create(&self, ip: Ipv4Addr) -> Arc<Mutex<TrafficCounter>> {
{
let read = self.inner.read();
if let Some(counter) = read.get(&ip) {
return counter.clone();
}
}
let mut write = self.inner.write();
write
.entry(ip)
.or_insert_with(|| Arc::new(Mutex::new(TrafficCounter::default())))
.clone()
}
pub fn record_tx(&self, ip: Ipv4Addr, bytes: u64) {
let counter = self.get_or_create(ip);
counter.lock().tx_bytes += bytes;
}
pub fn record_rx(&self, ip: Ipv4Addr, bytes: u64) {
let counter = self.get_or_create(ip);
counter.lock().rx_bytes += bytes;
}
pub fn get_traffic_info(&self, ip: &Ipv4Addr) -> Option<TrafficInfo> {
let read = self.inner.read();
read.get(ip).map(|counter| {
let guard = counter.lock();
TrafficInfo {
ip: *ip,
tx_bytes: guard.tx_bytes,
rx_bytes: guard.rx_bytes,
}
})
}
pub fn get_all_traffic_info(&self) -> Vec<TrafficInfo> {
let read = self.inner.read();
read.iter()
.map(|(ip, counter)| {
let guard = counter.lock();
TrafficInfo {
ip: *ip,
tx_bytes: guard.tx_bytes,
rx_bytes: guard.rx_bytes,
}
})
.collect()
}
pub fn reset(&self, ip: &Ipv4Addr) {
let read = self.inner.read();
if let Some(counter) = read.get(ip) {
*counter.lock() = TrafficCounter::default();
}
}
pub fn reset_all(&self) {
let read = self.inner.read();
for counter in read.values() {
*counter.lock() = TrafficCounter::default();
}
}
pub fn clear(&self) {
self.inner.write().clear();
}
}
#[derive(Clone, Default)]
pub struct PacketLossStats {
inner: Arc<RwLock<HashMap<Ipv4Addr, Arc<Mutex<PingStats>>>>>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct PacketLossInfo {
pub ip: Ipv4Addr,
pub sent: u64,
pub received: u64,
pub loss_rate: f64,
}
impl PacketLossStats {
fn get_or_create(&self, ip: Ipv4Addr) -> Arc<Mutex<PingStats>> {
{
let read = self.inner.read();
if let Some(stats) = read.get(&ip) {
return stats.clone();
}
}
let mut write = self.inner.write();
write
.entry(ip)
.or_insert_with(|| Arc::new(Mutex::new(PingStats::default())))
.clone()
}
pub fn record_sent(&self, ip: Ipv4Addr) {
let stats = self.get_or_create(ip);
stats.lock().sent += 1;
}
pub fn record_received(&self, ip: Ipv4Addr) {
let stats = self.get_or_create(ip);
stats.lock().received += 1;
}
pub fn get_loss_info(&self, ip: &Ipv4Addr) -> Option<PacketLossInfo> {
let read = self.inner.read();
read.get(ip).map(|stats| {
let guard = stats.lock();
let loss_rate = if guard.sent > 0 {
1.0 - (guard.received as f64 / guard.sent as f64)
} else {
0.0
};
PacketLossInfo {
ip: *ip,
sent: guard.sent,
received: guard.received,
loss_rate,
}
})
}
pub fn get_all_loss_info(&self) -> Vec<PacketLossInfo> {
let read = self.inner.read();
read.iter()
.map(|(ip, stats)| {
let guard = stats.lock();
let loss_rate = if guard.sent > 0 {
1.0 - (guard.received as f64 / guard.sent as f64)
} else {
0.0
};
PacketLossInfo {
ip: *ip,
sent: guard.sent,
received: guard.received,
loss_rate,
}
})
.collect()
}
pub fn reset(&self, ip: &Ipv4Addr) {
let read = self.inner.read();
if let Some(stats) = read.get(ip) {
*stats.lock() = PingStats::default();
}
}
pub fn reset_all(&self) {
let read = self.inner.read();
for stats in read.values() {
*stats.lock() = PingStats::default();
}
}
pub fn clear(&self) {
self.inner.write().clear();
}
}
pub mod config;
pub(crate) mod nat;
#[derive(Clone, Default)]
pub(crate) struct AppState {
config: Arc<Mutex<Option<Box<Config>>>>,
pub(crate) network: SharedNetworkAddr,
pub(crate) server_info_collection: ServerInfoCollection,
pub(crate) peer_map: PeerInfoMap,
pub(crate) route_table: RouteTable,
pub(crate) subnet_route: SubnetExternalRoute,
pub(crate) nat_info: MyNatInfo,
pub(crate) punch_backoff: PunchBackoff,
pub(crate) packet_loss_stats: PacketLossStats,
pub(crate) traffic_stats: TrafficStats,
}
#[derive(Clone, Default)]
pub(crate) struct SharedNetworkAddr {
inner: Arc<Mutex<Option<NetworkAddr>>>,
}
impl SharedNetworkAddr {
pub fn network(&self) -> Option<Ipv4Net> {
self.inner.lock().as_ref().map(|v| v.network())
}
pub fn ip(&self) -> Option<Ipv4Addr> {
self.inner.lock().map(|v| v.ip)
}
pub fn get(&self) -> Option<NetworkAddr> {
*self.inner.lock()
}
pub fn set(&self, addr: NetworkAddr) {
*self.inner.lock() = Some(addr);
}
pub fn clear(&self) {
*self.inner.lock() = None;
}
}
/// 网络路由封装,包含本地网络信息和子网路由
#[derive(Clone)]
pub(crate) struct NetworkRoute {
pub network: SharedNetworkAddr,
pub subnet_route: SubnetExternalRoute,
}
impl NetworkRoute {
pub fn new(network: SharedNetworkAddr, subnet_route: SubnetExternalRoute) -> Self {
Self {
network,
subnet_route,
}
}
/// 检查 IP 是否在本地网络或子网路由中
pub fn network_contains(&self, ip: &Ipv4Addr) -> bool {
if let Some(network) = self.network.network()
&& network.contains(ip)
{
return true;
}
self.subnet_route.route(ip).is_some()
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct PeerClientInfo {
pub nat_info: Option<NatInfo>,
}
#[derive(Clone, Default)]
pub(crate) struct PeerInfoMap {
inner: Arc<Mutex<HashMap<Ipv4Addr, PeerClientInfo>>>,
}
impl PeerInfoMap {
pub fn get(&self, ip: &Ipv4Addr) -> Option<PeerClientInfo> {
self.inner.lock().get(ip).cloned()
}
pub fn update_nat_info(&self, ip: Ipv4Addr, nat_info: NatInfo) {
let mut guard = self.inner.lock();
if let Some(v) = guard.get_mut(&ip) {
v.nat_info = Some(nat_info);
return;
}
guard.insert(
ip,
PeerClientInfo {
nat_info: Some(nat_info),
},
);
}
pub fn clear(&self) {
self.inner.lock().clear();
}
}
#[derive(Clone, Default)]
pub(crate) struct ServerInfoCollection {
client_simple_list: Arc<RwLock<Vec<ClientSimpleInfo>>>,
server_node_map: Arc<RwLock<HashMap<u32, ServerNodeInfo>>>,
}
#[derive(Clone, Default)]
pub struct ServerNodeInfo {
pub server_id: u32,
pub server_addr: ProtocolAddress,
pub connected: bool,
pub rtt: Option<u32>,
pub data_version: u64,
pub client_map: HashMap<Ipv4Addr, ClientSimpleInfo>,
pub last_connected_time: Option<i64>,
pub disconnected_time: Option<i64>,
pub server_version: Option<String>,
}
impl ServerInfoCollection {
pub fn server_client_ip_map(&self) -> HashMap<u32, (Vec<Ipv4Addr>, u32)> {
self.server_node_map
.read()
.iter()
.map(|(k, v)| {
(
*k,
(
v.client_map
.iter()
.filter(|(_, v)| v.online)
.map(|(k, _)| *k)
.collect(),
v.rtt.unwrap_or(500),
),
)
})
.collect()
}
pub fn server_node_list(&self) -> Vec<ServerNodeInfo> {
self.server_node_map.read().values().cloned().collect()
}
pub fn update_server(&self, addr: Vec<(u32, ProtocolAddress)>) {
let mut server_node_map_guard = self.server_node_map.write();
let mut client_simple_list_guard = self.client_simple_list.write();
server_node_map_guard.clear();
client_simple_list_guard.clear();
for (server_id, server_addr) in addr {
let server_node = ServerNodeInfo {
server_id,
server_addr,
..Default::default()
};
server_node_map_guard.insert(server_id, server_node);
}
}
pub fn find_connected_server(&self, server_ids: &[u32]) -> Option<u32> {
let map = self.server_node_map.read();
server_ids
.iter()
.filter_map(|id| {
let server = map.get(id)?;
if !server.connected {
return None;
}
let rtt = server.rtt.unwrap_or(u32::MAX);
Some((*id, rtt))
})
.min_by_key(|(_, rtt)| *rtt)
.map(|(id, _)| id)
}
pub fn find_ip_to_server(&self, server_ids: &[u32], ip: &Ipv4Addr) -> Option<u32> {
let map = self.server_node_map.read();
server_ids
.iter()
.filter_map(|id| {
let server = map.get(id)?;
if !server.connected {
return None;
}
let client = server.client_map.get(ip)?;
if !client.online {
return None;
}
let rtt = server.rtt.unwrap_or(u32::MAX);
Some((*id, rtt))
})
.min_by_key(|(_, rtt)| *rtt)
.map(|(id, _)| id)
}
pub fn client_online_ips(&self) -> Vec<Ipv4Addr> {
self.client_simple_list
.read()
.iter()
.filter(|v| v.online)
.map(|c| c.ip)
.collect()
}
pub fn client_ips(&self) -> Vec<ClientSimpleInfo> {
self.client_simple_list.read().clone()
}
pub fn data_version(&self, server_id: u32) -> u64 {
self.server_node_map
.read()
.get(&server_id)
.map(|v| v.data_version)
.unwrap_or(0)
}
pub fn update_client_simple_list(
&self,
server_id: u32,
self_ip: Ipv4Addr,
client_simple_list: ClientSimpleInfoList,
now: i64,
) {
let mut guard = self.server_node_map.write();
let server_node = guard.entry(server_id).or_default();
if now > client_simple_list.time {
server_node.rtt = Some((now - client_simple_list.time) as u32);
}
server_node.data_version = client_simple_list.data_version;
let map: HashMap<Ipv4Addr, ClientSimpleInfo> = client_simple_list
.list
.into_iter()
.filter(|v| v.ip != self_ip)
.map(|info| (info.ip, info))
.collect();
if client_simple_list.is_all {
server_node.client_map = map;
} else {
server_node.client_map.extend(map);
}
let mut client_simple_map = HashMap::<Ipv4Addr, ClientSimpleInfo>::new();
for (_, server_node) in guard.iter() {
for (_, x) in server_node.client_map.iter() {
if let Some(v) = client_simple_map.get_mut(&x.ip) {
if x.online {
v.online = true;
}
} else {
client_simple_map.insert(x.ip, x.clone());
}
}
}
let mut guard = self.client_simple_list.write();
*guard = client_simple_map.into_values().collect()
}
pub fn set_server_connected(&self, server_id: u32, val: bool) -> bool {
let mut mutex_guard = self.server_node_map.write();
let server_node = mutex_guard.entry(server_id).or_default();
let old = server_node.connected;
server_node.connected = val;
old
}
pub fn is_any_server_connected(&self, server_ids: Option<&[u32]>) -> bool {
let guard = self.server_node_map.read();
if let Some(server_ids) = server_ids {
for id in server_ids {
if guard.get(id).map(|v| v.connected).unwrap_or(false) {
return true;
}
}
} else {
for (_, server_node) in guard.iter() {
if server_node.connected {
return true;
}
}
}
false
}
pub fn is_server_connected(&self, server_id: u32) -> bool {
self.server_node_map
.read()
.get(&server_id)
.map(|v| v.connected)
.unwrap_or(false)
}
pub fn set_last_connected_time(&self, server_id: u32, last_connected_time: Option<i64>) {
self.server_node_map
.write()
.entry(server_id)
.or_default()
.last_connected_time = last_connected_time;
}
pub fn set_disconnected_time(&self, server_id: u32, last_connected_time: Option<i64>) {
self.server_node_map
.write()
.entry(server_id)
.or_default()
.disconnected_time = last_connected_time;
}
pub fn set_server_rtt(&self, server_id: u32, rtt: u32) {
if let Some(v) = self.server_node_map.write().get_mut(&server_id) {
v.rtt = Some(rtt);
}
}
pub fn set_server_version(&self, server_id: u32, version: String) {
if let Some(v) = self.server_node_map.write().get_mut(&server_id) {
v.server_version = Some(version);
}
}
pub fn get_server_rtt(&self, ip: &Ipv4Addr) -> Option<u32> {
let server_node_map_guard = self.server_node_map.read();
for (_, server_node) in server_node_map_guard.iter() {
if !server_node.connected {
continue;
}
if let Some(v) = server_node.client_map.get(ip)
&& v.online
{
return server_node.rtt;
}
}
None
}
pub fn exists_online_client_ip(&self, ip: &Ipv4Addr) -> bool {
self.client_simple_list
.read()
.iter()
.any(|v| v.ip == *ip && v.online)
}
pub fn clear(&self) {
self.client_simple_list.write().clear();
let mut guard = self.server_node_map.write();
for server_node in guard.values_mut() {
server_node.connected = false;
server_node.rtt = None;
server_node.data_version = 0;
server_node.client_map.clear();
server_node.last_connected_time = None;
server_node.disconnected_time = None;
}
}
}
#[derive(Copy, Clone, Debug)]
pub struct NetworkAddr {
pub gateway: Ipv4Addr,
pub broadcast: Ipv4Addr,
pub ip: Ipv4Addr,
pub prefix_len: u8,
}
impl NetworkAddr {
pub fn network(&self) -> Ipv4Net {
Ipv4Net::new_assert(self.ip, self.prefix_len)
}
}
impl AppState {
pub fn stop_network(&self) {
self.network.clear();
self.server_info_collection.clear();
self.peer_map.clear();
// route_table 来自外部 crate,会在任务停止后自动失效
self.nat_info.clear();
self.punch_backoff.clear();
self.packet_loss_stats.clear();
self.traffic_stats.clear();
}
fn network(&self) -> Option<Ipv4Net> {
self.network.network()
}
pub fn get_network(&self) -> Option<NetworkAddr> {
self.network.get()
}
fn network_contains(&self, ip: &Ipv4Addr) -> bool {
let Some(network) = self.network() else {
return false;
};
if network.contains(ip) {
return true;
}
self.subnet_route.route(ip).is_some()
}
pub fn client_ips(&self) -> Vec<ClientSimpleInfo> {
self.server_info_collection.client_ips()
}
pub fn get_peer_info(&self, ip: &Ipv4Addr) -> Option<PeerClientInfo> {
self.peer_map.get(ip)
}
pub fn set_config(&self, config: Box<Config>) {
*self.config.lock() = Some(config);
}
pub fn get_config(&self) -> Option<Box<Config>> {
self.config.lock().clone()
}
pub(crate) fn udp_stun(&self) -> Vec<String> {
self.config
.lock()
.as_ref()
.map(|v| v.udp_stun.clone())
.unwrap_or_default()
}
pub(crate) fn tcp_stun(&self) -> Vec<String> {
self.config
.lock()
.as_ref()
.map(|v| v.tcp_stun.clone())
.unwrap_or_default()
}
}
impl AppState {
pub fn get_punch_info(&self) -> Option<PunchInfo> {
self.nat_info.get().map(|info| PunchInfo {
nat_info: self.filter_ip(info),
})
}
pub fn get_nat_info(&self) -> Option<NatInfo> {
self.nat_info.get().map(|info| self.filter_ip(info))
}
pub fn filter_ip(&self, mut info: NatInfo) -> NatInfo {
if self.network_contains(&info.local_ipv4) {
info.local_ipv4 = Ipv4Addr::UNSPECIFIED;
}
info.local_ipv4s.retain(|ip| !self.network_contains(ip));
info
}
}
+147
View File
@@ -0,0 +1,147 @@
use parking_lot::RwLock;
use rust_p2p_core::nat::NatInfo;
use rust_p2p_core::route::Index;
use rust_p2p_core::tunnel::udp::UDPIndex;
use std::collections::HashMap;
use std::net::{Ipv4Addr, SocketAddr};
use std::sync::Arc;
#[derive(Clone, Default)]
pub struct MyNatInfo {
nat_info: Arc<RwLock<Option<NatInfo>>>,
}
impl MyNatInfo {
pub fn get(&self) -> Option<NatInfo> {
self.nat_info.read().clone()
}
pub fn update_public_addr(&self, index: Index, addr: SocketAddr) {
let (ip, port) = if let Some(r) = mapping_addr(addr) {
r
} else {
return;
};
log::debug!("public_addr:{},{},index={index:?}", ip, port);
let mut nat_info = self.nat_info.write();
let Some(nat_info) = nat_info.as_mut() else {
return;
};
if rust_p2p_core::extend::addr::is_ipv4_global(&ip) {
if !nat_info.public_ips.contains(&ip) {
nat_info.public_ips.push(ip);
}
match index {
Index::Udp(index) => {
let index = match index {
UDPIndex::MainV4(index) => index,
UDPIndex::MainV6(index) => index,
UDPIndex::SubV4(_) => return,
};
if let Some(p) = nat_info.public_udp_ports.get_mut(index) {
*p = port;
}
}
Index::Tcp(_) => {
nat_info.public_tcp_port = port;
}
_ => {}
}
} else {
log::debug!("not public addr: {addr:?}")
}
}
pub fn update_tcp_public_addr(&self, addr: SocketAddr) {
let SocketAddr::V4(addr) = addr else {
return;
};
let ip = *addr.ip();
let port = addr.port();
log::info!("tcp_public_addr, {}:{}", ip, port);
let mut nat_info = self.nat_info.write();
let Some(nat_info) = nat_info.as_mut() else {
return;
};
if ip.is_unspecified() && port == 0 {
nat_info.public_tcp_port = 0;
return;
}
if rust_p2p_core::extend::addr::is_ipv4_global(&ip) {
if !nat_info.public_ips.contains(&ip) {
nat_info.public_ips.push(ip);
}
nat_info.public_tcp_port = port;
} else {
log::debug!("not public addr: {addr:?}")
}
}
pub fn replace_nat_info(&self, nat_info: NatInfo) {
self.nat_info.write().replace(nat_info);
}
pub fn clear(&self) {
*self.nat_info.write() = None;
}
}
fn mapping_addr(addr: SocketAddr) -> Option<(Ipv4Addr, u16)> {
match addr {
SocketAddr::V4(addr) => Some((*addr.ip(), addr.port())),
SocketAddr::V6(addr) => addr.ip().to_ipv4_mapped().map(|ip| (ip, addr.port())),
}
}
#[derive(Copy, Clone, Debug)]
pub struct PunchState {
pub count: i64,
pub last_ts: i64,
}
#[derive(Clone, Default)]
pub struct PunchBackoff {
inner: Arc<RwLock<HashMap<Ipv4Addr, PunchState>>>,
}
impl PunchBackoff {
const MAX_BACKOFF_MS: i64 = 3_600_000; // 1h
const BASE_MS: i64 = 3000;
fn now() -> i64 {
crate::utils::time::now_ts_ms()
}
pub fn record(&self, ip: Ipv4Addr) {
let mut map = self.inner.write();
let entry = map.entry(ip).or_insert(PunchState {
count: 0,
last_ts: 0,
});
entry.count += 1;
entry.last_ts = Self::now();
}
#[allow(dead_code)]
pub fn reset(&self, ip: Ipv4Addr) {
self.inner.write().remove(&ip);
}
#[allow(dead_code)]
pub fn get(&self, ip: &Ipv4Addr) -> Option<PunchState> {
self.inner.read().get(ip).copied()
}
pub fn should_punch(&self, ip: Ipv4Addr) -> bool {
let map = self.inner.read();
let Some(state) = map.get(&ip) else {
return true;
};
let now = Self::now();
let elapsed = now - state.last_ts;
let mut backoff = Self::BASE_MS * state.count;
if backoff > Self::MAX_BACKOFF_MS {
backoff = Self::MAX_BACKOFF_MS;
}
elapsed >= backoff
}
#[allow(dead_code)]
pub fn clear(&self) {
self.inner.write().clear();
}
}
+353
View File
@@ -0,0 +1,353 @@
use crate::api::VntApi;
use crate::compression::PacketCompression;
use crate::context::config::Config;
use crate::context::{AppState, NetworkAddr, NetworkRoute};
use crate::crypto::PacketCrypto;
use crate::enhanced_tunnel::enhanced_ipv4_tunnel;
use crate::enhanced_tunnel::inbound::EnhancedInbound;
use crate::enhanced_tunnel::outbound::EnhancedOutbound;
use crate::fec::{FecDecoder, FecEncoder};
use crate::nat::internal_nat::{InternalNatInbound, PortMappingManager};
use crate::nat::{AllowSubnetExternalRoute, SubnetExternalRoute};
use crate::tun::enhanced_tun::EnhancedTunInbound;
use crate::tun::{DeviceConfig, DeviceIOManager, TunDataInbound, TunReceiver, tun_channel};
use crate::tunnel_core::outbound::{BasicOutbound, HybridOutbound};
use crate::tunnel_core::p2p::inbound::{P2pInboundConfig, P2pInboundHandler};
use crate::tunnel_core::p2p::transport::punch::NatPuncher;
use crate::tunnel_core::p2p::transport::task::init_tunnel;
use crate::tunnel_core::server::connection_manager::{
InboundHandlerConfig, ServerTurnManager, coordinated_registration, create_server_tunnel,
};
use crate::tunnel_core::server::rpc::ServerRPC;
use crate::utils::task_control::TaskGroup;
use anyhow::bail;
use ipnet::Ipv4Net;
use std::net::Ipv4Addr;
pub const DEFAULT_MTU: u16 = 1380;
/// Context for deferred registration
struct RegistrationContext {
server_managers: Vec<ServerTurnManager>,
subnet_external_route: SubnetExternalRoute,
puncher: NatPuncher,
packet_crypto: PacketCrypto,
packet_compression: PacketCompression,
enhanced_inbound: EnhancedInbound,
fec_decoder: FecDecoder,
}
pub struct NetworkManager {
config: Box<Config>,
app_state: AppState,
task_group: TaskGroup,
device_io_manager: DeviceIOManager,
enhanced_outbound: Option<EnhancedOutbound>,
server_rpc: ServerRPC,
tun_receiver: Option<TunReceiver>,
registration_context: Option<Box<RegistrationContext>>,
}
impl NetworkManager {
pub async fn create_network(
config: Box<Config>,
task_group: TaskGroup,
) -> anyhow::Result<NetworkManager> {
let app_state = AppState::default();
config.check()?;
let mtu = config.mtu.unwrap_or(DEFAULT_MTU);
let packet_crypto = PacketCrypto::new_from_str(config.password.as_deref());
let packet_compression = PacketCompression::new(config.compress);
let (server_manager_list, tunnel_to_server, server_rpc) =
create_server_tunnel(app_state.clone(), &config, packet_crypto.clone());
let device_io_manager = DeviceIOManager::new(task_group.clone());
let allow_subnet = AllowSubnetExternalRoute::new(config.output.clone());
let (puncher, p2p_socket, p2p_task) = if !config.no_punch {
let (puncher, p2p_socket_manager, p2p_task) = init_tunnel(
task_group.clone(),
app_state.clone(),
tunnel_to_server.clone(),
packet_crypto.clone(),
)
.await?;
(Some(puncher), Some(p2p_socket_manager), Some(p2p_task))
} else {
(None, None, None)
};
let puncher = NatPuncher::new(
app_state.network.clone(),
app_state.punch_backoff.clone(),
puncher,
packet_crypto.clone(),
);
let subnet_external_route = app_state.subnet_route.clone();
subnet_external_route.set_route_table(config.input.clone());
let fec_decoder = FecDecoder::new();
let basic_outbound = BasicOutbound::new(
tunnel_to_server.clone(),
p2p_socket.clone(),
packet_crypto.clone(),
);
let fec_encoder = if config.fec {
Some(FecEncoder::new(&task_group, basic_outbound.clone()))
} else {
None
};
let hybrid_outbound = HybridOutbound::new(
app_state.network.clone(),
app_state.server_info_collection.clone(),
app_state.traffic_stats.clone(),
basic_outbound,
packet_compression.clone(),
subnet_external_route.clone(),
fec_encoder,
);
let port_mapping_manager = PortMappingManager::new(
config.no_tun,
config.allow_port_mapping,
app_state.network.clone(),
);
let internal_nat_inbound = if config.no_nat && !config.no_tun {
None
} else {
let nat_inbound = InternalNatInbound::create(
&task_group,
mtu,
hybrid_outbound.clone(),
allow_subnet.clone(),
app_state.network.clone(),
config.no_tun,
)
.await?;
Some(nat_inbound)
};
let (enhanced_tun_inbound, tun_receiver) = if config.no_tun {
(
EnhancedTunInbound::Nat(
internal_nat_inbound
.clone()
.expect("internal_nat_inbound must be Some when no_tun is true"),
),
None,
)
} else {
let (tun_inbound, tun_receiver) = tun_channel();
let tun_data_sender = TunDataInbound::new(tun_inbound, allow_subnet.clone());
(EnhancedTunInbound::Tun(tun_data_sender), Some(tun_receiver))
};
let (enhanced_inbound, enhanced_outbound) = enhanced_ipv4_tunnel(
app_state.clone(),
task_group.clone(),
enhanced_tun_inbound,
crate::enhanced_tunnel::TunnelConfig {
mtu,
password: config.password.clone(),
open_quic_client: config.rtx,
port_mapping: config.port_mapping.clone(),
},
crate::enhanced_tunnel::TunnelComponents {
hybrid_outbound: hybrid_outbound.clone(),
external_route: subnet_external_route.clone(),
internal_nat_inbound,
port_mapping_manager,
},
)
.await?;
if let Some(p2p_task) = p2p_task {
let handler = P2pInboundHandler::new(P2pInboundConfig {
network_route: NetworkRoute::new(
app_state.network.clone(),
subnet_external_route.clone(),
),
route_table: app_state.route_table.clone(),
packet_loss_stats: app_state.packet_loss_stats.clone(),
packet_crypto: packet_crypto.clone(),
packet_compression: packet_compression.clone(),
enhanced_inbound: enhanced_inbound.clone(),
fec_decoder: fec_decoder.clone(),
});
p2p_task.start(handler);
}
let registration_context = Box::new(RegistrationContext {
server_managers: server_manager_list,
subnet_external_route,
puncher,
packet_crypto,
packet_compression,
enhanced_inbound,
fec_decoder,
});
app_state.set_config(config.clone());
Ok(Self {
config,
app_state,
task_group,
device_io_manager,
enhanced_outbound,
server_rpc,
tun_receiver,
registration_context: Some(registration_context),
})
}
/// Register with server(s) and start data handling tasks.
/// This method can only be called once.
/// Returns the registration response on success.
pub async fn register(&mut self) -> anyhow::Result<NetworkAddr> {
let Some(mut ctx) = self.registration_context.take() else {
bail!("register can only be called once");
};
let is_multi_server = ctx.server_managers.len() > 1;
let reg_response = if is_multi_server {
// Multi-server: coordinated pre-registration
log::info!(
"Multi-server mode: performing coordinated registration for {} servers",
ctx.server_managers.len()
);
let reg_response = coordinated_registration(&mut ctx.server_managers).await?;
log::info!(
"Coordinated registration completed, IP: {}, prefix_len: {}",
reg_response.ip,
reg_response.prefix_len
);
reg_response
} else {
// Single-server: normal registration
log::info!("Single-server mode: performing normal registration");
let response = ctx.server_managers[0]
.connect_and_reg(crate::protocol::control_message::RegistrationMode::Normal)
.await?;
match response {
crate::protocol::control_message::ResponseMessage::Reg(reg) => {
log::info!(
"Registration completed, IP: {}, prefix_len: {}",
reg.ip,
reg.prefix_len
);
reg
}
crate::protocol::control_message::ResponseMessage::Error(e) => {
bail!("Registration failed: {}", e.message);
}
crate::protocol::control_message::ResponseMessage::ConfirmReg(_) => {
bail!("Unexpected ConfirmReg response");
}
}
};
let network_addr = NetworkAddr {
gateway: reg_response.gateway,
broadcast: Ipv4Net::new(reg_response.ip, reg_response.prefix_len)?.broadcast(),
ip: reg_response.ip,
prefix_len: reg_response.prefix_len,
};
self.app_state.network.set(network_addr);
// 保存服务器版本信息
if !reg_response.server_version.is_empty() {
for (index, _) in ctx.server_managers.iter().enumerate() {
self.app_state.server_info_collection.set_server_version(
index as u32,
reg_response.server_version.clone(),
);
}
}
// Start data handling tasks for all servers
for turn_manager in ctx.server_managers {
let handler_config = Box::new(InboundHandlerConfig {
network_route: NetworkRoute::new(
self.app_state.network.clone(),
ctx.subnet_external_route.clone(),
),
server_info: self.app_state.server_info_collection.clone(),
nat_info: self.app_state.nat_info.clone(),
peer_map: self.app_state.peer_map.clone(),
punch_backoff: self.app_state.punch_backoff.clone(),
puncher: ctx.puncher.clone(),
packet_crypto: ctx.packet_crypto.clone(),
packet_compression: ctx.packet_compression.clone(),
enhanced_inbound: ctx.enhanced_inbound.clone(),
fec_decoder: ctx.fec_decoder.clone(),
});
turn_manager.data_handle_task_connected(&self.task_group, handler_config, network_addr);
}
Ok(network_addr)
}
pub fn is_no_tun(&self) -> bool {
self.config.no_tun
}
pub async fn start_tun(&mut self) -> anyhow::Result<()> {
let Some(receiver) = self.tun_receiver.take() else {
bail!("start_tun can only be called once");
};
let Some(enhanced_outbound) = self.enhanced_outbound.take() else {
bail!("start_tun can only be called once");
};
let mut config = DeviceConfig::default();
config = config.set_mtu(self.config.mtu.unwrap_or(DEFAULT_MTU));
if let Some(tun_name) = self.config.tun_name.clone() {
config = config.set_tun_name(tun_name);
}
self.device_io_manager
.start_task(config, receiver, enhanced_outbound)
.await
}
#[cfg(unix)]
pub async fn start_tun_fd(&mut self, tun_fd: Option<i32>) -> anyhow::Result<()> {
let Some(receiver) = self.tun_receiver.take() else {
bail!("start_tun_fd can only be called once");
};
let Some(enhanced_outbound) = self.enhanced_outbound.take() else {
bail!("start_tun_fd can only be called once");
};
let mut config = DeviceConfig::default();
if let Some(tun_fd) = tun_fd {
config = config.set_tun_fd(tun_fd);
}
if let Some(tun_name) = self.config.tun_name.clone() {
config = config.set_tun_name(tun_name);
}
self.device_io_manager
.start_task(config, receiver, enhanced_outbound)
.await
}
#[cfg(not(target_os = "android"))]
pub async fn set_network_ip(&self, ip: Ipv4Addr, prefix_len: u8) -> anyhow::Result<()> {
self.device_io_manager.set_network(ip, prefix_len).await?;
Ok(())
}
fn stop_network(&mut self) {
self.task_group.stop();
self.app_state.stop_network();
}
#[cfg(not(target_os = "android"))]
pub async fn tun_if_index(&self) -> anyhow::Result<u32> {
self.device_io_manager.tun_if_index().await
}
pub async fn wait_all_stopped(&mut self) {
self.task_group.wait_all_stopped().await;
}
pub fn vnt_api(&self) -> VntApi {
VntApi::new(self.app_state.clone(), self.server_rpc.clone())
}
}
impl Drop for NetworkManager {
fn drop(&mut self) {
self.stop_network();
}
}
+169
View File
@@ -0,0 +1,169 @@
use crate::protocol::ip_packet_protocol::{HEAD_LENGTH, NetPacket};
use ring::aead::{Aad, CHACHA20_POLY1305, LessSafeKey, Nonce, UnboundKey};
use std::io;
pub const TAG_LEN: usize = 16;
#[derive(Clone)]
pub struct PacketCrypto {
key: LessSafeKey,
}
impl PacketCrypto {
pub fn key_sign(s: &str) -> String {
use ring::digest::{Context, SHA256};
const PREFIX: &[u8] = b"KEY-BEGIN";
const SUFFIX: &[u8] = b"KEY-END";
let mut ctx = Context::new(&SHA256);
ctx.update(PREFIX);
ctx.update(s.as_bytes());
ctx.update(SUFFIX);
let digest = ctx.finish();
let mut key_bytes = [0u8; 16];
key_bytes.copy_from_slice(&digest.as_ref()[..16]);
key_bytes
.iter()
.map(|b| format!("{:02x}", b))
.collect::<String>()
}
pub fn new(key_bytes: [u8; 32]) -> Self {
let unbound = UnboundKey::new(&CHACHA20_POLY1305, &key_bytes).unwrap();
let key = LessSafeKey::new(unbound);
Self { key }
}
pub fn new_from_str(s: &str) -> Self {
let hash = ring::digest::digest(&ring::digest::SHA256, s.as_bytes());
let mut key_bytes = [0u8; 32];
key_bytes.copy_from_slice(hash.as_ref());
Self::new(key_bytes)
}
/// 根据包头生成 12 字节 nonce
pub fn make_nonce<B: AsRef<[u8]>>(&self, pkt: &NetPacket<B>) -> io::Result<[u8; 12]> {
let buf = pkt.buffer();
if buf.len() < HEAD_LENGTH {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"buffer too small",
));
}
let msg_type = buf[0];
let seq = &buf[4..8];
let src = &buf[8..12];
let dst = &buf[12..16];
let mut nonce12 = [0u8; 12];
nonce12[0..4].copy_from_slice(seq);
nonce12[4..8].copy_from_slice(dst);
nonce12[8..12].copy_from_slice(src);
nonce12[0] = msg_type;
Ok(nonce12)
}
/// 原地加密(in-place
/// payload 后需要预留16字节用于存放 tag
pub fn encrypt_in_place<B: AsRef<[u8]> + AsMut<[u8]>>(
&self,
pkt: &mut NetPacket<B>,
) -> io::Result<()> {
let nonce = Nonce::assume_unique_for_key(self.make_nonce(pkt)?);
let payload = pkt.payload_mut();
let payload_len = payload.len() - TAG_LEN; // 实际 payload 长度(不含 tag 预留空间)
// 只加密实际的 payload 部分
let tag = self
.key
.seal_in_place_separate_tag(nonce, Aad::empty(), &mut payload[..payload_len])
.map_err(|_| io::Error::other("encrypt failed"))?;
// 将 tag 写入 payload 后的预留空间
payload[payload_len..payload_len + TAG_LEN].copy_from_slice(tag.as_ref());
Ok(())
}
/// 原地解密(in-place
pub fn decrypt_in_place<B: AsRef<[u8]> + AsMut<[u8]>>(
&self,
pkt: &mut NetPacket<B>,
) -> io::Result<usize> {
let nonce = Nonce::assume_unique_for_key(self.make_nonce(pkt)?);
let payload_with_tag = pkt.payload_mut();
let plaintext = self
.key
.open_in_place(nonce, Aad::empty(), payload_with_tag)
.map_err(|_| io::Error::other("decrypt failed"))?;
Ok(plaintext.len())
}
}
#[cfg(test)]
mod tests {
use super::*;
use bytes::BytesMut;
// 用于构造一个简单的 NetPacket,包含头 16 字节 + payload + 16 字节 TAG 预留
fn build_test_packet(payload_len: usize) -> NetPacket<BytesMut> {
// 16 字节 head + payload + 16 字节预留 TAG
let total_len = HEAD_LENGTH + payload_len + TAG_LEN;
let mut buf = BytesMut::zeroed(total_len);
// 构造一个头(16 字节)
buf[0] = 4; // MsgType::Ping
buf[4..8].copy_from_slice(&12345u32.to_be_bytes());
buf[8..12].copy_from_slice(&111u32.to_be_bytes());
buf[12..16].copy_from_slice(&222u32.to_be_bytes());
// 构造 payload(明文)
let payload_plain = &mut buf[HEAD_LENGTH..HEAD_LENGTH + payload_len];
for (i, p) in payload_plain.iter_mut().enumerate() {
*p = (i as u8) ^ 0xAB;
}
NetPacket::new(buf).unwrap()
}
#[test]
fn test_encrypt_decrypt_in_place() {
let key = [7u8; 32];
let crypto = PacketCrypto::new(key);
let payload_len = 20;
let mut pkt = build_test_packet(payload_len);
// 备份原 payload
let original_payload: Vec<u8> =
pkt.buffer()[HEAD_LENGTH..HEAD_LENGTH + payload_len].to_vec();
// 加密
crypto.encrypt_in_place(&mut pkt).expect("encrypt failed");
let encrypted_buf = pkt.buffer();
let tag_start = HEAD_LENGTH + payload_len;
let tag_end = tag_start + TAG_LEN;
// TAG 不应该是全 0
assert_ne!(&encrypted_buf[tag_start..tag_end], &[0u8; TAG_LEN]);
// payload 已被加密,不等于明文
assert_ne!(
&encrypted_buf[HEAD_LENGTH..HEAD_LENGTH + payload_len],
&original_payload[..]
);
// 解密
crypto.decrypt_in_place(&mut pkt).expect("decrypt failed");
let decrypted_buf = pkt.buffer();
let decrypted_payload = &decrypted_buf[HEAD_LENGTH..HEAD_LENGTH + payload_len];
// 解密后与原文一致
assert_eq!(decrypted_payload, &original_payload[..]);
}
}
+47
View File
@@ -0,0 +1,47 @@
use crate::crypto::chacha20_poly1305::TAG_LEN;
use crate::protocol::ip_packet_protocol::NetPacket;
use std::io;
use std::sync::Arc;
mod chacha20_poly1305;
use crate::protocol::transmission::{ExtendEnd, ShrinkEnd};
#[derive(Clone)]
pub(crate) struct PacketCrypto {
crypto: Option<Arc<chacha20_poly1305::PacketCrypto>>,
}
impl PacketCrypto {
pub(crate) fn key_sign(s: &str) -> String {
chacha20_poly1305::PacketCrypto::key_sign(s)
}
pub(crate) fn new_from_str(s: Option<&str>) -> Self {
Self {
crypto: s.map(chacha20_poly1305::PacketCrypto::new_from_str).map(Arc::new),
}
}
pub(crate) fn encrypt_reserve(&self) -> usize {
if self.crypto.is_some() { TAG_LEN } else { 0 }
}
pub(crate) fn encrypt_in_place<B: AsRef<[u8]> + AsMut<[u8]> + ExtendEnd>(
&self,
pkt: &mut NetPacket<B>,
) -> io::Result<()> {
if let Some(crypto) = self.crypto.as_ref() {
pkt.source_buf_mut().extend_end(TAG_LEN);
return crypto.encrypt_in_place(pkt);
}
Ok(())
}
pub(crate) fn decrypt_in_place<B: AsRef<[u8]> + AsMut<[u8]> + ShrinkEnd>(
&self,
pkt: &mut NetPacket<B>,
) -> io::Result<()> {
if let Some(crypto) = self.crypto.as_ref() {
let _ = crypto.decrypt_in_place(pkt)?;
pkt.source_buf_mut().shrink_end(TAG_LEN);
}
Ok(())
}
}
+72
View File
@@ -0,0 +1,72 @@
use crate::context::{NetworkAddr, TrafficStats};
use crate::enhanced_tunnel::quic_over::quic_inbound::EnhancedQuicInbound;
use crate::nat::internal_nat::InternalNatInbound;
use crate::protocol::ip_packet_protocol::{HEAD_LENGTH, MsgType, NetPacket};
use crate::protocol::transmission::TransmissionBytes;
use crate::tun::enhanced_tun::EnhancedTunInbound;
use anyhow::{Context, bail};
use pnet_packet::ipv4::Ipv4Packet;
use std::net::Ipv4Addr;
#[derive(Clone)]
pub(crate) struct EnhancedInbound {
tun_data_inbound: EnhancedTunInbound,
quic_inbound: EnhancedQuicInbound,
internal_nat_inbound: Option<InternalNatInbound>,
traffic_stats: TrafficStats,
}
impl EnhancedInbound {
pub fn new(
tun_data_inbound: EnhancedTunInbound,
quic_inbound: EnhancedQuicInbound,
internal_nat_inbound: Option<InternalNatInbound>,
traffic_stats: TrafficStats,
) -> Self {
Self {
tun_data_inbound,
quic_inbound,
internal_nat_inbound,
traffic_stats,
}
}
pub async fn inbound(
&self,
network_addr: &NetworkAddr,
msg_type: MsgType,
src: Ipv4Addr,
packet: NetPacket<TransmissionBytes>,
) -> anyhow::Result<()> {
let mut buf = packet.into_buffer();
self.traffic_stats.record_rx(src, buf.len() as u64);
buf.advance_head(HEAD_LENGTH)?;
match msg_type {
MsgType::Turn => {
if let Some(internal_nat_inbound) = self.internal_nat_inbound.as_ref() {
let Some(ipv4) = Ipv4Packet::new(&buf) else {
bail!("EnhancedInbound not ipv4")
};
let dest = ipv4.get_destination();
if dest != network_addr.ip && !network_addr.network().contains(&dest) {
internal_nat_inbound.send(&buf, network_addr).await?;
return Ok(());
}
}
self.tun_data_inbound.inbound(buf, network_addr).await?;
}
MsgType::Broadcast | MsgType::ExcludeBroadcast => {
self.tun_data_inbound.inbound(buf, network_addr).await?;
}
MsgType::Quic => {
let payload = buf.into_bytes().freeze();
self.quic_inbound
.inbound(payload, src)
.await
.context("inbound quic")?;
}
_ => {}
}
Ok(())
}
}
+75
View File
@@ -0,0 +1,75 @@
use crate::context::AppState;
use crate::enhanced_tunnel::inbound::EnhancedInbound;
use crate::enhanced_tunnel::outbound::EnhancedOutbound;
use crate::nat::SubnetExternalRoute;
use crate::nat::internal_nat::{InternalNatInbound, PortMappingManager};
use crate::port_mapping::PortMapping;
use crate::tun::enhanced_tun::EnhancedTunInbound;
use crate::tunnel_core::outbound::HybridOutbound;
use crate::utils::task_control::TaskGroup;
pub(crate) mod quic_over;
pub(crate) mod inbound;
pub(crate) mod outbound;
pub(crate) struct TunnelConfig {
pub mtu: u16,
pub password: Option<String>,
pub open_quic_client: bool,
pub port_mapping: Vec<PortMapping>,
}
pub(crate) struct TunnelComponents {
pub hybrid_outbound: HybridOutbound,
pub external_route: SubnetExternalRoute,
pub internal_nat_inbound: Option<InternalNatInbound>,
pub port_mapping_manager: PortMappingManager,
}
pub(crate) async fn enhanced_ipv4_tunnel(
app_state: AppState,
task_group: TaskGroup,
tun_data_sender: EnhancedTunInbound,
config: TunnelConfig,
components: TunnelComponents,
) -> anyhow::Result<(EnhancedInbound, Option<EnhancedOutbound>)> {
let password = config.password.unwrap_or_else(|| "password".to_string());
let tun = match &tun_data_sender {
EnhancedTunInbound::Tun(tun) => Some(tun.clone()),
EnhancedTunInbound::Nat(_) => None,
};
let (inbound, outbound) = quic_over::boot::quic_tunnel_start(
app_state.clone(),
task_group,
tun,
quic_over::boot::QuicTunnelConfig {
mtu: config.mtu,
password,
open_quic_client: config.open_quic_client,
port_mapping: config.port_mapping,
},
quic_over::boot::QuicTunnelComponents {
hybrid_outbound: components.hybrid_outbound.clone(),
external_route: components.external_route,
internal_nat_manager: components.internal_nat_inbound.clone(),
port_mapping_manager: components.port_mapping_manager,
},
)
.await?;
let enhanced_inbound = EnhancedInbound::new(
tun_data_sender,
inbound,
components.internal_nat_inbound,
app_state.traffic_stats.clone(),
);
let enhanced_outbound = outbound.map(|outbound| {
EnhancedOutbound::new(
app_state.network.clone(),
outbound,
components.hybrid_outbound,
)
});
Ok((enhanced_inbound, enhanced_outbound))
}
+68
View File
@@ -0,0 +1,68 @@
use crate::context::SharedNetworkAddr;
use crate::enhanced_tunnel::quic_over::quic_outbound::EnhancedQuicOutbound;
use crate::protocol::transmission::TransmissionBytes;
use crate::tunnel_core::outbound::HybridOutbound;
use pnet_packet::ipv4::Ipv4Packet;
pub struct EnhancedOutbound {
network: SharedNetworkAddr,
enhanced_quic_outbound: EnhancedQuicOutbound,
hybrid_outbound: HybridOutbound,
}
impl EnhancedOutbound {
pub fn new(
network: SharedNetworkAddr,
enhanced_quic_outbound: EnhancedQuicOutbound,
hybrid_outbound: HybridOutbound,
) -> Self {
Self {
network,
enhanced_quic_outbound,
hybrid_outbound,
}
}
pub async fn ipv4_outbound(&self, data: TransmissionBytes) {
if data.is_empty() || data[0] >> 4 != 4 {
return;
}
if let Err(e) = self.ipv4_outbound_impl(data).await {
log::warn!("EnhancedOutbound error: {:?}", e);
}
}
async fn ipv4_outbound_impl(&self, data: TransmissionBytes) -> anyhow::Result<()> {
let Some(ipv4) = Ipv4Packet::new(data.as_ref()) else {
return Ok(());
};
let Some(net) = self.network.get() else {
return Ok(());
};
let src = ipv4.get_source();
let dest = ipv4.get_destination();
if dest == src || dest.is_unspecified() {
return Ok(());
}
if dest == net.gateway {
// 发送到网关
return self.hybrid_outbound.ipv4_gateway_outbound(net, data).await;
}
if dest.is_multicast() || dest == net.broadcast || dest.is_broadcast() {
// 广播
return self
.hybrid_outbound
.ipv4_broadcast_outbound(net, data)
.await;
}
if self
.enhanced_quic_outbound
.outbound(&net, data.as_ref())
.await
{
// 使用quic 通道传输
return Ok(());
}
// 使用通用通道传输
self.hybrid_outbound.ipv4_outbound(net, data).await
}
}
@@ -0,0 +1,200 @@
use crate::context::AppState;
use crate::enhanced_tunnel::quic_over::enhanced_io::enhanced_inbound::{
QuicDataInbound, create_enhanced_inbound,
};
use crate::enhanced_tunnel::quic_over::enhanced_io::enhanced_outbound::create_enhanced_outbound;
use crate::enhanced_tunnel::quic_over::enhanced_io::socket::ExtendedQuicSocket;
use crate::enhanced_tunnel::quic_over::quic_client::QuicTunnelClient;
use crate::enhanced_tunnel::quic_over::quic_inbound::EnhancedQuicInbound;
use crate::enhanced_tunnel::quic_over::quic_outbound::EnhancedQuicOutbound;
use crate::enhanced_tunnel::quic_over::{quic_client, quic_server};
use crate::nat::SubnetExternalRoute;
use crate::nat::internal_nat::{InternalNatInbound, PortMappingManager};
use crate::port_mapping::PortMapping;
use crate::tls;
use crate::tun::TunDataInbound;
use crate::tunnel_core::outbound::HybridOutbound;
use crate::utils::task_control::TaskGroup;
use anyhow::Context;
use quinn::congestion::BbrConfig;
use quinn::crypto::rustls::QuicServerConfig;
use quinn::{ClientConfig, Endpoint, EndpointConfig, TransportConfig, default_runtime};
use rustls::ServerConfig;
use sha2::{Digest, Sha256};
use std::io;
use std::sync::Arc;
use std::time::Duration;
use tcp_ip::{IpStackConfig, IpStackRecv};
pub(crate) struct QuicTunnelConfig {
pub mtu: u16,
pub password: String,
pub open_quic_client: bool,
pub port_mapping: Vec<PortMapping>,
}
pub(crate) struct QuicTunnelComponents {
pub hybrid_outbound: HybridOutbound,
pub external_route: SubnetExternalRoute,
pub internal_nat_manager: Option<InternalNatInbound>,
pub port_mapping_manager: PortMappingManager,
}
pub(crate) async fn quic_tunnel_start(
app_state: AppState,
task_group: TaskGroup,
tun_data_sender: Option<TunDataInbound>,
config: QuicTunnelConfig,
components: QuicTunnelComponents,
) -> anyhow::Result<(EnhancedQuicInbound, Option<EnhancedQuicOutbound>)> {
let ip_stack_config = IpStackConfig {
mtu: config.mtu,
..Default::default()
};
let (ip_stack, ip_socket, quic_outbound) = if let Some(tun_data_sender) = tun_data_sender {
let (ip_stack, ip_stack_send, ip_stack_recv) = tcp_ip::ip_stack(ip_stack_config)?;
let ip_socket = tcp_ip::ip::IpSocket::bind_all(None, ip_stack.clone()).await?;
let ip_socket = Arc::new(ip_socket);
task_group.spawn(ip_stack_recv_task(
ip_stack_recv,
app_state.clone(),
tun_data_sender,
));
let quic_outbound =
EnhancedQuicOutbound::new(config.open_quic_client, ip_stack_send, ip_stack.clone());
(Some(ip_stack), Some(ip_socket), Some(quic_outbound))
} else {
(None, None, None)
};
let (inbound, endpoint) = create_quic_endpoint(
config.password,
task_group.clone(),
components.hybrid_outbound,
)
.await?;
quic_server::server_listen(
&task_group,
endpoint.clone(),
ip_socket.clone(),
ip_stack.clone(),
components.internal_nat_manager,
components.port_mapping_manager,
)
.await;
if config.open_quic_client {
let quic_client =
QuicTunnelClient::new(app_state.clone(), endpoint, components.external_route);
// 客户端使用指纹验证
if let (Some(ip_stack), Some(ip_socket)) = (ip_stack, ip_socket) {
quic_client::create_client(
quic_client.clone(),
task_group.clone(),
ip_stack.clone(),
ip_socket,
)
.await;
}
if !config.port_mapping.is_empty() {
crate::port_mapping::port_mapping_start(&task_group, config.port_mapping, quic_client)
.await?;
}
} else if !config.port_mapping.is_empty() {
let quic_client =
QuicTunnelClient::new(app_state.clone(), endpoint, components.external_route);
crate::port_mapping::port_mapping_start(&task_group, config.port_mapping, quic_client)
.await?;
}
let quic_inbound = EnhancedQuicInbound::new(inbound);
Ok((quic_inbound, quic_outbound))
}
async fn create_quic_endpoint(
password: String,
task_group: TaskGroup,
hybrid_outbound: HybridOutbound,
) -> anyhow::Result<(QuicDataInbound, Endpoint)> {
let (cert, private_key) = crate::tls::cert::generate_deterministic_cert(&password)?;
let mut hasher = Sha256::new();
hasher.update(cert.as_ref());
let calculated_hash: [u8; 32] = hasher.finalize().into();
log::info!("QUIC Cert Fingerprint: {}", hex::encode(calculated_hash));
let outbound = create_enhanced_outbound(task_group.clone(), hybrid_outbound).await;
let (inbound, inbound_receiver) = create_enhanced_inbound();
let socket = ExtendedQuicSocket::new(inbound_receiver, outbound);
let server_config = ServerConfig::builder()
.with_no_client_auth()
.with_single_cert(vec![cert], private_key)
.context("TLS config error")?;
let server_crypto = QuicServerConfig::try_from(server_config)
.map_err(|e| anyhow::anyhow!("QUIC TLS config error: {:?}", e))?;
let server_config = quinn::ServerConfig::with_crypto(Arc::new(server_crypto));
// 替换运行时
let runtime = default_runtime().ok_or_else(|| io::Error::other("no async runtime found"))?;
let fingerprint_verifier = tls::verifier::FingerprintVerifier::new(calculated_hash);
let client_config = rustls::ClientConfig::builder()
.dangerous()
.with_custom_certificate_verifier(Arc::new(fingerprint_verifier))
.with_no_client_auth();
let mut client_config = ClientConfig::new(Arc::new(
quinn::crypto::rustls::QuicClientConfig::try_from(client_config)
.context("Failed to create QUIC client config")?,
));
client_config.transport_config(build_transport_config());
let mut endpoint_config = EndpointConfig::default();
endpoint_config.max_udp_payload_size(1300)?;
let mut endpoint = quinn::Endpoint::new_with_abstract_socket(
endpoint_config,
Some(server_config),
Arc::new(socket),
runtime,
)
.context("quic server create failed")?;
endpoint.set_default_client_config(client_config);
Ok((inbound, endpoint))
}
fn build_transport_config() -> Arc<TransportConfig> {
let mut transport = TransportConfig::default();
transport.congestion_controller_factory(Arc::new(BbrConfig::default()));
transport.keep_alive_interval(Some(Duration::from_secs(5)));
transport.max_idle_timeout(Some(Duration::from_secs(10).try_into().unwrap()));
Arc::new(transport)
}
async fn ip_stack_recv_task(
mut ip_stack_recv: IpStackRecv,
app_state: AppState,
tun_data_sender: TunDataInbound,
) {
let mut buf = vec![0u8; 1500];
loop {
let len = match ip_stack_recv.recv(&mut buf).await {
Ok(len) => len,
Err(e) => {
log::error!("IP stack recv error: {:?}", e);
break;
}
};
let Some(net) = app_state.get_network() else {
log::error!("not network");
break;
};
match tun_data_sender.send((&buf[..len]).into(), &net).await {
Ok(_) => {}
Err(e) => {
log::error!("IP stack send error: {:?}", e);
break;
}
}
}
}
@@ -0,0 +1,93 @@
use anyhow::anyhow;
use bytes::Bytes;
use parking_lot::Mutex;
use quinn::udp::RecvMeta;
use std::fmt::{Debug, Formatter};
use std::io::IoSliceMut;
use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4};
use std::sync::Arc;
use std::task::{Context, Poll};
use tokio::sync::mpsc::{Receiver, Sender};
#[derive(Clone)]
pub struct QuicInnerInboundReceiver {
receiver: Arc<Mutex<Receiver<(Bytes, Ipv4Addr)>>>,
}
#[derive(Clone)]
pub struct QuicDataInbound {
sender: Sender<(Bytes, Ipv4Addr)>,
}
impl QuicDataInbound {
pub async fn send(&self, data: Bytes, addr: Ipv4Addr) -> anyhow::Result<()> {
self.sender
.send((data, addr))
.await
.map_err(|_e| anyhow!("quic data inbound error"))
}
}
impl Debug for QuicInnerInboundReceiver {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.debug_struct("EnhancedInbound").finish()
}
}
pub fn create_enhanced_inbound() -> (QuicDataInbound, QuicInnerInboundReceiver) {
let (sender, receiver) = tokio::sync::mpsc::channel(256);
(
QuicDataInbound { sender },
QuicInnerInboundReceiver::new(receiver),
)
}
impl QuicInnerInboundReceiver {
pub fn new(receiver: Receiver<(Bytes, Ipv4Addr)>) -> Self {
Self {
receiver: Arc::new(Mutex::new(receiver)),
}
}
pub fn poll_recv(
&self,
cx: &mut Context,
bufs: &mut [IoSliceMut<'_>],
meta: &mut [RecvMeta],
) -> Poll<std::io::Result<usize>> {
let mut guard = self.receiver.lock();
let rs = guard.poll_recv(cx);
drop(guard);
match rs {
Poll::Ready(Some((buf, ip))) => {
let (buf_mut, meta) = match (bufs.get_mut(0), meta.get_mut(0)) {
(Some(b), Some(m)) => (b, m),
_ => {
return Poll::Ready(Err(std::io::Error::new(
std::io::ErrorKind::InvalidInput,
"no buffer available",
)));
}
};
if buf_mut.len() < buf.len() {
return Poll::Ready(Err(std::io::Error::new(
std::io::ErrorKind::InvalidInput,
format!(
"buffer too small: need {}, got {}",
buf.len(),
buf_mut.len()
),
)));
}
buf_mut[..buf.len()].copy_from_slice(&buf);
meta.len = buf.len();
meta.stride = buf.len();
meta.addr = SocketAddr::V4(SocketAddrV4::new(ip, 10000));
Poll::Ready(Ok(1))
}
Poll::Ready(None) => Poll::Ready(Err(std::io::Error::new(
std::io::ErrorKind::BrokenPipe,
"inbound channel closed",
))),
Poll::Pending => Poll::Pending,
}
}
}
@@ -0,0 +1,89 @@
use crate::protocol::ip_packet_protocol::{HEAD_LENGTH, MsgType, NetPacket};
use crate::protocol::transmission::TransmissionBytes;
use crate::tunnel_core::outbound::HybridOutbound;
use crate::utils::task_control::TaskGroup;
use quinn::UdpPoller;
use std::fmt::{Debug, Formatter};
use std::io;
use std::net::Ipv4Addr;
use std::pin::Pin;
use std::task::{Context, Poll};
use tokio::sync::mpsc::{Sender, error::TrySendError};
use tokio_util::sync::PollSender;
#[derive(Clone)]
pub struct QuicInnerOutbound {
sender: Sender<(Ipv4Addr, NetPacket<TransmissionBytes>)>,
}
pub async fn create_enhanced_outbound(
task_group: TaskGroup,
hybrid_outbound: HybridOutbound,
) -> QuicInnerOutbound {
let (s, mut r) = tokio::sync::mpsc::channel(256);
task_group.spawn(async move {
while let Some((dst, packet)) = r.recv().await {
if let Err(e) = hybrid_outbound.outbound_raw(dst, packet).await {
log::debug!("outbound error: {e:?}, dst={dst}");
}
}
});
QuicInnerOutbound { sender: s }
}
impl QuicInnerOutbound {
pub fn try_outbound(&self, buf: &[u8], dest: Ipv4Addr) -> io::Result<()> {
let send = match self.sender.try_reserve() {
Ok(send) => send,
Err(TrySendError::Full(_)) => {
return Err(io::Error::new(
io::ErrorKind::WouldBlock,
"outbound channel full",
));
}
Err(TrySendError::Closed(_)) => {
return Err(io::Error::new(
io::ErrorKind::BrokenPipe,
"outbound channel closed",
));
}
};
let mut packet = NetPacket::new(TransmissionBytes::zeroed(HEAD_LENGTH + buf.len()))?;
packet.set_ttl(5);
packet.set_msg_type(MsgType::Quic);
packet.set_dest_id(dest.into());
packet.set_payload(buf)?;
send.send((dest, packet));
Ok(())
}
pub fn create_io_poller(&self) -> Pin<Box<dyn UdpPoller>> {
Box::pin(EnhancedOutboundPoller {
sender: PollSender::new(self.sender.clone()),
})
}
}
pub struct EnhancedOutboundPoller {
sender: PollSender<(Ipv4Addr, NetPacket<TransmissionBytes>)>,
}
impl Debug for EnhancedOutboundPoller {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.debug_struct("EnhancedOutboundPoller").finish()
}
}
impl UdpPoller for EnhancedOutboundPoller {
fn poll_writable(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<io::Result<()>> {
match self.sender.poll_reserve(cx) {
Poll::Ready(Ok(_)) => {
self.sender.abort_send();
Poll::Ready(Ok(()))
}
Poll::Ready(Err(_e)) => Poll::Ready(Err(io::Error::new(
io::ErrorKind::BrokenPipe,
"outbound channel closed",
))),
Poll::Pending => Poll::Pending,
}
}
}
@@ -0,0 +1,3 @@
pub mod enhanced_inbound;
pub mod enhanced_outbound;
pub mod socket;
@@ -0,0 +1,55 @@
use crate::enhanced_tunnel::quic_over::enhanced_io::enhanced_inbound::QuicInnerInboundReceiver;
use crate::enhanced_tunnel::quic_over::enhanced_io::enhanced_outbound::QuicInnerOutbound;
use quinn::udp::{RecvMeta, Transmit};
use quinn::{AsyncUdpSocket, UdpPoller};
use std::fmt::{Debug, Formatter};
use std::io::IoSliceMut;
use std::net::{IpAddr, Ipv4Addr, SocketAddr, SocketAddrV4};
use std::pin::Pin;
use std::sync::Arc;
use std::task::{Context, Poll};
pub struct ExtendedQuicSocket {
inbound: QuicInnerInboundReceiver,
outbound: QuicInnerOutbound,
}
impl Debug for ExtendedQuicSocket {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.debug_struct("QuicSocket").finish()
}
}
impl ExtendedQuicSocket {
pub fn new(inbound: QuicInnerInboundReceiver, outbound: QuicInnerOutbound) -> Self {
Self { inbound, outbound }
}
}
impl AsyncUdpSocket for ExtendedQuicSocket {
fn create_io_poller(self: Arc<Self>) -> Pin<Box<dyn UdpPoller>> {
self.outbound.create_io_poller()
}
fn try_send(&self, transmit: &Transmit) -> std::io::Result<()> {
let IpAddr::V4(dest) = transmit.destination.ip() else {
return Ok(());
};
self.outbound.try_outbound(transmit.contents, dest)
}
fn poll_recv(
&self,
cx: &mut Context,
bufs: &mut [IoSliceMut<'_>],
meta: &mut [RecvMeta],
) -> Poll<std::io::Result<usize>> {
self.inbound.poll_recv(cx, bufs, meta)
}
fn local_addr(&self) -> std::io::Result<SocketAddr> {
Ok(SocketAddr::V4(SocketAddrV4::new(
Ipv4Addr::new(127, 0, 0, 1),
10000,
)))
}
}
@@ -0,0 +1,7 @@
mod enhanced_io;
pub(crate) mod quic_client;
pub(crate) mod quic_inbound;
pub(crate) mod quic_outbound;
mod quic_server;
pub(crate) mod boot;
@@ -0,0 +1,309 @@
use crate::context::AppState;
use crate::nat::SubnetExternalRoute;
use crate::protocol::client_message::{
IpProxyHandshake, QuicProxyHandshake, TcpProxyHandshake, quic_proxy_handshake,
};
use crate::utils::task_control::TaskGroup;
use anyhow::{Context, bail};
use bytes::Bytes;
use futures::SinkExt;
use parking_lot::Mutex;
use pnet_packet::ip::IpNextHeaderProtocol;
use prost::Message;
use quinn::{Connection, Endpoint, RecvStream, SendStream};
use std::collections::HashMap;
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
use std::sync::Arc;
use tcp_ip::IpStack;
use tcp_ip::ip::IpSocket;
use tcp_ip::tcp::TcpStream;
use tokio::io::AsyncWriteExt;
use tokio::sync::OnceCell;
use tokio::sync::mpsc::Sender;
use tokio::sync::mpsc::error::TrySendError;
use tokio_util::codec::{FramedWrite, LengthDelimitedCodec};
#[derive(Clone)]
pub struct QuicTunnelClient {
app_state: AppState,
endpoint: Endpoint,
connection_map: Arc<Mutex<HashMap<Ipv4Addr, Arc<OnceCell<Connection>>>>>,
external_route: SubnetExternalRoute,
}
impl QuicTunnelClient {
pub fn new(
app_state: AppState,
endpoint: Endpoint,
external_route: SubnetExternalRoute,
) -> QuicTunnelClient {
Self {
app_state,
endpoint,
connection_map: Arc::new(Default::default()),
external_route,
}
}
pub async fn open_bi(&self, mut dest: Ipv4Addr) -> anyhow::Result<(SendStream, RecvStream)> {
let Some(net) = self.app_state.get_network() else {
bail!("no network found");
};
if !net.network().contains(&dest) {
if let Some(v) = self.external_route.route(&dest) {
dest = v;
} else {
bail!("invalid route found:{dest}");
}
}
let mut count = 0;
loop {
count += 1;
let cell = self
.connection_map
.lock()
.entry(dest)
.or_insert_with(|| Arc::new(OnceCell::new()))
.clone();
let connection = cell
.get_or_try_init(|| async {
self.endpoint
.connect(SocketAddr::new(dest.into(), 10000), "localhost")?
.await
.context("connect failed")
})
.await?;
return match connection.open_bi().await {
Ok(rs) => Ok(rs),
Err(e) => {
self.connection_map.lock().remove(&dest);
if count == 1 {
continue;
}
Err(e.into())
}
};
}
}
pub async fn open_uni(&self, mut dest: Ipv4Addr) -> anyhow::Result<SendStream> {
let Some(net) = self.app_state.get_network() else {
bail!("no network found");
};
if !net.network().contains(&dest) {
if let Some(v) = self.external_route.route(&dest) {
dest = v;
} else {
bail!("invalid route found:{dest}");
}
}
let mut count = 0;
loop {
count += 1;
let cell = self
.connection_map
.lock()
.entry(dest)
.or_insert_with(|| Arc::new(OnceCell::new()))
.clone();
let connection = cell
.get_or_try_init(|| async {
self.endpoint
.connect(SocketAddr::new(dest.into(), 10000), "localhost")?
.await
.context("connect failed")
})
.await?;
return match connection.open_uni().await {
Ok(rs) => Ok(rs),
Err(e) => {
self.connection_map.lock().remove(&dest);
if count == 1 {
continue;
}
Err(e.into())
}
};
}
}
}
pub(crate) async fn send_handshake(
send_stream: &mut SendStream,
handshake: QuicProxyHandshake,
) -> anyhow::Result<()> {
let handshake = handshake.encode_to_vec();
send_stream.write_u16(handshake.len() as u16).await?;
send_stream.write_all(&handshake).await?;
Ok(())
}
pub async fn create_client(
quic_client: QuicTunnelClient,
task_group: TaskGroup,
ip_stack: IpStack,
ip_socket: Arc<IpSocket>,
) {
task_group.spawn(tcp_listen(
task_group.clone(),
ip_stack.clone(),
quic_client.clone(),
));
task_group.spawn(ip_listen(task_group.clone(), ip_socket, quic_client));
}
async fn tcp_listen(
task_group: TaskGroup,
ip_stack: IpStack,
quic_tunnel_client: QuicTunnelClient,
) {
if let Err(e) = tcp_listen_impl(task_group, ip_stack, quic_tunnel_client).await {
log::error!("tcp_listen {e:?}");
}
}
async fn tcp_listen_impl(
task_group: TaskGroup,
ip_stack: IpStack,
quic_tunnel_client: QuicTunnelClient,
) -> anyhow::Result<()> {
let mut listener = tcp_ip::tcp::TcpListener::bind_all(ip_stack).await?;
loop {
let (tcp_stream, addr) = listener.accept().await?;
let quic_tunnel_client = quic_tunnel_client.clone();
task_group.spawn(async move {
if let Err(e) = tcp_stream_handle(tcp_stream, quic_tunnel_client).await {
log::error!("TCP stream handle failed with error: {e:?},addr={addr}");
}
});
}
}
async fn tcp_stream_handle(
tcp_stream: TcpStream,
quic_tunnel_client: QuicTunnelClient,
) -> anyhow::Result<()> {
// 连接方向是反过来的,因为自己充当目标做了tcp卸载
let SocketAddr::V4(peer_addr) = tcp_stream.local_addr()? else {
bail!("invalid IP address");
};
let SocketAddr::V4(local_addr) = tcp_stream.peer_addr()? else {
bail!("invalid IP address");
};
log::debug!("connect TCP stream {}->{}", local_addr, peer_addr);
let (mut send_stream, mut recv_stream) = quic_tunnel_client.open_bi(*peer_addr.ip()).await?;
let handshake = QuicProxyHandshake {
handshake: Some(quic_proxy_handshake::Handshake::Tcp(TcpProxyHandshake {
src_ip: (*local_addr.ip()).into(),
src_port: local_addr.port().into(),
dst_ip: (*peer_addr.ip()).into(),
dst_port: peer_addr.port().into(),
})),
};
send_handshake(&mut send_stream, handshake).await?;
let (mut tcp_w, mut tcp_r) = tcp_stream.split()?;
tokio::select! {
_ = tokio::io::copy(&mut recv_stream, &mut tcp_w) => {},
_ = tokio::io::copy(&mut tcp_r, &mut send_stream) => {},
}
log::debug!("disconnect TCP stream {}->{}", local_addr, peer_addr);
Ok(())
}
async fn ip_listen(
task_group: TaskGroup,
ip_socket: Arc<IpSocket>,
quic_tunnel_client: QuicTunnelClient,
) {
if let Err(e) = ip_listen_impl(task_group, ip_socket, quic_tunnel_client).await {
log::error!("ip_listen {e:?}");
}
}
#[derive(Eq, PartialEq, Hash, Copy, Clone, Debug)]
struct IpKey {
protocol: IpNextHeaderProtocol,
src: Ipv4Addr,
dest: Ipv4Addr,
}
async fn ip_listen_impl(
task_group: TaskGroup,
ip_socket: Arc<IpSocket>,
quic_tunnel_client: QuicTunnelClient,
) -> anyhow::Result<()> {
let mut buf = vec![0u8; 65536];
let dest_map = Arc::new(Mutex::new(HashMap::<IpKey, Sender<Bytes>>::new()));
loop {
let (len, protocol, src, dest) = ip_socket.recv_protocol_from_to(&mut buf).await?;
let (IpAddr::V4(src), IpAddr::V4(dest)) = (src, dest) else {
continue;
};
let key = IpKey {
protocol,
src,
dest,
};
let bytes = Bytes::copy_from_slice(&buf[..len]);
let tx = {
let mut map = dest_map.lock();
if let Some(tx) = map.get(&key) {
tx.clone()
} else {
let (tx, rx) = tokio::sync::mpsc::channel::<Bytes>(128);
spawn_dest_sender(task_group.clone(), key, rx, quic_tunnel_client.clone());
map.insert(key, tx.clone());
tx
}
};
if let Err(err) = tx.try_send(bytes) {
match err {
TrySendError::Full(_) => {}
TrySendError::Closed(_) => {
let mut map = dest_map.lock();
map.remove(&key);
}
}
}
}
}
fn spawn_dest_sender(
task_group: TaskGroup,
key: IpKey,
mut rx: tokio::sync::mpsc::Receiver<Bytes>,
quic_tunnel_client: QuicTunnelClient,
) {
log::info!("send ip({}) packet {}->{}", key.protocol, key.src, key.dest);
task_group.spawn(async move {
let result = async {
let mut send_stream = quic_tunnel_client.open_uni(key.dest).await?;
let handshake = QuicProxyHandshake {
handshake: Some(quic_proxy_handshake::Handshake::Ip(IpProxyHandshake {
ip_next_header_protocol: key.protocol.0 as _,
src_ip: key.src.into(),
dst_ip: key.dest.into(),
})),
};
send_handshake(&mut send_stream, handshake).await?;
let mut framed = FramedWrite::new(send_stream, LengthDelimitedCodec::new());
while let Some(pkt) = rx.recv().await {
framed.send(pkt).await?;
}
Ok::<(), anyhow::Error>(())
}
.await;
if let Err(e) = result {
log::error!("key {:?} sender task exit: {:?}", key, e);
}
});
}
@@ -0,0 +1,18 @@
use crate::enhanced_tunnel::quic_over::enhanced_io::enhanced_inbound::QuicDataInbound;
use bytes::Bytes;
use std::net::Ipv4Addr;
#[derive(Clone)]
pub struct EnhancedQuicInbound {
quic_data_inbound: QuicDataInbound,
}
impl EnhancedQuicInbound {
pub fn new(quic_data_inbound: QuicDataInbound) -> Self {
Self { quic_data_inbound }
}
pub async fn inbound(&self, data: Bytes, src: Ipv4Addr) -> anyhow::Result<()> {
self.quic_data_inbound.send(data, src).await?;
Ok(())
}
}
@@ -0,0 +1,94 @@
use crate::context::NetworkAddr;
use pnet_packet::Packet;
use pnet_packet::ip::IpNextHeaderProtocols;
use pnet_packet::ipv4::{Ipv4Flags, Ipv4Packet};
use pnet_packet::tcp::TcpFlags::{ACK, SYN};
use pnet_packet::tcp::TcpPacket;
use std::net::SocketAddr;
use tcp_ip::{IpStack, IpStackSend};
pub struct EnhancedQuicOutbound {
open_quic_client: bool,
ip_stack_send: IpStackSend,
ip_stack: IpStack,
}
impl EnhancedQuicOutbound {
pub fn new(open_quic_client: bool, ip_stack_send: IpStackSend, ip_stack: IpStack) -> Self {
Self {
open_quic_client,
ip_stack_send,
ip_stack,
}
}
pub async fn outbound(&self, _net: &NetworkAddr, data: &[u8]) -> bool {
let Some(ipv4) = Ipv4Packet::new(data) else {
return true;
};
if self.open_quic_client {
// 针对tcp 如果不是从IpStack建立的连接,则不使用IpStack解析
if ipv4.get_next_level_protocol() == IpNextHeaderProtocols::Tcp {
let more_fragments =
ipv4.get_flags() & Ipv4Flags::MoreFragments == Ipv4Flags::MoreFragments;
let offset = ipv4.get_fragment_offset();
let segmented = more_fragments || offset > 0;
if !segmented {
let Some(tcp) = TcpPacket::new(ipv4.payload()) else {
return true;
};
// 不是第一个包
if !(tcp.get_flags() & SYN == SYN && tcp.get_flags() & ACK != ACK) {
let local_addr =
SocketAddr::new(ipv4.get_source().into(), tcp.get_source());
let peer_addr =
SocketAddr::new(ipv4.get_destination().into(), tcp.get_destination());
// 在IpStack中找不到连接
if !self
.ip_stack
.has_tcp_connection(local_addr, peer_addr)
.unwrap_or(false)
&& !self
.ip_stack
.has_tcp_connection(peer_addr, local_addr)
.unwrap_or(false)
&& !self
.ip_stack
.has_tcp_half_open(peer_addr, local_addr)
.unwrap_or(false)
{
return false;
}
}
}
}
_ = self.ip_stack_send.send_ip_packet(data).await;
return true;
}
// 判断tcp流
if ipv4.get_next_level_protocol() == IpNextHeaderProtocols::Tcp {
let more_fragments =
ipv4.get_flags() & Ipv4Flags::MoreFragments == Ipv4Flags::MoreFragments;
let offset = ipv4.get_fragment_offset();
let segmented = more_fragments || offset > 0;
if !segmented && let Some(tcp) = TcpPacket::new(ipv4.payload()) {
// 如果对端使用IpStack连接了自己,则也需要原路回复
// 这是连接回复,所以方向是和流方向相反的
let peer_addr = SocketAddr::new(ipv4.get_source().into(), tcp.get_source());
let local_addr =
SocketAddr::new(ipv4.get_destination().into(), tcp.get_destination());
if self
.ip_stack
.has_tcp_connection(local_addr, peer_addr)
.unwrap_or(false)
{
_ = self.ip_stack_send.send_ip_packet(data).await;
return true;
}
}
}
false
}
}
@@ -0,0 +1,245 @@
use crate::nat::internal_nat::{InternalNatInbound, PortMappingManager};
use crate::protocol::client_message::QuicProxyHandshake;
use crate::protocol::client_message::quic_proxy_handshake::Handshake;
use crate::utils::task_control::TaskGroup;
use anyhow::{Context, bail};
use futures::StreamExt;
use pnet_packet::ip::IpNextHeaderProtocol;
use prost::Message;
use quinn::{Connection, Endpoint, RecvStream, SendStream};
use std::net::{Ipv4Addr, SocketAddr};
use std::sync::Arc;
use tcp_ip::IpStack;
use tcp_ip::ip::IpSocket;
use tokio::io::AsyncReadExt;
use tokio_util::codec::{FramedRead, LengthDelimitedCodec};
pub async fn server_listen(
task_group: &TaskGroup,
endpoint: Endpoint,
ip_socket: Option<Arc<IpSocket>>,
ip_stack: Option<IpStack>,
internal_nat_manager: Option<InternalNatInbound>,
port_mapping_manager: PortMappingManager,
) {
task_group.spawn(quic_endpoint_accept(
ip_stack,
task_group.clone(),
endpoint,
ip_socket,
internal_nat_manager,
port_mapping_manager,
));
}
async fn quic_endpoint_accept(
ip_stack: Option<IpStack>,
task_group: TaskGroup,
endpoint: Endpoint,
ip_socket: Option<Arc<IpSocket>>,
internal_nat_manager: Option<InternalNatInbound>,
port_mapping_manager: PortMappingManager,
) {
while let Some(connecting) = endpoint.accept().await {
let remote_addr = connecting.remote_address();
let task_group_clone = task_group.clone();
let ip_socket = ip_socket.clone();
let ip_stack = ip_stack.clone();
let internal_nat_manager = internal_nat_manager.clone();
let port_mapping_manager = port_mapping_manager.clone();
task_group.spawn(async move {
match connecting.await {
Ok(connection) => {
log::info!("QUIC connection: {}", remote_addr);
if let Err(e) = quic_accept(
ip_stack,
task_group_clone,
connection,
ip_socket,
internal_nat_manager,
port_mapping_manager,
)
.await
{
log::info!("quic close: {remote_addr},{e:?}",);
}
}
Err(e) => {
log::error!("connect: {:?},remote_addr={remote_addr}", e);
}
}
});
}
log::warn!("quic server closed");
}
async fn quic_accept(
ip_stack: Option<IpStack>,
task_group_clone: TaskGroup,
connection: Connection,
ip_socket: Option<Arc<IpSocket>>,
internal_nat_manager: Option<InternalNatInbound>,
port_mapping_manager: PortMappingManager,
) -> anyhow::Result<()> {
loop {
tokio::select! {
rs = connection.accept_bi()=>{
let (send_stream, recv_stream) = rs?;
let ip_stack = ip_stack.clone();
let internal_nat_manager = internal_nat_manager.clone();
let port_mapping_manager = port_mapping_manager.clone();
task_group_clone.spawn(async move {
if let Err(e) = quic_stream_bi_handle(ip_stack,send_stream, recv_stream,&internal_nat_manager,port_mapping_manager).await{
log::error!("quic_stream_bi_handle: {e:?}");
}
});
}
rs = connection.accept_uni()=>{
let recv_stream = rs?;
let ip_socket = ip_socket.clone();
let internal_nat_manager = internal_nat_manager.clone();
task_group_clone.spawn(async move {
if let Err(e) = quic_stream_uni_handle(recv_stream, ip_socket,&internal_nat_manager).await{
log::error!("quic_stream_uni_handle: {e:?}");
}
});
}
}
}
}
async fn quic_stream_bi_handle(
ip_stack: Option<IpStack>,
mut send_stream: SendStream,
mut recv_stream: RecvStream,
internal_nat_manager: &Option<InternalNatInbound>,
port_mapping_manager: PortMappingManager,
) -> anyhow::Result<()> {
let handshake = recv_handshake(&mut recv_stream).await?;
let Some(handshake) = handshake.handshake else {
return Ok(());
};
match handshake {
Handshake::Tcp(handshake) => {
let src = SocketAddr::new(
Ipv4Addr::from(handshake.src_ip).into(),
handshake.src_port as _,
);
let dst_ip = Ipv4Addr::from(handshake.dst_ip);
let dst = SocketAddr::new(dst_ip.into(), handshake.dst_port as _);
if src == dst {
bail!("tcp handshake failed, ip: {}", src);
}
log::debug!("accept TCP stream {src}->{dst}");
// 如果不是网段内的,并且启用了内置nat,则直接转发
if let Some(internal_nat_manager) = internal_nat_manager {
if internal_nat_manager.use_nat(&dst_ip) {
internal_nat_manager
.tcp_nat(recv_stream, send_stream, dst_ip, dst.port())
.await?;
return Ok(());
}
if internal_nat_manager.no_tun() {
return Ok(());
}
}
if let Some(ip_stack) = ip_stack {
let stream = tcp_ip::tcp::TcpStream::bind(ip_stack, src)?
.connect_to(dst)
.await?;
let (mut tcp_w, mut tcp_r) = stream.split()?;
tokio::select! {
_ = tokio::io::copy(&mut recv_stream, &mut tcp_w) => {},
_ = tokio::io::copy(&mut tcp_r, &mut send_stream) => {},
}
log::debug!("accept close TCP stream {src}->{dst}");
}
}
Handshake::Ip(_) => {}
Handshake::TcpPortMapping(handshake) => {
port_mapping_manager
.tcp_mapping(
recv_stream,
send_stream,
handshake.dst_host,
handshake.dst_port as _,
)
.await?;
}
Handshake::UdpPortMapping(handshake) => {
port_mapping_manager
.udp_mapping(
recv_stream,
send_stream,
handshake.dst_host,
handshake.dst_port as _,
)
.await?;
}
}
Ok(())
}
async fn recv_handshake(recv_stream: &mut RecvStream) -> anyhow::Result<QuicProxyHandshake> {
let len = recv_stream.read_u16().await?;
let mut buf = vec![0u8; len as usize];
recv_stream.read_exact(&mut buf).await?;
let handshake = QuicProxyHandshake::decode(&buf[..])?;
Ok(handshake)
}
async fn quic_stream_uni_handle(
mut recv_stream: RecvStream,
ip_socket: Option<Arc<IpSocket>>,
internal_nat_manager: &Option<InternalNatInbound>,
) -> anyhow::Result<()> {
let handshake = recv_handshake(&mut recv_stream).await?;
let Some(handshake) = handshake.handshake else {
return Ok(());
};
match handshake {
Handshake::Tcp(_) => {}
Handshake::Ip(handshake) => {
let ip_next_header_protocol =
IpNextHeaderProtocol::new(handshake.ip_next_header_protocol as _);
let src_ip = Ipv4Addr::from(handshake.src_ip);
let dest_ip = Ipv4Addr::from(handshake.dst_ip);
log::debug!("recv IP({ip_next_header_protocol}) packet {src_ip}->{dest_ip}");
let mut framed_read = FramedRead::new(recv_stream, LengthDelimitedCodec::new());
// 如果不是网段内的,并且启用了内置nat,则直接转发
if let Some(internal_nat_manager) = internal_nat_manager {
if internal_nat_manager.use_nat(&dest_ip) {
loop {
let buf = framed_read
.next()
.await
.context("receive quic stream failed")??;
internal_nat_manager
.send_ipv4_payload(ip_next_header_protocol, src_ip, dest_ip, buf)
.await?;
}
}
if internal_nat_manager.no_tun() {
return Ok(());
}
}
let Some(ip_socket) = ip_socket else {
return Ok(());
};
let src_ip = src_ip.into();
let dest_ip = dest_ip.into();
loop {
let buf = framed_read
.next()
.await
.context("receive quic stream failed")??;
ip_socket
.send_protocol_from_to(&buf, ip_next_header_protocol, src_ip, dest_ip)
.await?;
}
}
Handshake::TcpPortMapping(_) => {}
Handshake::UdpPortMapping(_) => {}
}
Ok(())
}
+302
View File
@@ -0,0 +1,302 @@
use crate::fec::encoder::FecPacket;
use crate::protocol::ip_packet_protocol::{HEAD_LENGTH, NetPacket};
use crate::protocol::transmission::TransmissionBytes;
use anyhow::{Result, bail};
use prost::Message;
use reed_solomon_erasure::galois_8::ReedSolomon;
use std::collections::HashMap;
use std::net::Ipv4Addr;
use std::sync::Arc;
use std::time::{Duration, Instant};
const GROUP_TIMEOUT: Duration = Duration::from_secs(3);
const MAX_GROUPS: usize = 1000;
const MAX_NUM: usize = 50;
#[derive(Clone)]
pub struct FecDecoder {
inner: Arc<parking_lot::Mutex<FecDecoderInner>>,
}
struct FecDecoderInner {
groups: HashMap<(Ipv4Addr, u64), FecGroup>,
last_cleanup: Instant,
}
struct FecGroup {
data_shards: usize,
parity_shards: usize,
received_original_count: usize,
received_shards: Vec<Option<Vec<u8>>>,
last_update: Instant,
}
impl FecGroup {
fn is_done(&self) -> bool {
self.data_shards != 0 && self.received_original_count == self.data_shards
}
fn done(&mut self) {
self.received_original_count = self.data_shards;
self.received_shards = vec![];
}
}
impl Default for FecGroup {
fn default() -> Self {
Self {
data_shards: 0,
parity_shards: 0,
received_original_count: 0,
received_shards: Vec::with_capacity(16),
last_update: Instant::now(),
}
}
}
impl FecDecoder {
pub fn new() -> Self {
Self {
inner: Arc::new(parking_lot::Mutex::new(FecDecoderInner {
groups: HashMap::new(),
last_cleanup: Instant::now(),
})),
}
}
/// 接收FEC包并尝试恢复丢失的包
pub fn receive(
&self,
net_packet: NetPacket<TransmissionBytes>,
) -> Result<Option<Vec<NetPacket<TransmissionBytes>>>> {
let mut inner = self.inner.lock();
let src_ip = Ipv4Addr::from(net_packet.src_id());
let fec_packet = FecPacket::decode(net_packet.payload())?;
let group_id = fec_packet.group_id;
let packet_index = fec_packet.packet_index as usize;
let payload = fec_packet.payload;
if packet_index > MAX_NUM {
log::warn!(
"packet_index overflow, src={src_ip},group_id={group_id}, packet_index={packet_index}",
);
bail!("packet_index overflow {src_ip}");
}
let mut packet = None;
let group = inner.groups.entry((src_ip, group_id)).or_default();
if group.is_done() {
return Ok(None);
}
if group
.received_shards
.get(packet_index)
.is_some_and(|v| v.is_some())
{
return Ok(None);
}
if let Some(parity_data) = fec_packet.parity_data {
let data_shards = parity_data.data_shards as usize;
let parity_shards = parity_data.parity_shards as usize;
if data_shards > MAX_NUM {
bail!("data_shards overflow {src_ip}");
}
if parity_shards > MAX_NUM {
bail!("parity_shards overflow {src_ip}");
}
if data_shards + parity_shards <= packet_index {
log::warn!(
"packet_index overflow in parity, src={},group_id={}, packet_index={}, total_shards={}",
src_ip,
group_id,
packet_index,
data_shards + parity_shards
);
bail!("packet_index overflow {src_ip}");
}
if group.data_shards != 0 && group.data_shards != data_shards {
bail!("group data_shards!=data_shards {src_ip}");
}
if group.parity_shards != 0 && group.parity_shards != parity_shards {
bail!("group parity_shards!=parity_shards {src_ip}");
}
group.data_shards = data_shards;
group.parity_shards = parity_shards;
if group.received_shards.len() < data_shards + parity_shards {
group
.received_shards
.resize(data_shards + parity_shards, None);
}
group.received_shards[packet_index] = Some(payload);
} else {
let buffer = TransmissionBytes::zeroed(HEAD_LENGTH + payload.len());
let mut result_packet = NetPacket::new(buffer)?;
result_packet.head_mut().copy_from_slice(net_packet.head());
result_packet.set_fec_flag(false);
result_packet.set_payload(&payload)?;
packet = Some(result_packet);
if group.received_shards.len() <= packet_index {
group.received_shards.resize(packet_index + 1, None);
}
// 保存FEC数据: [type_byte, flags_byte, payload_len(u16), payload...]
let type_byte = net_packet.head()[0];
let flags_byte = net_packet.head()[2];
let mut batch_data = vec![0u8; 4 + payload.len()];
batch_data[0] = type_byte;
batch_data[1] = flags_byte;
batch_data[2..4].copy_from_slice(&(payload.len() as u16).to_be_bytes());
batch_data[4..].copy_from_slice(&payload);
group.received_shards[packet_index] = Some(batch_data);
group.received_original_count += 1;
}
group.last_update = Instant::now();
if group.is_done() {
group.done();
if inner.last_cleanup.elapsed() > Duration::from_secs(1) {
Self::cleanup_old_groups(&mut inner.groups);
inner.last_cleanup = Instant::now();
}
return Ok(packet.map(|v| vec![v]));
}
let result = Self::try_decode(group, (src_ip, group_id), &net_packet)?;
if inner.last_cleanup.elapsed() > Duration::from_secs(1) {
Self::cleanup_old_groups(&mut inner.groups);
inner.last_cleanup = Instant::now();
}
match (packet, result) {
(Some(packet), Some(mut result)) => {
result.push(packet);
Ok(Some(result))
}
(Some(packet), None) => Ok(Some(vec![packet])),
(None, Some(result)) => Ok(Some(result)),
(None, None) => Ok(None),
}
}
/// 检查是否可以恢复丢失的包
fn try_decode(
group: &mut FecGroup,
key: (Ipv4Addr, u64),
net_packet: &NetPacket<TransmissionBytes>,
) -> Result<Option<Vec<NetPacket<TransmissionBytes>>>> {
if group.data_shards == 0 {
return Ok(None);
}
if group.received_original_count == group.data_shards {
return Ok(None);
}
let received_count = group.received_shards.iter().filter(|s| s.is_some()).count();
if received_count < group.data_shards {
return Ok(None);
}
Self::decode_with_rs(group, key, net_packet)
}
/// Reed-Solomon解码恢复丢失的包
fn decode_with_rs(
group: &mut FecGroup,
key: (Ipv4Addr, u64),
net_packet: &NetPacket<TransmissionBytes>,
) -> Result<Option<Vec<NetPacket<TransmissionBytes>>>> {
let (src_ip, group_id) = key;
if group.received_shards.len() != group.data_shards + group.parity_shards {
bail!(
"received_shards.len()({}) != data_shards({})+parity_shards({}) src_ip={src_ip},group_id={group_id}",
group.received_shards.len(),
group.data_shards,
group.parity_shards,
)
}
let mut delivered_packets = vec![false; group.data_shards];
for (index, x) in group.received_shards[..group.data_shards]
.iter()
.enumerate()
{
if x.is_some() {
delivered_packets[index] = true;
}
}
let rs = ReedSolomon::new(group.data_shards, group.parity_shards)?;
rs.reconstruct(&mut group.received_shards)?;
let mut result = Vec::new();
for (i, shard) in group
.received_shards
.iter()
.enumerate()
.take(group.data_shards)
{
if let Some(shard) = shard {
if delivered_packets[i] {
continue;
}
let net_packet = Self::rebuild_net_packet(net_packet, shard)?;
result.push(net_packet);
}
}
group.done();
Ok(Some(result))
}
/// 从恢复的数据重建NetPacket
fn rebuild_net_packet(
current_packet: &NetPacket<TransmissionBytes>,
recovered_data: &[u8],
) -> Result<NetPacket<TransmissionBytes>> {
if recovered_data.len() < 4 {
bail!("recovered_data too short");
}
let type_byte = recovered_data[0];
let flags_byte = recovered_data[1];
let payload_len = u16::from_be_bytes([recovered_data[2], recovered_data[3]]) as usize;
if payload_len + 4 > recovered_data.len() {
bail!("invalid payload_len in recovered_data");
}
let payload = &recovered_data[4..4 + payload_len];
let buffer = TransmissionBytes::zeroed(HEAD_LENGTH + payload.len());
let mut net_packet = NetPacket::new(buffer)?;
net_packet.head_mut()[0] = type_byte;
net_packet.head_mut()[2] = flags_byte;
net_packet.set_src_id(current_packet.src_id());
net_packet.set_dest_id(current_packet.dest_id());
net_packet.set_ttl(current_packet.ttl());
net_packet.set_fec_flag(false);
net_packet.set_payload(payload)?;
Ok(net_packet)
}
fn cleanup_old_groups(groups: &mut HashMap<(Ipv4Addr, u64), FecGroup>) {
let now = Instant::now();
groups.retain(|_, group| now.duration_since(group.last_update) < GROUP_TIMEOUT);
if groups.len() > MAX_GROUPS {
let mut group_ids: Vec<_> = groups.iter().map(|(id, g)| (*id, g.last_update)).collect();
group_ids.sort_by_key(|(_, last_update)| *last_update);
let to_remove = group_ids.len() - MAX_GROUPS;
for (group_id, _) in group_ids.iter().take(to_remove) {
groups.remove(group_id);
}
}
}
}
+247
View File
@@ -0,0 +1,247 @@
use crate::protocol::ip_packet_protocol::{HEAD_LENGTH, MsgType, NetPacket};
use crate::protocol::transmission::TransmissionBytes;
use crate::tunnel_core::outbound::BasicOutbound;
use anyhow::{Result, bail};
use parking_lot::Mutex;
use prost::Message;
use reed_solomon_erasure::galois_8::ReedSolomon;
use std::collections::HashMap;
use std::net::Ipv4Addr;
use std::sync::Arc;
use std::time::{Duration, Instant};
use tokio::sync::mpsc;
mod fec_proto {
include!(concat!(env!("OUT_DIR"), "/protocol.fec.rs"));
}
use crate::utils::task_control::TaskGroup;
pub use fec_proto::FecPacket;
const BATCH_SIZE: usize = 10;
const REDUNDANCY_RATE: f32 = 0.2;
const BATCH_TIMEOUT_MS: u64 = 20;
const MIN_PARITY: usize = 1;
const BATCH_CHANNEL_SIZE: usize = 1024;
#[derive(Clone)]
pub struct FecEncoder {
batch_states: Arc<Mutex<HashMap<Ipv4Addr, DestBatchState>>>,
batch_tx: mpsc::Sender<(Ipv4Addr, Ipv4Addr, u64, Vec<TransmissionBytes>)>,
}
struct DestBatchState {
group_id: u64,
current_batch: Vec<TransmissionBytes>,
deadline: Instant,
src_ip: Ipv4Addr,
}
impl FecEncoder {
pub fn new(task_group: &TaskGroup, basic_outbound: BasicOutbound) -> Self {
let (batch_tx, batch_rx) = mpsc::channel(BATCH_CHANNEL_SIZE);
let batch_states = Arc::new(Mutex::new(HashMap::new()));
let encoder = Self {
batch_states: batch_states.clone(),
batch_tx,
};
task_group.spawn(fec_encoder_worker(batch_rx, basic_outbound, batch_states));
encoder
}
/// 将数据包加入FEC批次并返回包装后的包
pub fn encode(
&self,
mut packet: NetPacket<TransmissionBytes>,
) -> Result<NetPacket<TransmissionBytes>> {
let src_ip = Ipv4Addr::from(packet.src_id());
let dest = Ipv4Addr::from(packet.dest_id());
if packet.payload().len() > u16::MAX as usize {
bail!("Payload too big");
}
let original_payload = packet.payload().to_vec();
let original_payload_len = original_payload.len();
let type_byte = packet.head()[0];
let flags_byte = packet.head()[2];
// 组装FEC数据: [type_byte, flags_byte, payload_len(u16), payload...]
let batch_len = 4 + original_payload_len;
let mut batch_buffer = TransmissionBytes::zeroed(batch_len);
batch_buffer[0] = type_byte;
batch_buffer[1] = flags_byte;
batch_buffer[2..4].copy_from_slice(&(original_payload_len as u16).to_be_bytes());
batch_buffer[4..batch_len].copy_from_slice(&original_payload);
let (group_id, packet_index) = {
let mut states = self.batch_states.lock();
let state = states.entry(dest).or_insert_with(|| DestBatchState {
group_id: 0,
current_batch: Vec::with_capacity(BATCH_SIZE),
deadline: Instant::now() + Duration::from_millis(BATCH_TIMEOUT_MS),
src_ip,
});
let group_id = state.group_id;
let packet_index = state.current_batch.len();
state.current_batch.push(batch_buffer);
if state.current_batch.len() >= BATCH_SIZE {
let batch = std::mem::take(&mut state.current_batch);
state.group_id += 1;
state.deadline = Instant::now() + Duration::from_millis(BATCH_TIMEOUT_MS);
if self
.batch_tx
.try_send((src_ip, dest, group_id, batch))
.is_err()
{
log::warn!(
"failed to send batch to worker (channel full), dest={}, group_id={}",
dest,
group_id
);
}
}
(group_id, packet_index)
};
let fec_packet = FecPacket {
group_id,
packet_index: packet_index as u32,
payload: original_payload,
parity_data: None,
};
let fec_payload = fec_packet.encode_to_vec();
packet
.source_buf_mut()
.resize(HEAD_LENGTH + fec_payload.len(), 0);
packet.set_payload(&fec_payload)?;
packet.set_fec_flag(true);
Ok(packet)
}
}
/// 后台worker,处理满批次和超时批次
async fn fec_encoder_worker(
mut batch_rx: mpsc::Receiver<(Ipv4Addr, Ipv4Addr, u64, Vec<TransmissionBytes>)>,
basic_outbound: BasicOutbound,
batch_states: Arc<Mutex<HashMap<Ipv4Addr, DestBatchState>>>,
) {
let mut timer = tokio::time::interval(Duration::from_millis(5));
loop {
tokio::select! {
Some((src,dest, group_id, mut items)) = batch_rx.recv() => {
if let Err(e) = encode_and_send_parity(src,dest, group_id, &mut items, &basic_outbound).await {
log::warn!("encode_and_send_parity error for {} group {}: {:?}", dest, group_id, e);
}
}
_ = timer.tick() => {
let now = Instant::now();
let timeout_batches = {
let mut states = batch_states.lock();
let mut batches = Vec::new();
for (dest, state) in states.iter_mut() {
if !state.current_batch.is_empty() && now >= state.deadline {
let items = std::mem::take(&mut state.current_batch);
let group_id = state.group_id;
state.group_id += 1;
state.deadline = Instant::now() + Duration::from_millis(BATCH_TIMEOUT_MS);
batches.push((state.src_ip,*dest, group_id, items));
}
}
batches
};
for (src,dest, group_id, mut items) in timeout_batches {
if let Err(e) = encode_and_send_parity(src, dest, group_id, &mut items, &basic_outbound).await {
log::warn!("encode_and_send_parity timeout error for {} group {}: {:?}", dest, group_id, e);
}
}
}
}
}
}
/// Reed-Solomon编码并发送冗余包
async fn encode_and_send_parity(
src: Ipv4Addr,
dest: Ipv4Addr,
group_id: u64,
items: &mut Vec<TransmissionBytes>,
basic_outbound: &BasicOutbound,
) -> Result<()> {
if items.is_empty() {
return Ok(());
}
let data_shards = items.len();
let parity_shards = (data_shards as f32 * REDUNDANCY_RATE).ceil() as usize;
let parity_shards = parity_shards.max(MIN_PARITY);
let max_len = items.iter().map(|buf| buf.len()).max().unwrap_or(0);
if max_len == 0 {
log::warn!("max_len is 0, dest={}, group_id={}", dest, group_id);
return Ok(());
}
for buf in items.iter_mut() {
if buf.len() < max_len {
let padding = max_len - buf.len();
buf.extend_end(padding);
}
}
for _ in 0..parity_shards {
items.push(TransmissionBytes::zeroed(max_len));
}
let rs = ReedSolomon::new(data_shards, parity_shards)?;
let mut shard_refs: Vec<&mut [u8]> = items.iter_mut().map(|buf| buf.as_mut()).collect();
rs.encode(&mut shard_refs)?;
for (i, parity_buf) in items[data_shards..].iter().enumerate() {
let packet_index = (data_shards + i) as u32;
let fec_packet = FecPacket {
group_id,
packet_index,
payload: parity_buf.as_ref().to_vec(),
parity_data: Some(fec_proto::ParityData {
data_shards: data_shards as u32,
parity_shards: parity_shards as u32,
}),
};
let fec_payload = fec_packet.encode_to_vec();
let buffer = TransmissionBytes::zeroed(HEAD_LENGTH + fec_payload.len());
let mut net_packet = NetPacket::new(buffer)?;
net_packet.set_msg_type(MsgType::Turn);
net_packet.set_src_id(src.into());
net_packet.set_dest_id(dest.into());
net_packet.set_ttl(5);
net_packet.set_payload(&fec_payload)?;
net_packet.set_fec_flag(true);
if let Err(e) = basic_outbound.send_encrypted_packet(dest, net_packet).await {
log::warn!(
"failed to send parity packet {}: {:?}, dest={}, group_id={}",
packet_index,
e,
dest,
group_id
);
}
}
Ok(())
}
+5
View File
@@ -0,0 +1,5 @@
mod decoder;
mod encoder;
pub(crate) use decoder::FecDecoder;
pub(crate) use encoder::FecEncoder;
+15
View File
@@ -0,0 +1,15 @@
pub(crate) mod compression;
pub mod context;
pub mod core;
pub mod crypto;
pub(crate) mod fec;
pub mod nat;
pub mod protocol;
pub mod tls;
pub(crate) mod tun;
pub mod tunnel_core;
pub mod utils;
pub mod api;
pub(crate) mod enhanced_tunnel;
pub mod port_mapping;
+139
View File
@@ -0,0 +1,139 @@
use crate::context::SharedNetworkAddr;
use crate::utils::task_control::TaskGroup;
use anyhow::Context;
use pnet_packet::Packet;
use pnet_packet::icmp::echo_reply::{Identifier, SequenceNumber};
use pnet_packet::icmp::{IcmpPacket, IcmpTypes};
use pnet_packet::ipv4::Ipv4Packet;
use std::collections::HashMap;
use std::net::{IpAddr, Ipv4Addr, SocketAddr, SocketAddrV4};
use tcp_ip::IpStack;
use tcp_ip::icmp::IcmpSocket;
use tokio::net::UdpSocket;
pub async fn start_icmp_nat(
task_group: &TaskGroup,
ip_stack: &IpStack,
no_tun: bool,
network: SharedNetworkAddr,
) -> anyhow::Result<()> {
let net_icmp_socket = socket2::Socket::new(
socket2::Domain::IPV4,
socket2::Type::RAW,
Some(socket2::Protocol::ICMPV4),
)
.context("new Socket RAW ICMPV4 failed")?;
let addr: SocketAddrV4 = SocketAddrV4::new(Ipv4Addr::UNSPECIFIED, 0);
net_icmp_socket
.bind(&socket2::SockAddr::from(addr))
.context("bind Socket ICMPV4 failed")?;
net_icmp_socket.set_nonblocking(true)?;
let std_socket: std::net::UdpSocket = net_icmp_socket.into();
let tokio_icmp_socket = UdpSocket::from_std(std_socket)?;
let inner_icmp_socket = IcmpSocket::bind_all(ip_stack.clone()).await?;
task_group.spawn(async move {
if let Err(e) = task(tokio_icmp_socket, inner_icmp_socket, no_tun, network).await {
log::error!("icmp task failed: {:?}", e);
}
});
Ok(())
}
async fn task(
tokio_icmp_socket: UdpSocket,
inner_icmp_socket: IcmpSocket,
no_tun: bool,
network: SharedNetworkAddr,
) -> anyhow::Result<()> {
let mut buf1 = vec![0u8; 65536];
let mut buf2 = vec![0u8; 65536];
let mut map = HashMap::new();
loop {
tokio::select! {
rs = tokio_icmp_socket.recv(&mut buf1) => {
let len = rs?;
tokio_icmp_socket_recv(&buf1[..len],&inner_icmp_socket,&map,no_tun,&network).await?;
}
rs = inner_icmp_socket.recv_from_to(&mut buf2) => {
let (len,src,dst) = rs?;
inner_icmp_socket_recv(&buf2[..len],src,dst,&tokio_icmp_socket,&mut map,no_tun,&network).await?;
}
}
}
}
async fn tokio_icmp_socket_recv(
buf: &[u8],
inner_icmp_socket: &IcmpSocket,
map: &HashMap<(Ipv4Addr, Identifier, SequenceNumber), Ipv4Addr>,
no_tun: bool,
network: &SharedNetworkAddr,
) -> anyhow::Result<()> {
let Some(ipv4) = Ipv4Packet::new(buf) else {
return Ok(());
};
let Some(icmp) = IcmpPacket::new(ipv4.payload()) else {
return Ok(());
};
if icmp.get_icmp_type() != IcmpTypes::EchoReply
&& icmp.get_icmp_type() != IcmpTypes::EchoRequest
{
return Ok(());
}
let payload = icmp.payload();
if payload.len() < 4 {
return Ok(());
}
let mut src = ipv4.get_source();
let identifier = Identifier::new(u16::from_be_bytes([payload[0], payload[1]]));
let sequence_number = SequenceNumber::new(u16::from_be_bytes([payload[2], payload[3]]));
let Some(dst) = map.get(&(src, identifier, sequence_number)) else {
return Ok(());
};
if no_tun && src == Ipv4Addr::LOCALHOST {
src = network.ip().context("not ip")?;
}
inner_icmp_socket
.send_from_to(ipv4.payload(), src.into(), (*dst).into())
.await
.context("sending ICMPv4 failed")?;
Ok(())
}
async fn inner_icmp_socket_recv(
buf: &[u8],
src: IpAddr,
dst: IpAddr,
tokio_icmp_socket: &UdpSocket,
map: &mut HashMap<(Ipv4Addr, Identifier, SequenceNumber), Ipv4Addr>,
no_tun: bool,
network: &SharedNetworkAddr,
) -> anyhow::Result<()> {
let (IpAddr::V4(src), IpAddr::V4(mut dst)) = (src, dst) else {
return Ok(());
};
let Some(icmp) = IcmpPacket::new(buf) else {
return Ok(());
};
if icmp.get_icmp_type() != IcmpTypes::EchoReply
&& icmp.get_icmp_type() != IcmpTypes::EchoRequest
{
return Ok(());
}
let payload = icmp.payload();
if payload.len() < 4 {
return Ok(());
}
if no_tun && dst == network.ip().context("not ip")? {
dst = Ipv4Addr::LOCALHOST;
}
let identifier = Identifier::new(u16::from_be_bytes([payload[0], payload[1]]));
let sequence_number = SequenceNumber::new(u16::from_be_bytes([payload[2], payload[3]]));
map.insert((dst, identifier, sequence_number), src);
tokio_icmp_socket
.send_to(buf, SocketAddr::new(dst.into(), 0))
.await?;
Ok(())
}
+225
View File
@@ -0,0 +1,225 @@
use crate::context::{NetworkAddr, SharedNetworkAddr};
use crate::nat::AllowSubnetExternalRoute;
use crate::protocol::ip_packet_protocol::HEAD_LENGTH;
use crate::protocol::transmission::TransmissionBytes;
use crate::tunnel_core::outbound::HybridOutbound;
use crate::utils::task_control::TaskGroup;
use anyhow::Context;
use bytes::BytesMut;
use pnet_packet::ip::IpNextHeaderProtocol;
use pnet_packet::ipv4::Ipv4Packet;
use std::net::{Ipv4Addr, SocketAddr};
use std::str::FromStr;
use std::sync::Arc;
use tcp_ip::{IpStackConfig, IpStackRecv, IpStackSend};
use tokio::io::{AsyncRead, AsyncWrite};
#[cfg(not(target_os = "android"))]
mod icmp_nat;
mod tcp_nat;
mod udp_nat;
#[derive(Clone)]
pub(crate) struct InternalNatInbound {
no_tun: bool,
ip_stack_send: Arc<IpStackSend>,
allow_subnet: AllowSubnetExternalRoute,
network: SharedNetworkAddr,
}
impl InternalNatInbound {
pub async fn create(
task_group: &TaskGroup,
mtu: u16,
hybrid_outbound: HybridOutbound,
allow_subnet: AllowSubnetExternalRoute,
network: SharedNetworkAddr,
no_tun: bool,
) -> anyhow::Result<Self> {
let ip_stack_config = IpStackConfig {
mtu,
..Default::default()
};
let (ip_stack, ip_stack_send, ip_stack_recv) = tcp_ip::ip_stack(ip_stack_config)?;
#[cfg(not(target_os = "android"))]
icmp_nat::start_icmp_nat(task_group, &ip_stack, no_tun, network.clone()).await?;
tcp_nat::start_tcp_nat(task_group, &ip_stack, no_tun, network.clone()).await?;
udp_nat::start_udp_nat(task_group, &ip_stack).await?;
task_group.spawn(async move {
if let Err(e) = ip_stack_recv_task(ip_stack_recv, hybrid_outbound).await {
log::error!("ip stack recv task error: {e:?}");
}
});
Ok(Self {
no_tun,
ip_stack_send: Arc::new(ip_stack_send),
allow_subnet,
network,
})
}
pub async fn send(&self, data: &[u8], net: &NetworkAddr) -> anyhow::Result<()> {
if data[0] >> 4 != 4 {
return Ok(());
}
let Some(ipv4) = Ipv4Packet::new(data) else {
return Ok(());
};
let dest = ipv4.get_destination();
if net.network().contains(&dest)
|| dest == net.broadcast
|| dest.is_broadcast()
|| dest.is_multicast()
|| self.allow_subnet.allow(&dest)
{
self.ip_stack_send.send_ip_packet(data).await?;
}
Ok(())
}
pub async fn send_ipv4_payload(
&self,
protocol: IpNextHeaderProtocol,
src_ip: Ipv4Addr,
dest_ip: Ipv4Addr,
payload: BytesMut,
) -> anyhow::Result<()> {
self.ip_stack_send
.send_ipv4_payload(protocol, src_ip, dest_ip, payload)
.await?;
Ok(())
}
}
async fn ip_stack_recv_task(
mut ip_stack_recv: IpStackRecv,
hybrid_outbound: HybridOutbound,
) -> anyhow::Result<()> {
loop {
let mut bytes = TransmissionBytes::new_offset_zeroed(HEAD_LENGTH);
let len = ip_stack_recv.recv(&mut bytes).await?;
bytes.set_len(len)?;
if let Err(e) = hybrid_outbound.ipv4_outbound_common(bytes).await {
log::warn!("ip_stack_recv_task,{e:?}");
}
}
}
impl InternalNatInbound {
fn network_contains(&self, ip: &Ipv4Addr) -> bool {
self.network
.network()
.map(|net| net.contains(ip))
.unwrap_or(false)
}
pub fn use_nat(&self, dst: &Ipv4Addr) -> bool {
if self.no_tun {
return self.allow_nat(dst);
}
if self.network_contains(dst) {
return false;
}
self.allow_subnet.allow(dst)
}
pub fn no_tun(&self) -> bool {
self.no_tun
}
pub fn allow_nat(&self, dst: &Ipv4Addr) -> bool {
self.allow_subnet.allow(dst) || self.network_contains(dst)
}
pub async fn tcp_nat<R, W>(
&self,
recv_stream: R,
send_stream: W,
mut dest_ip: Ipv4Addr,
dest_port: u16,
) -> anyhow::Result<()>
where
R: AsyncRead + Unpin,
W: AsyncWrite + Unpin,
{
if self.no_tun {
let net = self.network.get().context("no network")?;
if dest_ip == net.ip {
dest_ip = Ipv4Addr::LOCALHOST;
} else if net.network().contains(&dest_ip) {
return Ok(());
}
}
let dst = SocketAddr::new(dest_ip.into(), dest_port);
tcp_nat::stream_nat(recv_stream, send_stream, dst).await
}
}
#[derive(Clone)]
pub(crate) struct PortMappingManager {
no_tun: bool,
allow_port_mapping: bool,
network: SharedNetworkAddr,
}
impl PortMappingManager {
pub fn new(no_tun: bool, allow_port_mapping: bool, network: SharedNetworkAddr) -> Self {
Self {
no_tun,
allow_port_mapping,
network,
}
}
pub async fn tcp_mapping<R, W>(
&self,
recv_stream: R,
send_stream: W,
dest: String,
dest_port: u16,
) -> anyhow::Result<()>
where
R: AsyncRead + Unpin,
W: AsyncWrite + Unpin,
{
if !self.allow_port_mapping {
log::debug!("port mapping not enabled");
return Ok(());
}
if self.no_tun
&& let Ok(dest_ip) = Ipv4Addr::from_str(&dest)
{
let net = self.network.get().context("no network")?;
if dest_ip == net.ip {
let dst = SocketAddr::new(Ipv4Addr::LOCALHOST.into(), dest_port);
return tcp_nat::stream_nat(recv_stream, send_stream, dst).await;
} else if net.network().contains(&dest_ip) {
return Ok(());
}
}
let dst = format!("{}:{}", dest, dest_port);
tcp_nat::stream_nat(recv_stream, send_stream, dst).await
}
pub async fn udp_mapping<R, W>(
&self,
recv_stream: R,
send_stream: W,
dest: String,
dest_port: u16,
) -> anyhow::Result<()>
where
R: AsyncRead + Unpin,
W: AsyncWrite + Unpin,
{
if !self.allow_port_mapping {
log::debug!("port mapping not enabled");
return Ok(());
}
if self.no_tun
&& let Ok(dest_ip) = Ipv4Addr::from_str(&dest)
{
let net = self.network.get().context("no network")?;
if dest_ip == net.ip {
let dst = SocketAddr::new(Ipv4Addr::LOCALHOST.into(), dest_port);
return udp_nat::stream_nat(recv_stream, send_stream, dst).await;
} else if net.network().contains(&dest_ip) {
return Ok(());
}
}
let dst = format!("{}:{}", dest, dest_port);
udp_nat::stream_nat(recv_stream, send_stream, dst).await
}
}
+81
View File
@@ -0,0 +1,81 @@
use crate::context::SharedNetworkAddr;
use crate::utils::task_control::TaskGroup;
use anyhow::Context;
use std::fmt::Debug;
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
use tcp_ip::IpStack;
use tcp_ip::tcp::TcpListener;
use tokio::io::{AsyncRead, AsyncWrite};
use tokio::net::{TcpStream, ToSocketAddrs};
pub async fn start_tcp_nat(
task_group: &TaskGroup,
ip_stack: &IpStack,
no_tun: bool,
network: SharedNetworkAddr,
) -> anyhow::Result<()> {
let tcp_listener = TcpListener::bind_all(ip_stack.clone()).await?;
let group = task_group.clone();
task_group.spawn(async move {
if let Err(e) = listen_task(&group, tcp_listener, no_tun, network).await {
log::error!("listen task error: {:?}", e);
}
});
Ok(())
}
async fn listen_task(
task_group: &TaskGroup,
mut tcp_listener: TcpListener,
no_tun: bool,
network: SharedNetworkAddr,
) -> anyhow::Result<()> {
loop {
let (stream, _addr) = tcp_listener.accept().await?;
let mut local_addr = stream.local_addr()?;
let peer_addr = stream.peer_addr()?;
if no_tun {
let IpAddr::V4(ip) = local_addr.ip() else {
continue;
};
if ip == network.ip().context("not ip")? {
// 无tun的情况下写入本机的则写到localhost
local_addr.set_ip(IpAddr::V4(Ipv4Addr::LOCALHOST));
}
}
task_group.spawn(async move {
if let Err(e) = stream_task(stream, local_addr).await {
log::error!("stream task Error: {:?},{peer_addr}->{local_addr}", e);
}
});
}
}
async fn stream_task(
mut inner_stream: tcp_ip::tcp::TcpStream,
addr: SocketAddr,
) -> anyhow::Result<()> {
let mut tokio_stream = TcpStream::connect(addr).await?;
tokio::io::copy_bidirectional(&mut inner_stream, &mut tokio_stream).await?;
Ok(())
}
pub(crate) async fn stream_nat<R, W, A: ToSocketAddrs + Debug>(
mut recv_stream: R,
mut send_stream: W,
addr: A,
) -> anyhow::Result<()>
where
R: AsyncRead + Unpin,
W: AsyncWrite + Unpin,
{
let mut tokio_stream = TcpStream::connect(&addr)
.await
.with_context(|| format!("error connecting to {:?}", addr))?;
let (mut tcp_r, mut tcp_w) = tokio_stream.split();
tokio::select! {
_ = tokio::io::copy(&mut recv_stream, &mut tcp_w) => {},
_ = tokio::io::copy(&mut tcp_r, &mut send_stream) => {},
}
Ok(())
}
+188
View File
@@ -0,0 +1,188 @@
use crate::utils::task_control::TaskGroup;
use anyhow::Context;
use bytes::Bytes;
use futures::{SinkExt, StreamExt};
use std::collections::HashMap;
use std::fmt::Debug;
use std::net::SocketAddr;
use std::sync::Arc;
use std::time::{Duration, Instant};
use tcp_ip::IpStack;
use tokio::io::{AsyncRead, AsyncWrite};
use tokio::net::ToSocketAddrs;
use tokio::sync::Mutex;
use tokio_util::codec::{FramedRead, FramedWrite, LengthDelimitedCodec};
struct NatEntry {
socket: Arc<tokio::net::UdpSocket>,
last_active: Instant,
}
type NatTable = Arc<Mutex<HashMap<(SocketAddr, SocketAddr), NatEntry>>>;
const NAT_IDLE_TIMEOUT: Duration = Duration::from_secs(60 * 5);
const NAT_GC_INTERVAL: Duration = Duration::from_secs(60);
pub async fn start_udp_nat(task_group: &TaskGroup, ip_stack: &IpStack) -> anyhow::Result<()> {
let inner_socket = tcp_ip::udp::UdpSocket::bind_all(ip_stack.clone()).await?;
let inner_socket = Arc::new(inner_socket);
let nat_table: NatTable = Arc::new(Mutex::new(HashMap::new()));
let mut buf = vec![0u8; 65536];
let group = task_group.clone();
let nat_table_clone = nat_table.clone();
task_group.spawn(async move {
loop {
let (len, src, dst) = match inner_socket.recv_from_to(&mut buf).await {
Ok(rs) => rs,
Err(e) => {
log::warn!("{e:?}");
break;
}
};
if let Err(e) =
handle_outbound(&group, &inner_socket, &nat_table, src, dst, &buf[..len]).await
{
log::warn!("udp nat outbound error: {e:?}");
}
}
});
spawn_nat_gc(task_group, nat_table_clone);
Ok(())
}
async fn handle_outbound(
task_group: &TaskGroup,
inner: &Arc<tcp_ip::udp::UdpSocket>,
nat: &NatTable,
src: SocketAddr,
dst: SocketAddr,
packet: &[u8],
) -> anyhow::Result<()> {
let key = (src, dst);
let socket = {
let mut table = nat.lock().await;
if let Some(entry) = table.get_mut(&key) {
entry.last_active = Instant::now();
entry.socket.clone()
} else {
// 创建真实 UDP socket
let sock = tokio::net::UdpSocket::bind("0.0.0.0:0").await?;
sock.connect(dst).await?;
let sock = Arc::new(sock);
table.insert(
key,
NatEntry {
socket: sock.clone(),
last_active: Instant::now(),
},
);
// 启动反向转发
spawn_inbound(
task_group,
inner.clone(),
nat.clone(),
src,
dst,
sock.clone(),
);
sock
}
};
socket.send(packet).await?;
Ok(())
}
fn spawn_inbound(
task_group: &TaskGroup,
inner: Arc<tcp_ip::udp::UdpSocket>,
nat: NatTable,
src: SocketAddr,
dst: SocketAddr,
socket: Arc<tokio::net::UdpSocket>,
) {
task_group.spawn(async move {
let mut buf = vec![0u8; 65536];
loop {
let len = match socket.recv(&mut buf).await {
Ok(n) => n,
Err(_) => break,
};
// 反向写回 inner socket
if inner.send_from_to(&buf[..len], dst, src).await.is_err() {
break;
}
// 更新活跃时间
if let Some(entry) = nat.lock().await.get_mut(&(src, dst)) {
entry.last_active = Instant::now();
}
}
// 回收 NAT
nat.lock().await.remove(&(src, dst));
});
}
fn spawn_nat_gc(task_group: &TaskGroup, nat: NatTable) {
task_group.spawn(async move {
let mut interval = tokio::time::interval(NAT_GC_INTERVAL);
loop {
interval.tick().await;
let now = Instant::now();
let mut table = nat.lock().await;
table.retain(|(src, dst), entry| {
let alive = now.duration_since(entry.last_active) < NAT_IDLE_TIMEOUT;
if !alive {
log::debug!("udp nat expired: {} -> {}", src, dst);
}
alive
});
}
});
}
pub(crate) async fn stream_nat<R, W, A: ToSocketAddrs + Debug>(
recv_stream: R,
send_stream: W,
addr: A,
) -> anyhow::Result<()>
where
R: AsyncRead + Unpin,
W: AsyncWrite + Unpin,
{
let udp_socket = tokio::net::UdpSocket::bind("0.0.0.0:0").await?;
udp_socket
.connect(&addr)
.await
.with_context(|| format!("error connecting to {:?}", addr))?;
let mut framed_read = FramedRead::new(recv_stream, LengthDelimitedCodec::new());
let mut framed_write = FramedWrite::new(send_stream, LengthDelimitedCodec::new());
let mut buf = vec![0u8; 65536];
loop {
tokio::select! {
Some(buf) = framed_read.next() => {
let buf = buf?;
udp_socket.send(&buf).await?;
},
rs = udp_socket.recv(&mut buf) =>{
let len = rs?;
framed_write.send(Bytes::copy_from_slice(&buf[..len])).await?;
},
else => {
break
}
}
}
Ok(())
}
+114
View File
@@ -0,0 +1,114 @@
use ipnet::Ipv4Net;
use parking_lot::Mutex;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use std::fmt;
use std::net::Ipv4Addr;
use std::str::FromStr;
use std::sync::Arc;
pub(crate) mod internal_nat;
#[derive(Clone, Debug)]
pub struct NetInput {
pub net: Ipv4Net,
pub target_ip: Ipv4Addr,
}
impl FromStr for NetInput {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let parts: Vec<&str> = s.split(',').map(|x| x.trim()).collect();
if parts.len() != 2 {
return Err("格式错误,应为 net,target_ip 例如: 192.168.0.0/24,10.26.0.2".into());
}
let net = Ipv4Net::from_str(parts[0]).map_err(|e| format!("网络段格式错误: {}", e))?;
let target_ip =
Ipv4Addr::from_str(parts[1]).map_err(|e| format!("目标 IP 格式错误: {}", e))?;
Ok(NetInput { net, target_ip })
}
}
impl fmt::Display for NetInput {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{},{}", self.net, self.target_ip)
}
}
impl Serialize for NetInput {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(&self.to_string())
}
}
impl<'de> Deserialize<'de> for NetInput {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
s.parse().map_err(serde::de::Error::custom)
}
}
#[derive(Clone, Default)]
pub struct SubnetExternalRoute {
route_table: Arc<Mutex<Vec<NetInput>>>,
}
impl SubnetExternalRoute {
pub fn new(mut route_table: Vec<NetInput>) -> Self {
route_table.sort_by_key(|r| std::cmp::Reverse(r.net.prefix_len()));
SubnetExternalRoute {
route_table: Arc::new(Mutex::new(route_table)),
}
}
pub fn set_route_table(&self, mut route_table: Vec<NetInput>) {
route_table.sort_by_key(|r| std::cmp::Reverse(r.net.prefix_len()));
*self.route_table.lock() = route_table;
}
pub fn route(&self, ip: &Ipv4Addr) -> Option<Ipv4Addr> {
let route_table = self.route_table.lock();
if route_table.is_empty() {
return None;
}
for net in route_table.iter() {
if net.net.contains(ip) {
return Some(net.target_ip);
}
}
None
}
pub fn all_route(&self) -> Vec<NetInput> {
self.route_table.lock().clone()
}
pub fn reset_route(&self, route_table: Vec<NetInput>) {
*self.route_table.lock() = route_table;
}
}
#[derive(Clone)]
pub struct AllowSubnetExternalRoute {
route_table: Arc<Vec<Ipv4Net>>,
}
impl AllowSubnetExternalRoute {
pub fn new(mut route_table: Vec<Ipv4Net>) -> Self {
route_table.sort_by_key(|r| r.prefix_len());
Self {
route_table: Arc::new(route_table),
}
}
pub fn allow(&self, ip: &Ipv4Addr) -> bool {
if self.route_table.is_empty() {
return false;
}
for net in self.route_table.iter() {
if net.contains(ip) {
return true;
}
}
false
}
}
+100
View File
@@ -0,0 +1,100 @@
use crate::enhanced_tunnel::quic_over::quic_client::QuicTunnelClient;
use crate::utils::task_control::TaskGroup;
use pnet_packet::ip::{IpNextHeaderProtocol, IpNextHeaderProtocols};
use std::fmt;
use std::net::{Ipv4Addr, SocketAddr};
use std::str::FromStr;
pub(crate) mod tcp_port_mapping;
pub(crate) mod udp_port_mapping;
pub(crate) async fn port_mapping_start(
task_group: &TaskGroup,
list: Vec<PortMapping>,
quic_tunnel_client: QuicTunnelClient,
) -> anyhow::Result<()> {
tcp_port_mapping::start(task_group, &list, quic_tunnel_client.clone()).await?;
udp_port_mapping::start(task_group, &list, quic_tunnel_client).await?;
Ok(())
}
#[derive(Debug, Clone)]
pub struct PortMapping {
pub protocol: IpNextHeaderProtocol,
pub src_addr: SocketAddr,
pub virtual_target_ip: Ipv4Addr,
pub dst_host: String,
pub dst_port: u16,
}
impl fmt::Display for PortMapping {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{}://{}-{}-{}:{}",
protocol_to_str(self.protocol),
self.src_addr,
self.virtual_target_ip,
self.dst_host,
self.dst_port
)
}
}
impl FromStr for PortMapping {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let (proto_str, rest) = s.split_once("://").ok_or("missing '://'")?;
let protocol =
str_to_protocol(proto_str).ok_or_else(|| format!("unknown protocol: {}", proto_str))?;
let mut parts = rest.splitn(3, '-');
let src_addr = parts
.next()
.ok_or("missing src_addr")?
.parse::<SocketAddr>()
.map_err(|e| format!("invalid src_addr: {}", e))?;
let virtual_target_ip = parts
.next()
.ok_or("missing virtual_target_ip")?
.parse::<Ipv4Addr>()
.map_err(|e| format!("invalid virtual_target_ip: {}", e))?;
let dst = parts.next().ok_or("missing destination")?;
let (dst_host, dst_port) = dst.rsplit_once(':').ok_or("missing dst port")?;
let dst_port = dst_port
.parse::<u16>()
.map_err(|e| format!("invalid dst_port: {}", e))?;
if dst_port == 0 {
return Err("invalid dst port: 0".to_string());
}
Ok(Self {
protocol,
src_addr,
virtual_target_ip,
dst_host: dst_host.to_string(),
dst_port,
})
}
}
fn protocol_to_str(p: IpNextHeaderProtocol) -> &'static str {
match p {
IpNextHeaderProtocols::Tcp => "tcp",
IpNextHeaderProtocols::Udp => "udp",
_ => "unknown",
}
}
fn str_to_protocol(s: &str) -> Option<IpNextHeaderProtocol> {
match s.to_ascii_lowercase().as_str() {
"tcp" => Some(IpNextHeaderProtocols::Tcp),
"udp" => Some(IpNextHeaderProtocols::Udp),
_ => None,
}
}
@@ -0,0 +1,85 @@
use crate::enhanced_tunnel::quic_over::quic_client::{QuicTunnelClient, send_handshake};
use crate::port_mapping::PortMapping;
use crate::protocol::client_message::{
PortProxyHandshake, QuicProxyHandshake, quic_proxy_handshake,
};
use crate::utils::task_control::TaskGroup;
use anyhow::Context;
use pnet_packet::ip::IpNextHeaderProtocols;
use std::net::{Ipv4Addr, SocketAddr};
use tokio::net::{TcpListener, TcpStream};
pub async fn start(
task_group: &TaskGroup,
list: &Vec<PortMapping>,
quic_tunnel_client: QuicTunnelClient,
) -> anyhow::Result<()> {
for x in list {
if x.protocol != IpNextHeaderProtocols::Tcp {
continue;
}
log::info!("Starting TCP port mapping on {}", x);
let listener = TcpListener::bind(x.src_addr)
.await
.with_context(|| format!("Tcp port mapping Failed to bind to {}", x.src_addr))?;
let group = task_group.clone();
let tunnel_client = quic_tunnel_client.clone();
let mapping = x.clone();
task_group.spawn(async move {
if let Err(e) = listen(&group, listener, &mapping, tunnel_client).await {
log::error!("listen {:?},mapping:{mapping}", e);
}
});
}
Ok(())
}
async fn listen(
task_group: &TaskGroup,
listener: TcpListener,
mapping: &PortMapping,
quic_tunnel_client: QuicTunnelClient,
) -> anyhow::Result<()> {
loop {
let (stream, addr) = listener.accept().await?;
let tunnel_client = quic_tunnel_client.clone();
let target_ip = mapping.virtual_target_ip;
let dst_host = mapping.dst_host.clone();
let dst_port = mapping.dst_port;
task_group.spawn(async move {
if let Err(e) =
stream_copy(stream, addr, target_ip, dst_host, dst_port, tunnel_client).await
{
log::error!("TCP TCP Stream Error: {:?}", e);
}
});
}
}
async fn stream_copy(
mut tcp_stream: TcpStream,
src: SocketAddr,
target_ip: Ipv4Addr,
dst_host: String,
dst_port: u16,
quic_tunnel_client: QuicTunnelClient,
) -> anyhow::Result<()> {
let (mut send_stream, mut recv_stream) = quic_tunnel_client.open_bi(target_ip).await?;
let handshake = QuicProxyHandshake {
handshake: Some(quic_proxy_handshake::Handshake::TcpPortMapping(
PortProxyHandshake {
src_ip: src.ip().to_string(),
src_port: src.port().into(),
dst_host,
dst_port: dst_port as _,
},
)),
};
send_handshake(&mut send_stream, handshake).await?;
let (mut tcp_r, mut tcp_w) = tcp_stream.split();
tokio::select! {
_ = tokio::io::copy(&mut recv_stream, &mut tcp_w) => {},
_ = tokio::io::copy(&mut tcp_r, &mut send_stream) => {},
}
Ok(())
}
@@ -0,0 +1,145 @@
use crate::enhanced_tunnel::quic_over::quic_client::{QuicTunnelClient, send_handshake};
use crate::port_mapping::PortMapping;
use crate::protocol::client_message::{
PortProxyHandshake, QuicProxyHandshake, quic_proxy_handshake,
};
use crate::utils::task_control::TaskGroup;
use anyhow::Context;
use bytes::Bytes;
use futures::{SinkExt, StreamExt};
use parking_lot::Mutex;
use pnet_packet::ip::IpNextHeaderProtocols;
use std::collections::HashMap;
use std::net::{Ipv4Addr, SocketAddr};
use std::sync::Arc;
use std::time::Duration;
use tokio::net::UdpSocket;
use tokio::sync::mpsc::Sender;
use tokio::sync::mpsc::error::TrySendError;
use tokio_util::codec::{FramedRead, FramedWrite, LengthDelimitedCodec};
pub async fn start(
task_group: &TaskGroup,
list: &Vec<PortMapping>,
quic_tunnel_client: QuicTunnelClient,
) -> anyhow::Result<()> {
for x in list {
if x.protocol != IpNextHeaderProtocols::Udp {
continue;
}
log::info!("Starting UDP port mapping on {}", x);
let udp = UdpSocket::bind(x.src_addr)
.await
.with_context(|| format!("Udp port mapping Failed to bind to {}", x.src_addr))?;
let udp_socket = Arc::new(udp);
let group = task_group.clone();
let tunnel_client = quic_tunnel_client.clone();
let mapping = x.clone();
task_group.spawn(async move {
if let Err(e) = recv(&group, udp_socket, &mapping, tunnel_client).await {
log::error!("recv {:?},mapping:{mapping}", e);
}
});
}
Ok(())
}
async fn recv(
task_group: &TaskGroup,
udp_socket: Arc<UdpSocket>,
mapping: &PortMapping,
quic_tunnel_client: QuicTunnelClient,
) -> anyhow::Result<()> {
let mut buf = vec![0u8; 65536];
let dest_map = Arc::new(Mutex::new(HashMap::<SocketAddr, Sender<Bytes>>::new()));
loop {
let (len, src) = udp_socket.recv_from(&mut buf).await?;
let bytes = Bytes::copy_from_slice(&buf[..len]);
let tx = {
let mut map = dest_map.lock();
if let Some(tx) = map.get(&src) {
tx.clone()
} else {
let (tx, rx) = tokio::sync::mpsc::channel::<Bytes>(128);
let udp_socket = udp_socket.clone();
let virtual_target_ip = mapping.virtual_target_ip;
let dst_host = mapping.dst_host.clone();
let dst_port = mapping.dst_port;
let tunnel_client = quic_tunnel_client.clone();
task_group.spawn(async move {
if let Err(e) = udp_mapping_handle(
udp_socket,
src,
virtual_target_ip,
dst_host,
dst_port,
rx,
tunnel_client,
)
.await
{
log::error!("udp_mapping_handle {e:?},src:{src}");
}
});
map.insert(src, tx.clone());
tx
}
};
if let Err(err) = tx.try_send(bytes) {
match err {
TrySendError::Full(_) => {}
TrySendError::Closed(_) => {
let mut map = dest_map.lock();
map.remove(&src);
}
}
}
}
}
async fn udp_mapping_handle(
udp_socket: Arc<UdpSocket>,
src: SocketAddr,
target_ip: Ipv4Addr,
dst_host: String,
dst_port: u16,
mut rx: tokio::sync::mpsc::Receiver<Bytes>,
quic_tunnel_client: QuicTunnelClient,
) -> anyhow::Result<()> {
let (mut send_stream, recv_stream) = quic_tunnel_client.open_bi(target_ip).await?;
let handshake = QuicProxyHandshake {
handshake: Some(quic_proxy_handshake::Handshake::UdpPortMapping(
PortProxyHandshake {
src_ip: src.ip().to_string(),
src_port: src.port().into(),
dst_host,
dst_port: dst_port as _,
},
)),
};
send_handshake(&mut send_stream, handshake).await?;
let mut framed_write = FramedWrite::new(send_stream, LengthDelimitedCodec::new());
let mut framed_read = FramedRead::new(recv_stream, LengthDelimitedCodec::new());
loop {
tokio::select! {
Some(buf) = framed_read.next()=>{
let buf = buf?;
udp_socket.send_to(&buf, src).await?;
},
Some(buf) = rx.recv()=>{
framed_write.send(buf).await?
},
_ = tokio::time::sleep(Duration::from_secs(60)) =>{
break;
},
else => {
break;
}
}
}
Ok(())
}
+98
View File
@@ -0,0 +1,98 @@
mod proto {
include!(concat!(env!("OUT_DIR"), "/protocol.client.rs"));
}
use anyhow::bail;
use bytes::BytesMut;
use prost::Message;
use std::net::{Ipv4Addr, Ipv6Addr};
use crate::protocol::ProtoToBytesMut;
pub use proto::*;
pub fn encode_nat_info(nat_info: &rust_p2p_core::nat::NatInfo) -> proto::NatInfo {
let nat_type = match nat_info.nat_type {
rust_p2p_core::nat::NatType::Cone => proto::NatType::Cone,
rust_p2p_core::nat::NatType::Symmetric => proto::NatType::Symmetric,
};
proto::NatInfo {
nat_type: nat_type.into(),
public_ips: nat_info.public_ips.iter().map(|v| (*v).into()).collect(),
public_udp_ports: nat_info
.public_udp_ports
.iter()
.map(|v| (*v).into())
.collect(),
public_port_range: nat_info.public_port_range.into(),
local_ipv4s: nat_info.local_ipv4s.iter().map(|v| (*v).into()).collect(),
ipv6: nat_info.ipv6.map(|v| v.octets().to_vec()),
local_udp_ports: nat_info
.local_udp_ports
.iter()
.map(|v| (*v).into())
.collect(),
local_tcp_port: nat_info.local_tcp_port.into(),
public_tcp_port: nat_info.public_tcp_port.into(),
}
}
pub fn decode_nat_info(msg: proto::NatInfo) -> anyhow::Result<rust_p2p_core::nat::NatInfo> {
let nat_type = match msg.nat_type() {
proto::NatType::Cone => rust_p2p_core::nat::NatType::Cone,
proto::NatType::Symmetric => rust_p2p_core::nat::NatType::Symmetric,
};
let ipv6: Option<[u8; 16]> = msg.ipv6.and_then(|v| v.as_slice().try_into().ok());
// Validate all ports fit in u16
let validate_port = |p: u32| -> anyhow::Result<u16> {
u16::try_from(p).map_err(|_| anyhow::anyhow!("invalid port number: {}", p))
};
let public_udp_ports: Result<Vec<_>, _> = msg
.public_udp_ports
.into_iter()
.map(validate_port)
.collect();
let local_udp_ports: Result<Vec<_>, _> =
msg.local_udp_ports.into_iter().map(validate_port).collect();
Ok(rust_p2p_core::nat::NatInfo {
nat_type,
public_ips: msg.public_ips.into_iter().map(|v| v.into()).collect(),
public_udp_ports: public_udp_ports?,
mapping_tcp_addr: vec![],
mapping_udp_addr: vec![],
public_port_range: validate_port(msg.public_port_range)?,
local_ipv4: msg
.local_ipv4s
.first()
.map(|v| (*v).into())
.unwrap_or(Ipv4Addr::UNSPECIFIED),
local_ipv4s: msg.local_ipv4s.into_iter().map(|v| v.into()).collect(),
ipv6: ipv6.map(Ipv6Addr::from),
local_udp_ports: local_udp_ports?,
local_tcp_port: validate_port(msg.local_tcp_port)?,
public_tcp_port: validate_port(msg.public_tcp_port)?,
})
}
#[derive(Clone, Debug)]
pub struct PunchInfo {
pub nat_info: rust_p2p_core::nat::NatInfo,
}
impl PunchInfo {
pub fn from_slice(buf: &[u8]) -> anyhow::Result<Self> {
let msg = proto::PunchInfo::decode(buf)?;
let Some(nat_info) = msg.nat_info else {
bail!("Punched info decode failed.");
};
let nat_info = decode_nat_info(nat_info)?;
Ok(Self { nat_info })
}
pub fn encode(&self) -> BytesMut {
let message = proto::PunchInfo {
nat_info: Some(encode_nat_info(&self.nat_info)),
};
message.encode_bytes_mut()
}
}
+275
View File
@@ -0,0 +1,275 @@
use crate::protocol::ProtoToBytesMut;
pub(crate) use crate::protocol::control_message::proto::SelectiveBroadcast;
use crate::protocol::control_message::proto::request_message::RequestPayload;
use crate::protocol::control_message::proto::response_message::ResponsePayload;
use anyhow::bail;
use bytes::BytesMut;
use prost::Message;
use std::net::Ipv4Addr;
mod proto {
include!(concat!(env!("OUT_DIR"), "/protocol.control_message.rs"));
}
#[derive(Debug, Clone, Copy, Eq, PartialEq, Default)]
pub enum RegistrationMode {
#[default]
Normal = 0,
PreRegister = 1,
}
impl From<RegistrationMode> for proto::RegistrationMode {
fn from(mode: RegistrationMode) -> Self {
match mode {
RegistrationMode::Normal => proto::RegistrationMode::Normal,
RegistrationMode::PreRegister => proto::RegistrationMode::PreRegister,
}
}
}
impl From<proto::RegistrationMode> for RegistrationMode {
fn from(mode: proto::RegistrationMode) -> Self {
match mode {
proto::RegistrationMode::Normal => RegistrationMode::Normal,
proto::RegistrationMode::PreRegister => RegistrationMode::PreRegister,
}
}
}
pub(crate) struct RegRequestMsg {
pub network_code: String,
pub device_id: String,
pub ip: Option<Ipv4Addr>,
pub name: String,
pub version: String,
pub key_sign: Option<String>,
pub ip_variable: bool,
pub server_id: u32,
pub registration_mode: RegistrationMode,
}
impl RegRequestMsg {
// pub fn check(&self) -> anyhow::Result<()> {
// if self.network_code.is_empty() {
// return Err(anyhow!("network_code cannot be empty"));
// }
// if self.network_code.len() > MAX_NETWORK_CODE_LEN {
// return Err(anyhow!(
// "network_code length exceeds {} characters (current: {})",
// MAX_NETWORK_CODE_LEN,
// self.network_code.len()
// ));
// }
// if self.device_id.is_empty() {
// return Err(anyhow!("device_id cannot be empty"));
// }
// if self.device_id.len() > MAX_DEVICE_ID_LEN {
// return Err(anyhow!(
// "device_id length exceeds {} characters (current: {})",
// MAX_DEVICE_ID_LEN,
// self.device_id.len()
// ));
// }
//
// if self.name.len() > MAX_NAME_LEN {
// return Err(anyhow!(
// "name length exceeds {} characters (current: {})",
// MAX_NAME_LEN,
// self.name.len()
// ));
// }
//
// if self.version.len() > MAX_VERSION_LEN {
// return Err(anyhow!(
// "version length exceeds {} characters (current: {})",
// MAX_VERSION_LEN,
// self.version.len()
// ));
// }
//
// Ok(())
// }
// pub fn from(msg: proto::RegRequestMsg) -> anyhow::Result<Self> {
// Ok(Self {
// network_code: msg.network_code,
// device_id: msg.device_id,
// ip: msg.ip.map(|ip| ip.into()),
// name: msg.name,
// version: msg.version,
// key_sign: msg.key_sign,
// ip_variable: msg.ip_variable,
// server_id: msg.server_id,
// })
// }
pub fn to(self) -> proto::RegRequestMsg {
proto::RegRequestMsg {
network_code: self.network_code,
device_id: self.device_id,
ip: self.ip.map(|ip| ip.into()),
name: self.name,
version: self.version,
key_sign: self.key_sign,
ip_variable: self.ip_variable,
server_id: self.server_id,
registration_mode: proto::RegistrationMode::from(self.registration_mode).into(),
}
}
}
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct RegResponseMsg {
pub ip: Ipv4Addr,
pub prefix_len: u8,
pub gateway: Ipv4Addr,
pub server_version: String,
}
impl RegResponseMsg {
pub fn from(msg: proto::RegResponseMsg) -> anyhow::Result<Self> {
Ok(Self {
ip: msg.ip.into(),
prefix_len: (msg.prefix_len & 0xFF) as u8,
gateway: msg.gateway.into(),
server_version: msg.server_version,
})
}
pub fn to(self) -> proto::RegResponseMsg {
proto::RegResponseMsg {
ip: self.ip.into(),
prefix_len: self.prefix_len as _,
gateway: self.gateway.into(),
server_version: self.server_version,
}
}
}
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct ErrorResponseMsg {
pub code: u32,
pub message: String,
}
impl ErrorResponseMsg {
pub fn from(msg: proto::ErrorResponseMsg) -> anyhow::Result<Self> {
Ok(Self {
code: msg.code,
message: msg.message,
})
}
pub fn to(self) -> proto::ErrorResponseMsg {
proto::ErrorResponseMsg {
code: self.code,
message: self.message,
}
}
}
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct ConfirmRegResponseMsg {
pub success: bool,
}
impl ConfirmRegResponseMsg {
pub fn from(msg: proto::ConfirmRegResponseMsg) -> anyhow::Result<Self> {
Ok(Self {
success: msg.success,
})
}
pub fn to(self) -> proto::ConfirmRegResponseMsg {
proto::ConfirmRegResponseMsg {
success: self.success,
}
}
}
pub(crate) enum RequestMessage {
Reg(RegRequestMsg),
ConfirmReg,
}
impl RequestMessage {
pub fn encode(self) -> BytesMut {
let request_payload = match self {
RequestMessage::Reg(reg) => RequestPayload::Reg(reg.to()),
RequestMessage::ConfirmReg => RequestPayload::ConfirmReg(proto::ConfirmRegMsg {}),
};
proto::RequestMessage {
request_payload: Some(request_payload),
}
.encode_bytes_mut()
}
}
#[derive(Debug, Clone, Eq, PartialEq)]
pub enum ResponseMessage {
Reg(RegResponseMsg),
Error(ErrorResponseMsg),
ConfirmReg(ConfirmRegResponseMsg),
}
impl ResponseMessage {
pub fn from_slice(buf: &[u8]) -> anyhow::Result<Self> {
let msg = proto::ResponseMessage::decode(buf)?;
let Some(payload) = msg.response_payload else {
bail!("unsupported")
};
match payload {
ResponsePayload::Reg(reg) => Ok(ResponseMessage::Reg(RegResponseMsg::from(reg)?)),
ResponsePayload::Error(e) => Ok(ResponseMessage::Error(ErrorResponseMsg::from(e)?)),
ResponsePayload::ConfirmReg(c) => {
Ok(ResponseMessage::ConfirmReg(ConfirmRegResponseMsg::from(c)?))
}
}
}
pub fn encode(self) -> BytesMut {
let response_payload = match self {
ResponseMessage::Reg(reg) => ResponsePayload::Reg(reg.to()),
ResponseMessage::Error(e) => ResponsePayload::Error(e.to()),
ResponseMessage::ConfirmReg(c) => ResponsePayload::ConfirmReg(c.to()),
};
proto::ResponseMessage {
response_payload: Some(response_payload),
}
.encode_bytes_mut()
}
}
impl SelectiveBroadcast {
pub fn new(ips: &[Ipv4Addr], data: Vec<u8>) -> Self {
SelectiveBroadcast {
ips: ips.iter().map(|v| (*v).into()).collect(),
data,
}
}
}
#[derive(Debug, Clone)]
pub struct ClientSimpleInfo {
pub ip: Ipv4Addr,
pub online: bool,
}
impl ClientSimpleInfo {
pub fn from(msg: proto::ClientSimpleInfo) -> anyhow::Result<Self> {
Ok(Self {
ip: msg.ip.into(),
online: msg.online,
})
}
pub fn to(self) -> proto::ClientSimpleInfo {
proto::ClientSimpleInfo {
ip: self.ip.into(),
online: self.online,
}
}
}
#[derive(Debug)]
pub struct ClientSimpleInfoList {
pub data_version: u64,
pub list: Vec<ClientSimpleInfo>,
pub is_all: bool,
pub time: i64,
}
impl ClientSimpleInfoList {
pub fn from_slice(buf: &[u8]) -> anyhow::Result<Self> {
let msg = proto::ClientSimpleInfoList::decode(buf)?;
let mut list = Vec::with_capacity(msg.list.len());
for x in msg.list {
list.push(ClientSimpleInfo::from(x)?);
}
Ok(Self {
data_version: msg.data_version,
list,
is_all: msg.is_all,
time: msg.time,
})
}
}
+302
View File
@@ -0,0 +1,302 @@
/*
0 15 31
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 1 | msg_type(7) |max ttl(4) |curr ttl(4)| C | G | R | reserve(13) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| seq(32) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| src ID(32) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| dest ID(32) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| payload(n) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
*/
#![allow(dead_code)]
use crate::protocol::transmission::TransmissionBytes;
use bytes::{Bytes, BytesMut};
use std::io;
use zerocopy::byteorder::{NetworkEndian, U32};
use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout, Ref, Unaligned};
#[derive(Debug, FromBytes, IntoBytes, Unaligned, KnownLayout, Immutable)]
#[repr(C)]
pub struct NetHeader {
/// Byte 0: bit7 = 1, bit0..6 = msg_type
pub type_byte: u8,
/// Byte 1: high 4 = max ttl, low 4 = curr ttl
pub ttl_byte: u8,
/// Byte 2: C(0x80) | G(0x40) | reserve
pub flags_byte: u8,
/// Byte 3: reserve
pub _reserved: u8,
pub seq: U32<NetworkEndian>,
pub src_id: U32<NetworkEndian>,
pub dest_id: U32<NetworkEndian>,
}
const COMPRESSED: u8 = 0x80;
const GATEWAY: u8 = 0x40;
const FEC: u8 = 0x20;
impl NetHeader {
#[inline]
pub fn msg_type(&self) -> u8 {
self.type_byte & 0x7F
}
#[inline]
pub fn set_msg_type(&mut self, msg_type: u8) {
self.type_byte = (msg_type & 0x7F) | 0x80;
}
#[inline]
pub fn max_ttl(&self) -> u8 {
self.ttl_byte >> 4
}
#[inline]
pub fn curr_ttl(&self) -> u8 {
self.ttl_byte & 0x0F
}
#[inline]
pub fn set_ttl(&mut self, max: u8, curr: u8) {
self.ttl_byte = (max << 4) | (curr & 0x0F);
}
#[inline]
pub fn decr_ttl(&mut self) {
let curr = self.curr_ttl();
if curr == 0 {
return;
}
self.ttl_byte = (self.ttl_byte & 0xF0) | (curr - 1);
}
fn set_flag(&mut self, mask: u8, val: bool) {
if val {
self.flags_byte |= mask;
} else {
self.flags_byte &= !mask;
}
}
}
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
pub enum MsgType {
Turn = 1,
Broadcast = 2,
ExcludeBroadcast = 3,
TargetBroadcast = 4,
Ping = 5,
Pong = 6,
PingTurn = 7,
PongTurn = 8,
PunchStart1 = 9,
PunchStart2 = 10,
PunchReq = 11,
PunchRes = 12,
PushClientIps = 13,
RpcReq = 14,
RpcRes = 15,
Quic = 17,
}
impl From<MsgType> for u8 {
fn from(val: MsgType) -> Self {
val as u8
}
}
impl TryFrom<u8> for MsgType {
type Error = io::Error;
fn try_from(value: u8) -> Result<Self, Self::Error> {
let val = match value {
1 => MsgType::Turn,
2 => MsgType::Broadcast,
3 => MsgType::ExcludeBroadcast,
4 => MsgType::TargetBroadcast,
5 => MsgType::Ping,
6 => MsgType::Pong,
7 => MsgType::PingTurn,
8 => MsgType::PongTurn,
9 => MsgType::PunchStart1,
10 => MsgType::PunchStart2,
11 => MsgType::PunchReq,
12 => MsgType::PunchRes,
13 => MsgType::PushClientIps,
14 => MsgType::RpcReq,
15 => MsgType::RpcRes,
17 => MsgType::Quic,
_ => {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
format!("invalid msg type:{value}"),
));
}
};
Ok(val)
}
}
pub const HEAD_LENGTH: usize = std::mem::size_of::<NetHeader>();
pub struct NetPacket<B> {
buffer: B,
}
impl<B: AsRef<[u8]>> NetPacket<B> {
pub fn new(buffer: B) -> io::Result<NetPacket<B>> {
if buffer.as_ref().len() < HEAD_LENGTH {
return Err(io::ErrorKind::InvalidInput.into());
}
Ok(NetPacket { buffer })
}
fn header(&self) -> Ref<&[u8], NetHeader> {
// Safe: NetHeader is Unaligned and length is validated in new()
let (header, _) = Ref::<&[u8], NetHeader>::from_prefix(self.buffer.as_ref()).unwrap();
header
}
pub fn buffer(&self) -> &[u8] {
self.buffer.as_ref()
}
pub fn into_buffer(self) -> B {
self.buffer
}
pub fn source_buf(&self) -> &B {
&self.buffer
}
pub fn msg_type(&self) -> io::Result<MsgType> {
self.header().msg_type().try_into()
}
pub fn max_ttl(&self) -> u8 {
self.header().max_ttl()
}
pub fn ttl(&self) -> u8 {
self.header().curr_ttl()
}
pub fn seq(&self) -> u32 {
self.header().seq.get()
}
pub fn src_id(&self) -> u32 {
self.header().src_id.get()
}
pub fn dest_id(&self) -> u32 {
self.header().dest_id.get()
}
pub fn is_compressed(&self) -> bool {
(self.header().flags_byte & COMPRESSED) != 0
}
pub fn is_gateway(&self) -> bool {
(self.header().flags_byte & GATEWAY) != 0
}
pub fn is_fec(&self) -> bool {
(self.header().flags_byte & FEC) != 0
}
pub fn head(&self) -> &[u8] {
&self.buffer.as_ref()[..HEAD_LENGTH]
}
pub fn payload(&self) -> &[u8] {
&self.buffer.as_ref()[HEAD_LENGTH..]
}
}
impl<B: AsRef<[u8]> + AsMut<[u8]>> NetPacket<B> {
fn header_mut(&mut self) -> Ref<&mut [u8], NetHeader> {
// Safe: NetHeader is Unaligned and length is validated in new()
let (header, _) = Ref::<&mut [u8], NetHeader>::from_prefix(self.buffer.as_mut()).unwrap();
header
}
pub fn set_msg_type(&mut self, msg_type: MsgType) {
self.header_mut().set_msg_type(msg_type.into());
}
pub fn decr_ttl(&mut self){
self.header_mut().decr_ttl()
}
pub fn set_ttl(&mut self, ttl: u8) {
self.header_mut().set_ttl(ttl, ttl);
}
pub fn set_seq(&mut self, seq: u32) {
self.header_mut().seq.set(seq);
}
pub fn set_src_id(&mut self, id: u32) {
self.header_mut().src_id.set(id);
}
pub fn set_dest_id(&mut self, id: u32) {
self.header_mut().dest_id.set(id);
}
pub fn set_compressed_flag(&mut self, compressed: bool) {
self.header_mut().set_flag(COMPRESSED, compressed);
}
pub fn set_gateway_flag(&mut self, gateway: bool) {
self.header_mut().set_flag(GATEWAY, gateway);
}
pub fn set_fec_flag(&mut self, fec: bool) {
self.header_mut().set_flag(FEC, fec);
}
pub fn set_payload(&mut self, data: &[u8]) -> io::Result<()> {
let buf = self.buffer.as_mut();
if buf.len() < HEAD_LENGTH + data.len() {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"Invalid message length",
));
}
buf[HEAD_LENGTH..HEAD_LENGTH + data.len()].copy_from_slice(data);
Ok(())
}
pub fn head_mut(&mut self) -> &mut [u8] {
&mut self.buffer.as_mut()[..HEAD_LENGTH]
}
pub fn payload_mut(&mut self) -> &mut [u8] {
&mut self.buffer.as_mut()[HEAD_LENGTH..]
}
pub fn source_buf_mut(&mut self) -> &mut B {
&mut self.buffer
}
}
impl Clone for NetPacket<Bytes> {
fn clone(&self) -> Self {
NetPacket {
buffer: self.buffer.clone(),
}
}
}
impl NetPacket<BytesMut> {
pub fn into_bytes(self) -> NetPacket<Bytes> {
NetPacket {
buffer: self.buffer.freeze(),
}
}
}
impl NetPacket<TransmissionBytes> {
pub fn into_bytes(self) -> NetPacket<Bytes> {
NetPacket {
buffer: self.buffer.into_bytes().freeze(),
}
}
}
+21
View File
@@ -0,0 +1,21 @@
use bytes::BytesMut;
use prost::Message;
pub(crate) mod client_message;
pub mod control_message;
pub(crate) mod ip_packet_protocol;
pub(crate) mod rpc_message;
pub(crate) mod transmission;
pub trait ProtoToBytesMut: Message {
fn encode_bytes_mut(&self) -> BytesMut
where
Self: Sized,
{
let mut bytes_mut = BytesMut::with_capacity(self.encoded_len());
self.encode_raw(&mut bytes_mut);
bytes_mut
}
}
impl<T: Message> ProtoToBytesMut for T {}
+4
View File
@@ -0,0 +1,4 @@
mod proto {
include!(concat!(env!("OUT_DIR"), "/protocol.rpc.rs"));
}
pub use proto::*;
+266
View File
@@ -0,0 +1,266 @@
use bytes::{Buf, Bytes, BytesMut};
use std::borrow::{Borrow, BorrowMut};
use std::io;
use std::ops::{Deref, DerefMut};
const DEFAULT_BUF_SIZE: usize = 2048;
#[derive(Clone)]
pub struct TransmissionBytes {
buf: BytesMut,
start: usize,
end: usize,
}
impl From<BytesMut> for TransmissionBytes {
fn from(buf: BytesMut) -> TransmissionBytes {
let end = buf.len();
Self { buf, start: 0, end }
}
}
impl From<Bytes> for TransmissionBytes {
fn from(buf: Bytes) -> TransmissionBytes {
let end = buf.len();
Self {
buf: BytesMut::from(buf),
start: 0,
end,
}
}
}
impl From<&[u8]> for TransmissionBytes {
fn from(buf: &[u8]) -> TransmissionBytes {
let end = buf.len();
Self {
buf: BytesMut::from(buf),
start: 0,
end,
}
}
}
impl TransmissionBytes {
pub fn new_offset(start: usize) -> Self {
TransmissionBytes {
buf: BytesMut::zeroed(DEFAULT_BUF_SIZE),
start,
end: start,
}
}
pub fn new_offset_zeroed(start: usize) -> Self {
TransmissionBytes {
buf: BytesMut::zeroed(DEFAULT_BUF_SIZE),
start,
end: DEFAULT_BUF_SIZE,
}
}
pub fn zeroed(cap: usize) -> Self {
TransmissionBytes {
buf: BytesMut::zeroed(cap),
start: 0,
end: cap,
}
}
pub fn zeroed_size(size: usize, reserve: usize) -> Self {
TransmissionBytes {
buf: BytesMut::zeroed(size + reserve),
start: 0,
end: size,
}
}
#[allow(dead_code)]
pub fn with_capacity(head_room: usize, capacity: usize) -> Self {
TransmissionBytes {
buf: BytesMut::zeroed(capacity),
start: head_room,
end: head_room,
}
}
pub fn len(&self) -> usize {
self.end - self.start
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
#[allow(dead_code)]
pub fn capacity(&self) -> usize {
self.buf.capacity()
}
/// 头部可用空间(可向前扩展的字节数)
#[inline]
pub fn head_room(&self) -> usize {
self.start
}
/// 尾部可用空间(可向后扩展的字节数)
#[inline]
#[allow(dead_code)]
pub fn tail_room(&self) -> usize {
self.buf.capacity() - self.end
}
#[inline]
fn as_slice(&self) -> &[u8] {
&self.buf[self.start..self.end]
}
#[inline]
fn as_slice_mut(&mut self) -> &mut [u8] {
&mut self.buf[self.start..self.end]
}
pub fn put(&mut self, data: &[u8]) -> io::Result<()> {
let need = data.len();
let free = self.buf.capacity() - self.end;
if need > free {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
format!("data too large:need={need},free={free}"),
));
}
self.buf[self.end..self.end + need].copy_from_slice(data);
self.end += need;
Ok(())
}
pub fn retreat_head(&mut self, len: usize) -> io::Result<()> {
if len > self.head_room() {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
format!(
"retreat_head beyond start: len={len}, head_room={}",
self.head_room()
),
));
}
self.start -= len;
Ok(())
}
pub fn advance_head(&mut self, len: usize) -> io::Result<()> {
if len > self.len() {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
format!(
"advance_head beyond end: len={len}, data_len={}",
self.len()
),
));
}
self.start += len;
Ok(())
}
pub fn set_len(&mut self, new_len: usize) -> io::Result<()> {
let new_end = self.start + new_len;
if new_end > self.buf.capacity() {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
format!(
"set_len exceeds capacity: new_len={new_len}, max={}",
self.buf.capacity() - self.start
),
));
}
self.end = new_end;
Ok(())
}
pub fn resize(&mut self, new_len: usize, value: u8) {
let new_end = self.start + new_len;
self.buf.resize(new_end, value);
self.end = new_end;
}
pub fn extend_end(&mut self, n: usize) {
if self.end + n > self.buf.len() {
self.buf.resize(self.end + n, 0);
}
self.end += n;
}
pub fn shrink_end(&mut self, n: usize) {
if n >= self.end - self.start {
self.end = self.start;
} else {
self.end -= n;
}
}
#[allow(dead_code)]
pub fn clear(&mut self) {
self.start = 0;
self.end = 0;
}
pub fn into_bytes(mut self) -> BytesMut {
self.buf.truncate(self.end);
if self.start > 0 {
self.buf.advance(self.start);
}
self.buf
}
}
impl AsRef<[u8]> for TransmissionBytes {
#[inline]
fn as_ref(&self) -> &[u8] {
self.as_slice()
}
}
impl Deref for TransmissionBytes {
type Target = [u8];
#[inline]
fn deref(&self) -> &[u8] {
self.as_ref()
}
}
impl AsMut<[u8]> for TransmissionBytes {
#[inline]
fn as_mut(&mut self) -> &mut [u8] {
self.as_slice_mut()
}
}
impl DerefMut for TransmissionBytes {
#[inline]
fn deref_mut(&mut self) -> &mut [u8] {
self.as_mut()
}
}
impl Borrow<[u8]> for TransmissionBytes {
fn borrow(&self) -> &[u8] {
self.as_ref()
}
}
impl BorrowMut<[u8]> for TransmissionBytes {
fn borrow_mut(&mut self) -> &mut [u8] {
self.as_mut()
}
}
pub trait ShrinkEnd {
fn shrink_end(&mut self, n: usize);
}
pub trait ExtendEnd {
fn extend_end(&mut self, n: usize);
}
impl ShrinkEnd for TransmissionBytes {
fn shrink_end(&mut self, n: usize) {
self.shrink_end(n);
}
}
impl ExtendEnd for TransmissionBytes {
fn extend_end(&mut self, n: usize) {
self.extend_end(n);
}
}
impl ShrinkEnd for &mut TransmissionBytes {
fn shrink_end(&mut self, n: usize) {
TransmissionBytes::shrink_end(self, n);
}
}
impl ExtendEnd for &mut TransmissionBytes {
fn extend_end(&mut self, n: usize) {
TransmissionBytes::extend_end(self, n);
}
}
+114
View File
@@ -0,0 +1,114 @@
use anyhow::{Context, Result};
use rcgen::{CertificateParams, DnType, KeyPair, PKCS_ED25519, SerialNumber};
use rustls::pki_types;
use rustls::pki_types::{CertificateDer, PrivateKeyDer, PrivatePkcs8KeyDer};
use sha2::{Digest, Sha256};
use time::{Duration, OffsetDateTime};
const ED25519_PKCS8_V1_PREFIX: [u8; 16] = [
0x30, 0x2e, // Sequence (len 46)
0x02, 0x01, 0x00, // Version 0
0x30, 0x05, // Sequence (len 5)
0x06, 0x03, 0x2b, 0x65, 0x70, // OID: 1.3.101.112 (Ed25519)
0x04, 0x22, // Octet String (len 34) - 包装私钥
0x04, 0x20, // Octet String (len 32) - 内部 CurvePrivateKey
];
pub fn generate_deterministic_cert(
password: &str,
) -> Result<(CertificateDer<'static>, PrivateKeyDer<'static>)> {
// 基于密码生成 32 字节的确定性种子
let seed = derive_seed_from_password(password);
let mut pkcs8_bytes = Vec::with_capacity(ED25519_PKCS8_V1_PREFIX.len() + seed.len());
pkcs8_bytes.extend_from_slice(&ED25519_PKCS8_V1_PREFIX);
pkcs8_bytes.extend_from_slice(&seed);
let private_key_der = pki_types::PrivateKeyDer::try_from(pkcs8_bytes.clone())
.map_err(|e| anyhow::anyhow!("Failed to convert private key: {}", e))?;
let key_pair = KeyPair::from_der_and_sign_algo(&private_key_der, &PKCS_ED25519)
.context("Failed to load determinstic Ed25519 key")?;
let mut params = CertificateParams::new(vec!["deterministic-node".to_string()])?;
params
.distinguished_name
.push(DnType::CommonName, "Deterministic Self-Signed Cert");
let not_before = OffsetDateTime::UNIX_EPOCH;
let not_after = not_before + Duration::days(365 * 1000);
params.not_before = not_before;
params.not_after = not_after;
let serial_number_bytes = derive_serial_number(password);
params.serial_number = Some(SerialNumber::from_slice(&serial_number_bytes));
// Ed25519 签名是确定性的 (RFC 8032),不需要随机数,因此每次运行结果字节完全一致
let cert = params
.self_signed(&key_pair)
.context("Failed to sign certificate")?;
let cert_der = cert.der().clone();
let private_key_der = PrivateKeyDer::Pkcs8(PrivatePkcs8KeyDer::from(pkcs8_bytes));
Ok((cert_der, private_key_der))
}
fn derive_serial_number(password: &str) -> [u8; 20] {
let mut hasher = Sha256::new();
hasher.update(b"vnt-serial-v1:");
hasher.update(password.as_bytes());
let result = hasher.finalize();
let mut serial = [0u8; 20];
serial.copy_from_slice(&result[..20]);
serial
}
fn derive_seed_from_password(password: &str) -> [u8; 32] {
let mut hasher = Sha256::new();
hasher.update(b"vnt-ed25519-seed-v1:");
hasher.update(password.as_bytes());
let mut result = hasher.finalize();
for _ in 0..10 {
let mut hasher = Sha256::new();
hasher.update(result);
result = hasher.finalize();
}
result.into()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_deterministic_cert_generation() {
let password = "test_password_123";
// 生成两次证书
let (cert1, key1) = generate_deterministic_cert(password).unwrap();
let (cert2, key2) = generate_deterministic_cert(password).unwrap();
// 验证私钥相同
assert_eq!(
key1.secret_der(),
key2.secret_der(),
"Private keys should be identical"
);
// 验证证书相同
assert_eq!(cert1, cert2, "Certificates should be identical");
}
#[test]
fn test_different_passwords_generate_different_certs() {
let (cert1, key1) = generate_deterministic_cert("password1").unwrap();
let (cert2, key2) = generate_deterministic_cert("password2").unwrap();
// 不同密码应该生成不同的证书和密钥
assert_ne!(cert1, cert2);
assert_ne!(key1.secret_der(), key2.secret_der());
}
}
+2
View File
@@ -0,0 +1,2 @@
pub(crate) mod cert;
pub mod verifier;
+228
View File
@@ -0,0 +1,228 @@
use anyhow::Context;
use rustls::client::danger::{HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier};
use rustls::pki_types::{CertificateDer, ServerName, UnixTime};
use rustls::{CertificateError, ClientConfig, Error, SignatureScheme};
use sha2::{Digest, Sha256};
use std::fmt;
use std::str::FromStr;
#[derive(Debug)]
pub struct FingerprintVerifier {
pub expected_fingerprint: [u8; 32],
}
impl FingerprintVerifier {
pub fn new(expected_fingerprint: [u8; 32]) -> Self {
Self {
expected_fingerprint,
}
}
}
impl ServerCertVerifier for FingerprintVerifier {
fn verify_server_cert(
&self,
end_entity: &CertificateDer<'_>,
_intermediates: &[CertificateDer<'_>],
_server_name: &ServerName,
_ocsp_response: &[u8],
_now: UnixTime,
) -> Result<ServerCertVerified, Error> {
let mut hasher = Sha256::new();
hasher.update(end_entity.as_ref());
let calculated_hash: [u8; 32] = hasher.finalize().into();
if calculated_hash == self.expected_fingerprint {
Ok(ServerCertVerified::assertion())
} else {
log::error!(
"Certificate fingerprint mismatch. Expected: {:X?}, Got: {:X?}",
self.expected_fingerprint,
calculated_hash
);
Err(Error::InvalidCertificate(CertificateError::BadSignature))
}
}
fn verify_tls12_signature(
&self,
_message: &[u8],
_cert: &CertificateDer<'_>,
_dss: &rustls::DigitallySignedStruct,
) -> Result<HandshakeSignatureValid, rustls::Error> {
Ok(HandshakeSignatureValid::assertion())
}
fn verify_tls13_signature(
&self,
_message: &[u8],
_cert: &CertificateDer<'_>,
_dss: &rustls::DigitallySignedStruct,
) -> Result<HandshakeSignatureValid, rustls::Error> {
Ok(HandshakeSignatureValid::assertion())
}
fn supported_verify_schemes(&self) -> Vec<rustls::SignatureScheme> {
vec![
// RSA schemes
rustls::SignatureScheme::RSA_PKCS1_SHA256,
rustls::SignatureScheme::RSA_PKCS1_SHA384,
rustls::SignatureScheme::RSA_PKCS1_SHA512,
rustls::SignatureScheme::RSA_PSS_SHA256,
rustls::SignatureScheme::RSA_PSS_SHA384,
rustls::SignatureScheme::RSA_PSS_SHA512,
// ECDSA schemes
rustls::SignatureScheme::ECDSA_NISTP256_SHA256,
rustls::SignatureScheme::ECDSA_NISTP384_SHA384,
rustls::SignatureScheme::ECDSA_NISTP521_SHA512,
// EdDSA schemes
rustls::SignatureScheme::ED25519,
rustls::SignatureScheme::ED448,
]
}
}
#[derive(Debug)]
pub struct InsecureVerifier;
impl ServerCertVerifier for InsecureVerifier {
fn verify_server_cert(
&self,
_end_entity: &CertificateDer<'_>,
_intermediates: &[CertificateDer<'_>],
_server_name: &ServerName,
_ocsp_response: &[u8],
_now: UnixTime,
) -> Result<rustls::client::danger::ServerCertVerified, Error> {
Ok(rustls::client::danger::ServerCertVerified::assertion())
}
fn verify_tls12_signature(
&self,
_message: &[u8],
_cert: &CertificateDer<'_>,
_dss: &rustls::DigitallySignedStruct,
) -> Result<HandshakeSignatureValid, Error> {
Ok(HandshakeSignatureValid::assertion())
}
fn verify_tls13_signature(
&self,
_message: &[u8],
_cert: &CertificateDer<'_>,
_dss: &rustls::DigitallySignedStruct,
) -> Result<HandshakeSignatureValid, Error> {
Ok(HandshakeSignatureValid::assertion())
}
fn supported_verify_schemes(&self) -> Vec<SignatureScheme> {
vec![
SignatureScheme::RSA_PKCS1_SHA256,
SignatureScheme::RSA_PKCS1_SHA384,
SignatureScheme::RSA_PKCS1_SHA512,
SignatureScheme::RSA_PSS_SHA256,
SignatureScheme::RSA_PSS_SHA384,
SignatureScheme::RSA_PSS_SHA512,
SignatureScheme::ECDSA_NISTP256_SHA256,
SignatureScheme::ECDSA_NISTP384_SHA384,
SignatureScheme::ECDSA_NISTP521_SHA512,
SignatureScheme::ED25519,
SignatureScheme::ED448,
]
}
}
pub fn load_root_cert() -> anyhow::Result<rustls::RootCertStore> {
let mut root_cert_store = rustls::RootCertStore::empty();
let certs = rustls_native_certs::load_native_certs().certs;
for cert in certs {
root_cert_store
.add(cert)
.context("Failed to add native cert to store")?;
}
Ok(root_cert_store)
}
#[derive(Debug, Clone, Default)]
pub enum CertValidationMode {
#[default]
InsecureSkipVerification,
VerifyFingerprint([u8; 32]),
Standard,
}
impl FromStr for CertValidationMode {
type Err = String;
fn from_str(value: &str) -> Result<Self, Self::Err> {
let val = value.trim().to_lowercase();
if val == "skip" {
return Ok(CertValidationMode::InsecureSkipVerification);
}
if val == "standard" {
return Ok(CertValidationMode::Standard);
}
if let Some(hex_str) = val.strip_prefix("finger:") {
let decoded =
hex::decode(hex_str).map_err(|e| format!("Invalid hex in fingerprint: {}", e))?;
if decoded.len() != 32 {
return Err(format!(
"Fingerprint must be 32 bytes (64 hex chars), got {} bytes",
decoded.len()
));
}
let mut arr = [0u8; 32];
arr.copy_from_slice(&decoded);
return Ok(CertValidationMode::VerifyFingerprint(arr));
}
Err(format!("Unknown certificate validation mode: {}", value))
}
}
impl fmt::Display for CertValidationMode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
CertValidationMode::InsecureSkipVerification => {
write!(f, "skip")
}
CertValidationMode::Standard => {
write!(f, "standard")
}
CertValidationMode::VerifyFingerprint(fingerprint) => {
let hex_str = hex::encode(fingerprint);
write!(f, "finger:{}", hex_str)
}
}
}
}
impl CertValidationMode {
pub fn build_verifier(&self) -> anyhow::Result<std::sync::Arc<dyn ServerCertVerifier>> {
match self {
CertValidationMode::InsecureSkipVerification => {
Ok(std::sync::Arc::new(InsecureVerifier))
}
CertValidationMode::VerifyFingerprint(fingerprint) => {
Ok(std::sync::Arc::new(FingerprintVerifier {
expected_fingerprint: *fingerprint,
}))
}
CertValidationMode::Standard => {
let root_store = load_root_cert()?;
let verifier =
rustls::client::WebPkiServerVerifier::builder(std::sync::Arc::new(root_store))
.build()?;
Ok(verifier)
}
}
}
pub fn create_tls_client_config(&self) -> anyhow::Result<ClientConfig> {
let verifier = self.build_verifier()?;
let config = ClientConfig::builder()
.dangerous()
.with_custom_certificate_verifier(verifier)
.with_no_client_auth();
Ok(config)
}
}
+18
View File
@@ -0,0 +1,18 @@
use crate::context::NetworkAddr;
use crate::nat::internal_nat::InternalNatInbound;
use crate::protocol::transmission::TransmissionBytes;
use crate::tun::TunDataInbound;
#[derive(Clone)]
pub enum EnhancedTunInbound {
Tun(TunDataInbound),
Nat(InternalNatInbound),
}
impl EnhancedTunInbound {
pub async fn inbound(&self, data: TransmissionBytes, net: &NetworkAddr) -> anyhow::Result<()> {
match self {
EnhancedTunInbound::Tun(tun) => tun.send(data, net).await,
EnhancedTunInbound::Nat(nat) => nat.send(&data, net).await,
}
}
}
+236
View File
@@ -0,0 +1,236 @@
use crate::enhanced_tunnel::outbound::EnhancedOutbound;
use crate::protocol::ip_packet_protocol::HEAD_LENGTH;
use crate::protocol::transmission::TransmissionBytes;
use crate::utils::task_control::{SubTask, TaskGroup};
use anyhow::{Context, bail};
use bytes::BytesMut;
use futures::{SinkExt, StreamExt};
use std::io;
use std::net::Ipv4Addr;
use std::sync::Arc;
use tokio::sync::mpsc::{Receiver, Sender};
use tun_rs::async_framed::{Decoder, DeviceFramedRead, DeviceFramedWrite, Encoder};
use tun_rs::{AsyncDevice, DeviceBuilder};
#[derive(Clone)]
pub struct DeviceIOManager {
task_group: TaskGroup,
device: DeviceMutex,
}
type DeviceMutex = Arc<tokio::sync::Mutex<(Option<DeviceTask>, Option<(Ipv4Addr, u8)>)>>;
pub struct DeviceTask {
device: Arc<AsyncDevice>,
task_recv: SubTask,
task_send: SubTask,
}
#[derive(Debug, Default)]
pub struct DeviceConfig {
pub tun_name: Option<String>,
#[cfg(unix)]
pub tun_fd: Option<i32>,
pub mtu: Option<u16>,
}
impl DeviceConfig {
pub fn set_tun_name(mut self, tun_name: String) -> Self {
self.tun_name = Some(tun_name);
self
}
#[cfg(unix)]
pub fn set_tun_fd(mut self, tun_fd: i32) -> Self {
self.tun_fd = Some(tun_fd);
self
}
pub fn set_mtu(mut self, mtu: u16) -> Self {
self.mtu = Some(mtu);
self
}
}
#[derive(Clone)]
pub struct TunInbound {
pub(crate) sender: Sender<TransmissionBytes>,
}
pub struct TunReceiver {
receiver: Receiver<TransmissionBytes>,
}
pub fn tun_channel() -> (TunInbound, TunReceiver) {
let (sender, receiver) = tokio::sync::mpsc::channel(1024);
(TunInbound { sender }, TunReceiver { receiver })
}
impl DeviceIOManager {
pub fn new(task_group: TaskGroup) -> DeviceIOManager {
Self {
task_group,
device: Arc::new(Default::default()),
}
}
pub async fn stop_task(&self) {
let mut guard = self.device.lock().await;
if let Some(dev) = guard.0.take() {
dev.task_recv.stop().await;
dev.task_send.stop().await;
}
}
pub async fn start_task(
&self,
device_config: DeviceConfig,
receiver: TunReceiver,
enhanced_outbound: EnhancedOutbound,
) -> anyhow::Result<()> {
self.stop_task().await;
let task = create(
&self.task_group,
device_config,
receiver.receiver,
enhanced_outbound,
)?;
self.device.lock().await.0.replace(task);
Ok(())
}
#[cfg(not(target_os = "android"))]
pub async fn tun_if_index(&self) -> anyhow::Result<u32> {
let guard = self.device.lock().await;
if let Some(v) = &guard.0 {
Ok(v.device.if_index()?)
} else {
bail!("device doesn't exist")
}
}
pub async fn set_network(&self, ip: Ipv4Addr, prefix_len: u8) -> anyhow::Result<()> {
let mut guard = self.device.lock().await;
let Some(dev) = guard.0.as_ref() else {
bail!("未启动tun")
};
if let Some(v) = guard.1.as_ref()
&& v.0 == ip
&& v.1 == prefix_len
{
return Ok(());
}
dev.device
.set_network_address(ip, prefix_len, None)
.context("设置IP失败")?;
guard.1 = Some((ip, prefix_len));
Ok(())
}
}
fn create_tun(config: DeviceConfig) -> anyhow::Result<AsyncDevice> {
#[cfg(unix)]
if let Some(fd) = config.tun_fd {
// SAFETY: Caller must ensure fd is a valid, open file descriptor for a TUN device.
// Using an invalid fd may cause undefined behavior.
unsafe { return Ok(AsyncDevice::from_fd(fd)?) }
}
let mut builder = DeviceBuilder::new();
if let Some(tun_name) = config.tun_name {
builder = builder.name(tun_name);
}
if let Some(mtu) = config.mtu {
builder = builder.mtu(mtu);
}
#[cfg(windows)]
{
builder = builder.metric(1);
}
#[cfg(target_os = "linux")]
{
builder = builder.offload(true);
}
let dev = builder.build_async().context("创建tun失败")?;
#[cfg(target_os = "linux")]
{
_ = dev.set_tx_queue_len(1000);
}
Ok(dev)
}
fn create(
task_group: &TaskGroup,
config: DeviceConfig,
receiver: Receiver<TransmissionBytes>,
enhanced_outbound: EnhancedOutbound,
) -> anyhow::Result<DeviceTask> {
let device = Arc::new(create_tun(config)?);
let device_framed_read = DeviceFramedRead::new(device.clone(), BytesCodec::new());
let device_framed_write = DeviceFramedWrite::new(device.clone(), BytesCodec::new());
let task_recv = task_group.spawn(async move {
if let Err(e) = in_tun_loop(receiver, device_framed_write).await {
log::error!("in_tun_loop error: {e:?}")
}
});
let task_send = task_group.spawn(async move {
if let Err(e) = out_tun_loop(device_framed_read, enhanced_outbound).await {
log::error!("out_tun_loop error: {e:?}");
}
});
Ok(DeviceTask {
device,
task_recv,
task_send,
})
}
async fn in_tun_loop(
mut receiver: Receiver<TransmissionBytes>,
mut device_framed_write: DeviceFramedWrite<BytesCodec, Arc<AsyncDevice>>,
) -> anyhow::Result<()> {
while let Some(data) = receiver.recv().await {
match device_framed_write.send(data).await {
Ok(_) => {}
Err(e) => {
log::error!("send to tun error: {:?}", e);
return Err(anyhow::anyhow!(e));
}
}
}
Ok(())
}
async fn out_tun_loop(
mut device_framed_read: DeviceFramedRead<BytesCodec, Arc<AsyncDevice>>,
enhanced_outbound: EnhancedOutbound,
) -> anyhow::Result<()> {
while let Some(rs) = device_framed_read.next().await {
let bytes_mut = rs?;
enhanced_outbound.ipv4_outbound(bytes_mut).await;
}
Ok(())
}
#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Default)]
pub struct BytesCodec(());
impl BytesCodec {
pub fn new() -> BytesCodec {
BytesCodec(())
}
}
impl Decoder for BytesCodec {
type Item = TransmissionBytes;
type Error = io::Error;
fn decode(&mut self, buf: &mut BytesMut) -> Result<Option<TransmissionBytes>, io::Error> {
if !buf.is_empty() {
let mut bytes = TransmissionBytes::new_offset(HEAD_LENGTH);
bytes.put(buf)?;
buf.clear();
Ok(Some(bytes))
} else {
Ok(None)
}
}
}
impl Encoder<TransmissionBytes> for BytesCodec {
type Error = io::Error;
fn encode(&mut self, data: TransmissionBytes, buf: &mut BytesMut) -> Result<(), io::Error> {
buf.reserve(data.len());
buf.extend_from_slice(&data);
Ok(())
}
}
+6
View File
@@ -0,0 +1,6 @@
mod general;
pub use general::*;
mod sender;
pub use sender::*;
pub mod enhanced_tun;
+40
View File
@@ -0,0 +1,40 @@
use crate::context::NetworkAddr;
use crate::nat::AllowSubnetExternalRoute;
use crate::protocol::transmission::TransmissionBytes;
use crate::tun::TunInbound;
use pnet_packet::ipv4::Ipv4Packet;
#[derive(Clone)]
pub struct TunDataInbound {
allow_subnet: AllowSubnetExternalRoute,
tun_inbound: TunInbound,
}
impl TunDataInbound {
pub fn new(tun_inbound: TunInbound, allow_subnet: AllowSubnetExternalRoute) -> Self {
Self {
allow_subnet,
tun_inbound,
}
}
}
impl TunDataInbound {
pub async fn send(&self, data: TransmissionBytes, net: &NetworkAddr) -> anyhow::Result<()> {
if data[0] >> 4 != 4 {
return Ok(());
}
let Some(ipv4) = Ipv4Packet::new(data.as_ref()) else {
return Ok(());
};
let dest = ipv4.get_destination();
if net.network().contains(&dest)
|| dest == net.broadcast
|| dest.is_broadcast()
|| dest.is_multicast()
|| self.allow_subnet.allow(&dest)
{
self.tun_inbound.sender.send(data).await?;
}
Ok(())
}
}
+4
View File
@@ -0,0 +1,4 @@
pub(crate) mod p2p;
pub mod server;
pub(crate) mod outbound;
+286
View File
@@ -0,0 +1,286 @@
use crate::compression::PacketCompression;
use crate::context::{NetworkAddr, ServerInfoCollection, SharedNetworkAddr, TrafficStats};
use crate::crypto::PacketCrypto;
use crate::fec::FecEncoder;
use crate::nat::SubnetExternalRoute;
use crate::protocol::ip_packet_protocol::{HEAD_LENGTH, MsgType, NetPacket};
use crate::protocol::transmission::TransmissionBytes;
use crate::tunnel_core::p2p::outbound::P2pOutbound;
use crate::tunnel_core::server::outbound::ServerOutbound;
use anyhow::bail;
use bytes::Bytes;
use pnet_packet::ipv4::Ipv4Packet;
use std::net::Ipv4Addr;
#[derive(Clone)]
pub(crate) struct BasicOutbound {
server_outbound: ServerOutbound,
p2p_outbound: Option<P2pOutbound>,
packet_crypto: PacketCrypto,
}
impl BasicOutbound {
pub fn new(
server_outbound: ServerOutbound,
p2p_outbound: Option<P2pOutbound>,
packet_crypto: PacketCrypto,
) -> Self {
Self {
server_outbound,
p2p_outbound,
packet_crypto,
}
}
/// 获取加密保留空间大小
pub fn encrypt_reserve(&self) -> usize {
self.packet_crypto.encrypt_reserve()
}
/// 加密数据包
pub fn encrypt_in_place(
&self,
packet: &mut NetPacket<TransmissionBytes>,
) -> anyhow::Result<()> {
Ok(self.packet_crypto.encrypt_in_place(packet)?)
}
/// 发送原始数据包到指定目标(通过P2P或服务器)
pub async fn send_raw(
&self,
dest: Ipv4Addr,
packet: NetPacket<TransmissionBytes>,
) -> anyhow::Result<()> {
let packet = packet.into_bytes();
if let Some(p2p) = self.p2p_outbound.as_ref()
&& let Some(route) = p2p.get_route_by_id(&dest)
{
p2p.send_raw_to(packet, &route.route_key()).await?;
} else {
self.server_outbound.send_raw(dest, packet).await?;
}
Ok(())
}
/// 发送到默认服务器
pub async fn send_default_raw(
&self,
packet: NetPacket<TransmissionBytes>,
) -> anyhow::Result<()> {
let bytes = packet.into_buffer().into_bytes().freeze();
self.server_outbound
.send_default_raw(NetPacket::new(bytes)?)
.await
}
/// 广播发送
pub async fn send_raw_broadcast(
&self,
exclude_ips: Option<Vec<Ipv4Addr>>,
packet: NetPacket<Bytes>,
) -> anyhow::Result<()> {
self.server_outbound
.send_raw_broadcast(exclude_ips, packet)
.await
}
/// 检查是否存在到目标的路由
pub fn exists_route(&self, dest: &Ipv4Addr) -> bool {
if let Some(p2p) = self.p2p_outbound.as_ref()
&& p2p.exists_route_by_id(dest)
{
return true;
}
self.server_outbound.exists_route(dest)
}
/// P2P广播(内部转换类型)
pub fn p2p_broadcast_transmission(
&self,
list: &[Ipv4Addr],
max_count: usize,
packet: &NetPacket<Bytes>,
) -> Option<Vec<Ipv4Addr>> {
if let Some(p2p) = self.p2p_outbound.as_ref() {
let vec = p2p.p2p_broadcast(list, max_count, packet);
if vec.is_empty() { None } else { Some(vec) }
} else {
None
}
}
/// 发送加密后的数据包
pub async fn send_encrypted_packet(
&self,
dest: Ipv4Addr,
mut packet: NetPacket<TransmissionBytes>,
) -> anyhow::Result<()> {
// 加密
self.packet_crypto.encrypt_in_place(&mut packet)?;
// 发送
if let Some(p2p) = self.p2p_outbound.as_ref()
&& let Some(route) = p2p.get_route_by_id(&dest)
{
let bytes = packet.into_buffer().into_bytes().freeze();
p2p.send_raw_to(NetPacket::new(bytes)?, &route.route_key())
.await?;
} else {
let bytes = packet.into_buffer().into_bytes().freeze();
self.server_outbound
.send_raw(dest, NetPacket::new(bytes)?)
.await?;
}
Ok(())
}
}
#[derive(Clone)]
pub(crate) struct HybridOutbound {
network: SharedNetworkAddr,
server_info: ServerInfoCollection,
traffic_stats: TrafficStats,
basic_outbound: BasicOutbound,
packet_compression: PacketCompression,
external_route: SubnetExternalRoute,
fec_encoder: Option<FecEncoder>,
}
impl HybridOutbound {
pub fn new(
network: SharedNetworkAddr,
server_info: ServerInfoCollection,
traffic_stats: TrafficStats,
basic_outbound: BasicOutbound,
packet_compression: PacketCompression,
external_route: SubnetExternalRoute,
fec_encoder: Option<FecEncoder>,
) -> Self {
Self {
network,
server_info,
traffic_stats,
basic_outbound,
packet_compression,
external_route,
fec_encoder,
}
}
pub async fn outbound_raw(
&self,
dest: Ipv4Addr,
mut packet: NetPacket<TransmissionBytes>,
) -> anyhow::Result<()> {
if packet.src_id() == 0 {
if let Some(ip) = self.network.ip() {
packet.set_src_id(ip.into());
} else {
bail!("Not src ip")
}
}
let len = packet.buffer().len() as u64;
if let Some(fec_encoder) = &self.fec_encoder {
packet = fec_encoder.encode(packet)?;
}
self.basic_outbound.send_raw(dest, packet).await?;
self.traffic_stats.record_tx(dest, len);
Ok(())
}
pub async fn ipv4_outbound_common(&self, data: TransmissionBytes) -> anyhow::Result<()> {
let Some(net) = self.network.get() else {
bail!("Not src ip")
};
self.ipv4_outbound(net, data).await
}
pub async fn ipv4_outbound(
&self,
net: NetworkAddr,
mut data: TransmissionBytes,
) -> anyhow::Result<()> {
let Some(ipv4) = Ipv4Packet::new(data.as_ref()) else {
return Ok(());
};
let mut dest = ipv4.get_destination();
let len = data.len() as u64;
data.retreat_head(HEAD_LENGTH)?;
let mut packet = NetPacket::new(data)?;
packet.set_msg_type(MsgType::Turn);
packet.set_src_id(net.ip.into());
packet.set_ttl(5);
// 路由
if !net.network().contains(&dest) {
if let Some(v) = self.external_route.route(&dest) {
dest = v;
} else {
return Ok(());
}
}
packet.set_dest_id(dest.into());
packet = self
.packet_compression
.compress(packet, self.basic_outbound.encrypt_reserve())?;
if let Some(fec_encoder) = &self.fec_encoder {
packet = fec_encoder.encode(packet)?;
}
// 发送
self.basic_outbound
.send_encrypted_packet(dest, packet)
.await?;
self.traffic_stats.record_tx(dest, len);
Ok(())
}
pub async fn ipv4_gateway_outbound(
&self,
net: NetworkAddr,
mut data: TransmissionBytes,
) -> anyhow::Result<()> {
data.retreat_head(HEAD_LENGTH)?;
let mut packet = NetPacket::new(data)?;
packet.set_msg_type(MsgType::Turn);
packet.set_src_id(net.ip.into());
packet.set_dest_id(net.gateway.into());
packet.set_ttl(5);
packet.set_gateway_flag(true);
self.basic_outbound.send_default_raw(packet).await?;
Ok(())
}
pub async fn ipv4_broadcast_outbound(
&self,
net: NetworkAddr,
mut data: TransmissionBytes,
) -> anyhow::Result<()> {
data.retreat_head(HEAD_LENGTH)?;
let mut packet = NetPacket::new(data)?;
packet.set_msg_type(MsgType::Broadcast);
packet.set_src_id(net.ip.into());
packet.set_dest_id(Ipv4Addr::BROADCAST.into());
packet.set_ttl(5);
let mut packet = self
.packet_compression
.compress(packet, self.basic_outbound.encrypt_reserve())?;
self.basic_outbound.encrypt_in_place(&mut packet)?;
let packet_bytes = packet.into_bytes();
let list = self.server_info.client_online_ips();
let exclude_ips = self
.basic_outbound
.p2p_broadcast_transmission(&list, 16, &packet_bytes);
if let Some(exclude_ips) = &exclude_ips
&& exclude_ips.len() == list.len()
{
return Ok(());
}
self.basic_outbound
.send_raw_broadcast(exclude_ips, packet_bytes)
.await
}
#[allow(dead_code)]
pub fn has_route(&self, dest: &Ipv4Addr) -> bool {
self.basic_outbound.exists_route(dest)
}
}
+268
View File
@@ -0,0 +1,268 @@
use crate::compression::PacketCompression;
use crate::context::{NetworkRoute, PacketLossStats};
use crate::crypto::PacketCrypto;
use crate::enhanced_tunnel::inbound::EnhancedInbound;
use crate::fec::FecDecoder;
use crate::protocol::ip_packet_protocol::{HEAD_LENGTH, MsgType, NetPacket};
use crate::protocol::transmission::TransmissionBytes;
use crate::tunnel_core::p2p::outbound::P2pOutbound;
use crate::tunnel_core::p2p::route_table::{Route, RouteTable};
use anyhow::bail;
use rust_p2p_core::route::RouteKey;
use rust_p2p_core::tunnel::Tunnel;
use std::net::{IpAddr, Ipv4Addr};
struct PacketContext {
msg_type: MsgType,
src_ip: Ipv4Addr,
dest_ip: Ipv4Addr,
max_ttl: u8,
ttl: u8,
}
pub(crate) struct P2pInboundConfig {
pub network_route: NetworkRoute,
pub route_table: RouteTable,
pub packet_loss_stats: PacketLossStats,
pub packet_crypto: PacketCrypto,
pub packet_compression: PacketCompression,
pub enhanced_inbound: EnhancedInbound,
pub fec_decoder: FecDecoder,
}
#[derive(Clone)]
pub(crate) struct P2pInboundHandler {
network_route: NetworkRoute,
route_table: RouteTable,
packet_loss_stats: PacketLossStats,
packet_crypto: PacketCrypto,
packet_compression: PacketCompression,
enhanced_inbound: EnhancedInbound,
fec_decoder: FecDecoder,
}
impl P2pInboundHandler {
pub fn new(config: P2pInboundConfig) -> Self {
Self {
network_route: config.network_route,
route_table: config.route_table,
packet_loss_stats: config.packet_loss_stats,
packet_crypto: config.packet_crypto,
packet_compression: config.packet_compression,
enhanced_inbound: config.enhanced_inbound,
fec_decoder: config.fec_decoder,
}
}
fn network_contains(&self, ip: &Ipv4Addr) -> bool {
self.network_route.network_contains(ip)
}
pub async fn next_handle(
&self,
buf: TransmissionBytes,
route_key: RouteKey,
p2p_socket_manager: &P2pOutbound,
tunnel: &mut Tunnel,
) {
if let Err(e) = self
.next_handle_impl(buf, route_key, p2p_socket_manager, tunnel)
.await
{
log::warn!(
"Error while handling P2pInboundHandler: {:?},route={route_key:?}",
e
);
}
}
async fn next_handle_impl(
&self,
buf: TransmissionBytes,
route_key: RouteKey,
p2p_socket_manager: &P2pOutbound,
tunnel: &mut Tunnel,
) -> anyhow::Result<()> {
let mut net_packet = NetPacket::new(buf)?;
let msg_type = net_packet.msg_type()?;
let src_ip = Ipv4Addr::from(net_packet.src_id());
let dest_ip = Ipv4Addr::from(net_packet.dest_id());
if src_ip == dest_ip {
return Ok(());
}
net_packet.decr_ttl();
let max_ttl = net_packet.max_ttl();
let ttl = net_packet.ttl();
if max_ttl <= ttl {
return Ok(());
}
let Some(net) = self.network_route.network.get() else {
bail!("未找到自身IP")
};
if net.ip != dest_ip
&& !dest_ip.is_broadcast()
&& !dest_ip.is_unspecified()
&& dest_ip != net.broadcast
{
// 帮忙转发数据包
if ttl >= 1 {
if let Some(route) = p2p_socket_manager.get_route_by_id(&dest_ip) {
p2p_socket_manager
.send_raw_to(net_packet.into_bytes(), &route.route_key())
.await?;
} else {
log::debug!("未找到到 {} 的路由,无法转发", dest_ip);
}
}
return Ok(());
}
if msg_type == MsgType::Quic {
if net_packet.is_fec() {
let packets = self.fec_decoder.receive(net_packet)?;
if let Some(packets) = packets {
for pkt in packets {
self.enhanced_inbound
.inbound(&net, msg_type, src_ip, pkt)
.await?;
}
}
return Ok(());
}
self.enhanced_inbound
.inbound(&net, msg_type, src_ip, net_packet)
.await?;
return Ok(());
}
// 解密
self.packet_crypto.decrypt_in_place(&mut net_packet)?;
let ctx = PacketContext {
msg_type,
src_ip,
dest_ip,
max_ttl,
ttl,
};
// FEC 解码(始终尝试解码,如果有 FEC 标志)
if net_packet.is_fec() {
let packets = self.fec_decoder.receive(net_packet)?;
if let Some(packets) = packets {
for pkt in packets {
let pkt = self.packet_compression.decompress(pkt)?;
self.process_decompressed_packet(&net, route_key, tunnel, pkt, &ctx)
.await?;
}
}
return Ok(());
}
// 解压缩
let net_packet = self.packet_compression.decompress(net_packet)?;
self.process_decompressed_packet(&net, route_key, tunnel, net_packet, &ctx)
.await
}
async fn process_decompressed_packet(
&self,
net: &crate::context::NetworkAddr,
route_key: RouteKey,
tunnel: &mut Tunnel,
net_packet: NetPacket<TransmissionBytes>,
ctx: &PacketContext,
) -> anyhow::Result<()> {
match ctx.msg_type {
MsgType::Turn | MsgType::Broadcast | MsgType::ExcludeBroadcast => {
self.enhanced_inbound
.inbound(net, ctx.msg_type, ctx.src_ip, net_packet)
.await?;
}
MsgType::Ping => {
let metric = ctx.max_ttl - ctx.ttl;
self.route_table
.add_route_if_absent(ctx.src_ip, Route::from_default_rt(route_key, metric));
let mut packet = NetPacket::new(TransmissionBytes::zeroed_size(
HEAD_LENGTH + 8,
self.packet_crypto.encrypt_reserve(),
))?;
packet.set_msg_type(MsgType::Pong);
packet.set_ttl(1);
packet.set_src_id(ctx.dest_ip.into());
packet.set_dest_id(ctx.src_ip.into());
packet.set_payload(net_packet.payload())?;
self.packet_crypto.encrypt_in_place(&mut packet)?;
tunnel
.send_to(packet.into_bytes().into_buffer(), route_key.addr())
.await?;
}
MsgType::Pong => {
if net_packet.payload().len() >= 8 {
let metric = ctx.max_ttl - ctx.ttl;
let time = i64::from_be_bytes(net_packet.payload()[..8].try_into()?);
let now = crate::utils::time::now_ts_ms();
if now >= time {
self.route_table.add_route(
ctx.src_ip,
Route::from(route_key, metric, (now - time) as _),
);
self.packet_loss_stats.record_received(ctx.src_ip);
}
}
}
MsgType::PunchStart1 => {}
MsgType::PunchStart2 => {}
MsgType::PunchReq => {
if let IpAddr::V4(ip) = route_key.addr().ip()
&& self.network_contains(&ip)
{
log::info!("===========loop PunchReq {route_key:?} {:?}", ctx.src_ip);
return Ok(());
}
log::info!(
"PunchReq 打洞成功 {}->{},route={route_key:?}",
ctx.src_ip,
ctx.dest_ip
);
self.route_table.add_owner_route(ctx.src_ip, route_key);
let mut packet = NetPacket::new(TransmissionBytes::zeroed_size(
HEAD_LENGTH + 8,
self.packet_crypto.encrypt_reserve(),
))?;
packet.set_msg_type(MsgType::PunchRes);
packet.set_ttl(1);
packet.set_src_id(ctx.dest_ip.into());
packet.set_dest_id(ctx.src_ip.into());
packet.set_payload(&crate::utils::time::now_ts_ms().to_be_bytes())?;
self.packet_crypto.encrypt_in_place(&mut packet)?;
tunnel
.send_to(packet.into_bytes().into_buffer(), route_key.addr())
.await?;
}
MsgType::PunchRes => {
if let IpAddr::V4(ip) = route_key.addr().ip()
&& self.network_contains(&ip)
{
log::info!("===========loop PunchRes {route_key:?} {:?}", ctx.src_ip);
return Ok(());
}
log::info!(
"PunchRes 打洞成功 {}->{},route={route_key:?}",
ctx.src_ip,
ctx.dest_ip
);
self.route_table.add_owner_route(ctx.src_ip, route_key);
}
MsgType::PingTurn => {}
MsgType::PongTurn => {}
_ => {}
}
Ok(())
}
pub async fn tcp_disconnect(&self, route_key: RouteKey) {
if let Some(ip) = self.route_table.get_id_by_route_key(&route_key) {
self.route_table.remove_route(&ip, &route_key);
}
}
}
+5
View File
@@ -0,0 +1,5 @@
pub(crate) mod inbound;
pub(crate) mod outbound;
pub(crate) mod transport;
pub(crate) mod route_table;
+133
View File
@@ -0,0 +1,133 @@
use crate::crypto::PacketCrypto;
use crate::protocol::ip_packet_protocol::NetPacket;
use crate::protocol::transmission::TransmissionBytes;
use crate::tunnel_core::p2p::route_table::{Route, RouteTable};
use bytes::Bytes;
use rust_p2p_core::route::RouteKey;
use rust_p2p_core::tunnel::SocketManager;
use std::net::Ipv4Addr;
#[derive(Clone)]
pub(crate) struct P2pOutbound {
manager: SocketManager,
route_table: RouteTable,
packet_crypto: PacketCrypto,
}
impl P2pOutbound {
pub fn new(
manager: SocketManager,
route_table: RouteTable,
packet_crypto: PacketCrypto,
) -> Self {
Self {
manager,
route_table,
packet_crypto,
}
}
pub fn encrypt_reserve(&self) -> usize {
self.packet_crypto.encrypt_reserve()
}
// pub async fn send_raw(&self, buf: NetPacket<Bytes>) -> anyhow::Result<()> {
// let dest_id = Ipv4Addr::from(buf.dest_id());
// let route = self.route_table.get_route_by_id(&dest_id)?;
// self.manager
// .send_to(buf.into_buffer(), &route.route_key())
// .await?;
// Ok(())
// }
// pub async fn send(&self, mut buf: NetPacket<TransmissionBytes>) -> anyhow::Result<()> {
// let dest_id = Ipv4Addr::from(buf.dest_id());
// let route = self.route_table.get_route_by_id(&dest_id)?;
// self.packet_crypto.encrypt_in_place(&mut buf)?;
// self.manager
// .send_to(buf.into_buffer().into_bytes().freeze(), &route.route_key())
// .await?;
// Ok(())
// }
pub async fn send_raw_to(
&self,
buf: NetPacket<Bytes>,
route_key: &RouteKey,
) -> anyhow::Result<()> {
self.manager.send_to(buf.into_buffer(), route_key).await?;
Ok(())
}
pub async fn send_to(
&self,
mut buf: NetPacket<TransmissionBytes>,
route_key: &RouteKey,
) -> anyhow::Result<()> {
self.packet_crypto.encrypt_in_place(&mut buf)?;
self.manager
.send_to(buf.into_buffer().into_bytes().freeze(), route_key)
.await?;
Ok(())
}
pub fn get_route_by_id(&self, id: &Ipv4Addr) -> Option<Route> {
self.route_table.get_route_by_id(id).ok()
}
pub fn get_p2p_route_by_id(&self, id: &Ipv4Addr) -> Option<Route> {
self.route_table.get_route_by_id(id).ok().filter(|v| v.is_direct())
}
pub fn exists_route_by_id(&self, id: &Ipv4Addr) -> bool {
self.route_table.exists(id)
}
// pub async fn send_to_id(
// &self,
// buf: NetPacket<TransmissionBytes>,
// id: &Ipv4Addr,
// ) -> anyhow::Result<bool> {
// let Ok(route) = self.route_table.get_route_by_id(id) else {
// return Ok(false);
// };
// self.send_to(buf, &route.route_key()).await?;
// Ok(true)
// }
// pub fn try_send_to_id(
// &self,
// buf: NetPacket<TransmissionBytes>,
// id: &Ipv4Addr,
// ) -> anyhow::Result<bool> {
// let Ok(route) = self.route_table.get_route_by_id(id) else {
// return Ok(false);
// };
// self.try_send_to(buf, &route.route_key())?;
// Ok(true)
// }
// pub fn try_send_to(
// &self,
// buf: NetPacket<TransmissionBytes>,
// route_key: &RouteKey,
// ) -> anyhow::Result<()> {
// self.manager
// .try_send_to(buf.into_buffer().into_bytes(), route_key)?;
// Ok(())
// }
pub fn p2p_broadcast(
&self,
ips: &[Ipv4Addr],
max: usize,
buf: &NetPacket<Bytes>,
) -> Vec<Ipv4Addr> {
let mut list = Vec::with_capacity(ips.len().min(max));
for id in ips {
let Some(route) = self.get_p2p_route_by_id(id) else {
continue;
};
if self
.manager
.try_send_to(buf.source_buf().clone(), &route.route_key())
.is_ok()
{
list.push(*id);
if list.len() >= max {
break;
}
}
}
list
}
}
+265
View File
@@ -0,0 +1,265 @@
use parking_lot::{Mutex, RwLock};
use rust_p2p_core::route::{RouteKey, DEFAULT_RTT};
use std::collections::HashMap;
use std::net::Ipv4Addr;
use std::sync::Arc;
use std::time::Instant;
#[derive(Copy, Clone, Debug)]
pub struct Route {
route_key: RouteKey,
metric: u8,
rtt: u32,
}
impl Route {
pub fn from(route_key: RouteKey, metric: u8, rtt: u32) -> Self {
Self {
route_key,
metric,
rtt,
}
}
pub fn from_default_rt(route_key: RouteKey, metric: u8) -> Self {
Self {
route_key,
metric,
rtt: DEFAULT_RTT,
}
}
pub fn route_key(&self) -> RouteKey {
self.route_key
}
pub fn is_direct(&self) -> bool {
self.metric == 1
}
pub fn rtt(&self) -> u32 {
self.rtt
}
pub fn metric(&self) -> u8 {
self.metric
}
}
#[derive(Clone)]
pub struct RouteTable {
inner: Arc<RouteTableInner>,
}
#[derive(Default)]
struct RouteTableInner {
route_table: RwLock<HashMap<Ipv4Addr, Vec<Route>>>,
route_key_time: Mutex<HashMap<(Ipv4Addr, RouteKey), Instant>>,
route_key_owner: Mutex<HashMap<RouteKey, Ipv4Addr>>,
}
impl Default for RouteTable {
fn default() -> Self {
Self::new()
}
}
impl RouteTable {
pub fn new() -> Self {
Self {
inner: Arc::new(RouteTableInner::default()),
}
}
/// 获取指定 ID 的最优路由
pub fn get_route_by_id(&self, id: &Ipv4Addr) -> anyhow::Result<Route> {
self.inner
.get_by_id(id)
.ok_or_else(|| anyhow::anyhow!("route not found for {}", id))
}
/// 检查是否存在到指定 ID 的路由
pub fn exists(&self, id: &Ipv4Addr) -> bool {
self.inner.get_by_id(id).is_some()
}
/// 判断是否需要打洞(没有路由或只有中继路由)
pub fn need_punch(&self, id: &Ipv4Addr) -> bool {
let guard = self.inner.route_table.read();
let Some(list) = guard.get(id) else {
return true;
};
// 如果没有直连路由(metric=1),则需要打洞
!list.iter().any(|r| r.is_direct())
}
/// 获取直连路由数量(用于判断是否直连)
pub fn p2p_num(&self, id: &Ipv4Addr) -> usize {
let guard = self.inner.route_table.read();
let Some(list) = guard.get(id) else {
return 0;
};
list.iter().filter(|r| r.is_direct()).count()
}
/// 添加 owner 路由(打洞请求响应时调用)
pub fn add_owner_route(&self, id: Ipv4Addr, key: RouteKey) {
self.inner.add_owner_route(id, key);
}
/// 添加路由(心跳时调用,用于更新路由时间和添加跨节点转发路由)
pub fn add_route(&self, id: Ipv4Addr, route: Route) {
self.inner.add_route(id, route);
}
/// 如果路由不存在则添加(用于 Ping 消息)
pub fn add_route_if_absent(&self, id: Ipv4Addr, route: Route) {
let guard = self.inner.route_table.read();
if guard.contains_key(&id) {
return;
}
drop(guard);
self.inner.add_route(id, route);
}
/// 获取所有路由表
pub fn route_table(&self) -> Vec<(Ipv4Addr, Vec<Route>)> {
let guard = self.inner.route_table.read();
guard.iter().map(|(k, v)| (*k, v.clone())).collect()
}
/// 根据 RouteKey 查找对应的 IP
pub fn get_id_by_route_key(&self, route_key: &RouteKey) -> Option<Ipv4Addr> {
let owner_map = self.inner.route_key_owner.lock();
owner_map.get(route_key).copied()
}
/// 移除指定 IP 和 RouteKey 的路由
pub fn remove_route(&self, id: &Ipv4Addr, route_key: &RouteKey) {
let mut table = self.inner.route_table.write();
let mut owner_map = self.inner.route_key_owner.lock();
let mut time_map = self.inner.route_key_time.lock();
if let Some(list) = table.get_mut(id) {
list.retain(|r| r.route_key() != *route_key);
if list.is_empty() {
table.remove(id);
}
}
if let Some(owner_id) = owner_map.get(route_key) {
if owner_id == id {
owner_map.remove(route_key);
}
}
time_map.remove(&(*id, *route_key));
}
/// 移除过期的路由
pub fn remove_oldest_route(&self, expired_time: Instant) {
self.inner.remove_oldest_route(expired_time);
}
}
impl RouteTableInner {
fn get_by_id(&self, id: &Ipv4Addr) -> Option<Route> {
let guard = self.route_table.read();
let list = guard.get(id)?;
list.first().cloned()
}
fn add_owner_route(&self, id: Ipv4Addr, key: RouteKey) {
let route = Route::from_default_rt(key, 1);
let mut guard = self.route_table.write();
self.route_key_owner.lock().insert(key, id);
self.route_key_time.lock().insert((id, key), Instant::now());
let list = guard.entry(id).or_insert_with(|| Vec::with_capacity(6));
if list.iter().any(|v| v.route_key() == key) {
return;
}
list.push(route);
}
fn add_route(&self, id: Ipv4Addr, route: Route) {
let key = route.route_key();
let mut guard = self.route_table.write();
// 检查是否是 owner 路由
let mut route_key_owner = self.route_key_owner.lock();
if route.is_direct() {
route_key_owner.entry(key).or_insert(id);
} else {
if !guard.contains_key(&id) {
return;
}
}
// 更新时间
self.route_key_time.lock().insert((id, key), Instant::now());
let list = guard.entry(id).or_insert_with(|| Vec::with_capacity(6));
// 如果路由已存,更新并重新排序
if let Some(idx) = list.iter().position(|v| v.route_key() == key) {
list[idx] = route;
// 向前冒泡(如果 RTT 更小)
let mut i = idx;
while i > 0 && list[i].rtt() < list[i - 1].rtt() {
list.swap(i, i - 1);
i -= 1;
}
// 向后冒泡(如果 RTT 更大)
while i + 1 < list.len() && list[i].rtt() > list[i + 1].rtt() {
list.swap(i, i + 1);
i += 1;
}
return;
}
// 插入新路由,保持按 RTT 排序
let mut pos = list.len();
for (i, r) in list.iter().enumerate() {
if route.rtt() < r.rtt() {
pos = i;
break;
}
}
list.insert(pos, route);
}
fn remove_oldest_route(&self, expired_time: Instant) {
let mut expired_keys = Vec::new();
{
let mut time_map = self.route_key_time.lock();
time_map.retain(|(id, route_key), t| {
if *t <= expired_time {
expired_keys.push((*id, *route_key));
false
} else {
true
}
});
}
if expired_keys.is_empty() {
return;
}
let mut table = self.route_table.write();
let mut owner_map = self.route_key_owner.lock();
for (id, route_key) in expired_keys {
if let Some(list) = table.get_mut(&id) {
list.retain(|r| r.route_key() != route_key);
if list.is_empty() {
table.remove(&id);
}
}
if let Some(owner_id) = owner_map.get(&route_key) {
if *owner_id == id {
owner_map.remove(&route_key);
}
}
}
}
}
@@ -0,0 +1,4 @@
pub(crate) mod nat_test;
pub(crate) mod punch;
pub(crate) mod task;
@@ -0,0 +1,387 @@
use crate::context::AppState;
use rust_p2p_core::nat::{NatInfo, NatType};
use rust_p2p_core::tunnel::SocketManager;
use rust_p2p_core::tunnel::udp::Model;
use std::collections::HashMap;
use std::io;
use std::net::{IpAddr, Ipv4Addr, SocketAddr, SocketAddrV4};
use std::time::Duration;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::net::TcpStream;
pub async fn my_nat_info(app_context: AppState, socket_manager: SocketManager) {
loop {
my_nat_info_impl(&app_context, &socket_manager).await;
tokio::time::sleep(Duration::from_secs(60 * 30)).await;
}
}
async fn my_nat_info_impl(app_context: &AppState, socket_manager: &SocketManager) {
let network = app_context.network.network();
let mut local_ipv4s = Vec::new();
let mut local_ipv6 = Vec::new();
match getifaddrs::getifaddrs() {
Ok(addrs) => {
for x in addrs {
let Some(ip) = x.address.ip_addr() else {
continue;
};
if ip.is_loopback() {
continue;
}
if ip.is_unspecified() {
continue;
}
if ip.is_multicast() {
continue;
}
match ip {
IpAddr::V4(addr) => {
if addr.is_documentation() {
continue;
}
if addr.is_broadcast() {
continue;
}
if let Some(network) = &network
&& network.contains(&addr)
{
continue;
}
local_ipv4s.push(addr);
}
IpAddr::V6(addr) => {
if addr.is_unique_local() {
continue;
}
if addr.is_unicast_link_local() {
continue;
}
local_ipv6.push(addr);
}
}
}
}
Err(e) => {
log::error!("getifaddrs error: {e}");
}
}
log::info!("local_ipv4s: {:?}", local_ipv4s);
let local_ipv4 = rust_p2p_core::extend::addr::local_ipv4()
.await
.unwrap_or_else(|e| {
log::warn!("local ipv4 failed {e:?}");
local_ipv4s
.first()
.cloned()
.unwrap_or(Ipv4Addr::UNSPECIFIED)
});
local_ipv4s = vec![local_ipv4];
let mut ipv6 = rust_p2p_core::extend::addr::local_ipv6().await.ok();
if let Some(addr) = ipv6 {
if addr.is_loopback()
|| addr.is_unique_local()
|| addr.is_unicast_link_local()
|| addr.is_unspecified()
|| addr.is_multicast()
{
ipv6 = local_ipv6.first().cloned();
}
} else {
ipv6 = local_ipv6.first().cloned();
}
let local_udp_ports = socket_manager
.udp_socket_manager_as_ref()
.unwrap()
.local_ports()
.unwrap();
let local_tcp_port = socket_manager
.tcp_socket_manager_as_ref()
.unwrap()
.local_addr()
.port();
log::info!(
"local_ipv4={local_ipv4},ipv6={ipv6:?},local_udp_ports:{local_udp_ports:?},local_tcp_port:{local_tcp_port:?}"
);
let mut public_ports = local_udp_ports.clone();
public_ports.fill(0);
let mut nat_info = NatInfo {
nat_type: NatType::Cone,
public_ips: vec![],
public_udp_ports: public_ports,
mapping_tcp_addr: vec![],
mapping_udp_addr: vec![],
public_port_range: 0,
local_ipv4s,
local_ipv4,
ipv6,
local_udp_ports,
local_tcp_port,
public_tcp_port: 0,
};
let mut stun_server = app_context.udp_stun();
if stun_server.is_empty() {
stun_server = default_udp_stun();
}
let (nat_type, public_ips, port_range) = rust_p2p_core::stun::stun_test_nat(stun_server, None)
.await
.unwrap_or_else(|e| {
log::warn!("stun_test_nat {e:?}");
(NatType::Cone, vec![], 0)
});
log::info!("nat_type:{nat_type:?},public_ips:{public_ips:?},port_range={port_range}");
nat_info.nat_type = nat_type;
nat_info.public_ips = public_ips;
nat_info.public_port_range = port_range;
app_context.nat_info.replace_nat_info(nat_info);
let model = match nat_type {
NatType::Cone => Model::Low,
NatType::Symmetric => Model::High,
};
if let Err(e) = socket_manager
.udp_socket_manager_as_ref()
.unwrap()
.switch_model(model)
{
log::error!("switch_model error: {e:?}");
}
}
pub async fn query_udp_public_addr_loop(app_context: AppState, socket_manager: SocketManager) {
let mut udp_stun_servers = app_context.udp_stun();
if udp_stun_servers.is_empty() {
udp_stun_servers = default_udp_stun();
}
let udp_len = udp_stun_servers.len();
let mut udp_count = 0;
let stun_request = rust_p2p_core::stun::send_stun_request();
loop {
let stun = &udp_stun_servers[udp_count % udp_len];
udp_count += 1;
match tokio::net::lookup_host(stun.as_str()).await {
Ok(mut addr) => {
if let Some(addr) = addr.next()
&& let Some(w) = socket_manager.udp_socket_manager_as_ref()
&& let Err(e) = w.detect_pub_addrs(&stun_request, addr).await
{
log::info!("detect_pub_addrs {e:?} {addr:?}");
}
}
Err(e) => {
log::info!("query_public_addr lookup_host {e:?} {stun:?}",);
}
}
let not_port = app_context
.get_nat_info()
.map(|v| v.public_udp_ports.contains(&0))
.unwrap_or(true);
if not_port {
tokio::time::sleep(Duration::from_secs(2)).await;
} else {
tokio::time::sleep(Duration::from_secs(60)).await;
}
}
}
pub(crate) async fn query_tcp_public_addr_loop(
app_context: AppState,
socket_manager: SocketManager,
) {
use rand::Rng;
use rand::seq::SliceRandom;
let tcp_stun_servers = {
let servers = app_context.tcp_stun();
if servers.is_empty() {
default_tcp_stun()
} else {
servers
}
};
if tcp_stun_servers.is_empty() {
return;
}
log::debug!("tcp_stun_servers = {tcp_stun_servers:?}");
let stun_request = rust_p2p_core::stun::send_stun_request();
let target_conn_count = tcp_stun_servers.len().min(2);
let mut active_connections: HashMap<SocketAddr, (TcpStream, SocketAddr)> = HashMap::new();
'outer: loop {
while active_connections.len() < target_conn_count {
let mut candidates: Vec<&String> = tcp_stun_servers.iter().collect();
candidates.shuffle(&mut rand::rng());
let mut connected = false;
for stun in candidates {
let addr = match tokio::net::lookup_host(stun.as_str()).await {
Ok(mut addrs) => addrs.next(),
Err(e) => {
log::debug!("lookup_host failed {stun} {e}");
continue;
}
};
let Some(addr) = addr else {
continue;
};
if active_connections.contains_key(&addr) {
continue;
}
let Some(w) = socket_manager.tcp_socket_manager_as_ref() else {
continue;
};
match tokio::time::timeout(Duration::from_secs(5), w.connect_reuse_port_raw(addr))
.await
{
Ok(Ok(mut tcp_stream)) => {
let write_result = tokio::time::timeout(
Duration::from_secs(5),
tcp_stream.write_all(&stun_request),
)
.await;
if let Ok(Ok(_)) = write_result {
match stun_tcp_read(&mut tcp_stream).await {
Ok(pub_addr) => {
log::debug!(
"update_tcp_public_addr {stun} {addr} -> {pub_addr}"
);
let existing_pub_addr =
active_connections.values().next().map(|(_, p)| *p);
if let Some(existing) = existing_pub_addr
&& existing != pub_addr
{
log::debug!(
"pub_addr mismatch: {existing} != {pub_addr}, wait 60s"
);
active_connections.clear();
app_context.nat_info.update_tcp_public_addr(
SocketAddrV4::new(Ipv4Addr::UNSPECIFIED, 0).into(),
);
tokio::time::sleep(Duration::from_secs(5 * 60)).await;
continue 'outer;
}
active_connections.insert(addr, (tcp_stream, pub_addr));
connected = true;
break;
}
Err(e) => {
log::debug!("stun_tcp_read failed {stun} {addr} {e}");
}
}
} else {
log::debug!("write stun request failed {stun} {addr}");
}
}
Ok(Err(e)) => {
log::debug!("connect_reuse_port_raw failed {stun} {addr} {e}");
}
Err(_) => {
log::debug!("connect_reuse_port_raw timeout {stun} {addr}");
}
}
}
if !connected {
break;
}
}
let existing_pub_addr = active_connections.values().next().map(|(_, p)| *p);
if let Some(existing) = existing_pub_addr {
app_context.nat_info.update_tcp_public_addr(existing);
}
let sleep_secs = rand::rng().random_range(10u64..=15);
tokio::time::sleep(Duration::from_secs(sleep_secs)).await;
let mut to_remove = Vec::new();
let addrs: Vec<SocketAddr> = active_connections.keys().cloned().collect();
for addr in addrs {
let (tcp_stream, _) = active_connections.get_mut(&addr).unwrap();
let mut buf = [0u8; 1024];
match tcp_stream.try_read(&mut buf) {
Ok(0) => {
log::warn!("stun tcp close {addr} EOF");
to_remove.push(addr);
continue;
}
Err(e) if e.kind() != std::io::ErrorKind::WouldBlock => {
log::warn!("stun tcp read error {addr} {e}");
to_remove.push(addr);
continue;
}
_ => {}
}
match tokio::time::timeout(Duration::from_secs(3), tcp_stream.write_all(&stun_request))
.await
{
Ok(Ok(_)) => {}
Ok(Err(e)) => {
log::warn!("stun tcp write error {addr} {e}");
to_remove.push(addr);
}
Err(_) => {
log::warn!("stun tcp write timeout {addr}");
to_remove.push(addr);
}
}
}
for addr in to_remove {
active_connections.remove(&addr);
}
}
}
async fn stun_tcp_read(tcp_stream: &mut TcpStream) -> io::Result<SocketAddr> {
let mut head = [0; 20];
match tokio::time::timeout(Duration::from_secs(5), tcp_stream.read_exact(&mut head)).await {
Ok(rs) => rs?,
Err(_) => Err(io::Error::from(io::ErrorKind::TimedOut))?,
};
let len = u16::from_be_bytes([head[2], head[3]]) as usize;
let mut buf = vec![0; len + 20];
buf[..20].copy_from_slice(&head);
match tokio::time::timeout(
Duration::from_secs(5),
tcp_stream.read_exact(&mut buf[20..]),
)
.await
{
Ok(rs) => rs?,
Err(_) => Err(io::Error::from(io::ErrorKind::TimedOut))?,
};
if let Some(addr) = rust_p2p_core::stun::recv_stun_response(&buf) {
Ok(addr)
} else {
log::debug!("stun_tcp_read {buf:?}");
Err(io::Error::from(io::ErrorKind::InvalidData))
}
}
fn default_udp_stun() -> Vec<String> {
vec![
"stun.miwifi.com:3478".to_string(),
"stun.chat.bilibili.com:3478".to_string(),
"stun.l.google.com:19302".to_string(),
]
}
fn default_tcp_stun() -> Vec<String> {
vec![
"stun.flashdance.cx:3478".to_string(),
"stun.sipnet.net:3478".to_string(),
"stun.nextcloud.com:443".to_string(),
]
}
@@ -0,0 +1,148 @@
use crate::context::nat::PunchBackoff;
use crate::context::{ServerInfoCollection, SharedNetworkAddr};
use crate::crypto::PacketCrypto;
use crate::protocol::client_message::PunchInfo;
use crate::protocol::ip_packet_protocol::{HEAD_LENGTH, MsgType, NetPacket};
use crate::protocol::transmission::TransmissionBytes;
use crate::tunnel_core::p2p::route_table::RouteTable;
use crate::tunnel_core::server::outbound::ServerOutbound;
use anyhow::bail;
use log::error;
use rand::seq::SliceRandom;
use rust_p2p_core::punch::{PunchModel, Puncher};
use std::net::Ipv4Addr;
use std::time::Duration;
pub struct PunchTaskContext {
pub network: SharedNetworkAddr,
pub server_info: ServerInfoCollection,
pub punch_backoff: PunchBackoff,
pub punch_info_getter: PunchInfoGetter,
}
pub type PunchInfoGetter = std::sync::Arc<dyn Fn() -> Option<PunchInfo> + Send + Sync>;
pub async fn punch_task(
tunnel_to_server: ServerOutbound,
route_table: RouteTable,
ctx: PunchTaskContext,
) -> anyhow::Result<()> {
loop {
tokio::time::sleep(Duration::from_secs(5)).await;
let Some(src_ip) = ctx.network.ip() else {
continue;
};
let Some(punch_info) = (ctx.punch_info_getter)() else {
continue;
};
let mut list = ctx.server_info.client_online_ips();
list.shuffle(&mut rand::rng());
list.truncate(5);
for dest_ip in list {
if dest_ip <= src_ip {
continue;
}
if ctx.server_info.is_any_server_connected(None) && route_table.need_punch(&dest_ip) {
if !ctx.punch_backoff.should_punch(dest_ip) {
continue;
}
log::info!("punching {dest_ip}");
let data = punch_info.encode();
let mut net_packet = NetPacket::new(TransmissionBytes::zeroed_size(
HEAD_LENGTH + data.len(),
tunnel_to_server.encrypt_reserve(),
))?;
net_packet.set_msg_type(MsgType::PunchStart1);
net_packet.set_ttl(2);
net_packet.set_src_id(src_ip.into());
net_packet.set_dest_id(dest_ip.into());
net_packet.set_payload(data.as_ref())?;
if let Err(e) = tunnel_to_server.send(dest_ip, net_packet).await {
error!("punch send error {:?}", e);
}
}
}
}
}
#[derive(Clone)]
pub struct NatPuncher {
network: SharedNetworkAddr,
punch_backoff: PunchBackoff,
puncher: Option<Puncher>,
packet_crypto: PacketCrypto,
}
impl NatPuncher {
pub fn new(
network: SharedNetworkAddr,
punch_backoff: PunchBackoff,
puncher: Option<Puncher>,
packet_crypto: PacketCrypto,
) -> Self {
Self {
network,
punch_backoff,
puncher,
packet_crypto,
}
}
pub fn punch(&self, dest_ip: Ipv4Addr, punch_info: PunchInfo) -> anyhow::Result<bool> {
if self.puncher.is_none() {
return Ok(false);
}
if !self.punch_backoff.should_punch(dest_ip) {
return Ok(false);
}
self.punch_uncheck_delay(dest_ip, punch_info, Some(Duration::from_millis(50)))?;
Ok(true)
}
pub fn punch_uncheck(&self, dest_ip: Ipv4Addr, punch_info: PunchInfo) -> anyhow::Result<()> {
self.punch_uncheck_delay(dest_ip, punch_info, None)
}
pub fn punch_uncheck_delay(
&self,
dest_ip: Ipv4Addr,
punch_info: PunchInfo,
time: Option<Duration>,
) -> anyhow::Result<()> {
let Some(puncher) = self.puncher.clone() else {
return Ok(());
};
let Some(src_ip) = self.network.ip() else {
bail!("not ip");
};
let packet_crypto = self.packet_crypto.clone();
tokio::spawn(async move {
if let Some(time) = time {
tokio::time::sleep(time).await;
}
if let Err(e) = punch_now(puncher, src_ip, dest_ip, punch_info, packet_crypto).await {
log::warn!("punch send error {:?}", e);
}
});
Ok(())
}
}
async fn punch_now(
puncher: Puncher,
src_ip: Ipv4Addr,
dest_ip: Ipv4Addr,
nat_info: PunchInfo,
packet_crypto: PacketCrypto,
) -> anyhow::Result<()> {
let mut packet = NetPacket::new(TransmissionBytes::zeroed_size(
HEAD_LENGTH + 8,
packet_crypto.encrypt_reserve(),
))?;
packet.set_msg_type(MsgType::PunchReq);
packet.set_ttl(1);
packet.set_src_id(src_ip.into());
packet.set_dest_id(dest_ip.into());
packet.set_payload(&crate::utils::time::now_ts_ms().to_be_bytes())?;
packet_crypto.encrypt_in_place(&mut packet)?;
let buf = packet.buffer();
let punch_info = rust_p2p_core::punch::PunchInfo::new(PunchModel::all(), nat_info.nat_info);
puncher.punch_now(Some(buf), buf, punch_info).await?;
Ok(())
}
@@ -0,0 +1,337 @@
use crate::context::nat::MyNatInfo;
use crate::context::{AppState, PacketLossStats, SharedNetworkAddr};
use crate::crypto::PacketCrypto;
use crate::protocol::ip_packet_protocol::{HEAD_LENGTH, MsgType, NetPacket};
use crate::protocol::transmission::TransmissionBytes;
use crate::tunnel_core::p2p::inbound::P2pInboundHandler;
use crate::tunnel_core::p2p::outbound::P2pOutbound;
use crate::tunnel_core::p2p::route_table::RouteTable;
use crate::tunnel_core::p2p::transport::nat_test::{
my_nat_info, query_tcp_public_addr_loop, query_udp_public_addr_loop,
};
use crate::tunnel_core::p2p::transport::punch::{PunchTaskContext, punch_task};
use crate::tunnel_core::server::outbound::ServerOutbound;
use crate::utils::task_control::TaskGroup;
use rust_p2p_core::punch::Puncher;
use rust_p2p_core::tunnel::{Tunnel, TunnelDispatcher, new_tunnel_component};
use std::net::Ipv4Addr;
use std::sync::Arc;
use std::time::Duration;
pub async fn init_tunnel(
task_group: TaskGroup,
app_state: AppState,
tunnel_to_server: ServerOutbound,
packet_crypto: PacketCrypto,
) -> anyhow::Result<(Puncher, P2pOutbound, P2pTask)> {
let udp_config = rust_p2p_core::tunnel::config::UdpTunnelConfig::default()
.set_main_udp_count(2)
.set_sub_udp_count(82);
let tcp_config = rust_p2p_core::tunnel::config::TcpTunnelConfig::new(Box::new(
rust_p2p_core::tunnel::tcp::LengthPrefixedInitCodec,
))
.set_tcp_multiplexing_limit(2);
let config = rust_p2p_core::tunnel::config::TunnelConfig::empty()
.set_udp_tunnel_config(udp_config)
.set_tcp_tunnel_config(tcp_config);
let (tunnel_dispatcher, puncher) = new_tunnel_component(config)?;
let route_table = app_state.route_table.clone();
let socket_manager = P2pOutbound::new(
tunnel_dispatcher.socket_manager(),
route_table.clone(),
packet_crypto,
);
task_group.spawn(my_nat_info(
app_state.clone(),
tunnel_dispatcher.socket_manager(),
));
let manager = tunnel_dispatcher.socket_manager();
task_group.spawn(query_udp_public_addr_loop(
app_state.clone(),
manager.clone(),
));
task_group.spawn(query_tcp_public_addr_loop(app_state.clone(), manager));
task_group.spawn(route_timeout_task(route_table.clone()));
let app_state_for_punch = app_state.clone();
let punch_ctx = PunchTaskContext {
network: app_state.network.clone(),
server_info: app_state.server_info_collection.clone(),
punch_backoff: app_state.punch_backoff.clone(),
punch_info_getter: Arc::new(move || app_state_for_punch.get_punch_info()),
};
task_group.spawn(punch_task(tunnel_to_server, route_table.clone(), punch_ctx));
task_group.spawn(ping_all(
app_state.network.clone(),
app_state.packet_loss_stats.clone(),
route_table.clone(),
socket_manager.clone(),
));
task_group.spawn(relay_probe_task(
app_state.network.clone(),
app_state.server_info_collection.clone(),
route_table.clone(),
socket_manager.clone(),
));
let p2p_task = P2pTask {
task_group,
nat_info: app_state.nat_info.clone(),
socket_manager: socket_manager.clone(),
tunnel_dispatcher,
};
Ok((puncher, socket_manager, p2p_task))
}
pub struct P2pTask {
task_group: TaskGroup,
nat_info: MyNatInfo,
socket_manager: P2pOutbound,
tunnel_dispatcher: TunnelDispatcher,
}
impl P2pTask {
pub fn start(self, p2p_inbound_handler: P2pInboundHandler) {
self.task_group.spawn(tunnel_dispatch_task(
self.nat_info,
self.task_group.clone(),
self.tunnel_dispatcher,
p2p_inbound_handler,
self.socket_manager,
));
}
}
pub async fn ping_all(
network: SharedNetworkAddr,
packet_loss_stats: PacketLossStats,
route_table: RouteTable,
socket_manager: P2pOutbound,
) {
loop {
tokio::time::sleep(Duration::from_secs(5)).await;
let Some(src) = network.ip() else {
continue;
};
let vec = route_table.route_table();
for (id, list) in vec {
for (index, route) in list.iter().enumerate() {
if index > 2 {
break;
}
let Ok(mut ping) = NetPacket::new(TransmissionBytes::zeroed_size(
HEAD_LENGTH + 8,
socket_manager.encrypt_reserve(),
)) else {
continue;
};
ping.set_msg_type(MsgType::Ping);
ping.set_ttl(1);
ping.set_src_id(src.into());
ping.set_dest_id(id.into());
ping.set_payload(&crate::utils::time::now_ts_ms().to_be_bytes())
.unwrap();
if socket_manager
.send_to(ping, &route.route_key())
.await
.is_ok()
{
packet_loss_stats.record_sent(id);
}
}
tokio::time::sleep(Duration::from_millis(10)).await;
}
}
}
pub async fn route_timeout_task(route_table: RouteTable) {
loop {
tokio::time::sleep(Duration::from_secs(10)).await;
let expired_time = std::time::Instant::now() - Duration::from_secs(10);
route_table.remove_oldest_route(expired_time);
}
}
/// 客户端中继探测任务
/// 每5分钟执行一次,找到所有未直连的目标IP,通过Ping消息发送给已打洞的客户端
pub async fn relay_probe_task(
network: SharedNetworkAddr,
server_info: crate::context::ServerInfoCollection,
route_table: RouteTable,
socket_manager: P2pOutbound,
) {
use rand::prelude::*;
loop {
tokio::time::sleep(Duration::from_secs(300)).await; // 5分钟
let Some(src) = network.ip() else {
continue;
};
let online_ips = server_info.client_online_ips();
if online_ips.is_empty() {
continue;
}
let mut non_direct_targets = Vec::new();
for ip in online_ips {
if ip == src {
continue;
}
let is_direct = route_table
.get_route_by_id(&ip)
.ok()
.map(|route| route.is_direct())
.unwrap_or(false);
if !is_direct {
non_direct_targets.push(ip);
if non_direct_targets.len() >= 20 {
break;
}
}
}
if non_direct_targets.is_empty() {
continue;
}
let non_direct_count = non_direct_targets.len();
let targets_to_probe: Vec<Ipv4Addr> = {
let mut rng = rand::rng();
if non_direct_targets.len() <= 10 {
non_direct_targets
} else {
non_direct_targets
.choose_multiple(&mut rng, 10)
.copied()
.collect()
}
};
let all_routes = route_table.route_table();
let mut direct_peers = Vec::new();
for (ip, routes) in &all_routes {
if let Some(best_route) = routes.first() {
if best_route.is_direct() {
direct_peers.push((*ip, best_route.route_key()));
}
}
}
if direct_peers.is_empty() {
log::debug!("No direct peers available for relay probe");
continue;
}
let max_probes_per_target = 3.min(direct_peers.len());
for target_ip in &targets_to_probe {
let selected_peers: Vec<_> = {
let mut rng = rand::rng();
direct_peers
.iter()
.filter(|(ip, _)| ip != target_ip)
.choose_multiple(&mut rng, max_probes_per_target)
.into_iter()
.cloned()
.collect()
};
for (relay_ip, route_key) in selected_peers {
// 构造Ping消息,目标是target_ip,但发送给relay_ip
let Ok(mut ping) = NetPacket::new(TransmissionBytes::zeroed_size(
HEAD_LENGTH + 8,
socket_manager.encrypt_reserve(),
)) else {
continue;
};
ping.set_msg_type(MsgType::Ping);
ping.set_ttl(2); // TTL设为2,允许中继一次
ping.set_src_id(src.into());
ping.set_dest_id((*target_ip).into());
ping.set_payload(&crate::utils::time::now_ts_ms().to_be_bytes())
.unwrap();
// 发送给已打洞的客户端,让它中继到目标
if let Err(e) = socket_manager.send_to(ping, &route_key).await {
log::debug!(
"Failed to send relay probe to {} for target {}: {:?}",
relay_ip,
target_ip,
e
);
}
}
// 控制发送速率,避免网络拥塞
tokio::time::sleep(Duration::from_millis(50)).await;
}
log::info!(
"Relay probe task completed: {} targets probed (from {} non-direct), {} direct peers",
targets_to_probe.len(),
non_direct_count,
direct_peers.len()
);
}
}
/// 隧道收发调度与数据分发
pub async fn tunnel_dispatch_task(
nat_info: MyNatInfo,
task_group: TaskGroup,
mut tunnel_factory: TunnelDispatcher,
p2p_inbound_handler: P2pInboundHandler,
p2p_socket_manager: P2pOutbound,
) {
loop {
let mut tunnel = match tunnel_factory.dispatch().await {
Ok(rs) => rs,
Err(e) => {
log::error!("tunnel disptach :{e:?}");
return;
}
};
log::info!("tunnel {:?}-{:?}", tunnel.protocol(), tunnel.remote_addr());
let p2p_inbound_handler = p2p_inbound_handler.clone();
let nat_info = nat_info.clone();
let p2p_socket_manager = p2p_socket_manager.clone();
task_group.spawn(async move {
let mut buf = vec![0; 65536];
while let Some(rs) = tunnel.recv_from(&mut buf).await {
let (len, route_key) = match rs {
Ok(rs) => rs,
Err(e) => {
log::warn!("recv_from {e:?}");
if tunnel.protocol().is_udp() {
continue;
}
break;
}
};
if tunnel.protocol().is_udp()
&& rust_p2p_core::stun::is_stun_response(&buf[..len])
&& let Some(pub_addr) = rust_p2p_core::stun::recv_stun_response(&buf[..len])
{
nat_info.update_public_addr(route_key.index(), pub_addr);
continue;
}
let mut bytes = TransmissionBytes::zeroed(len);
bytes.copy_from_slice(&buf[..len]);
p2p_inbound_handler
.next_handle(bytes, route_key, &p2p_socket_manager, &mut tunnel)
.await;
}
log::info!(
"drop tunnel {:?}-{:?}",
tunnel.protocol(),
tunnel.remote_addr()
);
if let Tunnel::Tcp(tcp) = tunnel {
p2p_inbound_handler.tcp_disconnect(tcp.route_key()).await;
}
});
}
}
@@ -0,0 +1,346 @@
use crate::compression::PacketCompression;
use crate::context::config::Config;
use crate::context::nat::{MyNatInfo, PunchBackoff};
use crate::context::{AppState, NetworkAddr, NetworkRoute, PeerInfoMap, ServerInfoCollection};
use crate::crypto::PacketCrypto;
use crate::enhanced_tunnel::inbound::EnhancedInbound;
use crate::fec::FecDecoder;
use crate::protocol::control_message::{
ConfirmRegResponseMsg, RegResponseMsg, RegistrationMode, RequestMessage, ResponseMessage,
};
use crate::tunnel_core::p2p::transport::punch::NatPuncher;
use crate::tunnel_core::server::inbound::ServerTurnInboundHandler;
use crate::tunnel_core::server::outbound::ServerOutbound;
use crate::tunnel_core::server::rpc::{RpcNotifier, ServerRPC};
use crate::tunnel_core::server::transport::TransportClient;
use crate::tunnel_core::server::transport::config::ConnectRegConfig;
use crate::utils::task_control::TaskGroup;
use anyhow::bail;
use bytes::Bytes;
use std::collections::HashMap;
use std::net::Ipv4Addr;
use std::sync::Arc;
use std::time::{Duration, Instant};
use tokio::sync::mpsc::{Receiver, Sender};
pub struct InboundHandlerConfig {
pub network_route: NetworkRoute,
pub server_info: ServerInfoCollection,
pub nat_info: MyNatInfo,
pub peer_map: PeerInfoMap,
pub punch_backoff: PunchBackoff,
pub puncher: NatPuncher,
pub packet_crypto: PacketCrypto,
pub packet_compression: PacketCompression,
pub enhanced_inbound: EnhancedInbound,
pub fec_decoder: FecDecoder,
}
pub struct ServerTurnManager {
server_id: u32,
config: ConnectRegConfig,
receiver: Option<Receiver<(Bytes, Instant)>>,
notifier: RpcNotifier,
transport_client: TransportClient,
}
pub(crate) fn create_server_tunnel(
app_state: AppState,
config: &Config,
packet_crypto: PacketCrypto,
) -> (Vec<ServerTurnManager>, ServerOutbound, ServerRPC) {
let mut rpc_notifier: HashMap<u32, RpcNotifier> = HashMap::new();
let mut sender_map: HashMap<u32, Sender<(Bytes, Instant)>> = HashMap::new();
let mut server_manager_list = Vec::with_capacity(config.server_addr.len());
let mut server_addr_list = Vec::with_capacity(config.server_addr.len());
for (index, server_addr) in config.server_addr.iter().enumerate() {
let connect_reg_config = config.to_connect_config(index);
let server_id = index as u32;
let (s, r) = tokio::sync::mpsc::channel(1024);
let notifier = RpcNotifier::new();
let manager =
ServerTurnManager::new(server_id, connect_reg_config.clone(), r, notifier.clone());
server_addr_list.push((server_id, server_addr.clone()));
rpc_notifier.insert(server_id, notifier);
sender_map.insert(server_id, s);
server_manager_list.push(manager);
}
let server_info_collection = app_state.server_info_collection.clone();
server_info_collection.update_server(server_addr_list);
let tunnel_to_server =
ServerOutbound::new(Arc::new(sender_map), server_info_collection, packet_crypto);
let server_rpc = ServerRPC::new(tunnel_to_server.clone(), rpc_notifier);
(server_manager_list, tunnel_to_server, server_rpc)
}
impl ServerTurnManager {
pub fn new(
server_id: u32,
config: ConnectRegConfig,
receiver: Receiver<(Bytes, Instant)>,
notifier: RpcNotifier,
) -> Self {
let connector = TransportClient::new();
Self {
server_id,
transport_client: connector,
config,
receiver: Some(receiver),
notifier,
}
}
pub fn disconnect(&mut self) {
self.transport_client.disconnect();
}
pub async fn connect_and_reg(
&mut self,
mode: RegistrationMode,
) -> anyhow::Result<ResponseMessage> {
let connect_config = self.config.to_connect_config().await?;
log::info!(
"Connecting to server[{}] {:?} with mode {:?}",
self.server_id,
connect_config,
mode,
);
self.transport_client
.connect_timeout(&connect_config, Duration::from_secs(10))
.await?;
let reg_msg = self.config.reg_msg_request(self.server_id, mode);
let request_msg = RequestMessage::Reg(reg_msg);
let encoded = request_msg.encode();
self.transport_client
.send(encoded.freeze())
.await?;
let buf = self
.transport_client
.next_timeout(Duration::from_secs(10))
.await?;
let response = ResponseMessage::from_slice(&buf)?;
match &response {
ResponseMessage::Reg(_) => {}
ResponseMessage::Error(_e) => {
self.disconnect();
}
ResponseMessage::ConfirmReg(_) => {
self.disconnect();
}
}
Ok(response)
}
pub async fn send_confirm(&mut self) -> anyhow::Result<ConfirmRegResponseMsg> {
self.transport_client
.send(RequestMessage::ConfirmReg.encode().freeze())
.await?;
let buf = self
.transport_client
.next_timeout(Duration::from_secs(10))
.await?;
let response = ResponseMessage::from_slice(&buf)?;
match response {
ResponseMessage::ConfirmReg(msg) => Ok(msg),
ResponseMessage::Error(e) => bail!("Confirm failed: {}", e.message),
_ => bail!("Unexpected response"),
}
}
pub fn set_ip(&mut self, ip: Ipv4Addr) {
self.config.ip = Some(ip);
}
/// Start data handling task with an already established connection.
pub fn data_handle_task_connected(
mut self,
task_group: &TaskGroup,
config: Box<InboundHandlerConfig>,
initial_response: NetworkAddr,
) {
let data_handler =
ServerTurnInboundHandler::new(self.server_id, initial_response, config);
let task_group_ = task_group.clone();
let Some(mut receiver) = self.receiver.take() else {
unreachable!()
};
task_group.spawn(async move {
let mut already_connected = true;
loop {
if !already_connected {
self.disconnect();
data_handler.handle_disconnected();
let msg = match self.connect_and_reg(RegistrationMode::Normal).await {
Ok(msg) => msg,
Err(e) => {
log::error!("连接服务器失败:{e:?}");
tokio::time::sleep(std::time::Duration::from_secs(5)).await;
continue;
}
};
match &msg {
ResponseMessage::Reg(reg) => {
if reg.ip != initial_response.ip
|| reg.prefix_len != initial_response.prefix_len
|| reg.gateway != initial_response.gateway
{
log::error!("虚拟网络发生变化");
break;
}
// 保存服务器版本
if !reg.server_version.is_empty() {
data_handler.set_server_version(reg.server_version.clone());
}
}
ResponseMessage::Error(e) => {
log::error!("注册失败 {e:?}");
break;
}
_ => {
log::error!("错误的注册消息");
break;
}
}
}
log::info!("已连接服务器:{}", self.config.server_addr);
data_handler.handle_connected();
if let Err(e) = self
.data_handle_loop(&mut receiver, &data_handler)
.await
{
log::error!("Error on data_handle_loop: {:?}", e);
}
already_connected = false;
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
}
self.disconnect();
data_handler.handle_disconnected();
task_group_.stop();
});
}
pub async fn data_handle_loop(
&mut self,
receiver: &mut Receiver<(Bytes, Instant)>,
data_handler: &ServerTurnInboundHandler,
) -> anyhow::Result<()> {
let mut time = crate::utils::time::now_ts_ms();
let mut ping_interval = tokio::time::interval(Duration::from_secs(5));
loop {
tokio::select! {
Some((buf,expired)) = receiver.recv() => {
if expired < Instant::now(){
continue;
}
self.transport_client.send(buf).await?;
}
rs = self.transport_client.next() => {
time = crate::utils::time::now_ts_ms();
let data = rs?;
if let Err(e) = data_handler.handle(&mut self.transport_client,data, &self.notifier,time).await{
log::warn!("Error handling data: {:?}", e);
}
}
_ = ping_interval.tick() => {
let now = crate::utils::time::now_ts_ms();
if now > time + Duration::from_secs(20).as_millis() as i64 {
bail!("timeout")
}
data_handler.handle_ping(&mut self.transport_client,now).await?;
}
else => {
bail!("receiver closed");
}
}
}
}
}
/// Coordinated multi-server pre-registration.
/// 1. First server uses PRE_REGISTER mode to get IP
/// 2. Other servers pre-register with the obtained IP
/// 3. Send confirmation to all servers
/// 4. Return the registration response
pub async fn coordinated_registration(
managers: &mut Vec<ServerTurnManager>,
) -> anyhow::Result<RegResponseMsg> {
if managers.is_empty() {
bail!("No servers to register");
}
// Step 1: First server pre-register to get IP
log::info!(
"Starting coordinated registration with {} servers",
managers.len()
);
let first_response = managers[0]
.connect_and_reg(RegistrationMode::PreRegister)
.await?;
let ip = match &first_response {
ResponseMessage::Reg(reg) => reg.ip,
ResponseMessage::Error(e) => bail!("First server registration failed: {}", e.message),
_ => bail!("Unexpected response from first server"),
};
log::info!("Got IP {} from first server", ip);
// Step 2: Set IP and pre-register with other servers
for manager in managers.iter_mut().skip(1) {
manager.set_ip(ip);
}
if managers.len() > 1 {
let other_results: Vec<_> = futures::future::join_all(
managers
.iter_mut()
.skip(1)
.map(|m| m.connect_and_reg(RegistrationMode::PreRegister)),
)
.await;
// Check all responses
for (i, result) in other_results.iter().enumerate() {
match result {
Ok(ResponseMessage::Reg(_)) => {
log::info!("Server {} pre-registered successfully", i + 1);
}
Ok(ResponseMessage::Error(e)) => {
bail!("Server {} registration failed: {}", i + 1, e.message)
}
Err(e) => bail!("Server {} registration failed: {}", i + 1, e),
_ => bail!("Unexpected response from server {}", i + 1),
}
}
}
// Step 3: Send confirmation to all servers
log::info!("Sending confirmation to all servers");
let confirm_results: Vec<_> =
futures::future::join_all(managers.iter_mut().map(|m| m.send_confirm())).await;
// Check all confirmation responses
for (i, result) in confirm_results.into_iter().enumerate() {
match result {
Ok(msg) if msg.success => {
log::info!("Server {} confirmed successfully", i);
}
Ok(_) => bail!("Server {} confirmation failed", i),
Err(e) => bail!("Server {} confirmation failed: {}", i, e),
}
}
log::info!("Coordinated registration completed successfully");
// Return first server's response (contains IP info)
match first_response {
ResponseMessage::Reg(reg) => Ok(reg),
_ => unreachable!(),
}
}
+332
View File
@@ -0,0 +1,332 @@
use crate::compression::PacketCompression;
use crate::context::nat::{MyNatInfo, PunchBackoff};
use crate::context::{NetworkAddr, NetworkRoute, PeerInfoMap, ServerInfoCollection};
use crate::crypto::PacketCrypto;
use crate::enhanced_tunnel::inbound::EnhancedInbound;
use crate::fec::FecDecoder;
use crate::protocol::client_message::PunchInfo;
use crate::protocol::control_message::ClientSimpleInfoList;
use crate::protocol::ip_packet_protocol::{HEAD_LENGTH, MsgType, NetPacket};
use crate::protocol::rpc_message::RpcMessageResponse;
use crate::protocol::transmission::TransmissionBytes;
use crate::tunnel_core::p2p::transport::punch::NatPuncher;
use crate::tunnel_core::server::rpc::RpcNotifier;
use crate::tunnel_core::server::transport::TransportClient;
use anyhow::bail;
use pnet_packet::Packet;
use pnet_packet::icmp::{IcmpPacket, IcmpTypes};
use pnet_packet::ipv4::Ipv4Packet;
use prost::Message;
use rust_p2p_core::nat::NatInfo;
use std::net::Ipv4Addr;
pub(crate) struct ServerTurnInboundHandler {
server_id: u32,
network_addr: Option<NetworkAddr>,
network_route: NetworkRoute,
server_info: ServerInfoCollection,
nat_info: MyNatInfo,
peer_map: PeerInfoMap,
punch_backoff: PunchBackoff,
puncher: NatPuncher,
packet_crypto: PacketCrypto,
packet_compression: PacketCompression,
enhanced_inbound: EnhancedInbound,
fec_decoder: FecDecoder,
}
impl ServerTurnInboundHandler {
pub fn new(
server_id: u32,
network_addr: NetworkAddr,
config: Box<super::connection_manager::InboundHandlerConfig>,
) -> Self {
let config = *config;
Self {
server_id,
network_addr: Some(network_addr),
network_route: config.network_route,
server_info: config.server_info,
nat_info: config.nat_info,
peer_map: config.peer_map,
punch_backoff: config.punch_backoff,
puncher: config.puncher,
packet_crypto: config.packet_crypto,
packet_compression: config.packet_compression,
enhanced_inbound: config.enhanced_inbound,
fec_decoder: config.fec_decoder,
}
}
fn network_contains(&self, ip: &Ipv4Addr) -> bool {
self.network_route.network_contains(ip)
}
fn filter_ip(&self, mut info: NatInfo) -> NatInfo {
if self.network_contains(&info.local_ipv4) {
info.local_ipv4 = Ipv4Addr::UNSPECIFIED;
}
info.local_ipv4s.retain(|ip| !self.network_contains(ip));
info
}
fn get_punch_info(&self) -> Option<PunchInfo> {
self.nat_info.get().map(|info| PunchInfo {
nat_info: self.filter_ip(info),
})
}
fn update_peer_nat_info(&self, ip: Ipv4Addr, nat_info: NatInfo) {
self.peer_map.update_nat_info(ip, nat_info);
}
pub async fn handle_server_data(
& self,
transport_client: &mut TransportClient,
network_addr: NetworkAddr,
data: TransmissionBytes,
rpc_notifier: &RpcNotifier,
now: i64,
) -> anyhow::Result<()> {
let net_packet = NetPacket::new(data)?;
let src = net_packet.src_id().into();
let msg_type = net_packet.msg_type()?;
let mut net_packet = self.packet_compression.decompress(net_packet)?;
match msg_type {
MsgType::Turn => {
// 只允许icmp EchoReply
let Some(ipv4) = Ipv4Packet::new(net_packet.payload()) else {
return Ok(());
};
if ipv4.get_version() != 4 {
return Ok(());
}
if ipv4.get_next_level_protocol() != pnet_packet::ip::IpNextHeaderProtocols::Icmp {
return Ok(());
}
let Some(icmp) = IcmpPacket::new(ipv4.payload()) else {
return Ok(());
};
if icmp.get_icmp_type() != IcmpTypes::EchoReply {
return Ok(());
}
self.enhanced_inbound
.inbound(&network_addr, msg_type, src, net_packet)
.await?;
}
MsgType::Ping => {
net_packet.set_ttl(2);
net_packet.set_msg_type(MsgType::Pong);
net_packet.set_src_id(network_addr.ip.into());
net_packet.set_dest_id(src.into());
transport_client.send_turn(net_packet).await?;
}
MsgType::PongTurn => {
// 服务端ping 回复,记录延迟
if net_packet.payload().len() == 8 + 8 {
let time = i64::from_be_bytes(net_packet.payload()[..8].try_into()?);
// let data_version = u64::from_be_bytes(net_packet.payload()[8..].try_into()?);
if now >= time {
self.server_info
.set_server_rtt(self.server_id, (now - time) as u32);
}
}
}
MsgType::PushClientIps => {
let list = ClientSimpleInfoList::from_slice(net_packet.payload())?;
self.server_info.update_client_simple_list(
self.server_id,
network_addr.ip,
list,
now,
);
}
MsgType::RpcRes => {
// 设置rpc响应
let response = RpcMessageResponse::decode(net_packet.payload())?;
rpc_notifier.notify_response(response);
}
_ => {}
}
Ok(())
}
pub async fn handle_client_data(
&self,
network_addr: NetworkAddr,
transport_client: &mut TransportClient,
data: TransmissionBytes,
) -> anyhow::Result<()> {
let mut net_packet = NetPacket::new(data)?;
let msg_type = net_packet.msg_type()?;
let src = Ipv4Addr::from(net_packet.src_id());
let dest = Ipv4Addr::from(net_packet.dest_id());
if msg_type == MsgType::Quic {
// QUIC 数据不加密不压缩,但可能有 FEC
if net_packet.is_fec() {
let packets = self.fec_decoder.receive(net_packet)?;
if let Some(packets) = packets {
for pkt in packets {
self.enhanced_inbound
.inbound(&network_addr, msg_type, src, pkt)
.await?;
}
}
return Ok(());
}
self.enhanced_inbound
.inbound(&network_addr, msg_type, src, net_packet)
.await?;
return Ok(());
}
// 解密
if let Err(e) = self.packet_crypto.decrypt_in_place(&mut net_packet) {
log::error!("{},mst_type={msg_type:?},src={src},dst={dest}", e);
return Ok(());
}
// FEC 解码(始终尝试解码,如果有 FEC 标志)
if net_packet.is_fec() {
let packets = self.fec_decoder.receive(net_packet)?;
if let Some(packets) = packets {
for pkt in packets {
let pkt = self.packet_compression.decompress(pkt)?;
self.process_decompressed_packet(
network_addr,
transport_client,
pkt,
msg_type,
src,
dest,
)
.await?;
}
}
return Ok(());
}
// 解压缩
let net_packet = self.packet_compression.decompress(net_packet)?;
self.process_decompressed_packet(
network_addr,
transport_client,
net_packet,
msg_type,
src,
dest,
)
.await
}
async fn process_decompressed_packet(
&self,
network_addr: NetworkAddr,
transport_client: &mut TransportClient,
net_packet: NetPacket<TransmissionBytes>,
msg_type: MsgType,
src: Ipv4Addr,
dest: Ipv4Addr,
) -> anyhow::Result<()> {
match msg_type {
MsgType::Turn | MsgType::Broadcast => {
self.enhanced_inbound
.inbound(&network_addr, msg_type, src, net_packet)
.await?;
}
MsgType::PunchStart1 => {
// 对方发起打洞
let peer_punch_info = PunchInfo::from_slice(net_packet.payload())?;
let Some(self_punch_info) = self.get_punch_info() else {
return Ok(());
};
log::info!(
"对方主动发起打洞 对方nat信息={peer_punch_info:?},自己nat信息={self_punch_info:?} {src}->{dest}"
);
self.update_peer_nat_info(src, peer_punch_info.nat_info.clone());
let rs = self.puncher.punch(src, peer_punch_info)?;
if rs {
let bytes_mut = self_punch_info.encode();
let mut net_packet = NetPacket::new(TransmissionBytes::zeroed_size(
HEAD_LENGTH + bytes_mut.len(),
self.packet_crypto.encrypt_reserve(),
))?;
net_packet.set_msg_type(MsgType::PunchStart2);
net_packet.set_ttl(2);
net_packet.set_src_id(dest.into());
net_packet.set_dest_id(src.into());
net_packet.set_payload(&bytes_mut)?;
self.packet_crypto.encrypt_in_place(&mut net_packet)?;
transport_client.send_turn(net_packet).await?;
}else{
log::info!("限制打洞频率")
}
}
MsgType::PunchStart2 => {
self.punch_backoff.record(src);
// 对方回复开始打洞
let peer_punch_info = PunchInfo::from_slice(net_packet.payload())?;
self.update_peer_nat_info(src, peer_punch_info.nat_info.clone());
log::info!("对方回复开始打洞 {:?} {src}->{dest}", peer_punch_info);
self.puncher.punch_uncheck(src, peer_punch_info)?;
}
_ => {}
}
Ok(())
}
pub async fn handle(
&self,
transport_client: &mut TransportClient,
data: TransmissionBytes,
rpc_notifier: &RpcNotifier,
now: i64,
) -> anyhow::Result<()> {
let net_packet = NetPacket::new(&data)?;
let Some(network_addr) = self.network_addr else {
bail!("未找到自身IP")
};
if net_packet.is_gateway() {
// 服务端数据
return self
.handle_server_data(transport_client, network_addr, data, rpc_notifier, now)
.await;
}
let dest = Ipv4Addr::from(net_packet.dest_id());
if !dest.is_broadcast() && !dest.is_unspecified() && network_addr.ip != dest {
return Ok(());
}
self.handle_client_data(network_addr, transport_client, data)
.await
}
pub async fn handle_ping(
&self,
transport_client: &mut TransportClient,
now: i64,
) -> anyhow::Result<()> {
let mut ping_packet = NetPacket::new(TransmissionBytes::zeroed(HEAD_LENGTH + 8 + 8))?;
ping_packet.set_ttl(1);
ping_packet.set_msg_type(MsgType::PingTurn);
ping_packet.set_gateway_flag(true);
ping_packet.set_payload(&now.to_be_bytes())?;
ping_packet.payload_mut()[0..8].copy_from_slice(&now.to_be_bytes());
ping_packet.payload_mut()[8..]
.copy_from_slice(&self.server_info.data_version(self.server_id).to_be_bytes());
transport_client
.send(ping_packet.into_buffer().into_bytes().freeze())
.await?;
Ok(())
}
pub fn handle_connected(&self) {
self.server_info.set_server_connected(self.server_id, true);
self.server_info
.set_last_connected_time(self.server_id, Some(crate::utils::time::now_ts_ms()));
self.server_info.set_disconnected_time(self.server_id, None);
}
pub fn set_server_version(&self, version: String) {
self.server_info.set_server_version(self.server_id, version);
}
pub fn handle_disconnected(&self) {
if self.server_info.set_server_connected(self.server_id, false) {
self.server_info
.set_disconnected_time(self.server_id, Some(crate::utils::time::now_ts_ms()));
}
}
}
+5
View File
@@ -0,0 +1,5 @@
pub(crate) mod connection_manager;
pub(crate) mod inbound;
pub(crate) mod outbound;
pub(crate) mod rpc;
pub mod transport;
+329
View File
@@ -0,0 +1,329 @@
use crate::context::ServerInfoCollection;
use crate::crypto::PacketCrypto;
use crate::protocol::ProtoToBytesMut;
use crate::protocol::control_message::SelectiveBroadcast;
use crate::protocol::ip_packet_protocol::{HEAD_LENGTH, MsgType, NetPacket};
use crate::protocol::transmission::TransmissionBytes;
use anyhow::{Context, bail};
use bytes::Bytes;
use std::collections::HashMap;
use std::net::Ipv4Addr;
use std::sync::Arc;
use std::time::{Duration, Instant};
use tokio::sync::mpsc::Sender;
#[derive(Clone)]
pub(crate) struct ServerOutbound {
server_id_list: Arc<Vec<u32>>,
sender: Arc<HashMap<u32, Sender<(Bytes, Instant)>>>,
server_info_collection: ServerInfoCollection,
packet_crypto: PacketCrypto,
}
impl ServerOutbound {
pub fn new(
sender: Arc<HashMap<u32, Sender<(Bytes, Instant)>>>,
server_info_collection: ServerInfoCollection,
packet_crypto: PacketCrypto,
) -> Self {
let server_id_list = Arc::new(sender.keys().copied().collect());
Self {
server_id_list,
sender,
server_info_collection,
packet_crypto,
}
}
pub fn exists_route(&self, dest: &Ipv4Addr) -> bool {
self.server_info_collection.exists_online_client_ip(dest)
}
pub fn server_id_list(&self) -> &Vec<u32> {
&self.server_id_list
}
pub fn encrypt_reserve(&self) -> usize {
self.packet_crypto.encrypt_reserve()
}
pub async fn send_to_gateway_expired(
&self,
server_id: u32,
mut buf: NetPacket<TransmissionBytes>,
expired: Duration,
) -> anyhow::Result<()> {
if !self.server_info_collection.is_server_connected(server_id) {
bail!("未连接服务器")
}
buf.set_gateway_flag(true);
self.send_expired_impl(server_id, buf, expired).await
}
pub async fn send(
&self,
dest_ip: Ipv4Addr,
buf: NetPacket<TransmissionBytes>,
) -> anyhow::Result<()> {
self.send_expired(dest_ip, buf, Duration::from_secs(5))
.await
}
pub async fn send_expired(
&self,
dest_ip: Ipv4Addr,
buf: NetPacket<TransmissionBytes>,
expired: Duration,
) -> anyhow::Result<()> {
let Some(server_id) = self
.server_info_collection
.find_ip_to_server(&self.server_id_list, &dest_ip)
else {
bail!("not found ip route: {dest_ip}")
};
self.send_expired_impl(server_id, buf, expired).await
}
async fn send_expired_impl(
&self,
server_id: u32,
mut buf: NetPacket<TransmissionBytes>,
expired: Duration,
) -> anyhow::Result<()> {
if !buf.is_gateway() {
self.packet_crypto.encrypt_in_place(&mut buf)?;
}
let Some(sender) = self.sender.get(&server_id) else {
bail!("not found server")
};
sender
.send_timeout(
(
buf.into_buffer().into_bytes().freeze(),
Instant::now() + expired,
),
expired,
)
.await
.context("connect server task failed")
}
pub async fn send_raw(&self, dest_ip: Ipv4Addr, buf: NetPacket<Bytes>) -> anyhow::Result<()> {
let Some(server_id) = self
.server_info_collection
.find_ip_to_server(&self.server_id_list, &dest_ip)
else {
bail!("not found ip route: {dest_ip}")
};
let expired = Duration::from_secs(5);
let Some(sender) = self.sender.get(&server_id) else {
bail!("not found server")
};
sender
.send_timeout((buf.into_buffer(), Instant::now() + expired), expired)
.await
.context("connect server task failed")
}
pub async fn send_default_raw(&self, buf: NetPacket<Bytes>) -> anyhow::Result<()> {
let Some(server_id) = self
.server_info_collection
.find_connected_server(&self.server_id_list)
else {
bail!("not found default route")
};
let expired = Duration::from_secs(5);
let Some(sender) = self.sender.get(&server_id) else {
bail!("not found server")
};
sender
.send_timeout((buf.into_buffer(), Instant::now() + expired), expired)
.await
.context("connect server task failed")
}
pub async fn send_raw_broadcast(
&self,
exclude_ips: Option<Vec<Ipv4Addr>>,
buf: NetPacket<Bytes>,
) -> anyhow::Result<()> {
let buf = buf.into_buffer();
let expired = Duration::from_secs(5);
let map: HashMap<u32, (Vec<Ipv4Addr>, u32)> =
self.server_info_collection.server_client_ip_map();
if map.is_empty() {
bail!("no connected servers with clients");
}
// 只有一个服务器,直接发送
if map.len() == 1 {
let (server_id, (ips, _)) = map.iter().next().expect("map has exactly one element");
if ips.is_empty() {
return Ok(());
}
let sender = self
.sender
.get(server_id)
.context("server sender not found")?;
return if let Some(exclude_ips) = exclude_ips {
send_exclude_broadcast(sender.clone(), buf, exclude_ips, expired).await
} else {
send_direct(sender.clone(), buf, expired).await
};
}
// 找到最优服务器
let (max_server_id, (max_ips, _)) = map
.iter()
.max_by(|(_, (ips_a, rtt_a)), (_, (ips_b, rtt_b))| {
let score_a = ips_a.len() as f64 / (*rtt_a as f64 + 1.0);
let score_b = ips_b.len() as f64 / (*rtt_b as f64 + 1.0);
score_a
.partial_cmp(&score_b)
.unwrap_or(std::cmp::Ordering::Equal)
})
.context("failed to find server with most IPs")?;
if max_ips.is_empty() {
return Ok(());
}
let max_ip_set: std::collections::HashSet<_> = max_ips.iter().collect();
let exclude_set: std::collections::HashSet<_> = exclude_ips
.as_ref()
.map(|ips| ips.iter().collect())
.unwrap_or_default();
let mut handles = Vec::new();
// 任务1: 向主服务器发送
let sender = self
.sender
.get(max_server_id)
.cloned()
.context("max server sender not found")?;
if let Some(exclude_ips) = exclude_ips.clone() {
let buf_clone = buf.clone();
let handle = tokio::spawn(async move {
send_exclude_broadcast(sender, buf_clone, exclude_ips, expired).await
});
handles.push(handle);
} else {
let buf_clone = buf.clone();
let handle = tokio::spawn(async move { send_direct(sender, buf_clone, expired).await });
handles.push(handle);
}
// 任务2-N: 向其他服务器发送目标广播
for (server_id, (ips, _rtt)) in map.iter() {
if *server_id == *max_server_id {
continue;
}
// 筛选目标IP:不在最大服务器中,也不在排除列表中
let target_ips: Vec<Ipv4Addr> = ips
.iter()
.filter(|ip| !max_ip_set.contains(ip) && !exclude_set.contains(ip))
.copied()
.collect();
if target_ips.is_empty() {
continue;
}
let sender = match self.sender.get(server_id).cloned() {
Some(s) => s,
None => continue,
};
let buf_clone = buf.clone();
let handle = tokio::spawn(async move {
send_target_broadcast(sender, target_ips, buf_clone, expired).await
});
handles.push(handle);
}
// 等待所有任务完成
let mut errors = Vec::new();
for handle in handles {
match handle.await {
Ok(Ok(())) => {}
Ok(Err(e)) => errors.push(e),
Err(e) => errors.push(anyhow::anyhow!("task join error: {}", e)),
}
}
if !errors.is_empty() {
bail!(
"broadcast failed with {} errors: {:?}",
errors.len(),
errors
);
}
Ok(())
}
}
async fn send_exclude_broadcast(
sender: Sender<(Bytes, Instant)>,
buf: Bytes,
exclude_ips: Vec<Ipv4Addr>,
expired: Duration,
) -> anyhow::Result<()> {
let broadcast = SelectiveBroadcast::new(&exclude_ips, buf.to_vec());
let bytes = broadcast.encode_bytes_mut();
let mut packet = NetPacket::new(TransmissionBytes::zeroed(HEAD_LENGTH + bytes.len()))?;
packet.set_msg_type(MsgType::ExcludeBroadcast);
packet.set_ttl(5);
packet.payload_mut().copy_from_slice(&bytes);
sender
.send_timeout(
(
packet.into_buffer().into_bytes().freeze(),
Instant::now() + expired,
),
expired,
)
.await
.context("failed to send exclude broadcast")?;
Ok(())
}
// 直接发送原始数据
async fn send_direct(
sender: Sender<(Bytes, Instant)>,
buf: Bytes,
expired: Duration,
) -> anyhow::Result<()> {
sender
.send_timeout((buf, Instant::now() + expired), expired)
.await
.context("failed to send direct broadcast")?;
Ok(())
}
async fn send_target_broadcast(
sender: Sender<(Bytes, Instant)>,
target_ips: Vec<Ipv4Addr>,
buf: Bytes,
expired: Duration,
) -> anyhow::Result<()> {
let target_broadcast = SelectiveBroadcast::new(&target_ips, buf.to_vec());
let target_bytes = target_broadcast.encode_bytes_mut();
let mut packet = NetPacket::new(TransmissionBytes::zeroed(HEAD_LENGTH + target_bytes.len()))?;
packet.set_msg_type(MsgType::TargetBroadcast);
packet.set_ttl(5);
packet.payload_mut().copy_from_slice(&target_bytes);
sender
.send_timeout(
(
packet.into_buffer().into_bytes().freeze(),
Instant::now() + expired,
),
expired,
)
.await
.context("failed to send target broadcast")?;
Ok(())
}
+150
View File
@@ -0,0 +1,150 @@
use crate::protocol::ip_packet_protocol::{HEAD_LENGTH, MsgType, NetPacket};
use crate::protocol::rpc_message::rpc_message_request::RpcReqPayload;
use crate::protocol::rpc_message::rpc_message_response::RpcResPayload;
use crate::protocol::rpc_message::{
ClientInfo, ClientListRequest, ClientListResponse, RpcMessageRequest, RpcMessageResponse,
};
use crate::protocol::transmission::TransmissionBytes;
use crate::tunnel_core::server::outbound::ServerOutbound;
use anyhow::bail;
use parking_lot::Mutex;
use prost::Message;
use std::collections::HashMap;
use std::sync::Arc;
use std::time::Duration;
use tokio::sync::oneshot;
use tokio::sync::oneshot::Sender;
#[derive(Clone)]
pub struct ServerRPC {
tunnel_to_server: ServerOutbound,
rpc_notifier: HashMap<u32, RpcNotifier>,
}
#[derive(Clone)]
pub(crate) struct RpcNotifier {
pending_requests: Arc<Mutex<HashMap<u64, Sender<RpcMessageResponse>>>>,
rpc_id: Arc<Mutex<u64>>,
}
impl RpcNotifier {
pub fn new() -> Self {
Self {
pending_requests: Arc::new(Mutex::new(HashMap::new())),
rpc_id: Arc::new(Mutex::new(0)),
}
}
pub fn create_request_and_waiter(&self) -> RpcResponseWaiter {
let id: u64 = {
let mut id_lock = self.rpc_id.lock();
*id_lock += 1;
*id_lock
};
let (tx, rx) = oneshot::channel();
{
let mut pending = self.pending_requests.lock();
pending.insert(id, tx);
}
RpcResponseWaiter {
id,
pending_requests_handle: Arc::clone(&self.pending_requests),
rx,
}
}
pub fn notify_response(&self, response: RpcMessageResponse) {
let mut pending = self.pending_requests.lock();
if let Some(tx) = pending.remove(&response.id) {
let _ = tx.send(response);
}
}
}
pub(crate) struct RpcResponseWaiter {
id: u64,
pending_requests_handle: Arc<Mutex<HashMap<u64, Sender<RpcMessageResponse>>>>,
rx: oneshot::Receiver<RpcMessageResponse>,
}
impl RpcResponseWaiter {
pub async fn wait_for_response(
mut self,
timeout: Duration,
) -> anyhow::Result<RpcMessageResponse> {
let result = tokio::time::timeout(timeout, &mut self.rx).await;
match result {
Err(_) => bail!("timeout waiting for response"),
Ok(Ok(response)) => Ok(response),
Ok(Err(_)) => bail!("closed connection"),
}
}
}
impl Drop for RpcResponseWaiter {
fn drop(&mut self) {
let mut pending = self.pending_requests_handle.lock();
let _ = pending.remove(&self.id);
}
}
impl ServerRPC {
pub(crate) fn new(
tunnel_to_server: ServerOutbound,
rpc_notifier: HashMap<u32, RpcNotifier>,
) -> Self {
Self {
tunnel_to_server,
rpc_notifier,
}
}
pub async fn client_list(&self) -> anyhow::Result<ClientListResponse> {
let mut map: HashMap<String, ClientInfo> = HashMap::new();
for server_id in self.tunnel_to_server.server_id_list() {
match self.client_list_target(*server_id).await {
Ok(rs) => {
for client in rs.list {
map.entry(client.id.clone()).or_insert(client);
}
}
Err(e) => {
log::error!("client list target failed: {}", e);
}
}
}
Ok(ClientListResponse {
list: map.into_values().collect(),
})
}
pub async fn client_list_target(&self, server_id: u32) -> anyhow::Result<ClientListResponse> {
let Some(rpc_notifier) = self.rpc_notifier.get(&server_id) else {
bail!("no RPC notifier");
};
let waiter = rpc_notifier.create_request_and_waiter();
let request = RpcMessageRequest {
id: waiter.id,
rpc_req_payload: Some(RpcReqPayload::ClientListReq(ClientListRequest::default())),
};
let buf = request.encode_to_vec();
let mut packet = NetPacket::new(TransmissionBytes::zeroed(HEAD_LENGTH + buf.len()))?;
packet.set_msg_type(MsgType::RpcReq);
packet.set_gateway_flag(true);
packet.set_ttl(1);
packet.set_payload(&buf)?;
self.tunnel_to_server
.send_to_gateway_expired(server_id, packet, Duration::from_secs(1))
.await?;
let response = waiter.wait_for_response(Duration::from_secs(3)).await?;
if let Some(RpcResPayload::ClientListRes(res)) = response.rpc_res_payload {
return Ok(res);
}
bail!("unexpected response: {:?}", response);
}
}
@@ -0,0 +1,173 @@
use crate::protocol::control_message::{RegRequestMsg, RegistrationMode};
use crate::tls::verifier::CertValidationMode;
use anyhow::Context;
use rand::seq::SliceRandom;
use std::fmt;
use std::net::{Ipv4Addr, SocketAddr};
use std::str::FromStr;
#[derive(Debug, Clone)]
pub(crate) struct ConnectRegConfig {
pub server_addr: ProtocolAddress,
pub cert_mode: CertValidationMode,
pub network_code: String,
pub device_id: String,
pub device_name: String,
pub ip: Option<Ipv4Addr>,
pub key_sign: Option<String>,
pub ip_variable: bool,
}
#[derive(Debug, Clone)]
pub(crate) struct ConnectConfig {
pub protocol_type: ProtocolType,
pub server_addr: SocketAddr,
pub server_domain: String,
pub cert_mode: CertValidationMode,
}
#[derive(Debug, Copy, Clone, Eq, PartialEq, Default)]
pub enum ProtocolType {
Quic,
#[default]
TlsTcp,
Wss,
Dynamic,
}
#[derive(Debug, Clone)]
pub struct ProtocolAddress {
pub protocol_type: ProtocolType,
pub address: String,
}
impl Default for ProtocolAddress {
fn default() -> Self {
Self {
protocol_type: ProtocolType::default(),
address: "127.0.0.1:29872".to_string(),
}
}
}
impl FromStr for ProtocolAddress {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let (protocol_type, server_addr) = parse_server(s)?;
Ok(Self {
protocol_type,
address: server_addr,
})
}
}
impl fmt::Display for ProtocolAddress {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let prefix = match self.protocol_type {
ProtocolType::Quic => "quic://",
ProtocolType::TlsTcp => "tcp://",
ProtocolType::Wss => "wss://",
ProtocolType::Dynamic => "dynamic://",
};
write!(f, "{}{}", prefix, self.address)
}
}
pub fn parse_server(val: &str) -> Result<(ProtocolType, String), String> {
let val = val.trim().to_lowercase();
if let Some(s) = val.strip_prefix("quic://") {
return Ok((ProtocolType::Quic, s.to_string()));
}
if let Some(s) = val.strip_prefix("tcp://") {
return Ok((ProtocolType::TlsTcp, s.to_string()));
}
if let Some(s) = val.strip_prefix("wss://") {
return Ok((ProtocolType::Wss, s.to_string()));
}
if let Some(s) = val.strip_prefix("dynamic://") {
return Ok((ProtocolType::Dynamic, s.to_string()));
}
if val.contains("://") {
return Err(format!("Unknown protocol in server address: {}", val));
}
Ok((ProtocolType::TlsTcp, val))
}
impl ConnectRegConfig {
pub fn reg_msg_request(
&self,
server_id: u32,
registration_mode: RegistrationMode,
) -> RegRequestMsg {
RegRequestMsg {
network_code: self.network_code.to_string(),
device_id: self.device_id.to_string(),
ip: self.ip,
name: self.device_name.to_string(),
version: env!("CARGO_PKG_VERSION").to_string(),
key_sign: self.key_sign.clone(),
ip_variable: self.ip_variable,
server_id,
registration_mode,
}
}
pub async fn to_connect_config(&self) -> anyhow::Result<ConnectConfig> {
let (protocol_type, server_domain) = match self.server_addr.protocol_type {
ProtocolType::Dynamic => {
let mut txt = crate::utils::dns_query::dns_query_txt(
&self.server_addr.address,
vec![],
&None,
)
.await?;
txt.shuffle(&mut rand::rng());
let x = txt.first().context("DNS query failed")?;
let x = x.to_lowercase();
let (protocol_type, domain) = if let Some(v) = x.strip_prefix("udp://") {
(ProtocolType::Quic, v)
} else if let Some(v) = x.strip_prefix("quic://") {
(ProtocolType::Quic, v)
} else if let Some(v) = x.strip_prefix("tcp://") {
(ProtocolType::TlsTcp, v)
} else if let Some(v) = x.strip_prefix("ws://") {
(ProtocolType::TlsTcp, v)
} else if let Some(v) = x.strip_prefix("wss://") {
(ProtocolType::TlsTcp, v)
} else {
(ProtocolType::TlsTcp, x.as_str())
};
(protocol_type, domain.to_owned())
}
v => (v, self.server_addr.address.to_string()),
};
let server_addr =
crate::utils::dns_query::dns_query_one(&server_domain, &vec![], &None).await?;
let server_domain = strip_port(&server_domain).to_owned();
Ok(ConnectConfig {
protocol_type,
server_addr,
server_domain,
cert_mode: self.cert_mode.clone(),
})
}
}
fn strip_port(addr: &str) -> &str {
if let Some(stripped) = addr.strip_prefix('[')
&& let Some(pos) = stripped.find(']')
{
return &stripped[..pos];
}
if addr.contains(':') && !addr.contains('.') && addr.matches(':').count() > 1 {
return addr;
}
if let Some((host, port)) = addr.rsplit_once(':')
&& port.chars().all(|c| c.is_ascii_digit())
{
return host;
}
addr
}
impl ConnectConfig {
pub fn server_addr(&self) -> SocketAddr {
self.server_addr
}
pub fn server_name(&self) -> &String {
&self.server_domain
}
}
@@ -0,0 +1,105 @@
use crate::protocol::ip_packet_protocol::NetPacket;
use crate::protocol::transmission::TransmissionBytes;
use crate::tunnel_core::server::transport::config::{ConnectConfig, ProtocolType};
use crate::tunnel_core::server::transport::quic::QuicTransport;
use crate::tunnel_core::server::transport::tcp::TlsTcpTransport;
use crate::tunnel_core::server::transport::wss::WssTransport;
use anyhow::{Context, bail};
use bytes::Bytes;
use std::time::Duration;
pub mod config;
pub(crate) mod quic;
pub(crate) mod tcp;
pub(crate) mod wss;
#[derive(Default)]
pub(crate) enum TransportClient {
Quic(QuicTransport),
TlsTcp(TlsTcpTransport),
Wss(WssTransport),
#[default]
Pending,
}
impl TransportClient {
pub fn new() -> Self {
TransportClient::default()
}
pub fn disconnect(&mut self) {
match self {
TransportClient::Quic(c) => c.disconnect(),
TransportClient::TlsTcp(c) => c.disconnect(),
TransportClient::Wss(c) => c.disconnect(),
TransportClient::Pending => {}
};
*self = TransportClient::Pending;
}
pub async fn connect_timeout(
&mut self,
config: &ConnectConfig,
timeout: Duration,
) -> anyhow::Result<()> {
tokio::time::timeout(timeout, self.connect(config))
.await
.context("timeout")?
}
pub async fn connect(&mut self, config: &ConnectConfig) -> anyhow::Result<()> {
match self {
TransportClient::Quic(c) => c.connect(config).await?,
TransportClient::TlsTcp(c) => c.connect(config).await?,
TransportClient::Wss(c) => c.connect(config).await?,
TransportClient::Pending => match config.protocol_type {
ProtocolType::Quic => {
let mut transport = QuicTransport::new();
transport.connect(config).await?;
*self = TransportClient::Quic(transport);
}
ProtocolType::TlsTcp => {
let mut transport = TlsTcpTransport::new();
transport.connect(config).await?;
*self = TransportClient::TlsTcp(transport);
}
ProtocolType::Wss => {
let mut transport = WssTransport::new();
transport.connect(config).await?;
*self = TransportClient::Wss(transport);
}
ProtocolType::Dynamic => {
bail!("unreachable connect")
}
},
};
Ok(())
}
pub async fn send(&mut self, buf: Bytes) -> anyhow::Result<()> {
match self {
TransportClient::Quic(c) => c.send(buf).await,
TransportClient::TlsTcp(c) => c.send(buf).await,
TransportClient::Wss(c) => c.send(buf).await,
TransportClient::Pending => {
bail!("Not connected");
}
}
}
pub async fn next(&mut self) -> anyhow::Result<TransmissionBytes> {
match self {
TransportClient::Quic(c) => c.next().await,
TransportClient::TlsTcp(c) => c.next().await,
TransportClient::Wss(c) => c.next().await,
TransportClient::Pending => {
bail!("Not connected");
}
}
}
pub async fn next_timeout(&mut self, timeout: Duration) -> anyhow::Result<TransmissionBytes> {
tokio::time::timeout(timeout, self.next())
.await
.context("timeout")?
}
pub async fn send_turn(&mut self, buf: NetPacket<TransmissionBytes>) -> anyhow::Result<()> {
self.send(buf.into_buffer().into_bytes().freeze()).await
}
}
@@ -0,0 +1,90 @@
use crate::protocol::transmission::TransmissionBytes;
use crate::tls::verifier::CertValidationMode;
use crate::tunnel_core::server::transport::config::ConnectConfig;
use anyhow::{Context, bail};
use bytes::Bytes;
use futures::{SinkExt, StreamExt};
use quinn::{ClientConfig, Endpoint, RecvStream, SendStream};
use std::sync::Arc;
use tokio_util::codec::{FramedRead, FramedWrite, LengthDelimitedCodec};
#[derive(Default)]
pub struct QuicTransport {
framed: Option<(
FramedWrite<SendStream, LengthDelimitedCodec>,
FramedRead<RecvStream, LengthDelimitedCodec>,
)>,
}
impl QuicTransport {
pub fn new() -> Self {
Default::default()
}
pub fn disconnect(&mut self) {
self.framed = None;
}
pub async fn connect(&mut self, config: &ConnectConfig) -> anyhow::Result<()> {
if self.framed.is_some() {
bail!("Already connected");
}
let (w, r) = connect_quic(config).await?;
self.framed = Some((w, r));
Ok(())
}
pub async fn send(&mut self, buf: Bytes) -> anyhow::Result<()> {
let Some((w, _r)) = self.framed.as_mut() else {
bail!("Not connected");
};
w.send(buf).await.context("send to server failed")
}
pub async fn next(&mut self) -> anyhow::Result<TransmissionBytes> {
let Some((_w, r)) = self.framed.as_mut() else {
bail!("Not connected");
};
r.next()
.await
.context("EOF")?
.context("receive from server failed")
.map(TransmissionBytes::from)
}
}
pub async fn connect_quic(
config: &ConnectConfig,
) -> anyhow::Result<(
FramedWrite<SendStream, LengthDelimitedCodec>,
FramedRead<RecvStream, LengthDelimitedCodec>,
)> {
let server_addr = config.server_addr();
let server_name = config.server_name();
let quic_config = create_client_config(&config.cert_mode)?;
let mut endpoint = match Endpoint::client((std::net::Ipv6Addr::UNSPECIFIED, 0).into()) {
Ok(endpoint) => endpoint,
Err(e) => {
log::warn!("Failed to create QUIC endpoint: {}", e);
Endpoint::client((std::net::Ipv4Addr::UNSPECIFIED, 0).into())
.context("Failed to create QUIC endpoint")?
}
};
endpoint.set_default_client_config(quic_config);
let connection = endpoint
.connect(server_addr, server_name)?
.await
.context("Failed to establish QUIC connection")?;
let (send_stream, recv_stream) = connection
.open_bi()
.await
.context("Failed to open bidirectional stream")?;
let framed_write = FramedWrite::new(send_stream, LengthDelimitedCodec::new());
let framed_read = FramedRead::new(recv_stream, LengthDelimitedCodec::new());
Ok((framed_write, framed_read))
}
fn create_client_config(cert_mode: &CertValidationMode) -> anyhow::Result<ClientConfig> {
let config = cert_mode.create_tls_client_config()?;
let client_config = ClientConfig::new(Arc::new(
quinn::crypto::rustls::QuicClientConfig::try_from(config)
.context("Failed to create QUIC client config")?,
));
Ok(client_config)
}
@@ -0,0 +1,79 @@
use crate::protocol::transmission::TransmissionBytes;
use crate::tunnel_core::server::transport::config::ConnectConfig;
use anyhow::{Context, bail};
use bytes::Bytes;
use futures::{SinkExt, StreamExt};
use std::sync::Arc;
use tokio::net::TcpStream;
use tokio_rustls::{TlsConnector, client::TlsStream};
use tokio_util::codec::{Framed, LengthDelimitedCodec};
type TlsTcpStream = TlsStream<TcpStream>;
#[derive(Default)]
pub struct TlsTcpTransport {
framed: Option<Framed<TlsTcpStream, LengthDelimitedCodec>>,
}
impl TlsTcpTransport {
pub fn new() -> Self {
Default::default()
}
pub fn disconnect(&mut self) {
self.framed = None;
}
pub async fn connect(&mut self, config: &ConnectConfig) -> anyhow::Result<()> {
if self.framed.is_some() {
bail!("Already connected");
}
let framed = connect_tls_tcp(config).await?;
self.framed = Some(framed);
Ok(())
}
pub async fn send(&mut self, buf: Bytes) -> anyhow::Result<()> {
let Some(framed) = self.framed.as_mut() else {
bail!("Not connected");
};
framed.send(buf).await.context("send to server failed")
}
pub async fn next(&mut self) -> anyhow::Result<TransmissionBytes> {
let Some(framed) = self.framed.as_mut() else {
bail!("Not connected");
};
framed
.next()
.await
.context("EOF")?
.context("receive from server failed")
.map(TransmissionBytes::from)
}
}
pub async fn connect_tls_tcp(
config: &ConnectConfig,
) -> anyhow::Result<Framed<TlsTcpStream, LengthDelimitedCodec>> {
let server_addr = config.server_addr();
let server_name = config.server_name().clone();
let rustls_config = config.cert_mode.create_tls_client_config()?;
let connector = TlsConnector::from(Arc::new(rustls_config));
let tcp_stream = TcpStream::connect(server_addr)
.await
.context("Failed to establish underlying TCP connection")?;
if let Err(e) = tcp_stream.set_nodelay(true) {
log::error!("Failed to set TCP_NODELAY: {}", e);
}
let dns_name = server_name
.try_into()
.context("Invalid server name for TLS")?;
let tls_stream = connector
.connect(dns_name, tcp_stream)
.await
.context("Failed to perform TLS handshake")?;
let framed = Framed::new(tls_stream, LengthDelimitedCodec::new());
Ok(framed)
}
@@ -0,0 +1,96 @@
use crate::protocol::transmission::TransmissionBytes;
use crate::tunnel_core::server::transport::config::ConnectConfig;
use anyhow::{Context, bail};
use bytes::Bytes;
use futures::{SinkExt, StreamExt};
use std::sync::Arc;
use tokio::net::TcpStream;
use tokio_rustls::{TlsConnector, client::TlsStream};
use tokio_tungstenite::{WebSocketStream, client_async, tungstenite::Message};
type WssStream = WebSocketStream<TlsStream<TcpStream>>;
#[derive(Default)]
pub struct WssTransport {
stream: Option<WssStream>,
}
impl WssTransport {
pub fn new() -> Self {
Default::default()
}
pub fn disconnect(&mut self) {
self.stream = None;
}
pub async fn connect(&mut self, config: &ConnectConfig) -> anyhow::Result<()> {
if self.stream.is_some() {
bail!("Already connected");
}
let stream = connect_wss(config).await?;
self.stream = Some(stream);
Ok(())
}
pub async fn send(&mut self, buf: Bytes) -> anyhow::Result<()> {
let Some(framed) = self.stream.as_mut() else {
bail!("Not connected");
};
framed
.send(Message::Binary(buf))
.await
.context("send to server failed")
}
pub async fn next(&mut self) -> anyhow::Result<TransmissionBytes> {
let Some(framed) = self.stream.as_mut() else {
bail!("Not connected");
};
loop {
let message = framed
.next()
.await
.context("EOF")?
.context("receive from server failed")?;
match message {
Message::Binary(buf) => {
return Ok(TransmissionBytes::from(buf));
}
Message::Close(_) => {
bail!("Disconnected");
}
_ => {
continue;
}
}
}
}
}
pub async fn connect_wss(config: &ConnectConfig) -> anyhow::Result<WssStream> {
let server_addr = config.server_addr();
let server_name = config.server_name().clone();
let rustls_config = config.cert_mode.create_tls_client_config()?;
let connector = TlsConnector::from(Arc::new(rustls_config));
let tcp_stream = TcpStream::connect(server_addr)
.await
.context("Failed to establish underlying TCP connection")?;
if let Err(e) = tcp_stream.set_nodelay(true) {
log::error!("Failed to set TCP_NODELAY: {}", e);
}
let url = format!("wss://{}", server_name);
let dns_name = server_name
.try_into()
.context("Invalid server name for TLS")?;
let tls_stream = connector
.connect(dns_name, tcp_stream)
.await
.context("Failed to perform TLS handshake")?;
let (ws_stream, _response) = client_async(url, tls_stream)
.await
.context("Failed to perform WebSocket handshake")?;
Ok(ws_stream)
}
+29
View File
@@ -0,0 +1,29 @@
use anyhow::Context;
use std::fs;
use std::path::Path;
pub fn get_device_id() -> anyhow::Result<String> {
match machine_uid::get() {
Ok(id) => return Ok(id),
Err(e) => {
log::warn!("Failed to get system ID: {}. Using fallback.", e);
}
}
get_fallback_id()
}
fn get_fallback_id() -> anyhow::Result<String> {
let path = Path::new("device_id");
if let Ok(content) = fs::read_to_string(path) {
let id = content.trim();
if !id.is_empty() {
return Ok(id.to_string());
}
}
let new_id = uuid::Uuid::new_v4().to_string();
fs::write(path, &new_id).context("Failed to write device_id file")?;
Ok(new_id)
}
+251
View File
@@ -0,0 +1,251 @@
use anyhow::{Context, anyhow};
use dns_parser::{Builder, Packet, QueryClass, QueryType, RData, ResponseCode};
use rand::seq::SliceRandom;
use std::io;
use std::net::{Ipv4Addr, Ipv6Addr, SocketAddr};
use std::str::FromStr;
use std::time::Duration;
use tokio::net::UdpSocket;
use rust_p2p_core::socket::LocalInterface;
pub async fn dns_query_txt(
domain: &str,
mut name_servers: Vec<String>,
default_interface: &Option<LocalInterface>,
) -> io::Result<Vec<String>> {
let mut err: Option<io::Error> = None;
if name_servers.is_empty() {
name_servers.push("223.5.5.5:53".into());
name_servers.push("114.114.114.114:53".into());
}
for name_server in name_servers {
match txt_dns(domain, name_server, default_interface).await {
Ok(addr) => {
if !addr.is_empty() {
return Ok(addr);
}
}
Err(e) => {
err.replace(e);
}
}
continue;
}
if let Some(e) = err {
Err(e)
} else {
Err(io::Error::other(format!("DNS query failed {domain:?}")))
}
}
pub async fn dns_query_one(
domain: &str,
name_servers: &Vec<String>,
default_interface: &Option<LocalInterface>,
) -> anyhow::Result<SocketAddr> {
let mut vec = dns_query_all(domain, name_servers, default_interface).await?;
vec.shuffle(&mut rand::rng());
vec.pop().context("DNS query failed")
}
pub async fn dns_query_all(
domain: &str,
name_servers: &Vec<String>,
default_interface: &Option<LocalInterface>,
) -> anyhow::Result<Vec<SocketAddr>> {
match SocketAddr::from_str(domain) {
Ok(addr) => Ok(vec![addr]),
Err(_) => {
if name_servers.is_empty() {
let addrs: Vec<SocketAddr> = tokio::net::lookup_host(domain)
.await
.map_err(|e| io::Error::other(format!("DNS query failed: {domain:?},{e:?}")))?
.collect();
return Ok(addrs);
}
let mut err: Option<io::Error> = None;
for name_server in name_servers {
let end_index = domain
.rfind(':')
.ok_or_else(|| io::Error::other(format!("not port: {domain:?}")))?;
let host = &domain[..end_index];
let port = u16::from_str(&domain[end_index + 1..])
.map_err(|_| io::Error::other(format!("not port: {domain:?}")))?;
let th1 = {
let host = host.to_string();
let name_server = name_server.clone();
let default_interface = default_interface.clone();
tokio::spawn(a_dns(host, name_server, default_interface.clone()))
};
let th2 = {
let host = host.to_string();
let name_server = name_server.clone();
let default_interface = default_interface.clone();
tokio::spawn(aaaa_dns(host, name_server, default_interface.clone()))
};
let mut addr = Vec::new();
match th1.await? {
Ok(rs) => {
for ip in rs {
addr.push(SocketAddr::new(ip.into(), port));
}
}
Err(e) => {
err.replace(e);
}
}
match th2.await? {
Ok(rs) => {
for ip in rs {
addr.push(SocketAddr::new(ip.into(), port));
}
}
Err(e) => {
if addr.is_empty() {
err.replace(e);
continue;
}
}
}
if addr.is_empty() {
continue;
}
return Ok(addr);
}
if let Some(e) = err {
Err(e.into())
} else {
Err(anyhow!("DNS query failed {domain:?}"))
}
}
}
}
async fn query<'a>(
udp: &UdpSocket,
domain: &str,
name_server: SocketAddr,
record_type: QueryType,
buf: &'a mut [u8],
) -> io::Result<Packet<'a>> {
let mut builder = Builder::new_query(1, true);
builder.add_question(domain, false, record_type, QueryClass::IN);
let packet = builder.build().unwrap();
udp.connect(name_server).await?;
let mut count = 0;
let len = loop {
udp.send(&packet).await?;
match tokio::time::timeout(Duration::from_secs(3), udp.recv(buf)).await {
Ok(len) => {
break len?;
}
Err(_) => {
count += 1;
if count < 3 {
continue;
}
Err(io::Error::other(format!("DNS {name_server:?} recv error ")))?
}
};
};
let pkt = Packet::parse(&buf[..len]).map_err(|e| {
io::Error::other(format!(
"domain {domain:?} DNS {name_server:?} data error: {e}"
))
})?;
if pkt.header.response_code != ResponseCode::NoError {
return Err(io::Error::other(format!(
"response_code {} DNS {:?} domain {:?}",
pkt.header.response_code, name_server, domain
)));
}
if pkt.answers.is_empty() {
return Err(io::Error::other(format!(
"No records received DNS {name_server:?} domain {domain:?}"
)));
}
Ok(pkt)
}
pub async fn txt_dns(
domain: &str,
name_server: String,
default_interface: &Option<LocalInterface>,
) -> io::Result<Vec<String>> {
let name_server: SocketAddr = name_server
.parse()
.map_err(|e| io::Error::other(format!("dns {name_server} is error :{e:?}")))?;
let udp = bind_udp(name_server, default_interface)?;
let mut buf = vec![0u8; 65536];
let message = query(&udp, domain, name_server, QueryType::TXT, &mut buf).await?;
let mut rs = Vec::new();
for record in message.answers {
if let RData::TXT(txt) = record.data {
for x in txt.iter() {
let txt = std::str::from_utf8(x)
.map_err(|_| io::Error::other("record type txt is not string"))?;
rs.push(txt.to_string());
}
}
}
Ok(rs)
}
fn bind_udp(
name_server: SocketAddr,
default_interface: &Option<LocalInterface>,
) -> io::Result<UdpSocket> {
let addr: SocketAddr = if name_server.is_ipv4() {
"0.0.0.0:0"
.parse()
.expect("valid IPv4 socket address literal")
} else {
"[::]:0".parse().expect("valid IPv6 socket address literal")
};
let socket = rust_p2p_core::socket::bind_udp(addr, default_interface.as_ref())?;
UdpSocket::from_std(socket.into())
}
pub async fn a_dns(
domain: String,
name_server: String,
default_interface: Option<LocalInterface>,
) -> io::Result<Vec<Ipv4Addr>> {
let name_server: SocketAddr = name_server
.parse()
.map_err(|e| io::Error::other(format!("dns {name_server} is error :{e:?}")))?;
let udp = bind_udp(name_server, &default_interface)?;
let mut buf = vec![0u8; 65536];
let message = query(&udp, &domain, name_server, QueryType::A, &mut buf).await?;
let mut rs = Vec::new();
for record in message.answers {
if let RData::A(a) = record.data {
rs.push(a.0);
}
}
Ok(rs)
}
pub async fn aaaa_dns(
domain: String,
name_server: String,
default_interface: Option<LocalInterface>,
) -> io::Result<Vec<Ipv6Addr>> {
let name_server: SocketAddr = name_server
.parse()
.map_err(|e| io::Error::other(format!("dns {name_server} is error :{e:?}")))?;
let udp = bind_udp(name_server, &default_interface)?;
let mut buf = vec![0u8; 65536];
let message = query(&udp, &domain, name_server, QueryType::AAAA, &mut buf).await?;
let mut rs = Vec::new();
for record in message.answers {
if let RData::AAAA(a) = record.data {
rs.push(a.0);
}
}
Ok(rs)
}
+11
View File
@@ -0,0 +1,11 @@
pub mod device_id;
pub(crate) mod dns_query;
pub mod task_control;
pub(crate) mod time {
pub fn now_ts_ms() -> i64 {
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_millis() as i64
}
}
+255
View File
@@ -0,0 +1,255 @@
use parking_lot::Mutex;
use std::collections::HashMap;
use std::future::Future;
use std::sync::{Arc, Weak};
use tokio::sync::Notify;
use tokio::task::{Id, JoinHandle};
struct TaskGroupState {
stopped: bool,
tasks: HashMap<Id, JoinHandle<()>>,
}
struct TaskGroupInner {
state: Mutex<TaskGroupState>,
all_stopped_notify: Notify,
}
impl TaskGroupInner {
fn new() -> Self {
Self {
state: Mutex::new(TaskGroupState {
stopped: false,
tasks: HashMap::new(),
}),
all_stopped_notify: Notify::new(),
}
}
fn spawn<F>(self: &Arc<Self>, f: F) -> Option<Id>
where
F: Future + Send + 'static,
F::Output: Send + 'static,
{
let mut state = self.state.lock();
if state.stopped {
return None;
}
let guard = TaskGuard {
inner: Arc::downgrade(self),
};
let handle = tokio::spawn(async move {
let _guard = guard;
f.await;
});
let task_id = handle.id();
state.tasks.insert(task_id, handle);
Some(task_id)
}
fn stop(&self) {
let mut state = self.state.lock();
state.stopped = true;
for (_, handle) in state.tasks.drain() {
handle.abort();
}
}
fn is_stopped(&self) -> bool {
self.state.lock().stopped
}
fn remove_task(&self, task_id: Id) {
let all_stopped = {
let mut state = self.state.lock();
state.tasks.remove(&task_id);
if state.tasks.is_empty() {
state.stopped = true;
true
} else {
false
}
};
if all_stopped {
self.all_stopped_notify.notify_waiters();
}
}
async fn abort_task(&self, task_id: Id) {
let handle = self.state.lock().tasks.remove(&task_id);
if let Some(handle) = handle {
handle.abort();
_ = handle.await;
}
}
async fn join_all(&self) {
let tasks = std::mem::take(&mut self.state.lock().tasks);
for (_, h) in tasks {
let _ = h.await;
}
}
fn all_tasks_stopped(&self) -> bool {
let state = self.state.lock();
state.stopped && state.tasks.is_empty()
}
}
impl Drop for TaskGroupInner {
fn drop(&mut self) {
self.stop();
}
}
struct TaskGuard {
inner: Weak<TaskGroupInner>,
}
impl Drop for TaskGuard {
fn drop(&mut self) {
if let Some(inner) = self.inner.upgrade() {
let task_id = tokio::task::id();
inner.remove_task(task_id);
}
}
}
#[derive(Clone)]
pub struct TaskGroup {
inner: Arc<TaskGroupInner>,
}
impl TaskGroup {
fn new() -> Self {
Self {
inner: Arc::new(TaskGroupInner::new()),
}
}
pub fn stop(&self) {
self.inner.stop();
}
pub fn is_stopped(&self) -> bool {
self.inner.is_stopped()
}
pub fn spawn<F>(&self, f: F) -> SubTask
where
F: Future + Send + 'static,
F::Output: Send + 'static,
{
match self.inner.spawn(f) {
Some(task_id) => SubTask::new(task_id, Arc::downgrade(&self.inner)),
None => SubTask::empty(),
}
}
pub async fn join_all(&self) {
self.inner.join_all().await;
}
pub async fn wait_all_stopped(&self) {
loop {
if self.inner.all_tasks_stopped() {
return;
}
self.inner.all_stopped_notify.notified().await;
}
}
}
pub struct SubTask {
task_id: Option<Id>,
inner: Weak<TaskGroupInner>,
}
impl SubTask {
fn new(task_id: Id, inner: Weak<TaskGroupInner>) -> Self {
Self {
task_id: Some(task_id),
inner,
}
}
fn empty() -> Self {
Self {
task_id: None,
inner: Weak::new(),
}
}
pub async fn stop(&self) {
if let Some(task_id) = self.task_id
&& let Some(inner) = self.inner.upgrade()
{
inner.abort_task(task_id).await;
}
}
pub fn is_running(&self) -> bool {
if let Some(task_id) = self.task_id
&& let Some(inner) = self.inner.upgrade()
{
return inner.state.lock().tasks.contains_key(&task_id);
}
false
}
pub fn id(&self) -> Option<Id> {
self.task_id
}
}
#[derive(Clone, Default)]
pub struct TaskGroupManager {
task_group: Arc<Mutex<Option<TaskGroup>>>,
}
impl TaskGroupManager {
pub fn new() -> Self {
TaskGroupManager::default()
}
pub fn is_running(&self) -> bool {
self.task_group.lock().is_some()
}
pub fn is_stopped(&self) -> bool {
self.task_group.lock().is_none()
}
pub fn create_task(&self) -> anyhow::Result<(TaskGroup, TaskGroupGuard)> {
let mut guard = self.task_group.lock();
if guard.is_some() {
anyhow::bail!("运行中")
}
let task_group = TaskGroup::new();
guard.replace(task_group.clone());
let stop_guard = TaskGroupGuard {
task_group: self.task_group.clone(),
};
Ok((task_group, stop_guard))
}
pub fn stop(&self) {
let option = self.task_group.lock();
if let Some(task_group) = option.as_ref() {
task_group.stop();
}
}
}
pub struct TaskGroupGuard {
task_group: Arc<Mutex<Option<TaskGroup>>>,
}
impl Drop for TaskGroupGuard {
fn drop(&mut self) {
if let Some(task_group) = self.task_group.lock().take() {
task_group.stop();
}
}
}
+20
View File
@@ -0,0 +1,20 @@
[package]
name = "vnt-ipc"
version = "2.0.0"
edition = "2024"
[dependencies]
vnt-core = { path = "../vnt-core" }
anyhow = "1.0.100"
futures = "0.3.31"
prost = "0.14.1"
tokio = "1.48.0"
tokio-util = "0.7.17"
log = "0.4.29"
console = "0.16.2"
time = "0.3.44"
cli-table = "0.5.0"
[build-dependencies]
prost-build = "0.14"
+7
View File
@@ -0,0 +1,7 @@
fn main() {
let mut config = prost_build::Config::new();
config.protoc_arg("--experimental_allow_proto3_optional");
config
.compile_protos(&["proto/local_ipc.proto"], &["proto"])
.unwrap();
}
+103
View File
@@ -0,0 +1,103 @@
syntax = "proto3";
package protocol.local_ipc;
message IpcRequest{
oneof ipc_cmd{
AppInfoCMD app_info = 1;
ClientIpsCMD client_ips = 2;
ClientListCMD client_list = 3;
AllRouteCMD all_route = 4;
}
}
message AppInfoCMD{
}
message ClientIpsCMD{
}
message ClientListCMD{
}
message AllRouteCMD{
}
message IpcResponse{
oneof response_payload{
AppInfo app_info = 1;
ClientIpList client_ips = 2;
ClientInfoList client_list = 3;
ClientRouteList all_route = 4;
}
}
message AppInfo{
repeated ServerInfo server_info = 1;
string name = 3;
string version = 4;
optional uint32 ip = 5;
uint32 online_client_num = 6;
uint32 offline_client_num = 7;
uint32 direct_client_num = 8;
string device_id = 11;
optional string nat_type = 12;
repeated string public_ipv4s = 13;
optional string public_ipv6 = 14;
}
message ServerInfo{
string server = 1;
bool connected = 2;
optional uint32 server_rtt = 3;
optional int64 last_connected_time = 4;
}
message ClientInfoList{
repeated ClientInfoItem items = 1;
}
message ClientInfoItem{
uint32 ip = 1;
string name = 2;
string version = 3;
bool online = 4;
bool is_direct = 5;
int64 last_connected_time = 6;
optional uint32 rtt = 7;
bool key_equal = 8;
optional PacketLoss packet_loss = 9;
}
message PacketLoss{
uint64 sent = 1;
uint64 received = 2;
double loss_rate = 3;
}
message ClientRouteList{
repeated ClientRouteItem items = 1;
}
message ClientRouteItem{
uint32 ip = 1;
repeated Route route_list = 2;
}
message Route{
string addr = 1;
uint32 metric = 2;
uint32 rtt = 3;
}
message ClientIpList{
repeated ClientIpItem items = 1;
}
message ClientIpItem{
uint32 ip = 1;
bool online = 2;
bool is_direct = 3;
optional uint32 rtt = 4;
}
+285
View File
@@ -0,0 +1,285 @@
use crate::message::ipc_request::IpcCmd;
use crate::message::ipc_response::ResponsePayload;
use crate::message::{
AppInfo, ClientInfoList, ClientIpList, ClientRouteList, IpcRequest, IpcResponse,
};
use crate::{DEFAULT_PORT, get_port_file_path};
use anyhow::Context;
use cli_table::{Cell, Style, Table, print_stdout};
use console::style;
use futures::{SinkExt, StreamExt};
use prost::Message;
use std::fs;
use std::net::Ipv4Addr;
use std::time::Duration;
use time::macros::format_description;
use time::{OffsetDateTime, UtcOffset};
use tokio::net::TcpStream;
use tokio_util::codec::{Framed, LengthDelimitedCodec};
pub async fn run_client(cmd: IpcCmd, port: Option<u16>) -> anyhow::Result<()> {
let path = get_port_file_path();
let port = if let Some(port) = port {
port
} else {
match fs::read_to_string(&path) {
Ok(port_string) => port_string.trim().parse().context("parse port error")?,
Err(_) => DEFAULT_PORT,
}
};
let addr = format!("127.0.0.1:{}", port);
let stream = tokio::time::timeout(Duration::from_secs(3), TcpStream::connect(&addr))
.await
.context("Connection timed out")??;
let mut framed = Framed::new(stream, LengthDelimitedCodec::new());
framed
.send(IpcRequest { ipc_cmd: Some(cmd) }.encode_to_vec().into())
.await?;
let response = framed.next().await.context("Unexpected end of stream")??;
let response = IpcResponse::decode(response).context("decode response error")?;
match response
.response_payload
.context("response payload is empty")?
{
ResponsePayload::AppInfo(info) => {
print_app_info(info);
}
ResponsePayload::ClientIps(list) => print_client_ip_list(list)?,
ResponsePayload::ClientList(list) => {
print_client_list(list)?;
}
ResponsePayload::AllRoute(route_list) => {
print_route_list(route_list)?;
}
};
Ok(())
}
fn key_style(s: &str) -> console::StyledObject<&str> {
style(s).cyan().bright().bold()
}
pub fn print_app_info(info: AppInfo) {
println!(
"\n{}",
style("--- Application Information ---").italic().dim()
);
for server_info in info.server_info {
let status_colored = if server_info.connected {
if let Some(rtt) = server_info.server_rtt {
style(format!("Online {rtt}ms"))
.green()
.bright()
.to_string()
} else {
style("Online").green().bright().to_string()
}
} else {
style("Offline").red().bright().to_string()
};
println!(
"{:24}{} ({})",
key_style("Server:"),
style(&server_info.server).white(),
status_colored
);
let time_str = server_info
.last_connected_time
.map(|v| style(ts_to_string(v / 1000)).white().to_string())
.unwrap_or_else(|| "Never".to_string());
println!("{:24}{}", key_style("Last Connected Time:"), time_str);
}
println!("{:24}{}", key_style("Name:"), style(&info.name).white());
println!("{:24}{}", key_style("Id:"), style(&info.device_id).white());
println!(
"{:24}{}",
key_style("Version:"),
style(&info.version).white()
);
let ip_str = info
.ip
.map(|v| {
style(Ipv4Addr::from(v).to_string())
.yellow()
.bright()
.to_string()
})
.unwrap_or_else(|| style("N/A").red().bright().to_string());
println!("{:24}{}", key_style("IP:"), ip_str);
let total_clients = info.online_client_num + info.offline_client_num;
println!(
"{:24}{}",
key_style("Total Clients:"),
style(total_clients).color256(213)
);
println!(
"{:24}{}",
key_style("Online Clients:"),
style(info.online_client_num).color256(82)
);
println!(
"{:24}{}",
key_style("P2P Clients:"),
style(info.direct_client_num).color256(117)
);
println!(
"{:24}{}",
key_style("Nat Type:"),
style(info.nat_type.unwrap_or_else(|| "Unknown".to_string())).color256(117)
);
println!(
"{:24}{}",
key_style("Ipv6:"),
style(info.public_ipv6.unwrap_or_default()).color256(117)
);
println!(
"{:24}{}",
key_style("Public Ipv4:"),
style(info.public_ipv4s.join(",")).color256(117)
);
println!(
"{}",
style("-------------------------------").italic().dim()
);
}
fn print_client_ip_list(list: ClientIpList) -> anyhow::Result<()> {
println!("\n--- Client List ({}) ---", list.items.len());
let table = list
.items
.iter()
.map(|item| {
vec![
Ipv4Addr::from(item.ip).to_string().cell(),
item.online.to_string().cell(),
item.is_direct.to_string().cell(),
item.rtt.map(|v| v.to_string()).unwrap_or_default().cell(),
]
})
.table()
.title(vec![
"IP".cell().bold(true),
"Online".cell().bold(true),
"P2P".cell().bold(true),
"RTT".cell().bold(true),
]);
print_stdout(table)?;
println!("\n");
Ok(())
}
fn print_client_list(list: ClientInfoList) -> anyhow::Result<()> {
println!("\n--- Client List ({}) ---", list.items.len());
let table = list
.items
.iter()
.map(|item| {
let key_equal: bool = item.key_equal;
let mut ip_str = Ipv4Addr::from(item.ip).to_string();
if !key_equal {
ip_str.push_str("(Key Mismatch)");
}
let loss_str = item
.packet_loss
.as_ref()
.map(|v| format!("{:.1}%", v.loss_rate))
.unwrap_or_default();
vec![
ip_str.cell(),
item.name.clone().cell(),
item.version.clone().cell(),
item.online.to_string().cell(),
item.is_direct.to_string().cell(),
item.rtt.map(|v| v.to_string()).unwrap_or_default().cell(),
loss_str.cell(),
ts_to_string(item.last_connected_time).cell(),
]
})
.table()
.title(vec![
"IP".cell().bold(true),
"Name".cell().bold(true),
"Version".cell().bold(true),
"Online".cell().bold(true),
"P2P".cell().bold(true),
"RTT".cell().bold(true),
"Loss".cell().bold(true),
"Last Connected Time".cell().bold(true),
]);
print_stdout(table)?;
println!("\n");
Ok(())
}
pub fn print_route_list(route_list: ClientRouteList) -> anyhow::Result<()> {
let total_routes: usize = route_list
.items
.iter()
.map(|item| item.route_list.len())
.sum();
println!("\n--- All Routes List (Total: {}) ---", total_routes);
let mut rows = Vec::new();
for client_route in route_list.items {
let client_ip_str = Ipv4Addr::from(client_route.ip).to_string();
for route in client_route.route_list {
rows.push(vec![
client_ip_str.clone().cell(),
route.metric.to_string().cell(),
route.rtt.to_string().cell(),
route.addr.clone().cell(),
]);
}
}
let table = rows.table().title(vec![
"Destination IP".cell().bold(true),
"Metric".cell().bold(true),
"RTT (ms)".cell().bold(true),
"Remote Address".cell().bold(true),
]);
print_stdout(table)?;
println!("\n");
Ok(())
}
pub fn ts_to_string(ts_secs: i64) -> String {
let dt = match OffsetDateTime::from_unix_timestamp(ts_secs) {
Ok(dt) => dt,
Err(_) => {
return String::new();
}
};
let local_offset = match UtcOffset::local_offset_at(dt) {
Ok(offset) => offset,
Err(_e) => match UtcOffset::from_hms(8, 0, 0) {
Ok(offset) => offset,
Err(_) => return String::new(),
},
};
let dt_local = dt.to_offset(local_offset);
let format = format_description!("[year]-[month]-[day] [hour]:[minute]:[second]");
dt_local.format(&format).unwrap()
}
+12
View File
@@ -0,0 +1,12 @@
pub mod client;
pub mod message;
pub mod server;
pub use vnt_core::*;
const DEFAULT_PORT: u16 = 11233;
const PORT_FILE: &str = "PORT";
fn get_port_file_path() -> std::path::PathBuf {
std::path::PathBuf::from(PORT_FILE)
}
+4
View File
@@ -0,0 +1,4 @@
mod proto {
include!(concat!(env!("OUT_DIR"), "/protocol.local_ipc.rs"));
}
pub use proto::*;
+194
View File
@@ -0,0 +1,194 @@
use crate::{DEFAULT_PORT, get_port_file_path};
use crate::message::ipc_request::IpcCmd;
use crate::message::ipc_response::ResponsePayload;
use crate::message::{
AppInfo, ClientInfoItem, ClientInfoList, ClientIpItem, ClientIpList, ClientRouteItem,
ClientRouteList, IpcRequest, IpcResponse, PacketLoss, Route, ServerInfo,
};
use anyhow::bail;
use futures::{SinkExt, StreamExt};
use prost::Message;
use std::fs;
use std::net::Ipv4Addr;
use tokio::io::{self};
use tokio::net::{TcpListener, TcpStream};
use tokio_util::codec::{Framed, LengthDelimitedCodec};
use vnt_core::api::VntApi;
async fn handle_connection(stream: TcpStream, vnt_api: VntApi) -> anyhow::Result<()> {
let mut framed = Framed::new(stream, LengthDelimitedCodec::new());
if let Some(Ok(message)) = framed.next().await {
let request = IpcRequest::decode(message.as_ref())?;
let Some(cmd) = request.ipc_cmd else {
bail!("Received an IpcRequest but it was None");
};
let rs = match cmd {
IpcCmd::AppInfo(_) => {
let info = app_info(&vnt_api);
ResponsePayload::AppInfo(info)
}
IpcCmd::ClientIps(_) => {
let items = vnt_api
.client_ips()
.into_iter()
.map(|v| {
let rtt = if let Some(v) = vnt_api.find_route(&v.ip) {
Some(v.rtt())
} else {
vnt_api.server_node_rtt(&v.ip).map(|v| v * 2)
};
ClientIpItem {
ip: v.ip.into(),
online: v.online,
is_direct: vnt_api.is_direct(&v.ip),
rtt,
}
})
.collect();
ResponsePayload::ClientIps(ClientIpList { items })
}
IpcCmd::ClientList(_) => {
let client_list = vnt_api.server_rpc().client_list().await?;
let key_sign = vnt_api.get_config().and_then(|config| config.key_sign());
let items = client_list
.list
.into_iter()
.map(|v| {
let ip = Ipv4Addr::from(v.ip);
let rtt = if let Some(v) = vnt_api.find_route(&ip) {
Some(v.rtt())
} else {
vnt_api.server_node_rtt(&ip).map(|v| v * 2)
};
let packet_loss = vnt_api.packet_loss_info(&ip).map(|info| PacketLoss {
sent: info.sent,
received: info.received,
loss_rate: info.loss_rate,
});
ClientInfoItem {
ip: v.ip,
name: v.name,
version: v.version,
online: v.online,
is_direct: vnt_api.is_direct(&Ipv4Addr::from(v.ip)),
last_connected_time: v.last_connected_time,
rtt,
key_equal: key_sign == v.key_sign,
packet_loss,
}
})
.collect();
ResponsePayload::ClientList(ClientInfoList { items })
}
IpcCmd::AllRoute(_) => {
let route_list = all_route(&vnt_api);
ResponsePayload::AllRoute(route_list)
}
};
let rs = IpcResponse {
response_payload: Some(rs),
};
framed.send(rs.encode_to_vec().into()).await?;
}
Ok(())
}
fn app_info(vnt_api: &VntApi) -> AppInfo {
let config = vnt_api.get_config();
let ips = vnt_api.client_ips();
let online_client_num = ips.iter().filter(|v| v.online).count() as _;
let offline_client_num = ips.iter().filter(|v| !v.online).count() as _;
let direct_client_num = ips.iter().filter(|ip| vnt_api.is_direct(&ip.ip)).count() as _;
let server_node_list = vnt_api.server_node_list();
let nat_info = vnt_api.nat_info();
AppInfo {
server_info: server_node_list
.into_iter()
.map(|v| ServerInfo {
server: v.server_addr.to_string(),
connected: v.connected,
server_rtt: v.rtt,
last_connected_time: v.last_connected_time,
})
.collect(),
name: config
.as_ref()
.map(|v| v.device_name.clone())
.unwrap_or_default(),
version: env!("CARGO_PKG_VERSION").to_string(),
ip: vnt_api.network().map(|v| v.ip.into()),
online_client_num,
offline_client_num,
direct_client_num,
device_id: config.map(|v| v.device_id.clone()).unwrap_or_default(),
nat_type: nat_info.as_ref().map(|v| format!("{:?}", v.nat_type)),
public_ipv4s: nat_info
.as_ref()
.map(|v| {
v.public_ips
.iter()
.map(|v| v.to_string())
.collect::<Vec<String>>()
})
.unwrap_or_default(),
public_ipv6: nat_info
.as_ref()
.and_then(|v| v.ipv6.map(|v| v.to_string())),
}
}
fn all_route(vnt_api: &VntApi) -> ClientRouteList {
let vec = vnt_api.route_table();
let mut items = Vec::with_capacity(vec.len());
for (ip, route_list) in vec {
items.push(ClientRouteItem {
ip: ip.into(),
route_list: route_list
.into_iter()
.map(|v| Route {
addr: v.route_key().to_string(),
metric: v.metric() as _,
rtt: v.rtt(),
})
.collect(),
})
}
ClientRouteList { items }
}
pub async fn run_server(bind_port: Option<u16>, vnt_api: VntApi) -> anyhow::Result<()> {
let mut port = bind_port.unwrap_or(DEFAULT_PORT);
let listener;
loop {
match TcpListener::bind(format!("127.0.0.1:{}", port)).await {
Ok(l) => {
listener = l;
break;
}
Err(e) if bind_port.is_none() && e.kind() == io::ErrorKind::AddrInUse => {
port = 0;
}
Err(e) => bail!("bind :{e:?}"),
}
}
let bound_addr = listener.local_addr()?;
log::info!("IPC Listening on {}", bound_addr);
let actual_port = bound_addr.port();
let path = get_port_file_path();
fs::write(&path, actual_port.to_string())?;
loop {
let (stream, peer_addr) = listener.accept().await?;
log::info!("IPC Connection from {}", peer_addr);
let vnt_api = vnt_api.clone();
tokio::spawn(async move {
if let Err(e) = handle_connection(stream, vnt_api).await {
log::warn!("IPC Error handling client: {:?},peer_addr={peer_addr}", e);
}
});
}
}
+23
View File
@@ -0,0 +1,23 @@
[package]
name = "vnt-web"
version = "2.0.0"
edition = "2024"
[dependencies]
vnt-core = { path = "../vnt-core" }
tokio = { version = "1", features = ["full"] }
axum = "0.8.8"
tower-http = { version = "0.6", features = ["fs", "cors", "trace"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
anyhow = "1.0.100"
log = "0.4.29"
ipnet = "2.11.0"
hostname = "0.4.2"
route_manager = "0.2.11"
toml = "0.9.8"
rust-embed = "8.0"
mime_guess = "2.0"
parking_lot = "0.12"
time = { version = "0.3.45", features = ["local-offset", "formatting", "macros"] }
+17
View File
@@ -0,0 +1,17 @@
mod service_http;
pub use service_http::run_http_server;
struct ScopeGuard<F: FnOnce()>(Option<F>);
impl<F: FnOnce()> Drop for ScopeGuard<F> {
fn drop(&mut self) {
if let Some(f) = self.0.take() {
f();
}
}
}
fn defer<F: FnOnce()>(f: F) -> ScopeGuard<F> {
ScopeGuard(Some(f))
}
+1105
View File
@@ -0,0 +1,1105 @@
use crate::defer;
use anyhow::{Context, anyhow};
use axum::body::Body;
use axum::http::{HeaderMap, HeaderValue, StatusCode, Uri, header};
use axum::response::IntoResponse;
use axum::{
Json, Router,
extract::{Query, Request, State},
middleware,
response::Response,
routing::{get, post},
};
use ipnet::Ipv4Net;
use mime_guess::from_path;
use parking_lot::Mutex;
use rust_embed::RustEmbed;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::net::{Ipv4Addr, Ipv6Addr, SocketAddr};
use std::path::{Path, PathBuf};
use std::sync::Arc;
use std::time::{Instant, SystemTime, UNIX_EPOCH};
use time::{OffsetDateTime, format_description};
use tokio::fs;
use tokio::net::TcpListener;
use tower_http::cors::{Any, CorsLayer};
use vnt_core::api::VntApi;
use vnt_core::context::config::Config as CoreConfig;
use vnt_core::core::{DEFAULT_MTU, NetworkManager};
use vnt_core::nat::NetInput;
use vnt_core::port_mapping::PortMapping;
use vnt_core::tls::verifier::CertValidationMode;
use vnt_core::tunnel_core::server::transport::config::ProtocolAddress;
use vnt_core::utils::task_control::TaskGroupManager;
const CONFIG_DIR: &str = "vnt_config";
const CURRENT_CONFIG_RECORD: &str = "vnt_current_config.txt";
#[derive(Serialize, Clone, Copy, PartialEq, Eq, Default)]
#[serde(rename_all = "lowercase")]
enum VntStatus {
#[default]
Stopped,
Starting,
Running,
}
#[derive(Clone)]
struct HttpAppState {
task_group_manager: TaskGroupManager,
inner: Arc<Mutex<HttpAppStateInner>>,
}
#[derive(Default)]
struct HttpAppStateInner {
vnt: Option<VntHandler>,
status: VntStatus,
start_logs: Vec<String>,
}
impl HttpAppState {
fn starting(&self) -> anyhow::Result<()> {
let mut inner = self.inner.lock();
if inner.status != VntStatus::Stopped {
return Err(anyhow!("VNT is already starting or running"));
}
if inner.vnt.is_some() {
return Err(anyhow!("VNT is already running"));
}
inner.status = VntStatus::Starting;
inner.start_logs.clear();
Ok(())
}
fn stopped(&self) {
let mut inner = self.inner.lock();
inner.vnt.take();
inner.status = VntStatus::Stopped;
}
fn starting_to_stopped(&self) {
let mut inner = self.inner.lock();
if inner.status != VntStatus::Starting {
return;
}
inner.vnt.take();
inner.status = VntStatus::Stopped;
inner
.start_logs
.push(format!("[{}] 启动中断", HttpAppState::timestamp()));
}
fn starting_to_running(&self) {
let mut inner = self.inner.lock();
if inner.status != VntStatus::Starting {
log::error!("starting_to_running VNT is not starting");
return;
}
inner.status = VntStatus::Running;
inner.start_logs.clear();
}
fn record_log(&self, msg: impl Into<String>) {
let mut inner = self.inner.lock();
if inner.status != VntStatus::Starting {
return;
}
inner
.start_logs
.push(format!("[{}] {}", Self::timestamp(), msg.into()));
}
fn record_log_and_stopped(&self, msg: impl Into<String>) {
let mut inner = self.inner.lock();
if inner.status != VntStatus::Starting {
return;
}
inner
.start_logs
.push(format!("[{}] {}", Self::timestamp(), msg.into()));
inner.status = VntStatus::Stopped;
}
fn status(&self) -> VntStatus {
self.inner.lock().status
}
fn timestamp() -> String {
let now = OffsetDateTime::now_local().unwrap_or_else(|_| OffsetDateTime::now_utc());
let format = format_description::parse("[hour]:[minute]:[second]").unwrap();
now.format(&format)
.unwrap_or_else(|_| "00:00:00".to_string())
}
}
struct VntHandler {
api: VntApi,
config_name: String,
config_file_name: String,
}
#[derive(Serialize)]
struct ApiResponse<T> {
code: i32,
msg: String,
data: Option<T>,
}
impl<T> ApiResponse<T> {
fn success(data: T) -> Self {
Self {
code: 0,
msg: "success".to_string(),
data: Some(data),
}
}
fn error(msg: impl Into<String>) -> Self {
Self {
code: -1,
msg: msg.into(),
data: None,
}
}
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct StartConfig {
pub config_name: Option<String>,
pub server: Vec<String>,
pub cert_mode: Option<String>,
pub network_code: String,
pub device_id: Option<String>,
pub device_name: Option<String>,
pub tun_name: Option<String>,
pub ip: Option<Ipv4Addr>,
pub password: Option<String>,
#[serde(default)]
pub no_punch: bool,
#[serde(default)]
pub compress: bool,
#[serde(default)]
pub rtx: bool,
#[serde(default)]
pub fec: bool,
#[serde(default)]
pub input: Vec<NetInput>,
#[serde(default)]
pub output: Vec<Ipv4Net>,
#[serde(default)]
pub no_nat: bool,
#[serde(default)]
pub no_tun: bool,
pub mtu: Option<u16>,
#[serde(default)]
pub port_mapping: Vec<String>,
#[serde(default)]
pub allow_mapping: bool,
#[serde(default)]
pub udp_stun: Vec<String>,
#[serde(default)]
pub tcp_stun: Vec<String>,
}
#[derive(Deserialize)]
struct SaveConfigReq {
file_name: Option<String>,
config: String,
}
#[derive(Deserialize)]
struct FileReq {
file_name: String,
}
#[derive(Serialize)]
struct ConfigSummary {
file_name: String,
config_name: String,
}
#[derive(Serialize, Default)]
struct HttpAppInfo {
name: String,
version: String,
ip: Option<Ipv4Addr>,
prefix_len: Option<u8>,
gateway: Option<Ipv4Addr>,
device_id: String,
status: VntStatus,
current_config_name: Option<String>,
current_config_file: Option<String>,
online_client_num: usize,
offline_client_num: usize,
direct_client_num: usize,
server_info: Vec<HttpServerInfo>,
nat_type: Option<String>,
public_ipv6: Option<Ipv6Addr>,
public_ipv4s: Vec<Ipv4Addr>,
network_code: Option<String>,
mtu: Option<u16>,
fec: Option<bool>,
compress: Option<bool>,
encrypt: Option<bool>,
rtx: Option<bool>,
}
#[derive(Serialize)]
struct HttpServerInfo {
server: String,
connected: bool,
server_rtt: Option<u32>,
server_version: Option<String>,
}
#[derive(Serialize)]
struct HttpClientItem {
ip: Ipv4Addr,
name: Option<String>,
online: bool,
route: Option<HttpRouteDetail>,
version: String,
last_connected_time: i64,
key_equal: i32,
nat_info: Option<HttpClientNatInfo>,
packet_loss: Option<HttpPacketLoss>,
traffic: Option<HttpTraffic>,
}
#[derive(Serialize)]
struct HttpClientNatInfo {
nat_type: String,
public_ips: Vec<Ipv4Addr>,
ipv6: Option<Ipv6Addr>,
}
#[derive(Serialize)]
struct HttpPacketLoss {
sent: u64,
received: u64,
loss_rate: f64,
}
#[derive(Serialize)]
struct HttpTraffic {
tx_bytes: u64,
rx_bytes: u64,
}
#[derive(Serialize)]
struct HttpRouteItem {
ip: Ipv4Addr,
routes: Vec<HttpRouteDetail>,
}
#[derive(Serialize)]
struct HttpRouteDetail {
addr: String,
protocol: String,
metric: u8,
rtt: u32,
}
#[derive(Serialize)]
struct StartStatusResponse {
status: VntStatus,
logs: Vec<String>,
}
async fn get_start_status(
State(state): State<HttpAppState>,
) -> Json<ApiResponse<StartStatusResponse>> {
let lock = state.inner.lock();
Json(ApiResponse::success(StartStatusResponse {
status: lock.status,
logs: lock.start_logs.clone(),
}))
}
async fn logging_middleware(req: Request, next: axum::middleware::Next) -> Response {
let method = req.method().clone();
let uri = req.uri().clone();
let start = Instant::now();
let response = next.run(req).await;
log::info!(
"Request: {} {} | Status: {} | Took: {:?}",
method,
uri,
response.status(),
start.elapsed()
);
response
}
#[derive(RustEmbed)]
#[folder = "static/"]
struct Asset;
pub async fn run_http_server(
addr: SocketAddr,
start_config_file_name: Option<PathBuf>,
) -> anyhow::Result<()> {
fs::create_dir_all(CONFIG_DIR)
.await
.context("Failed to create config directory")?;
let state = HttpAppState {
task_group_manager: TaskGroupManager::new(),
inner: Arc::new(Default::default()),
};
// 自动启动逻辑
let auto_start_file = determine_auto_start_file(start_config_file_name).await;
if let Some((file_name, path)) = auto_start_file {
log::info!("Auto starting VNT with config: {:?}", path);
let state_clone = state.clone();
tokio::spawn(async move {
if let Err(e) = start_vnt_internal(&state_clone, file_name, path).await {
log::error!("Auto start failed: {:?}", e);
}
});
}
let cors = CorsLayer::new()
.allow_origin(Any)
.allow_methods(Any)
.allow_headers(Any);
let app = Router::new()
.route("/api/info", get(get_info))
.route("/api/peers", get(get_peers))
.route("/api/routes", get(get_routes))
.route("/api/start/status", get(get_start_status))
.route("/api/start", post(start_vnt_handler))
.route("/api/stop", post(stop_vnt_handler))
.route("/api/restart", post(restart_vnt_handler))
.route("/api/config/list", get(list_configs))
.route(
"/api/config",
get(get_config).post(save_config).delete(delete_config),
)
.layer(cors)
.layer(middleware::from_fn(logging_middleware))
.with_state(state)
.fallback(static_handler);
log::info!("HTTP API Listening on http://{}", addr);
let listener = TcpListener::bind(addr).await?;
axum::serve(listener, app)
.with_graceful_shutdown(shutdown_signal())
.await?;
Ok(())
}
/// 确定自动启动的配置文件
async fn determine_auto_start_file(
start_config_file_name: Option<PathBuf>,
) -> Option<(String, PathBuf)> {
let path = if let Some(name) = start_config_file_name {
Some(name)
} else if Path::new(CURRENT_CONFIG_RECORD).exists() {
fs::read_to_string(CURRENT_CONFIG_RECORD)
.await
.ok()
.filter(|content| !content.trim().is_empty())
.map(|content| Path::new(CONFIG_DIR).join(content.trim()))
} else {
None
};
path.and_then(|p| {
let file_name = p.file_name()?.to_str()?.to_string();
if p.exists() {
Some((file_name, p))
} else {
log::warn!("Auto start config file not found: {:?}", p);
None
}
})
}
fn build_headers_for_path(path: &str) -> HeaderMap {
let mut headers = HeaderMap::new();
let is_gz = path.ends_with(".gz");
let mime = if is_gz {
let original = path.trim_end_matches(".gz");
from_path(original).first_or_octet_stream()
} else {
from_path(path).first_or_octet_stream()
};
headers.insert(
header::CONTENT_TYPE,
HeaderValue::from_str(mime.as_ref()).unwrap(),
);
if is_gz {
headers.insert(header::CONTENT_ENCODING, HeaderValue::from_static("gzip"));
headers.insert(header::VARY, HeaderValue::from_static("Accept-Encoding"));
}
headers.insert(
header::CACHE_CONTROL,
HeaderValue::from_static("public, max-age=31536000, immutable"),
);
headers
}
async fn static_handler(uri: Uri) -> impl IntoResponse {
let path = uri.path().trim_start_matches('/');
let path = if path.is_empty() { "index.html" } else { path };
// 先尝试从本地文件读取
let local_path = Path::new("static").join(path);
if local_path.is_file()
&& let Ok(content) = tokio::fs::read(&local_path).await
{
log::debug!("Serving file from local filesystem: {:?}", local_path);
let mime = from_path(&local_path).first_or_octet_stream();
return ([(header::CONTENT_TYPE, mime.as_ref())], content).into_response();
}
// 从内嵌数据中读取
if let Some(content) = Asset::get(path) {
log::debug!("Serving file from embedded assets: {}", path);
let headers = build_headers_for_path(path);
return (headers, Body::from(content.data)).into_response();
}
(StatusCode::NOT_FOUND, "404 Not Found").into_response()
}
/// 启动 VNT 服务的入口函数
async fn start_vnt_internal(
state: &HttpAppState,
file_name: String,
file_path: PathBuf,
) -> anyhow::Result<()> {
log::info!("Starting VNT service: {}", file_name);
state.starting()?;
let state_for_error = state.clone();
let on_error_guard = defer(move || {
state_for_error.starting_to_stopped();
});
state.record_log(format!("启动配置: {}", file_name));
state.record_log("读取配置文件");
// 读取并解析配置
let content = fs::read_to_string(&file_path)
.await
.with_context(|| format!("Config file not found: {:?}", file_path))?;
state.record_log("解析配置文件内容");
let cfg: StartConfig = toml::from_str(&content).context("Failed to parse TOML config")?;
let config_display_name = cfg.config_name.clone().unwrap_or_else(|| file_name.clone());
let core_config = convert_config(cfg)?;
let sub_input = core_config.input.clone();
state.record_log("创建异步任务组");
let (task_group, task_group_guard) = state
.task_group_manager
.create_task()
.context("Create task failed")?;
state.record_log("创建组网管理器");
let state_clone = state.clone();
tokio::spawn(async move {
let result = start_vnt_network(
state_clone.clone(),
file_name,
config_display_name,
core_config,
sub_input,
task_group,
task_group_guard,
)
.await;
if let Err(e) = result {
log::error!("Failed to start VNT network: {:?}", e);
state_clone.record_log_and_stopped(format!("启动失败: {}", e));
}
drop(on_error_guard);
});
Ok(())
}
/// 执行实际的网络启动操作
async fn start_vnt_network(
state: HttpAppState,
file_name: String,
config_display_name: String,
core_config: CoreConfig,
sub_input: Vec<NetInput>,
task_group: vnt_core::utils::task_control::TaskGroup,
task_group_guard: vnt_core::utils::task_control::TaskGroupGuard,
) -> anyhow::Result<()> {
let mut network_manager = NetworkManager::create_network(Box::new(core_config), task_group.clone())
.await
.map_err(|e| anyhow!("Create network failed: {:?}", e))?;
let vnt_api = network_manager.vnt_api();
{
let mut lock = state.inner.lock();
if lock.vnt.is_some() {
return Err(anyhow!("VNT is already running"));
}
lock.vnt = Some(VntHandler {
api: vnt_api,
config_name: config_display_name,
config_file_name: file_name.clone(),
});
}
let state_for_vnt_cleanup = state.clone();
let vnt_cleanup_guard = defer(move || {
state_for_vnt_cleanup.stopped();
});
state.record_log("连接服务器,执行注册");
log::info!("Registering with server");
let reg_msg = network_manager
.register()
.await
.context("Registration failed")?;
state.record_log(format!("注册成功 {}/{}", reg_msg.ip, reg_msg.prefix_len));
log::info!("Network Started: {}/{}", reg_msg.ip, reg_msg.prefix_len);
if !network_manager.is_no_tun() {
state.record_log("正在创建 TUN 虚拟网卡");
network_manager.start_tun().await?;
state.record_log("创建 TUN 虚拟网卡成功,设置 IP");
network_manager
.set_network_ip(reg_msg.ip, reg_msg.prefix_len)
.await?;
state.record_log("设置 IP 成功");
// 配置子网路由
if !sub_input.is_empty()
&& let Ok(if_index) = network_manager.tun_if_index().await
&& let Ok(mut route_manager) = route_manager::RouteManager::new()
{
state.record_log("配置子网路由");
for input in &sub_input {
let route =
route_manager::Route::new(input.net.network().into(), input.net.prefix_len())
.with_gateway(input.target_ip.into())
.with_if_index(if_index);
if let Err(e) = route_manager.add(&route) {
log::error!("add route [{route}] error: {e:?}");
} else {
log::info!("add route [{route}] successful");
}
}
}
}
state.starting_to_running();
// 启动网络管理任务
task_group.spawn(async move {
network_manager.wait_all_stopped().await;
drop(task_group_guard);
drop(network_manager);
drop(vnt_cleanup_guard);
log::info!("Network manager stopped.");
});
// 记录当前配置
if let Err(e) = fs::write(CURRENT_CONFIG_RECORD, &file_name).await {
log::warn!("Failed to record current config: {}", e);
}
Ok(())
}
fn is_valid_file_name(file_name: &str) -> bool {
!file_name.is_empty()
&& !file_name.contains("..")
&& !file_name.contains('/')
&& !file_name.contains('\\')
}
async fn start_vnt_handler(
State(state): State<HttpAppState>,
Json(req): Json<FileReq>,
) -> Json<ApiResponse<()>> {
if !is_valid_file_name(&req.file_name) {
return Json(ApiResponse::error("Invalid file name"));
}
let path = Path::new(CONFIG_DIR).join(&req.file_name);
if !path.exists() {
return Json(ApiResponse::error("Config file not found"));
}
match start_vnt_internal(&state, req.file_name, path).await {
Ok(_) => Json(ApiResponse::success(())),
Err(e) => Json(ApiResponse::error(format!("Start failed: {:?}", e))),
}
}
async fn stop_vnt_handler(State(state): State<HttpAppState>) -> Json<ApiResponse<()>> {
if state.status() == VntStatus::Stopped {
return Json(ApiResponse::error("Vnt stopped"));
}
state.task_group_manager.stop();
let _ = fs::write(CURRENT_CONFIG_RECORD, "").await;
Json(ApiResponse::success(()))
}
async fn restart_vnt_handler(
State(state): State<HttpAppState>,
Json(req): Json<FileReq>,
) -> Json<ApiResponse<()>> {
if !is_valid_file_name(&req.file_name) {
return Json(ApiResponse::error("Invalid file name"));
}
let path = Path::new(CONFIG_DIR).join(&req.file_name);
if !path.exists() {
return Json(ApiResponse::error("Config file not found"));
}
// 先停止(如果正在运行则停止,否则忽略)
if state.status() != VntStatus::Stopped {
state.task_group_manager.stop();
// 等待停止完成
for _ in 0..50 {
if state.status() == VntStatus::Stopped {
break;
}
tokio::time::sleep(std::time::Duration::from_millis(100)).await;
}
}
// 再启动
match start_vnt_internal(&state, req.file_name, path).await {
Ok(_) => Json(ApiResponse::success(())),
Err(e) => Json(ApiResponse::error(format!("Restart failed: {:?}", e))),
}
}
async fn get_info(State(state): State<HttpAppState>) -> Json<ApiResponse<HttpAppInfo>> {
let lock = state.inner.lock();
let status = lock.status;
let info = if let Some(handler) = lock.vnt.as_ref() {
let api = &handler.api;
let config = api.get_config();
let ips = api.client_ips();
let server_node_list = api.server_node_list();
let nat_info = api.nat_info();
let network = api.network();
HttpAppInfo {
name: config
.as_ref()
.map(|v| v.device_name.clone())
.unwrap_or_default(),
version: env!("CARGO_PKG_VERSION").to_string(),
ip: network.map(|v| v.ip),
prefix_len: network.map(|v| v.prefix_len),
gateway: network.map(|v| v.gateway),
device_id: config
.as_ref()
.map(|v| v.device_id.clone())
.unwrap_or_default(),
status,
current_config_name: Some(handler.config_name.clone()),
current_config_file: Some(handler.config_file_name.clone()),
online_client_num: ips.iter().filter(|v| v.online).count(),
offline_client_num: ips.iter().filter(|v| !v.online).count(),
direct_client_num: ips.iter().filter(|ip| api.is_direct(&ip.ip)).count(),
server_info: server_node_list
.into_iter()
.map(|v| HttpServerInfo {
server: v.server_addr.to_string(),
connected: v.connected,
server_rtt: v.rtt,
server_version: v.server_version,
})
.collect(),
nat_type: nat_info.as_ref().map(|v| format!("{:?}", v.nat_type)),
public_ipv4s: nat_info
.as_ref()
.map(|v| v.public_ips.clone())
.unwrap_or_default(),
public_ipv6: nat_info.as_ref().and_then(|v| v.ipv6),
network_code: config.as_ref().map(|v| v.network_code.clone()),
mtu: config.as_ref().map(|v| v.mtu.unwrap_or(DEFAULT_MTU)),
fec: config.as_ref().map(|v| v.fec),
compress: config.as_ref().map(|v| v.compress),
encrypt: config.as_ref().map(|v| v.password.is_some()),
rtx: config.as_ref().map(|v| v.rtx),
}
} else {
HttpAppInfo {
version: env!("CARGO_PKG_VERSION").to_string(),
status,
..Default::default()
}
};
Json(ApiResponse::success(info))
}
async fn list_configs() -> Json<ApiResponse<Vec<ConfigSummary>>> {
let mut result = Vec::new();
let Ok(mut entries) = fs::read_dir(CONFIG_DIR).await else {
return Json(ApiResponse::success(result));
};
while let Ok(Some(entry)) = entries.next_entry().await {
let path = entry.path();
if path.extension().is_none_or(|ext| ext != "toml") {
continue;
}
let Ok(content) = fs::read_to_string(&path).await else {
continue;
};
match toml::from_str::<StartConfig>(&content) {
Ok(cfg) => {
let file_name = path
.file_name()
.and_then(|s| s.to_str())
.unwrap_or("")
.to_string();
result.push(ConfigSummary {
file_name,
config_name: cfg
.config_name
.unwrap_or_else(|| entry.file_name().to_string_lossy().to_string()),
});
}
Err(e) => {
log::warn!("Failed to parse configuration file {:?}: {:?}", path, e);
}
}
}
result.sort_by(|a, b| b.file_name.cmp(&a.file_name));
Json(ApiResponse::success(result))
}
async fn save_config(Json(req): Json<SaveConfigReq>) -> Json<ApiResponse<()>> {
// 验证配置格式
if let Err(e) = toml::from_str::<StartConfig>(&req.config) {
log::warn!("Failed to parse configuration: {:?}", e);
return Json(ApiResponse::error(format!("Invalid TOML format: {}", e)));
}
let file_name = req
.file_name
.filter(|name| !name.is_empty())
.unwrap_or_else(|| {
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_millis();
format!("{}.toml", now)
});
if !is_valid_file_name(&file_name) {
return Json(ApiResponse::error("Invalid file name"));
}
let target_path = Path::new(CONFIG_DIR).join(&file_name);
match fs::write(&target_path, &req.config).await {
Ok(_) => Json(ApiResponse::success(())),
Err(e) => Json(ApiResponse::error(format!("Write config failed: {}", e))),
}
}
async fn get_config(Query(req): Query<FileReq>) -> Json<ApiResponse<String>> {
if !is_valid_file_name(&req.file_name) {
return Json(ApiResponse::error("Invalid file name"));
}
let path = Path::new(CONFIG_DIR).join(&req.file_name);
if !path.exists() {
return Json(ApiResponse::error("Config file not found"));
}
match fs::read_to_string(&path).await {
Ok(content) => Json(ApiResponse::success(content)),
Err(e) => Json(ApiResponse::error(format!("Read file failed: {}", e))),
}
}
async fn delete_config(
State(state): State<HttpAppState>,
Query(req): Query<FileReq>,
) -> Json<ApiResponse<()>> {
if !is_valid_file_name(&req.file_name) {
return Json(ApiResponse::error("Invalid file name"));
}
{
if let Some(vnt) = &state.inner.lock().vnt
&& vnt.config_file_name == req.file_name
{
return Json(ApiResponse::error("此配置已被使用,不能删除"));
}
}
let path = Path::new(CONFIG_DIR).join(&req.file_name);
if !path.exists() {
return Json(ApiResponse::error("Config file not found"));
}
match fs::remove_file(&path).await {
Ok(_) => Json(ApiResponse::success(())),
Err(e) => Json(ApiResponse::error(format!("Delete failed: {}", e))),
}
}
fn convert_config(cfg: StartConfig) -> anyhow::Result<CoreConfig> {
let server_addrs: Vec<ProtocolAddress> = cfg
.server
.iter()
.map(|s| {
s.parse()
.map_err(|e| anyhow!("invalid server address '{}': {}", s, e))
})
.collect::<anyhow::Result<_>>()?;
let port_mapping: Vec<PortMapping> = cfg
.port_mapping
.iter()
.map(|s| {
s.parse()
.map_err(|e| anyhow!("invalid port_mapping '{}': {}", s, e))
})
.collect::<anyhow::Result<_>>()?;
let cert_mode = match cfg.cert_mode.as_deref() {
Some(s) => s
.parse()
.map_err(|e| anyhow!("invalid cert_mode '{}': {}", s, e))?,
None => CertValidationMode::InsecureSkipVerification,
};
let device_id = match cfg.device_id {
Some(id) => id,
None => vnt_core::utils::device_id::get_device_id()
.map_err(|e| anyhow!("failed to get device_id: {}", e))?,
};
let device_name = cfg.device_name.unwrap_or_else(|| {
hostname::get()
.ok()
.and_then(|v| v.into_string().ok())
.unwrap_or_default()
});
let mut udp_stun = cfg.udp_stun;
for x in udp_stun.iter_mut() {
if !x.contains(':') {
x.push_str(":3478");
}
}
let mut tcp_stun = cfg.tcp_stun;
for x in tcp_stun.iter_mut() {
if !x.contains(':') {
x.push_str(":3478");
}
}
Ok(CoreConfig {
server_addr: server_addrs,
network_code: cfg.network_code,
ip: cfg.ip,
no_punch: cfg.no_punch,
rtx: cfg.rtx,
compress: cfg.compress,
device_id,
device_name,
tun_name: cfg.tun_name,
password: cfg.password,
cert_mode,
input: cfg.input,
output: cfg.output,
no_nat: cfg.no_nat,
no_tun: cfg.no_tun,
mtu: cfg.mtu,
port_mapping,
allow_port_mapping: cfg.allow_mapping,
udp_stun,
tcp_stun,
fec: cfg.fec,
})
}
async fn shutdown_signal() {
let ctrl_c = async {
tokio::signal::ctrl_c()
.await
.expect("failed to install Ctrl+C handler");
};
#[cfg(unix)]
let terminate = async {
tokio::signal::unix::signal(tokio::signal::unix::SignalKind::terminate())
.expect("failed to install signal handler")
.recv()
.await;
};
#[cfg(not(unix))]
let terminate = std::future::pending::<()>();
tokio::select! {
_ = ctrl_c => {},
_ = terminate => {},
}
}
async fn get_peers(State(state): State<HttpAppState>) -> Json<ApiResponse<Vec<HttpClientItem>>> {
let api = state.inner.lock().vnt.as_ref().map(|v| v.api.clone());
let Some(api) = api else {
return Json(ApiResponse::error("VNT not running"));
};
let key_sign = api.get_config().and_then(|config| config.key_sign());
let calc_key_equal = |peer_key_sign: &Option<String>| -> i32 {
match (&key_sign, peer_key_sign) {
(None, None) => 2,
(Some(k1), Some(k2)) if k1 == k2 => 1,
(Some(_), Some(_)) => 5,
(Some(_), None) => 3,
(None, Some(_)) => 4,
}
};
let build_nat_info = |ip: &Ipv4Addr| -> Option<HttpClientNatInfo> {
api.peer_nat_info(ip).map(|v| HttpClientNatInfo {
nat_type: format!("{:?}", v.nat_type),
public_ips: v.public_ips,
ipv6: v.ipv6,
})
};
let build_packet_loss = |ip: &Ipv4Addr| -> Option<HttpPacketLoss> {
api.packet_loss_info(ip).map(|v| HttpPacketLoss {
sent: v.sent,
received: v.received,
loss_rate: v.loss_rate,
})
};
let build_traffic = |ip: &Ipv4Addr| -> Option<HttpTraffic> {
api.traffic_info(ip).map(|v| HttpTraffic {
tx_bytes: v.tx_bytes,
rx_bytes: v.rx_bytes,
})
};
let build_route = |ip: &Ipv4Addr| -> Option<HttpRouteDetail> {
api.find_route(ip).map(|route| HttpRouteDetail {
addr: route.route_key().to_string(),
protocol: route.route_key().protocol().to_string(),
metric: route.metric(),
rtt: route.rtt(),
})
};
// 先从本地获取基础数据
let mut merged: HashMap<Ipv4Addr, HttpClientItem> = api
.client_ips()
.into_iter()
.map(|v| {
let ip = v.ip;
let route = build_route(&ip);
// 如果有路由,说明设备在线(可以直接通信)
let has_route = route.is_some();
(
ip,
HttpClientItem {
ip,
name: None,
online: v.online || has_route,
route,
version: String::new(),
last_connected_time: 0,
key_equal: 0,
nat_info: build_nat_info(&ip),
packet_loss: build_packet_loss(&ip),
traffic: build_traffic(&ip),
},
)
})
.collect();
// 从服务器获取更详细的信息
if let Ok(resp) = api.server_rpc().client_list().await {
for v in resp.list {
let ip = Ipv4Addr::from(v.ip);
let route = build_route(&ip);
// 如果有路由,说明设备在线(可以直接通信)
let has_route = route.is_some();
merged.insert(
ip,
HttpClientItem {
ip,
name: Some(v.name),
online: v.online || has_route,
route,
version: v.version,
last_connected_time: v.last_connected_time,
key_equal: calc_key_equal(&v.key_sign),
nat_info: build_nat_info(&ip),
packet_loss: build_packet_loss(&ip),
traffic: build_traffic(&ip),
},
);
}
} else {
log::warn!("Failed to get client list from server");
}
let mut items: Vec<HttpClientItem> = merged.into_values().collect();
items.sort_by_key(|it| it.ip);
Json(ApiResponse::success(items))
}
async fn get_routes(State(state): State<HttpAppState>) -> Json<ApiResponse<Vec<HttpRouteItem>>> {
let lock = state.inner.lock();
let Some(handler) = lock.vnt.as_ref() else {
return Json(ApiResponse::error("VNT not running"));
};
let table = handler.api.route_table();
let items: Vec<HttpRouteItem> = table
.into_iter()
.map(|(ip, route_list)| HttpRouteItem {
ip,
routes: route_list
.into_iter()
.map(|v| HttpRouteDetail {
addr: v.route_key().to_string(),
protocol: v.route_key().protocol().to_string(),
metric: v.metric(),
rtt: v.rtt(),
})
.collect(),
})
.collect();
Json(ApiResponse::success(items))
}
+2884
View File
@@ -0,0 +1,2884 @@
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>VNT Dashboard</title>
<link rel="icon"
href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 64 64'%3E%3Crect x='0' y='0' width='64' height='64' rx='16' fill='%230f172a'/%3E%3Cpath d='M16 20 L32 48 L48 20' fill='none' stroke='%233b82f6' stroke-width='6' stroke-linecap='round' stroke-linejoin='round'/%3E%3Ccircle cx='16' cy='20' r='6' fill='%2360a5fa'/%3E%3Ccircle cx='48' cy='20' r='6' fill='%2360a5fa'/%3E%3Ccircle cx='32' cy='48' r='6' fill='%2322c55e'/%3E%3C/svg%3E"
type="image/svg+xml">
<script src="tailwindcss3.4.17.js.gz"></script>
<script src="vue.global.prod.js.gz"></script>
<script src="vue-router.global.prod.js.gz"></script>
<style>
[v-cloak] {
display: none;
}
.scrollbar-hide::-webkit-scrollbar {
display: none;
}
.scrollbar-hide {
-ms-overflow-style: none;
scrollbar-width: none;
}
body {
background-color: #0f172a;
color: #e2e8f0;
}
.glass-panel {
background: rgba(30, 41, 59, 0.7);
backdrop-filter: blur(10px);
border: 1px solid rgba(255, 255, 255, 0.1);
}
.input-dark {
background-color: #1e293b;
border-color: #334155;
color: white;
}
.input-dark:focus {
border-color: #3b82f6;
outline: none;
ring: 2px;
}
/* Tooltip CSS (保留用于表格内的静态 tooltip) */
.tooltip {
position: relative;
display: inline-flex;
align-items: center;
justify-content: center;
}
.tooltip .tooltip-text {
visibility: hidden;
width: 140px;
background-color: rgba(0, 0, 0, 0.9);
color: #fff;
text-align: center;
border-radius: 6px;
padding: 6px 4px;
position: absolute;
z-index: 10;
bottom: 125%;
left: 50%;
transform: translateX(-50%);
opacity: 0;
transition: opacity 0.3s;
font-size: 12px;
pointer-events: none;
border: 1px solid #475569;
}
.tooltip .tooltip-text::after {
content: "";
position: absolute;
top: 100%;
left: 50%;
margin-left: -5px;
border-width: 5px;
border-style: solid;
border-color: rgba(0, 0, 0, 0.9) transparent transparent transparent;
}
.tooltip:hover .tooltip-text {
visibility: visible;
opacity: 1;
}
/* 路由切换动画 */
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.2s ease;
}
.fade-enter-from,
.fade-leave-to {
opacity: 0;
}
/* 自定义滚动条样式 */
.custom-scrollbar::-webkit-scrollbar {
width: 8px;
height: 8px;
}
.custom-scrollbar::-webkit-scrollbar-track {
background: rgba(15, 23, 42, 0.5);
border-radius: 4px;
}
.custom-scrollbar::-webkit-scrollbar-thumb {
background: rgba(71, 85, 105, 0.8);
border-radius: 4px;
transition: background 0.2s;
}
.custom-scrollbar::-webkit-scrollbar-thumb:hover {
background: rgba(100, 116, 139, 0.9);
}
/* Firefox 滚动条样式 */
.custom-scrollbar {
scrollbar-width: thin;
scrollbar-color: rgba(71, 85, 105, 0.8) rgba(15, 23, 42, 0.5);
}
</style>
</head>
<body class="h-screen overflow-hidden flex flex-col font-sans">
<div id="app" v-cloak class="flex h-full">
<!-- 侧边栏 -->
<aside
class="w-64 bg-slate-900 border-r border-slate-800 flex flex-col shrink-0"
>
<div
class="p-6 flex items-center justify-center border-b border-slate-800"
>
<h1 class="text-2xl font-bold text-blue-500 tracking-wider">
VNT<span class="text-slate-400 text-sm ml-2">Web</span>
</h1>
</div>
<nav class="flex-1 p-4 space-y-2">
<!-- 使用 router-link 和 v-slot 自定义渲染按钮 -->
<router-link
to="/general"
custom
v-slot="{ navigate, isActive }"
>
<button
@click="navigate"
:class="navClass(isActive)"
class="w-full flex items-center p-3 rounded-lg transition-all duration-200"
>
<svg
class="w-5 h-5 mr-3"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6"
></path>
</svg>
通用
</button>
</router-link>
<router-link
to="/config"
custom
v-slot="{ navigate, isActive }"
>
<button
@click="navigate"
:class="navClass(isActive)"
class="w-full flex items-center p-3 rounded-lg transition-all duration-200"
>
<svg
class="w-5 h-5 mr-3"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z"
></path>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"
></path>
</svg>
配置
</button>
</router-link>
<router-link
to="/peers"
custom
v-slot="{ navigate, isActive }"
>
<button
@click="navigate"
:class="navClass(isActive)"
class="w-full flex items-center p-3 rounded-lg transition-all duration-200"
>
<svg
class="w-5 h-5 mr-3"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197M13 7a4 4 0 11-8 0 4 4 0 018 0z"
></path>
</svg>
设备列表
</button>
</router-link>
<router-link
to="/routes"
custom
v-slot="{ navigate, isActive }"
>
<button
@click="navigate"
:class="navClass(isActive)"
class="w-full flex items-center p-3 rounded-lg transition-all duration-200"
>
<svg
class="w-5 h-5 mr-3"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M9 20l-5.447-2.724A1 1 0 013 16.382V5.618a1 1 0 011.447-.894L9 7m0 13l6-3m-6 3V7m6 10l4.553 2.276A1 1 0 0021 18.382V7.618a1 1 0 01-.806-.98l-3.747-1.874O12 7m3 13V7m-3 0l3 3"
></path>
</svg>
路由
</button>
</router-link>
</nav>
<div class="p-4 text-xs text-slate-500 text-center">
Client: v{{ info.version }}
</div>
</aside>
<main
class="flex-1 flex flex-col bg-slate-900/50 relative overflow-hidden"
>
<!-- Header -->
<header
class="h-16 glass-panel border-b border-slate-700 flex items-center justify-between px-8 z-10 shrink-0"
>
<div class="flex items-center space-x-6">
<div
class="flex items-center space-x-2 bg-slate-800 rounded-full px-3 py-1 border border-slate-700"
>
<span
class="w-2.5 h-2.5 rounded-full"
:class="info.status === 'running' ? 'bg-green-500 animate-pulse' : (info.status === 'starting' ? 'bg-yellow-500 animate-pulse' : 'bg-red-500')"
></span>
<span class="text-sm font-medium"
>{{ info.status === 'running' ? '已运行' :
(info.status === 'starting' ? '启动中...' :
'未启动') }}</span
>
</div>
<div
v-if="info.status === 'running'"
class="flex items-center space-x-2 bg-slate-800 rounded-full px-3 py-1 border border-slate-700"
:title="serverStatusText"
>
<svg
class="w-4 h-4"
:class="isServerConnected ? 'text-green-400' : 'text-red-400'"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1"
></path>
</svg>
<span
class="text-sm font-medium"
:class="isServerConnected ? 'text-green-400' : 'text-red-400'"
>服务器: {{ isServerConnected ? '已连接' :
'未连接' }}</span
>
</div>
<div
v-if="info.ip"
class="flex items-center space-x-2 text-slate-300"
>
<span
class="font-mono text-blue-400 font-bold bg-blue-900/20 px-2 py-0.5 rounded"
>{{ info.ip }}</span
>
</div>
</div>
<div class="flex items-center space-x-2 text-slate-400">
<span class="text-sm">设备:</span>
<span class="font-bold text-white"
>{{ info.name || '' }}</span
>
<span
class="text-xs px-2 py-0.5 rounded bg-slate-800 text-slate-500"
title="Device ID"
>{{ info.device_id.substring(0, 8) }}...</span
>
</div>
</header>
<!-- Router View with Transition -->
<div class="flex-1 overflow-auto scrollbar-hide p-8 relative">
<router-view v-slot="{ Component }">
<transition name="fade" mode="out-in">
<component :is="Component"/>
</transition>
</router-view>
</div>
<!-- 启动日志弹窗 (Global) -->
<div
v-if="showStartLog"
class="fixed inset-0 z-[60] flex items-center justify-center bg-black/50 backdrop-blur-sm p-4"
>
<div
class="bg-slate-900/95 border border-slate-700 rounded-xl w-full max-w-2xl flex flex-col shadow-2xl overflow-hidden"
>
<div
class="px-6 py-4 border-b border-slate-700 flex justify-between items-center bg-slate-800/50"
>
<div class="flex items-center space-x-3">
<div
v-if="startStatus === 'starting'"
class="w-3 h-3 bg-blue-500 rounded-full animate-ping"
></div>
<div
v-else-if="startStatus === 'running'"
class="w-3 h-3 bg-green-500 rounded-full"
></div>
<div
v-else
class="w-3 h-3 bg-red-500 rounded-full"
></div>
<h3 class="text-lg font-bold text-white">
{{ startStatus === 'starting' ?
'正在启动组网...' : (startStatus ===
'running' ? '启动成功' : '启动失败') }}
</h3>
</div>
<span
class="text-xs font-mono text-slate-500 uppercase tracking-widest"
>{{ startStatus }}</span
>
</div>
<div
ref="logContainer"
class="flex-1 p-6 h-80 overflow-y-auto scrollbar-hide font-mono text-sm space-y-2 bg-black/20"
>
<div
v-for="(log, idx) in startLogs"
:key="idx"
class="flex space-x-3 animate-in fade-in slide-in-from-left-2"
>
<span class="text-blue-500 shrink-0">>>></span>
<span class="text-slate-300 break-all"
>{{ log }}</span
>
</div>
<div
v-if="startStatus === 'starting'"
class="text-blue-400 animate-pulse italic mt-4"
>
等待后续步骤...
</div>
<div
v-if="startStatus === 'stopped' && startLogs.length > 0"
class="p-3 bg-red-900/20 border border-red-900/50 rounded-lg text-red-400 mt-4"
>
<strong>启动失败:</strong>
请检查配置或网络连接。
</div>
</div>
<div
class="p-4 border-t border-slate-700 flex justify-end space-x-3 bg-slate-800/50"
>
<button
v-if="startStatus === 'starting'"
@click="cancelStart"
class="px-6 py-2 bg-slate-700 hover:bg-red-600 text-white rounded-lg transition-colors font-medium"
>
取消组网
</button>
<button
v-if="startStatus === 'stopped' || startStatus === 'running'"
@click="showStartLog = false"
class="px-6 py-2 bg-blue-600 hover:bg-blue-500 text-white rounded-lg transition-colors font-medium"
>
关闭窗口
</button>
</div>
</div>
</div>
<!-- Tooltip (Global Teleport) -->
<teleport to="body">
<div
v-if="tooltipState.show"
:style="{ top: tooltipState.y + 'px', left: tooltipState.x + 'px' }"
class="fixed z-[9999] transform -translate-x-1/2 mt-1"
@mouseenter="onTooltipEnter"
@mouseleave="onTooltipLeave"
>
<div
class="w-auto min-w-[260px] max-w-[320px] text-left p-4 bg-slate-800 border border-slate-600 shadow-2xl rounded-lg text-sm text-slate-200"
>
<div
class="absolute -top-2 left-1/2 -translate-x-1/2 w-4 h-4 bg-slate-800 border-t border-l border-slate-600 transform rotate-45"
></div>
<div
class="flex justify-between items-center border-b border-slate-600 pb-2 mb-2 relative z-10"
>
<span
class="text-slate-400 text-xs uppercase font-bold"
>NAT Type</span
>
<span
class="text-green-400 font-bold bg-green-900/30 px-2 py-0.5 rounded text-xs border border-green-800"
>{{ tooltipState.info.nat_type }}</span
>
</div>
<div
v-if="tooltipState.info.public_ips && tooltipState.info.public_ips.length > 0"
class="mb-3 relative z-10"
>
<span class="text-slate-400 text-xs block mb-1"
>Public IPv4:</span
>
<div class="flex flex-wrap gap-1">
<span
v-for="pip in tooltipState.info.public_ips"
:key="pip"
class="text-xs bg-slate-700 text-slate-200 px-1.5 py-0.5 rounded border border-slate-600"
>{{ pip }}</span
>
</div>
</div>
<div
v-if="tooltipState.info.ipv6"
class="relative z-10"
>
<span class="text-slate-400 text-xs block mb-1"
>IPv6:</span
>
<div
class="text-slate-200 text-xs break-all whitespace-normal leading-relaxed bg-slate-900/50 p-1.5 rounded border border-slate-700/50"
>
{{ tooltipState.info.ipv6 }}
</div>
</div>
</div>
</div>
</teleport>
</main>
</div>
<!-- ================= TEMPLATES ================= -->
<!-- 1. General (通用) -->
<template id="tpl-general">
<div class="space-y-6 max-w-5xl mx-auto">
<div class="glass-panel rounded-xl p-6 shadow-lg">
<h2
class="text-xl font-bold mb-6 text-white border-l-4 border-blue-500 pl-3"
>
运行控制
</h2>
<div class="flex items-end space-x-4">
<div class="flex-1">
<label
class="block text-sm font-medium text-slate-400 mb-2"
>选择配置</label
>
<select
v-model="localSelectedConfig"
:disabled="info.status === 'starting'"
class="w-full bg-slate-800 border border-slate-600 rounded-lg px-4 py-2.5 text-white focus:ring-2 focus:ring-blue-500 focus:border-transparent disabled:opacity-50 disabled:cursor-not-allowed"
>
<option value="" disabled>请选择配置...</option>
<option
v-for="cfg in configList"
:key="cfg.file_name"
:value="cfg.file_name"
>
{{ cfg.config_name || cfg.file_name }}
</option>
</select>
</div>
<button
v-if="info.status === 'running'"
@click="handleRestart"
:disabled="loading || !localSelectedConfig"
class="px-6 py-2.5 rounded-lg font-bold text-white shadow-lg transition-transform active:scale-95 flex items-center disabled:opacity-50 disabled:cursor-not-allowed bg-blue-500 hover:bg-blue-600"
>
<span v-if="loading" class="mr-2 animate-spin"></span>
重启
</button>
<button
@click="handleToggle"
:disabled="loading || (!localSelectedConfig && info.status !== 'running' && info.status !== 'starting')"
:class="(info.status === 'running' || info.status === 'starting') ? 'bg-red-500 hover:bg-red-600' : 'bg-green-500 hover:bg-green-600'"
class="px-8 py-2.5 rounded-lg font-bold text-white shadow-lg transition-transform active:scale-95 flex items-center disabled:opacity-50 disabled:cursor-not-allowed"
>
<span v-if="loading" class="mr-2 animate-spin"
></span
>
{{ (info.status === 'running' || info.status ===
'starting') ? '停止运行' : '启动运行' }}
</button>
</div>
</div>
<div class="grid grid-cols-2 md:grid-cols-4 gap-6">
<div
class="glass-panel p-5 rounded-xl flex flex-col items-center justify-center border-t-4 border-t-blue-500"
>
<span class="text-slate-400 text-sm mb-1"
>在线设备</span
>
<span class="text-3xl font-bold text-blue-400"
>{{ info.online_client_num }}</span
>
</div>
<div
class="glass-panel p-5 rounded-xl flex flex-col items-center justify-center border-t-4 border-t-green-500"
>
<span class="text-slate-400 text-sm mb-1"
>直连设备</span
>
<span class="text-3xl font-bold text-green-400"
>{{ info.direct_client_num }}</span
>
</div>
<div
class="glass-panel p-5 rounded-xl flex flex-col items-center justify-center border-t-4 border-t-slate-500"
>
<span class="text-slate-400 text-sm mb-1"
>离线设备</span
>
<span class="text-3xl font-bold text-slate-400"
>{{ info.offline_client_num }}</span
>
</div>
<div
class="glass-panel p-5 rounded-xl flex flex-col items-center justify-center border-t-4 border-t-yellow-500"
>
<span class="text-slate-400 text-sm mb-1"
>当前配置</span
>
<span
class="text-lg font-medium text-yellow-400 truncate w-full text-center px-2"
:title="info.current_config_file"
>
{{ info.current_config_name || '-' }}
</span>
</div>
</div>
<div class="glass-panel rounded-xl p-6 shadow-lg">
<h2 class="text-lg font-bold mb-4 text-white">网络详情</h2>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 text-sm">
<div
class="flex justify-between border-b border-slate-700 pb-2"
>
<span class="text-slate-400">虚拟 IP / 掩码</span>
<span class="font-mono text-white"
>{{ info.ip || '-' }} / {{ info.prefix_len ||
'-' }}</span
>
</div>
<div
class="flex justify-between border-b border-slate-700 pb-2"
>
<span class="text-slate-400">网关</span>
<span class="font-mono text-white"
>{{ info.gateway || '-' }}</span
>
</div>
<div
class="flex justify-between border-b border-slate-700 pb-2"
>
<span class="text-slate-400">网络编号</span>
<span class="font-mono text-white"
>{{ info.network_code || '-' }}</span
>
</div>
<div
class="flex justify-between border-b border-slate-700 pb-2"
>
<span class="text-slate-400">MTU</span>
<span class="font-mono text-white"
>{{ info.mtu || '' }}</span
>
</div>
<div
class="flex justify-between border-b border-slate-700 pb-2"
>
<span class="text-slate-400">NAT 类型</span>
<span class="font-mono text-blue-300"
>{{ info.nat_type || 'Unknown' }}</span
>
</div>
<div
class="flex justify-between border-b border-slate-700 pb-2"
>
<span class="text-slate-400">Public IPv6</span>
<span
class="font-mono text-white truncate max-w-[200px]"
:title="info.public_ipv6"
>{{ info.public_ipv6 || '-' }}</span
>
</div>
</div>
<div class="mt-4 grid grid-cols-2 md:grid-cols-4 gap-3">
<div class="flex items-center space-x-2 bg-slate-800/50 rounded-lg px-3 py-2">
<span class="w-2 h-2 rounded-full" :class="info.encrypt ? 'bg-green-500' : 'bg-slate-600'"></span>
<span class="text-sm text-slate-300">加密</span>
</div>
<div class="flex items-center space-x-2 bg-slate-800/50 rounded-lg px-3 py-2">
<span class="w-2 h-2 rounded-full" :class="info.compress ? 'bg-green-500' : 'bg-slate-600'"></span>
<span class="text-sm text-slate-300">压缩</span>
</div>
<div class="flex items-center space-x-2 bg-slate-800/50 rounded-lg px-3 py-2">
<span class="w-2 h-2 rounded-full" :class="info.fec ? 'bg-green-500' : 'bg-slate-600'"></span>
<span class="text-sm text-slate-300">FEC纠错</span>
</div>
<div class="flex items-center space-x-2 bg-slate-800/50 rounded-lg px-3 py-2">
<span class="w-2 h-2 rounded-full" :class="info.rtx ? 'bg-green-500' : 'bg-slate-600'"></span>
<span class="text-sm text-slate-300">QUIC传输</span>
</div>
</div>
<div class="mt-4">
<span class="text-slate-400 text-sm block mb-2"
>Public IPv4s</span
>
<div class="flex flex-wrap gap-2">
<span
v-for="pip in info.public_ipv4s"
:key="pip"
class="px-2 py-1 bg-slate-800 rounded text-xs font-mono text-green-300 border border-slate-600"
>{{ pip }}</span
>
<span
v-if="!info.public_ipv4s || info.public_ipv4s.length === 0"
class="text-slate-600 text-xs"
></span
>
</div>
</div>
</div>
<div class="glass-panel rounded-xl p-6 shadow-lg">
<h2 class="text-lg font-bold mb-4 text-white">
服务器连接列表
</h2>
<div
class="overflow-auto max-h-[400px] custom-scrollbar rounded-lg border border-slate-700"
>
<table class="min-w-full divide-y divide-slate-700">
<thead class="bg-slate-800">
<tr>
<th
class="px-6 py-3 text-left text-xs font-medium text-slate-400 uppercase tracking-wider"
>
地址
</th>
<th
class="px-6 py-3 text-left text-xs font-medium text-slate-400 uppercase tracking-wider"
>
状态
</th>
<th
class="px-6 py-3 text-left text-xs font-medium text-slate-400 uppercase tracking-wider"
>
延迟
</th>
<th
class="px-6 py-3 text-left text-xs font-medium text-slate-400 uppercase tracking-wider"
>
版本
</th>
</tr>
</thead>
<tbody
class="divide-y divide-slate-700 bg-slate-900/30"
>
<tr
v-for="(server, idx) in info.server_info"
:key="idx"
>
<td
class="px-6 py-4 whitespace-nowrap text-sm text-slate-300 font-mono"
>
{{ server.server }}
</td>
<td class="px-6 py-4 whitespace-nowrap">
<span
:class="server.connected ? 'bg-green-900 text-green-300' : 'bg-red-900 text-red-300'"
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full"
>
{{ server.connected ? '已连接' :
'未连接' }}
</span>
</td>
<td
class="px-6 py-4 whitespace-nowrap text-sm text-slate-400"
>
{{ server.server_rtt ? server.server_rtt
+ ' ms' : '-' }}
</td>
<td
class="px-6 py-4 whitespace-nowrap text-sm text-slate-400"
>
{{ server.server_version || '-' }}
</td>
</tr>
<tr
v-if="!info.server_info || info.server_info.length === 0"
>
<td
colspan="4"
class="px-6 py-4 text-center text-slate-500"
>
暂无数据
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</template>
<!-- 2. Config (配置) -->
<template id="tpl-config">
<div class="space-y-6 max-w-5xl mx-auto h-full flex flex-col">
<div class="flex justify-between items-center">
<h2 class="text-2xl font-bold text-white">配置管理</h2>
<button
@click="openEditor(null)"
class="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-lg text-sm font-medium flex items-center transition-colors"
>
<svg
class="w-4 h-4 mr-2"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M12 4v16m8-8H4"
></path>
</svg>
新建配置
</button>
</div>
<div
class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4"
>
<div
v-for="cfg in configList"
:key="cfg.file_name"
:class="{'ring-2 ring-green-500 bg-slate-800/80': info.current_config_file === cfg.file_name, 'bg-slate-800/40 hover:bg-slate-800/60': info.current_config_file !== cfg.file_name}"
class="rounded-xl p-5 border border-slate-700 transition-all cursor-pointer group relative overflow-hidden flex flex-col justify-between min-h-[140px]"
@click="openEditor(cfg.file_name)"
>
<div
v-if="info.current_config_file === cfg.file_name"
class="absolute top-0 right-0 bg-green-500 text-white text-xs px-2 py-1 rounded-bl"
>
Running
</div>
<div class="flex items-start">
<div
class="p-2 rounded bg-blue-500/10 text-blue-400 mr-3 mt-1"
>
<svg
class="w-6 h-6"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"
></path>
</svg>
</div>
<div class="overflow-hidden">
<h3
class="font-bold text-lg text-white truncate"
:title="cfg.config_name"
>
{{ cfg.config_name || 'Unnamed' }}
</h3>
<p
class="text-xs text-slate-500 font-mono truncate"
:title="cfg.file_name"
>
{{ cfg.file_name }}
</p>
</div>
</div>
<div
class="mt-4 flex justify-end opacity-0 group-hover:opacity-100 transition-opacity"
>
<button
@click.stop="openEditor(cfg.file_name)"
class="text-blue-400 hover:text-blue-300 text-sm mr-4"
>
编辑
</button>
<button
@click.stop="deleteConfig(cfg.file_name)"
class="text-red-400 hover:text-red-300 text-sm"
>
删除
</button>
</div>
</div>
</div>
<!-- 编辑器 Modal -->
<div
v-if="showEditor"
class="fixed inset-0 z-50 flex items-center justify-center bg-black/80 backdrop-blur-sm p-4"
>
<div
class="bg-slate-900 border border-slate-700 rounded-xl w-full max-w-6xl h-[85vh] flex flex-col shadow-2xl"
>
<!-- Header -->
<div class="flex justify-between items-center p-4 border-b border-slate-700 bg-slate-800/50">
<h3 class="text-lg font-bold text-white">
{{ editorMode === 'new' ? '新建配置' : '编辑配置' }}
</h3>
<div class="flex items-center space-x-4">
<!-- 模式切换按钮 -->
<div class="flex bg-slate-800 rounded-lg p-1 border border-slate-600">
<button
@click="switchToFormMode"
:class="editMode === 'form' ? 'bg-blue-600 text-white' : 'text-slate-400 hover:text-white'"
class="px-4 py-1.5 rounded text-sm font-medium transition-colors flex items-center"
>
<svg class="w-4 h-4 mr-1.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"></path>
</svg>
表单模式
</button>
<button
@click="switchToTomlMode"
:class="editMode === 'toml' ? 'bg-blue-600 text-white' : 'text-slate-400 hover:text-white'"
class="px-4 py-1.5 rounded text-sm font-medium transition-colors flex items-center"
>
<svg class="w-4 h-4 mr-1.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 20l4-16m4 4l4 4-4 4M6 16l-4-4 4-4"></path>
</svg>
TOML模式
</button>
</div>
<div class="text-sm text-slate-500 font-mono" v-if="editorFileName">
{{ editorFileName }}
</div>
</div>
</div>
<!-- Content -->
<div class="flex-1 overflow-hidden">
<!-- 表单模式 -->
<div v-show="editMode === 'form'" class="h-full overflow-y-auto scrollbar-hide p-6">
<div class="max-w-4xl mx-auto space-y-6">
<!-- 基础配置 -->
<div class="glass-panel rounded-lg p-5 border border-slate-700">
<h4 class="text-md font-bold text-blue-400 mb-4 flex items-center">
<svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z"></path>
</svg>
基础配置
</h4>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label class="block text-sm font-medium text-slate-300 mb-2">配置名称</label>
<input v-model="formData.config_name" type="text" placeholder="例如: 我的VPN配置"
class="w-full bg-slate-800 border border-slate-600 rounded-lg px-3 py-2 text-white placeholder-slate-500 focus:ring-2 focus:ring-blue-500 focus:border-transparent">
</div>
<div>
<label class="block text-sm font-medium text-slate-300 mb-2">
网络编号 <span class="text-red-400">*</span>
</label>
<input v-model="formData.network_code" type="text" placeholder="例如: my_network" required
class="w-full bg-slate-800 border border-slate-600 rounded-lg px-3 py-2 text-white placeholder-slate-500 focus:ring-2 focus:ring-blue-500 focus:border-transparent">
</div>
</div>
<div class="mt-4">
<label class="block text-sm font-medium text-slate-300 mb-2">
服务器地址 <span class="text-red-400">*</span>
<span class="text-xs text-slate-500 ml-2">支持 quic:// tcp:// wss:// dynamic://</span>
</label>
<div class="space-y-2">
<div v-for="(server, idx) in formData.server" :key="idx" class="flex space-x-2">
<input v-model="formData.server[idx]" type="text" placeholder="例如: quic://1.2.3.4:29872"
class="flex-1 bg-slate-800 border border-slate-600 rounded-lg px-3 py-2 text-white placeholder-slate-500 focus:ring-2 focus:ring-blue-500 focus:border-transparent">
<button @click="formData.server.splice(idx, 1)" v-if="formData.server.length > 1"
class="px-3 py-2 bg-red-600/20 hover:bg-red-600/40 text-red-400 rounded-lg transition-colors">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"></path>
</svg>
</button>
</div>
<button @click="formData.server.push('')"
class="w-full px-3 py-2 bg-blue-600/20 hover:bg-blue-600/40 text-blue-400 rounded-lg transition-colors text-sm flex items-center justify-center">
<svg class="w-4 h-4 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4"></path>
</svg>
添加服务器
</button>
</div>
</div>
</div>
<!-- 网络设置 -->
<div class="glass-panel rounded-lg p-5 border border-slate-700">
<h4 class="text-md font-bold text-green-400 mb-4 flex items-center">
<svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 12a9 9 0 01-9 9m9-9a9 9 0 00-9-9m9 9H3m9 9a9 9 0 01-9-9m9 9c1.657 0 3-4.03 3-9s-1.343-9-3-9m0 18c-1.657 0-3-4.03-3-9s1.343-9 3-9m-9 9a9 9 0 019-9"></path>
</svg>
网络设置
</h4>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label class="block text-sm font-medium text-slate-300 mb-2">
自定义虚拟IP
<span class="text-xs text-slate-500 ml-1">(可选)</span>
</label>
<input v-model="formData.ip" type="text" placeholder="例如: 10.26.0.2"
class="w-full bg-slate-800 border border-slate-600 rounded-lg px-3 py-2 text-white placeholder-slate-500 focus:ring-2 focus:ring-blue-500 focus:border-transparent">
</div>
<div>
<label class="block text-sm font-medium text-slate-300 mb-2">MTU</label>
<input v-model.number="formData.mtu" type="number" placeholder="1380"
class="w-full bg-slate-800 border border-slate-600 rounded-lg px-3 py-2 text-white placeholder-slate-500 focus:ring-2 focus:ring-blue-500 focus:border-transparent">
</div>
</div>
</div>
<!-- 传输优化 -->
<div class="glass-panel rounded-lg p-5 border border-slate-700">
<h4 class="text-md font-bold text-purple-400 mb-4 flex items-center">
<svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z"></path>
</svg>
传输优化
</h4>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<label class="flex items-center justify-between p-3 bg-slate-800/50 rounded-lg border border-slate-700 cursor-pointer hover:bg-slate-800 transition-colors">
<div class="flex-1">
<div class="text-sm font-medium text-white">QUIC传输优化</div>
<div class="text-xs text-slate-400 mt-0.5">重传丢包</div>
</div>
<input v-model="formData.rtx" type="checkbox" class="w-5 h-5 text-blue-600 bg-slate-700 border-slate-600 rounded focus:ring-blue-500">
</label>
<label class="flex items-center justify-between p-3 bg-slate-800/50 rounded-lg border border-slate-700 cursor-pointer hover:bg-slate-800 transition-colors">
<div class="flex-1">
<div class="text-sm font-medium text-white">FEC前向纠错</div>
<div class="text-xs text-slate-400 mt-0.5">损失部分带宽提升稳定性</div>
</div>
<input v-model="formData.fec" type="checkbox" class="w-5 h-5 text-blue-600 bg-slate-700 border-slate-600 rounded focus:ring-blue-500">
</label>
<label class="flex items-center justify-between p-3 bg-slate-800/50 rounded-lg border border-slate-700 cursor-pointer hover:bg-slate-800 transition-colors">
<div class="flex-1">
<div class="text-sm font-medium text-white">LZ4压缩</div>
<div class="text-xs text-slate-400 mt-0.5">减少传输数据量</div>
</div>
<input v-model="formData.compress" type="checkbox" class="w-5 h-5 text-blue-600 bg-slate-700 border-slate-600 rounded focus:ring-blue-500">
</label>
<label class="flex items-center justify-between p-3 bg-slate-800/50 rounded-lg border border-slate-700 cursor-pointer hover:bg-slate-800 transition-colors">
<div class="flex-1">
<div class="text-sm font-medium text-white">关闭P2P打洞</div>
<div class="text-xs text-slate-400 mt-0.5">仅通过服务器中转</div>
</div>
<input v-model="formData.no_punch" type="checkbox" class="w-5 h-5 text-blue-600 bg-slate-700 border-slate-600 rounded focus:ring-blue-500">
</label>
</div>
</div>
<!-- 安全配置 -->
<div class="glass-panel rounded-lg p-5 border border-slate-700">
<h4 class="text-md font-bold text-red-400 mb-4 flex items-center">
<svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z"></path>
</svg>
安全配置
</h4>
<div class="space-y-4">
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label class="block text-sm font-medium text-slate-300 mb-2">组网加密密码(连接公共服务器时建议填写,同一组网密码需要相同)</label>
<input v-model="formData.password" type="password" placeholder="留空则不加密"
class="w-full bg-slate-800 border border-slate-600 rounded-lg px-3 py-2 text-white placeholder-slate-500 focus:ring-2 focus:ring-blue-500 focus:border-transparent">
</div>
<div>
<label class="block text-sm font-medium text-slate-300 mb-2">服务端证书校验模式</label>
<select v-model="formData.cert_mode"
class="w-full bg-slate-800 border border-slate-600 rounded-lg px-3 py-2 text-white focus:ring-2 focus:ring-blue-500 focus:border-transparent">
<option value="skip">跳过验证 (默认)</option>
<option value="standard">系统证书验证</option>
<option value="finger">证书指纹验证</option>
</select>
</div>
</div>
<div v-if="formData.cert_mode === 'finger'" class="animate-in fade-in slide-in-from-top-2">
<label class="block text-sm font-medium text-slate-300 mb-2">
证书指纹
<span class="text-xs text-slate-500 ml-1">(服务端启动时日志会输出指纹)</span>
</label>
<input v-model="formData.fingerprint" type="text" placeholder="例如: 3bdd8675606837cdf95d5e13445606315762315a78555f9da652940a25feaec1"
class="w-full bg-slate-800 border border-slate-600 rounded-lg px-3 py-2 text-white placeholder-slate-500 focus:ring-2 focus:ring-blue-500 focus:border-transparent font-mono text-sm">
</div>
</div>
</div>
<!-- NAT与路由 -->
<div class="glass-panel rounded-lg p-5 border border-slate-700">
<h4 class="text-md font-bold text-yellow-400 mb-4 flex items-center">
<svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 20l-5.447-2.724A1 1 0 013 16.382V5.618a1 1 0 011.447-.894L9 7m0 13l6-3m-6 3V7m6 10l4.553 2.276A1 1 0 0021 18.382V7.618a1 1 0 00-1.447-.894L15 9m0 0V7m0 2v6"></path>
</svg>
NAT与路由 (点对网)
</h4>
<div class="space-y-4">
<div>
<label class="block text-sm font-medium text-slate-300 mb-2">
入栈网段
<span class="text-xs text-slate-500 ml-1">格式: CIDR,目标IP</span>
</label>
<div class="space-y-2">
<div v-for="(item, idx) in formData.input" :key="idx" class="flex space-x-2">
<input v-model="formData.input[idx]" type="text" placeholder="例如: 192.168.0.0/24,10.26.0.2"
class="flex-1 bg-slate-800 border border-slate-600 rounded-lg px-3 py-2 text-white placeholder-slate-500 focus:ring-2 focus:ring-blue-500 focus:border-transparent text-sm">
<button @click="formData.input.splice(idx, 1)"
class="px-3 py-2 bg-red-600/20 hover:bg-red-600/40 text-red-400 rounded-lg transition-colors">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
</svg>
</button>
</div>
<button @click="formData.input.push('')"
class="w-full px-3 py-1.5 bg-yellow-600/20 hover:bg-yellow-600/40 text-yellow-400 rounded-lg transition-colors text-sm">
+ 添加入栈网段
</button>
</div>
</div>
<div>
<label class="block text-sm font-medium text-slate-300 mb-2">
出栈网段
<span class="text-xs text-slate-500 ml-1">格式: CIDR (允许转发的网段)</span>
</label>
<div class="space-y-2">
<div v-for="(item, idx) in formData.output" :key="idx" class="flex space-x-2">
<input v-model="formData.output[idx]" type="text" placeholder="例如: 0.0.0.0/0"
class="flex-1 bg-slate-800 border border-slate-600 rounded-lg px-3 py-2 text-white placeholder-slate-500 focus:ring-2 focus:ring-blue-500 focus:border-transparent text-sm">
<button @click="formData.output.splice(idx, 1)"
class="px-3 py-2 bg-red-600/20 hover:bg-red-600/40 text-red-400 rounded-lg transition-colors">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
</svg>
</button>
</div>
<button @click="formData.output.push('')"
class="w-full px-3 py-1.5 bg-yellow-600/20 hover:bg-yellow-600/40 text-yellow-400 rounded-lg transition-colors text-sm">
+ 添加出栈网段
</button>
</div>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<label class="flex items-center justify-between p-3 bg-slate-800/50 rounded-lg border border-slate-700 cursor-pointer hover:bg-slate-800 transition-colors">
<div class="flex-1">
<div class="text-sm font-medium text-white">关闭内置NAT</div>
<div class="text-xs text-slate-400 mt-0.5">使用系统网卡转发</div>
</div>
<input v-model="formData.no_nat" type="checkbox" class="w-5 h-5 text-blue-600 bg-slate-700 border-slate-600 rounded focus:ring-blue-500">
</label>
<label class="flex items-center justify-between p-3 bg-slate-800/50 rounded-lg border border-slate-700 cursor-pointer hover:bg-slate-800 transition-colors">
<div class="flex-1">
<div class="text-sm font-medium text-white">关闭TUN网卡</div>
<div class="text-xs text-slate-400 mt-0.5">仅作流量出口或端口映射</div>
</div>
<input v-model="formData.no_tun" type="checkbox" class="w-5 h-5 text-blue-600 bg-slate-700 border-slate-600 rounded focus:ring-blue-500">
</label>
</div>
</div>
</div>
<!-- 端口映射 -->
<div class="glass-panel rounded-lg p-5 border border-slate-700">
<h4 class="text-md font-bold text-orange-400 mb-4 flex items-center">
<svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 9l3 3-3 3m5 0h3M5 20h14a2 2 0 002-2V6a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"></path>
</svg>
端口映射
</h4>
<div class="space-y-4">
<div>
<label class="block text-sm font-medium text-slate-300 mb-2">
映射规则
<span class="text-xs text-slate-500 ml-1">格式: 协议://监听地址-虚拟IP-目标地址</span>
</label>
<div class="space-y-2">
<div v-for="(item, idx) in formData.port_mapping" :key="idx" class="flex space-x-2">
<input v-model="formData.port_mapping[idx]" type="text" placeholder="例如: tcp://0.0.0.0:81-10.0.0.2-10.0.0.2:80"
class="flex-1 bg-slate-800 border border-slate-600 rounded-lg px-3 py-2 text-white placeholder-slate-500 focus:ring-2 focus:ring-blue-500 focus:border-transparent text-sm font-mono">
<button @click="formData.port_mapping.splice(idx, 1)"
class="px-3 py-2 bg-red-600/20 hover:bg-red-600/40 text-red-400 rounded-lg transition-colors">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
</svg>
</button>
</div>
<button @click="formData.port_mapping.push('')"
class="w-full px-3 py-1.5 bg-orange-600/20 hover:bg-orange-600/40 text-orange-400 rounded-lg transition-colors text-sm">
+ 添加映射规则
</button>
</div>
</div>
<label class="flex items-center justify-between p-3 bg-slate-800/50 rounded-lg border border-slate-700 cursor-pointer hover:bg-slate-800 transition-colors">
<div class="flex-1">
<div class="text-sm font-medium text-white">允许作为映射出口</div>
<div class="text-xs text-slate-400 mt-0.5">允许其他设备使用本机作跳板</div>
</div>
<input v-model="formData.allow_mapping" type="checkbox" class="w-5 h-5 text-blue-600 bg-slate-700 border-slate-600 rounded focus:ring-blue-500">
</label>
</div>
</div>
<!-- 设备配置 -->
<div class="glass-panel rounded-lg p-5 border border-slate-700">
<h4 class="text-md font-bold text-cyan-400 mb-4 flex items-center">
<svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9.75 17L9 20l-1 1h8l-1-1-.75-3M3 13h18M5 17h14a2 2 0 002-2V5a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"></path>
</svg>
设备配置
</h4>
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
<div>
<label class="block text-sm font-medium text-slate-300 mb-2">设备名称</label>
<input v-model="formData.device_name" type="text" placeholder="默认为主机名"
class="w-full bg-slate-800 border border-slate-600 rounded-lg px-3 py-2 text-white placeholder-slate-500 focus:ring-2 focus:ring-blue-500 focus:border-transparent">
</div>
<div>
<label class="block text-sm font-medium text-slate-300 mb-2">设备ID</label>
<input v-model="formData.device_id" type="text" placeholder="自动生成"
class="w-full bg-slate-800 border border-slate-600 rounded-lg px-3 py-2 text-white placeholder-slate-500 focus:ring-2 focus:ring-blue-500 focus:border-transparent">
</div>
<div>
<label class="block text-sm font-medium text-slate-300 mb-2">虚拟网卡名</label>
<input v-model="formData.tun_name" type="text" placeholder="默认为vnt-tun"
class="w-full bg-slate-800 border border-slate-600 rounded-lg px-3 py-2 text-white placeholder-slate-500 focus:ring-2 focus:ring-blue-500 focus:border-transparent">
</div>
</div>
</div>
<!-- STUN配置 -->
<div class="glass-panel rounded-lg p-5 border border-slate-700">
<h4 class="text-md font-bold text-pink-400 mb-4 flex items-center">
<svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3.055 11H5a2 2 0 012 2v1a2 2 0 002 2 2 2 0 012 2v2.945M8 3.935V5.5A2.5 2.5 0 0010.5 8h.5a2 2 0 012 2 2 2 0 104 0 2 2 0 012-2h1.064M15 20.488V18a2 2 0 012-2h3.064M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path>
</svg>
STUN配置 (高级)
</h4>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label class="block text-sm font-medium text-slate-300 mb-2">UDP STUN服务器</label>
<div class="space-y-2">
<div v-for="(item, idx) in formData.udp_stun" :key="idx" class="flex space-x-2">
<input v-model="formData.udp_stun[idx]" type="text" placeholder="例如: stun.l.google.com:19302"
class="flex-1 bg-slate-800 border border-slate-600 rounded-lg px-3 py-2 text-white placeholder-slate-500 focus:ring-2 focus:ring-blue-500 focus:border-transparent text-sm">
<button @click="formData.udp_stun.splice(idx, 1)"
class="px-3 py-2 bg-red-600/20 hover:bg-red-600/40 text-red-400 rounded-lg transition-colors">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
</svg>
</button>
</div>
<button @click="formData.udp_stun.push('')"
class="w-full px-3 py-1.5 bg-pink-600/20 hover:bg-pink-600/40 text-pink-400 rounded-lg transition-colors text-sm">
+ 添加UDP STUN
</button>
</div>
</div>
<div>
<label class="block text-sm font-medium text-slate-300 mb-2">TCP STUN服务器</label>
<div class="space-y-2">
<div v-for="(item, idx) in formData.tcp_stun" :key="idx" class="flex space-x-2">
<input v-model="formData.tcp_stun[idx]" type="text" placeholder="例如: stun.nextcloud.com:443"
class="flex-1 bg-slate-800 border border-slate-600 rounded-lg px-3 py-2 text-white placeholder-slate-500 focus:ring-2 focus:ring-blue-500 focus:border-transparent text-sm">
<button @click="formData.tcp_stun.splice(idx, 1)"
class="px-3 py-2 bg-red-600/20 hover:bg-red-600/40 text-red-400 rounded-lg transition-colors">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
</svg>
</button>
</div>
<button @click="formData.tcp_stun.push('')"
class="w-full px-3 py-1.5 bg-pink-600/20 hover:bg-pink-600/40 text-pink-400 rounded-lg transition-colors text-sm">
+ 添加TCP STUN
</button>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- TOML模式 -->
<div v-show="editMode === 'toml'" class="h-full">
<textarea
v-model="editorContent"
class="w-full h-full bg-[#1e1e1e] text-[#d4d4d4] font-mono p-4 resize-none focus:outline-none text-sm"
spellcheck="false"
placeholder="# 在此处输入 TOML 配置..."
></textarea>
</div>
</div>
<!-- Footer -->
<div class="p-4 border-t border-slate-700 flex justify-between items-center bg-slate-800/50">
<div class="text-xs text-slate-500">
<span v-if="editMode === 'form'">填写完成后保存即可生成配置文件</span>
<span v-else>* 请使用标准 TOML 格式</span>
</div>
<div class="space-x-3">
<button
@click="showEditor = false"
class="px-4 py-2 rounded text-slate-300 hover:text-white transition-colors"
>
取消
</button>
<button
@click="saveConfig"
class="px-6 py-2 bg-blue-600 hover:bg-blue-500 text-white rounded font-medium shadow-lg transition-colors"
>
保存配置
</button>
</div>
</div>
</div>
</div>
</div>
</template>
<!-- 3. Peers (设备列表) -->
<template id="tpl-peers">
<div class="max-w-7xl mx-auto">
<div class="glass-panel rounded-xl shadow-lg overflow-hidden">
<div
class="px-6 py-4 border-b border-slate-700 flex justify-between items-center"
>
<h2 class="text-xl font-bold text-white">设备列表</h2>
<div class="flex space-x-4 text-sm text-slate-400">
<span
>Online:
<span class="text-white"
>{{ peers.filter(p => p.online).length
}}</span
></span
>
<span
>Total:
<span class="text-white"
>{{ peers.length }}</span
></span
>
</div>
</div>
<div class="overflow-auto max-h-[600px] custom-scrollbar">
<table class="min-w-full divide-y divide-slate-700">
<thead class="bg-slate-800">
<tr>
<th class="w-8 px-2 py-3"></th>
<th
class="px-6 py-3 text-left text-xs font-medium text-slate-400 tracking-wider"
>
IP地址
</th>
<th
class="px-6 py-3 text-left text-xs font-medium text-slate-400 tracking-wider"
>
名称
</th>
<th
class="px-6 py-3 text-left text-xs font-medium text-slate-400 tracking-wider"
>
版本
</th>
<th
class="px-6 py-3 text-left text-xs font-medium text-slate-400 tracking-wider"
>
状态
</th>
<th
class="px-6 py-3 text-left text-xs font-medium text-slate-400 tracking-wider"
>
模式
</th>
<th
class="px-6 py-3 text-left text-xs font-medium text-slate-400 tracking-wider"
>
延迟
</th>
<th
class="px-6 py-3 text-left text-xs font-medium text-slate-400 tracking-wider"
>
丢包率
</th>
<th
class="px-6 py-3 text-left text-xs font-medium text-slate-400 tracking-wider"
>
流量
</th>
<th
class="px-6 py-3 text-left text-xs font-medium text-slate-400 tracking-wider"
>
最后在线
</th>
</tr>
</thead>
<tbody
class="divide-y divide-slate-700 bg-slate-900/30"
>
<template v-for="peer in peers" :key="peer.ip">
<tr class="hover:bg-slate-800/50 transition-colors">
<td class="px-2 py-4 text-center cursor-pointer select-none" @click="toggleExpand(peer.ip)">
<svg class="w-4 h-4 text-slate-500 transition-transform duration-200 inline-block" :class="{ 'rotate-90': expandedPeers[peer.ip] }" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path>
</svg>
</td>
<td
class="px-6 py-4 whitespace-nowrap font-mono text-sm text-blue-300"
>
<span
class="border-b border-dotted border-blue-500/50 pb-0.5 cursor-help"
@mouseenter="showPeerTooltip($event, peer)"
@mouseleave="hidePeerTooltip"
>
{{ peer.ip }}
</span>
</td>
<td
class="px-6 py-4 whitespace-nowrap text-sm text-slate-300"
>
{{ peer.name || '-' }}
</td>
<td
class="px-6 py-4 whitespace-nowrap text-xs text-slate-500"
>
{{ peer.version || '-' }}
</td>
<td class="px-6 py-4 whitespace-nowrap">
<div class="flex items-center space-x-2">
<span
:class="peer.online ? 'bg-green-900 text-green-300' : 'bg-slate-700 text-slate-400'"
class="px-2 py-0.5 text-xs rounded-full font-medium"
>{{ peer.online ? 'Online' : 'Offline' }}</span>
<!-- 加密状态图标 -->
<div v-if="peer.online && peer.key_equal === 1" class="tooltip">
<svg
class="w-4 h-4 text-green-400"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z"
></path>
</svg>
<span class="tooltip-text">双方加密传输</span>
</div>
<div v-else-if="peer.online && peer.key_equal === 2" class="tooltip">
<svg
class="w-4 h-4 text-yellow-400"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M8 11V7a4 4 0 118 0m-4 8v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2z"
></path>
</svg>
<span class="tooltip-text">双方未加密</span>
</div>
<div v-else-if="peer.online && [3,4,5].includes(peer.key_equal)" class="tooltip cursor-help group">
<svg
class="w-4 h-4 text-red-500"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
></path>
</svg>
<span class="tooltip-text">{{ peer.key_equal === 3 ? '己方加密对方未加密' : peer.key_equal === 4 ? '己方未加密对方加密' : peer.key_equal === 5 ? '密钥不一致' : '未知错误' }}</span>
</div>
</div>
</td>
<td
class="px-6 py-4 whitespace-nowrap text-sm"
>
<span
v-if="peer.online && peer.route"
:class="getRouteModeClass(peer.route)"
>{{ getRouteModeText(peer.route) }}</span
>
<span v-else-if="peer.online" class="text-yellow-400">服务器中继</span>
<span v-else class="text-slate-600"
>-</span
>
</td>
<td
class="px-6 py-4 whitespace-nowrap text-sm text-slate-400"
>
{{ peer.route ? peer.route.rtt + ' ms' : '-' }}
</td>
<td
class="px-6 py-4 whitespace-nowrap text-sm"
>
<span v-if="peer.packet_loss"
:class="peer.packet_loss.loss_rate > 10 ? 'text-red-400' : peer.packet_loss.loss_rate > 5 ? 'text-yellow-400' : 'text-green-400'"
:title="'Sent: ' + peer.packet_loss.sent + ', Received: ' + peer.packet_loss.received"
>{{ peer.packet_loss.loss_rate.toFixed(1) }}%</span>
<span v-else class="text-slate-600">-</span>
</td>
<td
class="px-6 py-4 whitespace-nowrap text-xs"
>
<div v-if="peer.traffic" class="leading-relaxed">
<div class="text-green-400">↑ {{ formatBytes(peer.traffic.tx_bytes) }} ({{ formatSpeed(peer.traffic.tx_speed) }})</div>
<div class="text-blue-400">↓ {{ formatBytes(peer.traffic.rx_bytes) }} ({{ formatSpeed(peer.traffic.rx_speed) }})</div>
</div>
<span v-else class="text-slate-600">-</span>
</td>
<td
class="px-6 py-4 whitespace-nowrap text-sm text-slate-500 font-mono text-xs"
>
{{ formatTime(peer.last_connected_time)
}}
</td>
</tr>
<tr v-if="expandedPeers[peer.ip]">
<td :colspan="10" class="p-0">
<div class="px-4 py-3 bg-slate-950/60 border-t border-slate-700/50">
<div class="flex items-center space-x-4 mb-2 text-xs text-slate-400">
<span class="flex items-center"><span class="inline-block w-3 h-0.5 bg-green-400 mr-1"></span>上传速度</span>
<span class="flex items-center"><span class="inline-block w-3 h-0.5 bg-blue-400 mr-1"></span>下载速度</span>
<span class="ml-auto" :id="'chart-max-' + peer.ip.replaceAll('.', '-')"></span>
</div>
<canvas :id="'chart-' + peer.ip.replaceAll('.', '-')" style="width:100%;height:150px;display:block;" class="rounded"></canvas>
</div>
</td>
</tr>
</template>
</tbody>
</table>
</div>
</div>
</div>
</template>
<!-- 4. Routes (路由) -->
<template id="tpl-routes">
<div class="max-w-6xl mx-auto">
<div class="glass-panel rounded-xl shadow-lg overflow-hidden">
<div class="px-6 py-4 border-b border-slate-700">
<h2 class="text-xl font-bold text-white">路由表</h2>
</div>
<div class="overflow-auto max-h-[600px] custom-scrollbar">
<table class="min-w-full divide-y divide-slate-700">
<thead class="bg-slate-800">
<tr>
<th
class="px-6 py-3 text-left text-xs font-medium text-slate-400 uppercase tracking-wider"
>
目标节点IP
</th>
<th
class="px-6 py-3 text-left text-xs font-medium text-slate-400 uppercase tracking-wider"
>
目标网络
</th>
<th
class="px-6 py-3 text-left text-xs font-medium text-slate-400 uppercase tracking-wider"
>
跳数
</th>
<th
class="px-6 py-3 text-left text-xs font-medium text-slate-400 uppercase tracking-wider"
>
延迟
</th>
</tr>
</thead>
<tbody
class="divide-y divide-slate-700 bg-slate-900/30"
>
<template v-for="item in routes" :key="item.ip">
<tr
v-for="(route, rIdx) in item.routes"
:key="rIdx"
class="hover:bg-slate-800/50"
>
<td
class="px-6 py-4 whitespace-nowrap font-mono text-sm text-blue-300"
v-if="rIdx===0"
:rowspan="item.routes.length"
>
{{ item.ip }}
</td>
<td
class="px-6 py-4 whitespace-nowrap font-mono text-sm text-yellow-300"
>
{{ route.addr }}
</td>
<td
class="px-6 py-4 whitespace-nowrap text-sm text-slate-400"
>
{{ route.metric }}
</td>
<td
class="px-6 py-4 whitespace-nowrap text-sm text-slate-400"
>
{{ route.rtt }} ms
</td>
</tr>
</template>
</tbody>
</table>
</div>
</div>
</div>
</template>
<script>
const {
createApp,
ref,
reactive,
onMounted,
onUnmounted,
computed,
watch,
nextTick,
inject,
provide,
} = Vue;
const {createRouter, createWebHashHistory} = VueRouter;
const API_BASE = "";
// 格式化时间工具
const formatTime = (timestamp) => {
if (!timestamp) return "-";
const date = new Date(timestamp * 1000);
const pad = (n) => (n < 10 ? "0" + n : n);
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}`;
};
// 格式化字节数
const formatBytes = (bytes) => {
if (bytes === 0 || bytes === undefined || bytes === null) return "0B";
const units = ["B", "KB", "MB", "GB", "TB"];
let i = 0;
let value = bytes;
while (value >= 1024 && i < units.length - 1) {
value /= 1024;
i++;
}
return i === 0 ? value + units[i] : value.toFixed(2) + units[i];
};
// 格式化速度(字节/秒)
const formatSpeed = (bytesPerSecond) => {
if (bytesPerSecond === 0 || bytesPerSecond === undefined || bytesPerSecond === null) return "0B/s";
const units = ["B/s", "KB/s", "MB/s", "GB/s"];
let i = 0;
let value = bytesPerSecond;
while (value >= 1024 && i < units.length - 1) {
value /= 1024;
i++;
}
return i === 0 ? value + units[i] : value.toFixed(2) + units[i];
};
// --- 组件定义 ---
const GeneralView = {
template: "#tpl-general",
setup() {
const info = inject("info");
const configList = inject("configList");
const toggleVnt = inject("toggleVnt");
const restartVnt = inject("restartVnt");
const loading = inject("loading");
const localSelectedConfig = ref("");
// 同步当前配置
watch(
() => info.value.current_config_file,
(newVal) => {
if (
(info.value.status === "running" ||
info.value.status === "starting") &&
newVal
) {
localSelectedConfig.value = newVal;
}
},
{immediate: true},
);
const handleToggle = () => {
// 如果是启动,且有本地选择的配置,传递给 toggle
if (
info.value.status !== "running" &&
info.value.status !== "starting"
) {
toggleVnt(localSelectedConfig.value);
} else {
toggleVnt(null);
}
};
const handleRestart = () => {
if (localSelectedConfig.value) {
restartVnt(localSelectedConfig.value);
}
};
return {
info,
configList,
localSelectedConfig,
loading,
handleToggle,
handleRestart,
};
},
};
const ConfigView = {
template: "#tpl-config",
setup() {
const info = inject("info");
const configList = inject("configList");
const fetchConfigList = inject("fetchConfigList");
// 编辑器状态
const showEditor = ref(false);
const editorContent = ref("");
const editorFileName = ref("");
const editorMode = ref("new");
const editMode = ref("form"); // 'form' 或 'toml'
const originalToml = ref(""); // 保存原始TOML内容(包含用户注释)
const hasTomlChanges = ref(false); // 标记TOML是否被修改过
const hasFormChanges = ref(false); // 标记表单是否被修改过
const isParsingToml = ref(false); // 标记是否正在解析TOML(用于避免触发watch)
// 表单数据
const formData = ref({
config_name: "",
network_code: "",
server: [""],
ip: "",
mtu: null,
rtx: false,
fec: false,
compress: false,
no_punch: false,
input: [],
output: [],
no_nat: false,
no_tun: false,
port_mapping: [],
allow_mapping: false,
device_name: "",
device_id: "",
tun_name: "",
password: "",
cert_mode: "skip",
fingerprint: "",
udp_stun: [],
tcp_stun: []
});
// 从TOML解析到表单
const parseTomlToForm = (toml) => {
const data = {
config_name: "",
network_code: "",
server: [""],
ip: "",
mtu: null,
rtx: false,
fec: false,
compress: false,
no_punch: false,
input: [],
output: [],
no_nat: false,
no_tun: false,
port_mapping: [],
allow_mapping: false,
device_name: "",
device_id: "",
tun_name: "",
password: "",
cert_mode: "skip",
fingerprint: "",
udp_stun: [],
tcp_stun: []
};
const lines = toml.split('\n');
for (const line of lines) {
const trimmed = line.trim();
if (!trimmed || trimmed.startsWith('#')) continue;
if (trimmed.includes('config_name')) {
const match = trimmed.match(/config_name\s*=\s*"([^"]*)"/);
if (match) data.config_name = match[1];
} else if (trimmed.includes('network_code')) {
const match = trimmed.match(/network_code\s*=\s*"([^"]*)"/);
if (match) data.network_code = match[1];
} else if (trimmed.startsWith('server')) {
const match = trimmed.match(/server\s*=\s*\[(.*)\]/);
if (match) {
const items = match[1].match(/"([^"]*)"/g);
if (items) data.server = items.map(s => s.replace(/"/g, ''));
}
} else if (trimmed.includes('ip =')) {
const match = trimmed.match(/ip\s*=\s*"([^"]*)"/);
if (match) data.ip = match[1];
} else if (trimmed.includes('mtu =')) {
const match = trimmed.match(/mtu\s*=\s*(\d+)/);
if (match) data.mtu = parseInt(match[1]);
} else if (trimmed.match(/^rtx\s*=/)) {
data.rtx = trimmed.includes('true');
} else if (trimmed.match(/^fec\s*=/)) {
data.fec = trimmed.includes('true');
} else if (trimmed.match(/^compress\s*=/)) {
data.compress = trimmed.includes('true');
} else if (trimmed.match(/^no_punch\s*=/)) {
data.no_punch = trimmed.includes('true');
} else if (trimmed.startsWith('input')) {
const match = trimmed.match(/input\s*=\s*\[(.*)\]/);
if (match) {
const items = match[1].match(/"([^"]*)"/g);
if (items) data.input = items.map(s => s.replace(/"/g, ''));
}
} else if (trimmed.startsWith('output')) {
const match = trimmed.match(/output\s*=\s*\[(.*)\]/);
if (match) {
const items = match[1].match(/"([^"]*)"/g);
if (items) data.output = items.map(s => s.replace(/"/g, ''));
}
} else if (trimmed.match(/^no_nat\s*=/)) {
data.no_nat = trimmed.includes('true');
} else if (trimmed.match(/^no_tun\s*=/)) {
data.no_tun = trimmed.includes('true');
} else if (trimmed.startsWith('port_mapping')) {
const match = trimmed.match(/port_mapping\s*=\s*\[(.*)\]/);
if (match) {
const items = match[1].match(/"([^"]*)"/g);
if (items) data.port_mapping = items.map(s => s.replace(/"/g, ''));
}
} else if (trimmed.match(/^allow_mapping\s*=/)) {
data.allow_mapping = trimmed.includes('true');
} else if (trimmed.includes('device_name')) {
const match = trimmed.match(/device_name\s*=\s*"([^"]*)"/);
if (match) data.device_name = match[1];
} else if (trimmed.includes('device_id')) {
const match = trimmed.match(/device_id\s*=\s*"([^"]*)"/);
if (match) data.device_id = match[1];
} else if (trimmed.includes('tun_name')) {
const match = trimmed.match(/tun_name\s*=\s*"([^"]*)"/);
if (match) data.tun_name = match[1];
} else if (trimmed.includes('password =')) {
const match = trimmed.match(/password\s*=\s*"([^"]*)"/);
if (match) data.password = match[1];
} else if (trimmed.includes('cert_mode')) {
const match = trimmed.match(/cert_mode\s*=\s*"([^"]*)"/);
if (match) {
const value = match[1];
if (value.startsWith('finger:')) {
data.cert_mode = 'finger';
data.fingerprint = value.substring(7); // 去掉 "finger:" 前缀
} else {
data.cert_mode = value;
}
}
} else if (trimmed.startsWith('udp_stun')) {
const match = trimmed.match(/udp_stun\s*=\s*\[(.*)\]/);
if (match) {
const items = match[1].match(/"([^"]*)"/g);
if (items) data.udp_stun = items.map(s => s.replace(/"/g, ''));
}
} else if (trimmed.startsWith('tcp_stun')) {
const match = trimmed.match(/tcp_stun\s*=\s*\[(.*)\]/);
if (match) {
const items = match[1].match(/"([^"]*)"/g);
if (items) data.tcp_stun = items.map(s => s.replace(/"/g, ''));
}
}
}
return data;
};
// 从表单生成TOML
const formToToml = () => {
let toml = '';
if (formData.value.config_name) {
toml += `# 配置名称\nconfig_name = "${formData.value.config_name}"\n`;
}
toml += '\n# --- 网络配置 ---\n';
toml += '# 网络编号,相同网络编号的会组在同一个虚拟网 (必填)\n';
toml += `network_code = "${formData.value.network_code}"\n\n`;
const servers = formData.value.server.filter(s => s.trim());
if (servers.length > 0) {
toml += '# 服务器地址列表(支持 quic / tcp / wss / dynamic) (必填)\n';
toml += '# dynamic 协议使用dns txt解析记录值\n';
toml += `server = [${servers.map(s => `"${s}"`).join(', ')}]\n`;
}
if (formData.value.ip) {
toml += '\n# 自定义虚拟 IP (可选)\n';
toml += `ip = "${formData.value.ip}"\n`;
}
if (formData.value.rtx) {
toml += '\n# 是否启用quic优化传输 (默认 false)\n';
toml += '# 开启后传输过程几乎不会丢包,但是延迟可能会有波动\n';
toml += 'rtx = true\n';
}
if (formData.value.fec) {
toml += '\n# 是否启用 FEC 前向纠错 (默认 false)\n';
toml += '# 开启后可以减少丢包率,损失带宽但是延迟比较稳定,带宽充足时可以使用此功能\n';
toml += 'fec = true\n';
}
if (formData.value.no_punch) {
toml += '\n# 是否关闭 P2P 打洞 (默认 false)\n';
toml += 'no_punch = true\n';
}
if (formData.value.compress) {
toml += '\n# 是否启用 LZ4 压缩 (默认 false)\n';
toml += 'compress = true\n';
}
const inputs = formData.value.input.filter(s => s.trim());
if (inputs.length > 0) {
toml += '\n# 入栈监听网段 (逗号分隔的 CIDR 和目标 IP),用于点对网,将指定网段的流量发送到目标节点\n';
toml += '# 例如192.168.0.0/24,10.26.0.2 表示将192.168.0.0/24网段的数据转发到10.26.0.2\n';
toml += `input = [${inputs.map(s => `"${s}"`).join(', ')}]\n`;
}
const outputs = formData.value.output.filter(s => s.trim());
if (outputs.length > 0) {
toml += '\n# 出栈允许网段,用于点对网,允许指定网段的转发\n';
toml += `output = [${outputs.map(s => `"${s}"`).join(', ')}]\n`;
}
if (formData.value.no_nat) {
toml += '\n# 是否关闭内置子网NAT,关闭后需要配置网卡转发,否则无法使用点对网\n';
toml += '# 通常关闭内置子网NAT,使用系统的网卡转发,点对网性能会更好\n';
toml += 'no_nat = true\n';
}
if (formData.value.no_tun) {
toml += '\n# 是否关闭TUN虚拟网卡,关闭后只能充当流量出口或者进行端口映射,关闭后无需管理员权限\n';
toml += 'no_tun = true\n';
}
const portMappings = formData.value.port_mapping.filter(s => s.trim());
if (portMappings.length > 0) {
toml += '\n# 端口映射,格式为:协议://本地监听地址-目标虚拟IP-目标映射地址\n';
toml += '# 端口映射用于在本地监听指定端口,并将收到的网络流量经由指定虚拟节点转发到目标地址\n';
toml += '# 例如: tcp://0.0.0.0:81-10.0.0.2-10.0.0.2:80 表示将本地tcp的81端口的数据转发到10.0.0.2:80\n';
toml += '# 例如: tcp://0.0.0.0:81-10.0.0.2-192.168.1.10:80 则表示将本地tcp的81端口的数据经过10.0.0.2转到192.168.1.10:80\n';
toml += '# 例如: tcp://0.0.0.0:81-10.0.0.2-anyonehost:80 则表示将本地tcp的81端口的数据经过10.0.0.2转到anyonehost:80\n';
toml += `port_mapping = [${portMappings.map(s => `"${s}"`).join(', ')}]\n`;
}
if (formData.value.allow_mapping) {
toml += '\n# 是否允许作为端口映射出口,开启后其他设备才可使用本设备的ip为"目标虚拟IP"\n';
toml += '# 开启后虚拟网络其他设备可以使用此设备当跳板访问其他网络\n';
toml += 'allow_mapping = true\n';
}
if (formData.value.mtu) {
toml += '\n# MTU 设置\n';
toml += `mtu = ${formData.value.mtu}\n`;
}
toml += '\n# --- 设备配置 ---\n';
if (formData.value.device_name) {
toml += '\n# 设备名称 (可选,默认读取本机 hostname)\n';
toml += `device_name = "${formData.value.device_name}"\n`;
}
if (formData.value.device_id) {
toml += '\n# 设备 ID (可选,不填自动生成,不同设备ID不能相同)\n';
toml += `device_id = "${formData.value.device_id}"\n`;
}
if (formData.value.tun_name) {
toml += '\n# 虚拟网卡名称\n';
toml += `tun_name = "${formData.value.tun_name}"\n`;
}
toml += '\n# --- 安全配置 ---\n';
if (formData.value.password) {
toml += '\n# 组网加密密码 (可选)\n';
toml += `password = "${formData.value.password}"\n`;
}
if (formData.value.cert_mode && formData.value.cert_mode !== 'skip') {
toml += '\n# 证书校验方式:\n';
toml += '# skip 跳过验证(默认)\n';
toml += '# standard 使用系统证书验证\n';
toml += '# finger 使用证书指纹验证,服务端启动时日志会输出指纹\n';
if (formData.value.cert_mode === 'finger' && formData.value.fingerprint) {
toml += `cert_mode = "finger:${formData.value.fingerprint}"\n`;
} else {
toml += `cert_mode = "${formData.value.cert_mode}"\n`;
}
}
const udpStuns = formData.value.udp_stun.filter(s => s.trim());
if (udpStuns.length > 0) {
toml += '\n# 自定义UDP STUN地址,不设置则用默认stun\n';
toml += `udp_stun = [${udpStuns.map(s => `"${s}"`).join(', ')}]\n`;
}
const tcpStuns = formData.value.tcp_stun.filter(s => s.trim());
if (tcpStuns.length > 0) {
toml += '\n# 自定义TCP STUN地址,不设置则用默认stun\n';
toml += `tcp_stun = [${tcpStuns.map(s => `"${s}"`).join(', ')}]\n`;
}
return toml;
};
// 切换到表单模式
const switchToFormMode = () => {
if (editMode.value === 'toml') {
editMode.value = 'form';
// 从TOML解析到表单
isParsingToml.value = true;
formData.value = parseTomlToForm(editorContent.value);
nextTick(() => {
isParsingToml.value = false;
});
} else {
editMode.value = 'form';
}
};
// 切换到TOML模式
const switchToTomlMode = () => {
if (editMode.value === 'form') {
// 如果表单被修改过,生成新的TOML
if (hasFormChanges.value) {
editorContent.value = formToToml();
// 重置标记,因为表单修改已经应用到TOML了
hasFormChanges.value = false;
} else if (originalToml.value && !hasTomlChanges.value) {
// 如果表单没被修改,且TOML也没被修改,使用原始TOML(保留用户注释)
editorContent.value = originalToml.value;
} else {
// 其他情况生成新的TOML
editorContent.value = formToToml();
}
}
editMode.value = 'toml';
};
// 监听TOML内容变化(只在TOML模式下)
watch(editorContent, (newVal, oldVal) => {
if (editMode.value === 'toml' && oldVal !== undefined) {
hasTomlChanges.value = true;
}
});
// 监听表单数据变化
watch(formData, () => {
if (editMode.value === 'form' && showEditor.value && !isParsingToml.value) {
hasFormChanges.value = true;
}
}, { deep: true });
const openEditor = async (fileName) => {
editorFileName.value = fileName || "";
editorMode.value = fileName ? "edit" : "new";
editMode.value = "form"; // 默认表单模式
hasTomlChanges.value = false; // 重置TOML修改标记
hasFormChanges.value = false; // 重置表单修改标记
if (fileName) {
try {
const res = await fetch(
`${API_BASE}/api/config?file_name=${fileName}`,
);
const json = await res.json();
if (json.code === 0) {
editorContent.value = json.data;
originalToml.value = json.data; // 保存原始TOML
isParsingToml.value = true;
formData.value = parseTomlToForm(json.data);
nextTick(() => {
isParsingToml.value = false;
});
showEditor.value = true;
} else alert("获取配置失败: " + json.msg);
} catch (e) {
alert("网络错误");
}
} else {
// 新建配置,初始化表单
originalToml.value = ""; // 新建时清空原始TOML
formData.value = {
config_name: "",
network_code: "",
server: [""],
ip: "",
mtu: null,
rtx: false,
fec: false,
compress: false,
no_punch: false,
input: [],
output: [],
no_nat: false,
no_tun: false,
port_mapping: [],
allow_mapping: false,
device_name: "",
device_id: "",
tun_name: "",
password: "",
cert_mode: "skip",
fingerprint: "",
udp_stun: [],
tcp_stun: []
};
editorContent.value = `# config_name = "配置名称"
# --- 网络配置 ---
# 网络编号,相同网络编号的会组在同一个虚拟网 (必填)
network_code = "your_network_code"
# 服务器地址列表(支持 quic / tcp / wss / dynamic) (必填)
# dynamic 协议使用dns txt解析记录值
server = ["quic://1.2.3.4:29872"]
# ===简单使用以下参数可以不动===
# 自定义虚拟 IP (可选)
# ip = "10.10.0.2"
# 是否启用quic优化传输 (默认 false,设置为true时开启)
# 开启后传输过程几乎不会丢包,但是延迟会有波动
# rtx = false
# 是否启用 FEC 前向纠错,损失一定带宽来提升网络稳定性(默认 false,设置为true时开启)
# 开启后可以减少丢包率,损失带宽但是延迟比较稳定,带宽充足时可以使用此功能
# fec = false
# 是否关闭 P2P 打洞 (默认 false,设置为true时关闭)
# no_punch = false
# 是否启用 LZ4 压缩 (默认 false,设置为true时开启)
# compress = false
# 入栈监听网段 (逗号分隔的 CIDR 和目标 IP),用于点对网,将指定网段的流量发送到目标节点
# input = ["192.168.0.0/24,10.26.0.2", "192.168.1.0/24,10.26.0.3"]
# 出栈允许网段,用于点对网,允许指定网段的转发
# output = ["0.0.0.0/0"]
# 是否关闭内置子网NAT,关闭(设为true)后需要配置网卡转发,否则无法使用点对网。通常关闭内置子网NAT,使用系统的网卡转发,点对网性能会更好
# no_nat = false
# 是否关闭TUN虚拟网卡,关闭(设为true)后只能充当流量出口或者进行端口映射,关闭后无需管理员权限
# no_tun = false
# 端口映射,格式为:协议://本地监听地址-目标虚拟IP-目标映射地址
# 端口映射用于在本地监听指定端口,并将收到的网络流量经由指定虚拟节点转发到目标地址,从而实现跨网络或内网服务访问
# 例如 port_mapping = ["tcp://0.0.0.0:81-10.0.0.2-10.0.0.2:80"]
# tcp://0.0.0.0:81-10.0.0.2-10.0.0.2:80 则表示将本地tcp的81端口的数据转发到10.0.0.2:80
# tcp://0.0.0.0:81-10.0.0.2-192.168.1.10:80 则表示将本地tcp的81端口的数据经过10.0.0.2转到192.168.1.10:80
# tcp://0.0.0.0:81-10.0.0.2-anyonehost:80 则表示将本地tcp的81端口的数据经过10.0.0.2转到anyonehost:80
# port_mapping = []
# 是否允许作为端口映射出口,开启(设置为true)后其他设备才可使用本设备的ip为"目标虚拟IP"
# 开启后虚拟网络其他设备可以使用此设备当跳板访问其他网络
# allow_mapping = false
# MTU 设置
# mtu = 1400
# --- 设备配置 ---
# 设备名称 (可选,默认读取本机 hostname)
# device_name = "my-device"
# 设备 ID (可选,不填自动生成,不同设备ID不能相同)
# device_id = "device-id-xxxx"
# 虚拟网卡名称
# tun_name = "vnt-tun"
# --- 安全配置 ---
# 加密密码 (可选)
# password = "123456"
# 证书校验方式:
# skip 跳过验证(默认)
# standard 使用系统证书验证
# finger 使用证书指纹验证,服务端启动时日志会输出指纹,
# 例如 finger:3bdd8675606837cdf95d5e13445606315762315a78555f9da652940a25feaec1
# cert_mode = "skip"
# --- 其他配置 ---
# 自定义stun地址,分别用于udp打洞和tcp打洞,需要单独配置,不设置则用默认stun
# udp_stun = ["stun.chat.bilibili.com"]
# tcp_stun = ["stun.nextcloud.com:443"]`;
showEditor.value = true;
}
};
const saveConfig = async () => {
try {
// 如果是表单模式,先转换为TOML
let configContent = editorContent.value;
if (editMode.value === 'form') {
// 验证必填项
if (!formData.value.network_code.trim()) {
alert('请填写网络编号');
return;
}
const servers = formData.value.server.filter(s => s.trim());
if (servers.length === 0) {
alert('请至少填写一个服务器地址');
return;
}
configContent = formToToml();
}
const payload = {
file_name: editorFileName.value || null,
config: configContent,
};
const res = await fetch(`${API_BASE}/api/config`, {
method: "POST",
headers: {"Content-Type": "application/json"},
body: JSON.stringify(payload),
});
const json = await res.json();
if (json.code === 0) {
showEditor.value = false;
fetchConfigList();
} else alert("保存失败: " + json.msg);
} catch (e) {
alert("保存失败: " + e.message);
}
};
const deleteConfig = async (fileName) => {
if (!confirm(`确定要删除配置 ${fileName} 吗?`)) return;
try {
const res = await fetch(
`${API_BASE}/api/config?file_name=${fileName}`,
{method: "DELETE"},
);
const json = await res.json();
if (json.code === 0) fetchConfigList();
else alert(json.msg);
} catch (e) {
alert(e.message);
}
};
return {
info,
configList,
showEditor,
editorContent,
editorFileName,
editorMode,
editMode,
formData,
openEditor,
saveConfig,
deleteConfig,
switchToFormMode,
switchToTomlMode,
};
},
};
const PeersView = {
template: "#tpl-peers",
setup() {
const peers = ref([]);
const info = inject("info");
const isPageVisible = inject("isPageVisible");
const showTooltipGlobal = inject("showPeerTooltip");
const hideTooltipGlobal = inject("hidePeerTooltip");
let timer = null;
// 记录上次流量数据和时间,用于前端计算网速
let lastTrafficMap = {};
let lastFetchTime = 0;
// 展开状态和网速历史
const expandedPeers = reactive({});
const speedHistoryMap = {};
const HISTORY_SIZE = 60;
const toggleExpand = (ip) => {
expandedPeers[ip] = !expandedPeers[ip];
if (expandedPeers[ip]) {
nextTick(() => drawChart(ip));
}
};
const drawChart = (ip) => {
const canvasId = 'chart-' + ip.replaceAll('.', '-');
const canvas = document.getElementById(canvasId);
if (!canvas) return;
const ctx = canvas.getContext('2d');
const history = speedHistoryMap[ip];
const txArr = history ? history.tx : [];
const rxArr = history ? history.rx : [];
// 高清适配
const dpr = window.devicePixelRatio || 1;
const rect = canvas.getBoundingClientRect();
canvas.width = rect.width * dpr;
canvas.height = rect.height * dpr;
ctx.scale(dpr, dpr);
const w = rect.width;
const h = rect.height;
const padTop = 8, padBottom = 4, padLeft = 0, padRight = 0;
const chartW = w - padLeft - padRight;
const chartH = h - padTop - padBottom;
// 背景
ctx.fillStyle = '#0c1222';
ctx.fillRect(0, 0, w, h);
// 计算Y轴最大值
const allValues = [...txArr, ...rxArr];
let maxVal = allValues.length > 0 ? Math.max(...allValues) : 0;
if (maxVal < 1024) maxVal = 1024; // 最小1KB
// 向上取整到合适的刻度
const niceMax = niceNumber(maxVal);
// 更新最大值标签
const maxLabel = document.getElementById('chart-max-' + ip.replaceAll('.', '-'));
if (maxLabel) maxLabel.textContent = '峰值: ' + formatSpeed(niceMax);
// 网格线
const gridLines = 4;
ctx.strokeStyle = 'rgba(71, 85, 105, 0.3)';
ctx.lineWidth = 1;
for (let i = 0; i <= gridLines; i++) {
const y = padTop + (chartH / gridLines) * i;
ctx.beginPath();
ctx.moveTo(padLeft, y);
ctx.lineTo(padLeft + chartW, y);
ctx.stroke();
}
// 垂直网格线
const vLines = 6;
for (let i = 0; i <= vLines; i++) {
const x = padLeft + (chartW / vLines) * i;
ctx.beginPath();
ctx.moveTo(x, padTop);
ctx.lineTo(x, padTop + chartH);
ctx.stroke();
}
// 绘制曲线
const drawLine = (data, strokeColor, fillColor) => {
if (data.length < 2) return;
const step = chartW / (HISTORY_SIZE - 1);
const offset = HISTORY_SIZE - data.length;
// 填充区域
ctx.beginPath();
ctx.moveTo(padLeft + offset * step, padTop + chartH);
for (let i = 0; i < data.length; i++) {
const x = padLeft + (offset + i) * step;
const y = padTop + chartH - (data[i] / niceMax) * chartH;
if (i === 0) ctx.lineTo(x, y);
else ctx.lineTo(x, y);
}
ctx.lineTo(padLeft + (offset + data.length - 1) * step, padTop + chartH);
ctx.closePath();
ctx.fillStyle = fillColor;
ctx.fill();
// 线条
ctx.beginPath();
for (let i = 0; i < data.length; i++) {
const x = padLeft + (offset + i) * step;
const y = padTop + chartH - (data[i] / niceMax) * chartH;
if (i === 0) ctx.moveTo(x, y);
else ctx.lineTo(x, y);
}
ctx.strokeStyle = strokeColor;
ctx.lineWidth = 1.5;
ctx.stroke();
};
drawLine(rxArr, '#60a5fa', 'rgba(96, 165, 250, 0.15)');
drawLine(txArr, '#4ade80', 'rgba(74, 222, 128, 0.15)');
};
// 将数值取整到适合的刻度
const niceNumber = (val) => {
const units = [
1024, // 1KB
10 * 1024, // 10KB
100 * 1024, // 100KB
1024 * 1024, // 1MB
10 * 1024 * 1024, // 10MB
100 * 1024 * 1024,// 100MB
1024 * 1024 * 1024,// 1GB
];
for (const u of units) {
if (val <= u) return u;
}
return Math.ceil(val / (1024 * 1024 * 1024)) * 1024 * 1024 * 1024;
};
const fetchPeers = async () => {
if (info.value.status !== "running") {
peers.value = [];
lastTrafficMap = {};
lastFetchTime = 0;
return;
}
try {
const res = await fetch(`${API_BASE}/api/peers`);
const json = await res.json();
if (json.code === 0) {
const now = Date.now();
const list = json.data || [];
const elapsed = lastFetchTime > 0 ? (now - lastFetchTime) / 1000 : 0;
const newTrafficMap = {};
for (const peer of list) {
if (peer.traffic) {
const key = peer.ip;
const prev = lastTrafficMap[key];
if (prev && elapsed > 0) {
const txDiff = Math.max(0, peer.traffic.tx_bytes - prev.tx_bytes);
const rxDiff = Math.max(0, peer.traffic.rx_bytes - prev.rx_bytes);
peer.traffic.tx_speed = Math.round(txDiff / elapsed);
peer.traffic.rx_speed = Math.round(rxDiff / elapsed);
} else {
peer.traffic.tx_speed = 0;
peer.traffic.rx_speed = 0;
}
newTrafficMap[key] = { tx_bytes: peer.traffic.tx_bytes, rx_bytes: peer.traffic.rx_bytes };
// 记录速度历史
if (!speedHistoryMap[key]) speedHistoryMap[key] = { tx: [], rx: [] };
speedHistoryMap[key].tx.push(peer.traffic.tx_speed);
speedHistoryMap[key].rx.push(peer.traffic.rx_speed);
if (speedHistoryMap[key].tx.length > HISTORY_SIZE) {
speedHistoryMap[key].tx.shift();
speedHistoryMap[key].rx.shift();
}
}
}
lastTrafficMap = newTrafficMap;
lastFetchTime = now;
peers.value = list;
// 重绘所有展开的图表
nextTick(() => {
for (const ip in expandedPeers) {
if (expandedPeers[ip]) drawChart(ip);
}
});
}
} catch (e) {
console.error(e);
}
};
onMounted(() => {
fetchPeers();
timer = setInterval(() => {
if (isPageVisible.value) fetchPeers();
}, 3000);
});
onUnmounted(() => {
if (timer) clearInterval(timer);
});
// 监听 status 变化,当变为 running 时立即获取数据
watch(() => info.value.status, (newStatus) => {
if (newStatus === "running") {
fetchPeers();
}
});
const getRouteModeClass = (route) => {
// route 包含 addr, protocol, metric, rtt
// 判断是否直连:metric === 1
const isDirect = route.metric === 1;
if (isDirect) {
return 'text-purple-400 font-medium'; // 直连 - 紫色
} else {
return 'text-blue-400'; // 客户端中继 - 蓝色
}
};
const getRouteModeText = (route) => {
const isDirect = route.metric === 1;
const isTcp = route.protocol.includes('Tcp');
if (isDirect) {
return isTcp ? '打洞TCP直连' : '打洞UDP直连';
} else {
return isTcp ? '客户端TCP中继' : '客户端UDP中继';
}
};
return {
peers,
expandedPeers,
toggleExpand,
formatTime,
formatBytes,
formatSpeed,
getRouteModeClass,
getRouteModeText,
showPeerTooltip: showTooltipGlobal,
hidePeerTooltip: hideTooltipGlobal,
};
},
};
const RoutesView = {
template: "#tpl-routes",
setup() {
const routes = ref([]);
const info = inject("info");
const isPageVisible = inject("isPageVisible");
let timer = null;
const fetchRoutes = async () => {
if (info.value.status !== "running") {
routes.value = [];
return;
}
try {
const res = await fetch(`${API_BASE}/api/routes`);
const json = await res.json();
if (json.code === 0) routes.value = json.data || [];
} catch (e) {
console.error(e);
}
};
onMounted(() => {
fetchRoutes();
timer = setInterval(() => {
if (isPageVisible.value) fetchRoutes();
}, 3000);
});
onUnmounted(() => {
if (timer) clearInterval(timer);
});
// 监听 status 变化,当变为 running 时立即获取数据
watch(() => info.value.status, (newStatus) => {
if (newStatus === "running") {
fetchRoutes();
}
});
return {routes};
},
};
// --- 路由配置 ---
const routes = [
{path: "/", redirect: "/general"},
{path: "/general", component: GeneralView},
{path: "/config", component: ConfigView},
{path: "/peers", component: PeersView},
{path: "/routes", component: RoutesView},
];
const router = createRouter({
history: createWebHashHistory(),
routes,
});
// --- 主应用 ---
createApp({
setup() {
const info = ref({
status: "stopped",
ip: "",
name: "",
device_id: "",
version: "",
server_info: [],
online_client_num: 0,
direct_client_num: 0,
offline_client_num: 0,
current_config_file: null,
current_config_name: null,
});
const configList = ref([]);
const loading = ref(false);
// 启动日志相关
const showStartLog = ref(false);
const startLogs = ref([]);
const startStatus = ref("stopped");
const logContainer = ref(null);
let statusInterval = null;
let infoTimer = null;
// Tooltip 相关
const tooltipState = ref({
show: false,
x: 0,
y: 0,
info: null,
});
let tooltipHideTimer = null;
const showPeerTooltip = (event, peer) => {
if (!peer.nat_info) return;
if (tooltipHideTimer) {
clearTimeout(tooltipHideTimer);
tooltipHideTimer = null;
}
const rect =
event.currentTarget.getBoundingClientRect();
tooltipState.value = {
show: true,
x: rect.left + rect.width / 2,
y: rect.bottom + 10,
info: peer.nat_info,
};
};
const hidePeerTooltip = () => {
tooltipHideTimer = setTimeout(() => {
tooltipState.value.show = false;
}, 100);
};
const onTooltipEnter = () => {
if (tooltipHideTimer) {
clearTimeout(tooltipHideTimer);
tooltipHideTimer = null;
}
};
const onTooltipLeave = () => {
tooltipState.value.show = false;
};
// 计算属性
const isServerConnected = computed(
() =>
info.value.server_info &&
info.value.server_info.some((s) => s.connected),
);
const serverStatusText = computed(() => {
if (
!info.value.server_info ||
!info.value.server_info.length
)
return "未配置服务器";
return `${info.value.server_info.filter((s) => s.connected).length} / ${info.value.server_info.length} 已连接`;
});
// 基础 API
const fetchInfo = async () => {
try {
const res = await fetch(`${API_BASE}/api/info`);
const json = await res.json();
if (json.code === 0) info.value = json.data;
} catch (e) {
console.error("Fetch info error", e);
}
};
const fetchConfigList = async () => {
try {
const res = await fetch(
`${API_BASE}/api/config/list`,
);
const json = await res.json();
if (json.code === 0) configList.value = json.data;
} catch (e) {
console.error("Fetch list error", e);
}
};
// 启动流程控制
const pollStartStatus = async () => {
try {
const res = await fetch(
`${API_BASE}/api/start/status`,
);
const json = await res.json();
if (json.code === 0) {
startLogs.value = json.data.logs || [];
startStatus.value = json.data.status;
nextTick(() => {
if (logContainer.value)
logContainer.value.scrollTop =
logContainer.value.scrollHeight;
});
if (startStatus.value === "running") {
stopPolling();
fetchInfo();
showStartLog.value = false;
} else if (
startStatus.value === "stopped" &&
startLogs.value.length > 0
) {
stopPolling();
fetchInfo();
}
}
} catch (e) {
console.error(e);
}
};
const stopPolling = () => {
if (statusInterval) {
clearInterval(statusInterval);
statusInterval = null;
}
};
const startPolling = () => {
stopPolling();
statusInterval = setInterval(pollStartStatus, 1000);
pollStartStatus();
};
const openStartingModal = () => {
startLogs.value = [];
startStatus.value = "starting";
showStartLog.value = true;
startPolling();
};
const toggleVnt = async (selectedFile) => {
if (loading.value) return;
// 停止逻辑
if (
info.value.status === "running" ||
info.value.status === "starting"
) {
loading.value = true;
await fetch(`${API_BASE}/api/stop`, {
method: "POST",
});
loading.value = false;
stopPolling();
showStartLog.value = false;
fetchInfo();
return;
}
// 启动逻辑
if (!selectedFile) {
alert("请先选择一个配置");
return;
}
loading.value = true;
try {
const res = await fetch(`${API_BASE}/api/start`, {
method: "POST",
headers: {"Content-Type": "application/json"},
body: JSON.stringify({
file_name: selectedFile,
}),
});
const json = await res.json();
loading.value = false;
if (json.code !== 0) {
alert("启动失败: " + json.msg);
return;
}
openStartingModal();
} catch (e) {
loading.value = false;
alert("网络请求失败: " + e.message);
}
};
const cancelStart = async () => {
stopPolling();
try {
await fetch(`${API_BASE}/api/stop`, {
method: "POST",
});
startLogs.value.push("启动已手动取消");
} catch (e) {
}
startStatus.value = "stopped";
fetchInfo();
};
const restartVnt = async (selectedFile) => {
if (loading.value) return;
if (!selectedFile) {
alert("请先选择一个配置");
return;
}
loading.value = true;
try {
const res = await fetch(`${API_BASE}/api/restart`, {
method: "POST",
headers: {"Content-Type": "application/json"},
body: JSON.stringify({
file_name: selectedFile,
}),
});
const json = await res.json();
loading.value = false;
if (json.code !== 0) {
alert("重启失败: " + json.msg);
return;
}
openStartingModal();
} catch (e) {
loading.value = false;
alert("网络请求失败: " + e.message);
}
};
// 导航辅助
const navClass = (isActive) =>
isActive
? "bg-blue-600 text-white shadow-lg shadow-blue-500/30"
: "text-slate-400 hover:bg-slate-800 hover:text-white";
// 页面可见性处理
const isPageVisible = ref(!document.hidden);
const visibilityHandler = () => {
isPageVisible.value = !document.hidden;
};
// 依赖注入
provide("info", info);
provide("isPageVisible", isPageVisible);
provide("configList", configList);
provide("fetchConfigList", fetchConfigList);
provide("toggleVnt", toggleVnt);
provide("restartVnt", restartVnt);
provide("loading", loading);
provide("showPeerTooltip", showPeerTooltip);
provide("hidePeerTooltip", hidePeerTooltip);
onMounted(async () => {
document.addEventListener(
"visibilitychange",
visibilityHandler,
);
await fetchInfo();
fetchConfigList();
if (info.value.status === "starting")
openStartingModal();
// 全局轮询 info (状态/IP等)
infoTimer = setInterval(() => {
if (info.value.status !== "running") return;
if (isPageVisible.value) fetchInfo();
}, 3000);
});
onUnmounted(() => {
document.removeEventListener(
"visibilitychange",
visibilityHandler,
);
stopPolling();
if (infoTimer) clearInterval(infoTimer);
});
return {
info,
isServerConnected,
serverStatusText,
navClass,
showStartLog,
startStatus,
startLogs,
logContainer,
cancelStart,
tooltipState,
onTooltipEnter,
onTooltipLeave,
};
},
})
.use(router)
.mount("#app");
</script>
</body>
</html>
Binary file not shown.
Binary file not shown.
Binary file not shown.