Compare commits

...

244 Commits

Author SHA1 Message Date
刘祥超
20bee16d28 版本号修改为1.0.4 2023-04-24 10:17:55 +08:00
刘祥超
b1cd971a21 缓存索引数据库加载失败时自动尝试重建数据库文件 2023-04-21 17:38:31 +08:00
刘祥超
d976a39711 环路(127.0.0.1)请求也统计带宽 2023-04-21 15:08:44 +08:00
刘祥超
accd0236ea 优化统计相关代码 2023-04-19 22:15:57 +08:00
刘祥超
fc401a1426 限制单次处理的服务城市数量 2023-04-19 22:06:39 +08:00
刘祥超
7e8c09a684 优化nftables集合元素过期时间判断 2023-04-19 13:20:31 +08:00
刘祥超
37ddff86f1 优化IP名单同步速度 2023-04-19 12:01:02 +08:00
刘祥超
4dc25fb71e 优化请求统计 2023-04-18 15:07:22 +08:00
刘祥超
42883fbe22 优化可用内存检查 2023-04-11 18:51:56 +08:00
刘祥超
a88d9a07be 版本号改为1.1.0 2023-04-11 18:51:47 +08:00
刘祥超
544f1e482a 版本号修改为1.0.1 2023-04-10 09:18:45 +08:00
刘祥超
f53d4c8951 更好地从访问日志中删除非UTF-8字符内容 2023-04-09 17:50:54 +08:00
刘祥超
70d8aa5b33 版本号改为1.0.0 2023-04-09 17:17:49 +08:00
刘祥超
1aa4be9000 优化代码 2023-04-08 13:49:41 +08:00
刘祥超
a7c7c73f70 优化代码:使用fasttime取代以往的utils.UnixTime 2023-04-08 12:47:04 +08:00
刘祥超
0b441021d8 URL跳转时默认对搜索引擎访问使用301,以提升SEO效果 2023-04-07 19:19:53 +08:00
刘祥超
7db0c8cf62 优化HTTP2、HTTP跳转 2023-04-07 15:09:06 +08:00
刘祥超
6da9cb6dcf 从文件恢复带宽数据时跳过非今天的数据 2023-04-07 11:32:40 +08:00
刘祥超
0af580eb26 优化统计性能 2023-04-07 11:23:37 +08:00
刘祥超
52085bdc1c 增加测试用例 2023-04-07 10:52:09 +08:00
刘祥超
72f1eea721 提供批量更新服务配置API(阶段性提交) 2023-04-06 20:50:34 +08:00
刘祥超
6d52b022b2 访问不存在的域名加入到黑名单时,只对当前节点有效 2023-04-05 19:42:14 +08:00
刘祥超
ea41c9b0b3 健康检查时不记录统计 2023-04-05 16:44:26 +08:00
刘祥超
ed6127c2bb 更好地处理访问日志中的非UTF-8字节 2023-04-05 15:54:07 +08:00
刘祥超
b6d95a84fc 进程退出时停止上传带宽数据 2023-04-05 11:34:15 +08:00
刘祥超
c71e68bdea 优化nftables查找程序 2023-04-05 09:33:03 +08:00
刘祥超
c44583f249 优化IP黑名单检测 2023-04-05 09:25:33 +08:00
刘祥超
c53773c2db 可以只更新UAM策略变化 2023-04-03 16:12:14 +08:00
刘祥超
793994a3fe 在节点启动时自动调整系统内核参数 2023-04-02 21:30:03 +08:00
刘祥超
4c3deb1156 将nftables黑名单扩展到5个 2023-04-02 20:32:36 +08:00
刘祥超
24ca5a5ace 优化nftables可执行文件查找方法 2023-04-02 18:37:24 +08:00
刘祥超
8bbbf57827 重启服务前先将带宽统计数据缓存,以便于在重启后继续使用 2023-04-02 17:24:55 +08:00
刘祥超
888df02d0c 优化IP名单上传程序 2023-04-01 20:51:49 +08:00
刘祥超
8988765cef 修复在高并发下修改服务配置可能导致服务崩溃(panic)的问题 2023-04-01 18:41:50 +08:00
刘祥超
f675b88761 nftables:自动升级以前的drop规则为reject规则 2023-04-01 17:09:53 +08:00
刘祥超
9bd4975478 创建nftables规则时,使用REJECT代替DROP 2023-04-01 15:55:24 +08:00
刘祥超
95abb7bfae 集群服务设置增加“支持低版本HTTP”选项/分片内容提示不支持低版本HTTP协议 2023-04-01 09:57:24 +08:00
刘祥超
d9fa3dcc3b 优化WAF黑名单处理 2023-03-31 21:37:15 +08:00
刘祥超
964524816f 支持商业版本状态查询 2023-03-31 14:06:01 +08:00
刘祥超
d124c9be18 增加注释 2023-03-29 20:09:19 +08:00
刘祥超
1a05402076 增加固定Map定义 2023-03-23 10:52:52 +08:00
刘祥超
c4b1790102 上传流量统计数据时同时上传用户ID 2023-03-22 19:51:36 +08:00
刘祥超
613acbff95 限制单个服务每次上传的域名统计数不超过20个 2023-03-22 19:05:10 +08:00
刘祥超
e6ab98ad11 上传带宽信息时同时缓存流量、统计流量、请求数等参数,为将来的流量和带宽合并做准备 2023-03-22 18:00:35 +08:00
刘祥超
1121869f14 增加网站服务加载和删除调试日志 2023-03-19 11:05:03 +08:00
刘祥超
91efe57e1b 不提示单个端口Reload信息,防止不重要的日志过多 2023-03-19 11:00:27 +08:00
刘祥超
95f2573263 增加RPC消息最大尺寸到512MB 2023-03-18 22:43:48 +08:00
刘祥超
09aa85f51c 节点组合配置时服务间可以共用证书数据 2023-03-18 22:18:48 +08:00
刘祥超
c6279a1076 版本号更改为0.6.5 2023-03-17 15:54:44 +08:00
刘祥超
47ccb64cfb 优化日志提示 2023-03-16 17:24:55 +08:00
刘祥超
5c218567e1 在GET302和CAPTCHA验证中不记录特殊URL的访问日志 2023-03-16 10:38:40 +08:00
刘祥超
c161d84fdf 版本号变更为0.6.4.2 2023-03-16 08:59:46 +08:00
刘祥超
495b553285 修复存储空间统计可能为负值的问题 2023-03-16 08:59:35 +08:00
刘祥超
21b770ba8b 修复运行测试用例时init()无法起作用的Bug 2023-03-13 21:49:41 +08:00
刘祥超
e9f94e0767 改进README.md 2023-03-13 08:56:18 +08:00
刘祥超
644ada1da9 优化代码 2023-03-12 20:32:15 +08:00
刘祥超
0c40250849 优化代码 2023-03-12 16:36:59 +08:00
刘祥超
1d1134a86d 优化代码 2023-03-12 16:18:16 +08:00
刘祥超
28e7664eb7 修复源站重试时可能产生的file is writing错误 2023-03-12 16:09:06 +08:00
刘祥超
50f3ad641c 优化统计相关程序 2023-03-12 12:19:25 +08:00
刘祥超
cc7cf5f8c5 限制统计系统和浏览器的最大长度,减少随机UserAgent攻击影响 2023-03-11 11:58:05 +08:00
刘祥超
339f0f6e94 上传统计数据时增加单次上传数量限制 2023-03-11 11:21:03 +08:00
刘祥超
f558e43342 根据系统内存调整访问日志队列长度 2023-03-10 22:31:40 +08:00
刘祥超
e374e5c90c 优化命令识别 2023-03-10 22:05:40 +08:00
刘祥超
563b775e49 优化命令执行速度 2023-03-10 22:01:39 +08:00
刘祥超
de9e1a4515 执行一般命令时不运行init()中内容 2023-03-10 15:14:14 +08:00
刘祥超
f64b36f17a WAF cc防护增加“检查请求来源指纹”选项 2023-03-10 10:41:16 +08:00
刘祥超
f0e8c82d31 增加CC防护(开源用户需要自己实现) 2023-03-09 15:15:22 +08:00
刘祥超
5770d43230 WAF cc2尝试使用指纹统计方法 2023-03-08 16:59:44 +08:00
刘祥超
d4944c236f 修复几处测试用例 2023-03-08 10:13:41 +08:00
刘祥超
33c761a187 修复本地数据库无法异步提交事务的Bug 2023-03-07 16:48:03 +08:00
刘祥超
d7e6da8d2c 对本地数据库文件进行加锁 2023-03-07 16:22:32 +08:00
刘祥超
44d1a2415c 集群服务设置增加“记录找不到网站日志”选项 2023-03-07 10:30:55 +08:00
刘祥超
c98ff50f06 暂时取消GET302和POST307的迟滞 2023-03-07 08:51:39 +08:00
刘祥超
8835fcb09e GET302/POST307增加访问迟滞 2023-03-06 16:28:54 +08:00
刘祥超
77c56e58c0 GET302/POST307兼容safari浏览器 2023-03-06 16:27:06 +08:00
刘祥超
72c65ca4ee 修复GET302和POST307关闭连接后无法响应的问题 2023-03-06 16:10:58 +08:00
刘祥超
ab019b0bdc 修复在连接读写优化模式下fastcgi无法正常工作的Bug 2023-03-06 10:33:54 +08:00
刘祥超
9709e45ad2 修改软内存限制设置错误 2023-03-05 21:19:10 +08:00
刘祥超
be1f80003c 增加软内存最大值限制 2023-03-05 12:34:46 +08:00
刘祥超
252fcca383 增加测试用例 2023-03-03 14:28:58 +08:00
刘祥超
04ae8fa4a0 系统内存不足时,尝试自动回收内存 2023-03-02 10:54:25 +08:00
刘祥超
c95bd7776a WAF拦截动作可以设置最大封禁时间,从而实现封禁时间随机 2023-03-01 19:00:08 +08:00
刘祥超
8219167d05 WAF支持忽略全局WAF规则 2023-03-01 16:46:43 +08:00
刘祥超
e0a6881343 上传带宽数据时同时上传流量数据 2023-02-27 10:48:16 +08:00
刘祥超
6e985d7f06 修复GET302和POST307无限循环的问题 2023-02-24 17:02:59 +08:00
刘祥超
66719b05dd 修复WAF验证码不能输入超出6位数字的Bug 2023-02-16 14:44:56 +08:00
刘祥超
7197583fea 增加变量${requestPathLowerExtension} 2023-02-10 10:43:30 +08:00
刘祥超
ce29024eef 写入Agent IP时,不提示id重复错误 2023-02-01 10:18:08 +08:00
刘祥超
e1ac67f7fa 版本更改为0.6.4 2023-02-01 10:07:30 +08:00
刘祥超
01812144dd 优化带宽统计 2023-01-12 19:09:57 +08:00
刘祥超
1c34e49629 优化代码 2023-01-12 19:02:38 +08:00
刘祥超
f233fbfb25 版本号修改为0.6.3 2023-01-11 15:44:53 +08:00
刘祥超
5387115e4a 优化代码 2023-01-11 15:24:48 +08:00
刘祥超
d82c03db23 修复在HTTPS下无法连接Websocket的问题 2023-01-10 21:20:27 +08:00
刘祥超
230c5c3766 版本号修改为0.6.2 2023-01-10 21:18:53 +08:00
刘祥超
927425149e 优化代码 2023-01-10 09:47:56 +08:00
刘祥超
5ce1aab92c 修复域名跳转时没有携带参数的Bug 2023-01-09 20:06:09 +08:00
刘祥超
195742bb26 修复读超时时间(ReadDeadline)导致WAFGET302、POST307延时关闭连接的问题 2023-01-09 15:56:59 +08:00
刘祥超
006cc2912d 版本修改为0.6.1 2023-01-09 15:49:16 +08:00
刘祥超
2d4ba90c3b 改进在自动读超时模式下的Websocket连接 2023-01-09 12:36:33 +08:00
刘祥超
a2e6aaaa18 WAF增加“在IP列表内”操作符/优化部分操作符代号 2023-01-08 10:15:46 +08:00
刘祥超
8e68da7725 集群服务设置增加自动读超时选项 2023-01-07 20:04:05 +08:00
刘祥超
7abb84c880 优化网络连接关闭速度 2023-01-07 10:03:32 +08:00
刘祥超
a17878f5b2 WAF增加包含任一字符串、包含所有字符串操作符 2023-01-06 20:07:15 +08:00
刘祥超
8a8881ac47 IP范围支持多行 2023-01-06 19:14:09 +08:00
刘祥超
c567404b7a 优化连接相关代码 2023-01-05 11:13:35 +08:00
刘祥超
b220b0f48e 优化读取HTTP请求Header和握手超时时间 2023-01-05 00:40:49 +08:00
刘祥超
9609c90d75 边缘节点增加数据读超时,以改进客户端上传数据过慢的问题 2023-01-04 20:43:10 +08:00
刘祥超
2c3c32af5b 优化代码 2023-01-02 10:44:10 +08:00
刘祥超
b4a4b2e9b1 集群服务设置中增加性能设置 2023-01-01 19:27:38 +08:00
刘祥超
c42ff1e1e9 实现UA名单功能 2022-12-30 20:49:43 +08:00
刘祥超
9fed1141c2 默认情况下内容压缩不支持Partial Content 2022-12-30 11:44:07 +08:00
刘祥超
e87f031293 增加CORS自适应跨域 2022-12-29 17:16:42 +08:00
刘祥超
c4bac7f43c 优化代码 2022-12-27 18:58:29 +08:00
刘祥超
47818f972e 自动转换访问域名中的大写字母 2022-12-25 15:23:56 +08:00
刘祥超
218a0300c5 修复测试用例 2022-12-23 18:53:49 +08:00
刘祥超
63f6c4177f 修复测试用例 2022-12-23 18:17:32 +08:00
刘祥超
1830c22a31 增加自动Agent识别 2022-12-22 11:38:59 +08:00
刘祥超
18611e8a7c 写数据超时时断开同客户端连接 2022-12-21 16:11:55 +08:00
刘祥超
c45f7adf04 优化连接相关代码 2022-12-21 15:59:07 +08:00
刘祥超
1a200918a8 不支持CONNECT方法 2022-12-19 16:27:58 +08:00
刘祥超
b942bb776e 国家/地区封禁、省份封禁时支持IP变量 2022-12-18 16:04:12 +08:00
刘祥超
5cf84efccd 优化内容为空的缓存 2022-12-14 15:26:18 +08:00
刘祥超
ebb6ebd10c 修复WAF中反斜杠符号(\)有可能解析错误的Bug 2022-12-14 12:27:07 +08:00
刘祥超
42d0d63cf4 优化代码 2022-12-13 18:08:50 +08:00
刘祥超
96f8f7e925 增加edge-node ip.close IP命令 2022-12-12 19:23:58 +08:00
刘祥超
e7e7214d58 调整慢连接超时算法 2022-12-12 10:04:36 +08:00
刘祥超
ade979a725 向客户端写入数据超时时立即关闭连接 2022-12-10 19:51:05 +08:00
刘祥超
60a8de13e7 TCP单次向客户端写入数据时超过30秒即认为超时 2022-12-10 18:22:00 +08:00
刘祥超
9fa24bed0a 修复WAF记录IP动作时无法不超时的Bug 2022-12-06 11:01:34 +08:00
刘祥超
87bc1a7e03 优化OpenFileCache 2022-12-05 11:16:04 +08:00
刘祥超
1a05f56149 优化缓存相关代码 2022-12-05 10:46:44 +08:00
刘祥超
f88db576e1 优化代码 2022-12-05 09:57:01 +08:00
刘祥超
dc3f26ea1a 减少WAF预读尺寸 2022-12-02 21:08:03 +08:00
刘祥超
6fc30144f7 在edge-node conns命令中显示连接时长 2022-12-02 17:03:16 +08:00
刘祥超
25b0b98bd4 增加默认的源站连接数 2022-12-02 10:39:07 +08:00
刘祥超
27b5817d5e 优化请求限制逻辑,连接关闭时自动终止内容发送 2022-11-29 19:14:46 +08:00
刘祥超
dcb61dfd33 版本号更改为0.6.0 2022-11-29 15:42:21 +08:00
刘祥超
bbcfdbbf5e 优化代码 2022-11-29 15:33:12 +08:00
刘祥超
b2a1bef08f 修复服务WAF配置无法更新的Bug 2022-11-28 18:13:08 +08:00
刘祥超
2b18b5c2ca 修改版本号为0.5.9 2022-11-28 18:08:19 +08:00
刘祥超
6ff030dbd8 编译时加入configs/cluster.template.yaml文件 2022-11-27 14:52:48 +08:00
刘祥超
0ddeef6986 支持使用域名中含有通配符清除缓存数据 2022-11-26 11:05:46 +08:00
刘祥超
976bd3600b 优化OpenFileCache功能 2022-11-25 14:52:04 +08:00
刘祥超
a64047a934 优化配置重载程序 2022-11-25 10:50:57 +08:00
刘祥超
e82f207935 统计API调用时低于一半的采样率返回总统计 2022-11-23 20:23:46 +08:00
刘祥超
61b5316a1f 优化代码 2022-11-23 20:13:34 +08:00
刘祥超
82329aa8b0 修复一处编译错误 2022-11-22 18:40:03 +08:00
刘祥超
7dabd9c19c 在监控系统运行时上报API连接状况 2022-11-22 11:23:39 +08:00
刘祥超
9437acd18c 优化代码 2022-11-21 21:08:47 +08:00
刘祥超
9da7a34edf 节点可以单独设置所使用的API节点地址 2022-11-21 19:55:28 +08:00
刘祥超
b6a5491dcc 优化Partial Content兼容性 2022-11-20 18:07:46 +08:00
刘祥超
bcee658567 优化Partial Content配置编码速度 2022-11-19 23:11:05 +08:00
刘祥超
afc8f7b703 优化Partial Content缓存 2022-11-19 21:20:53 +08:00
刘祥超
7a4b89d2fb 缓存Header中忽略Set-Cookie 2022-11-19 17:35:23 +08:00
刘祥超
c6299a2fb0 减少文件缓存写入次数 2022-11-19 17:23:45 +08:00
刘祥超
8b5d74af9b 进一步提升文件缓存写入速度 2022-11-19 15:55:05 +08:00
刘祥超
a194360a56 Update go.mod 2022-11-18 17:39:49 +08:00
刘祥超
b12f7f69ba 优化代码 2022-11-17 20:28:55 +08:00
刘祥超
06ec4d3fba 优化代码 2022-11-17 10:38:20 +08:00
刘祥超
c209ab912f 优化代码 2022-11-17 10:35:43 +08:00
刘祥超
32720d772d 优化代码 2022-11-17 10:32:26 +08:00
刘祥超
a89c02fd10 请求变量增加${cname},WAF checkpoint增加cname和isCNAME 2022-11-16 15:01:10 +08:00
刘祥超
37ef86b92f 接收HTTP请求时去除域名后面的点符号 2022-11-16 11:25:11 +08:00
刘祥超
4c19c37f49 写入缓存时减少对缓存目录的检查频率 2022-11-15 22:25:49 +08:00
刘祥超
1bb818b5b0 边缘节点支持设置多个缓存目录 2022-11-15 20:42:25 +08:00
刘祥超
825e46458f 优化代码 2022-11-15 10:06:57 +08:00
刘祥超
a42737bd28 缩短节点运行日志队列长度 2022-11-14 16:42:50 +08:00
刘祥超
5f76be2cfd 优化代码 2022-11-13 10:32:12 +08:00
刘祥超
dbddf8a91a 优化代码 2022-11-08 21:37:20 +08:00
刘祥超
6c457f41f6 优化代码 2022-11-08 20:58:17 +08:00
刘祥超
e4b2a650f0 优化代码 2022-11-08 20:19:51 +08:00
刘祥超
913ba95801 优化缓存相关代码 2022-11-08 11:03:37 +08:00
刘祥超
a9f8e39703 修复节点设置的“缓存磁盘容量”不起作用的问题 2022-11-07 21:32:20 +08:00
刘祥超
534f013f59 使用版本号来读取节点任务,提升任务同步稳定性 2022-11-06 12:07:26 +08:00
刘祥超
258380f75c 修复无法回报任务执行失败的问题 2022-11-05 14:56:57 +08:00
刘祥超
8c0e51ec46 域名跳转增加是否忽略端口选项 2022-11-05 14:30:29 +08:00
刘祥超
4c37c7ab84 时钟同步增加是否检查chrony选项 2022-11-03 14:59:26 +08:00
刘祥超
f005da1d5f 防盗链提示增加缓存时间,以提升性能 2022-11-02 15:24:30 +08:00
刘祥超
e99acc4694 版本修改为0.5.8 2022-11-02 15:11:55 +08:00
刘祥超
408357dfcf 优化DDoS防护相关错误提示信息 2022-11-01 17:37:40 +08:00
刘祥超
0109a27c06 上传访问日志发生网络错误时不提交 2022-11-01 14:55:06 +08:00
刘祥超
e6e2dccc42 版本号修改为0.5.7 2022-10-31 19:14:03 +08:00
刘祥超
09dcf0d712 集群全局服务配置中增加多个访问日志相关选项 2022-10-26 17:51:16 +08:00
刘祥超
60aebd9306 URL跳转中增加域名跳转、端口跳转 2022-10-26 16:14:37 +08:00
刘祥超
04191d04d3 节点设置中增加“通过IP名单”选项 2022-10-26 10:42:16 +08:00
刘祥超
b80a5c525f 节点缓存目录所在磁盘空间不足时(<5G),暂停缓存写入,同时启动LFU清理 2022-10-25 15:14:28 +08:00
刘祥超
265c1e5312 WAF参数定义增加优先级,可以让“轻”任务优先执行 2022-10-24 17:57:07 +08:00
刘祥超
2723f705b6 修复在iptables中加入ipv6的错误 2022-10-24 16:37:54 +08:00
刘祥超
b4cddd6341 集群服务设置--访问日志中可以设置是否只记录通用Header 2022-10-24 14:39:18 +08:00
刘祥超
5636a81d48 防盗链功能增加禁止的来源域名 2022-10-24 10:21:23 +08:00
刘祥超
d8059960de 文件缓存索引表取消UNIQUE索引,尽可能避免 sqlite malformed 错误 2022-10-23 20:45:41 +08:00
刘祥超
17af4064af 带宽和流量提交失败时,将在一定时间内重试 2022-10-23 19:41:21 +08:00
刘祥超
15f37d2c93 优化用户服务整体启用和禁用 2022-10-23 16:21:11 +08:00
刘祥超
6dc3aa8cb7 单请求写入时间从1个小时增加到2个小时 2022-10-23 09:52:50 +08:00
刘祥超
900cccf2f1 修复源站Websocket源站读取失败导致的异常错误 2022-10-18 19:43:53 +08:00
刘祥超
1fec88dfc6 优化代码 2022-10-14 15:00:05 +08:00
刘祥超
7da9363336 上传带宽信息时附带区域ID信息 2022-10-11 18:57:35 +08:00
刘祥超
d82e633bba 时钟同步程序每天只提示一次警告信息 2022-10-11 11:31:00 +08:00
刘祥超
b363bbaafd 版本修改为0.5.6 2022-10-01 08:50:12 +08:00
刘祥超
92a20e3c9a 修复Websocket无法正常交互的问题 2022-09-30 16:34:21 +08:00
刘祥超
5742dfb263 修复Websocket响应可能被缓存的问题 2022-09-30 14:55:42 +08:00
刘祥超
0ae63511d5 版本调整为v0.5.5 2022-09-28 18:57:27 +08:00
刘祥超
aa60092c20 修复开启WAF后,自动记录请求Body的Bug 2022-09-28 16:46:05 +08:00
刘祥超
54fc265d24 systemd服务增加BEGIN INIT INFO 2022-09-28 08:17:25 +08:00
刘祥超
a5ac900784 版本修改为0.5.4 2022-09-27 08:05:20 +08:00
刘祥超
4053f1da32 程序延时100ms退出 2022-09-26 16:27:51 +08:00
刘祥超
0374ccd8a8 版本号改为0.5.3.2 2022-09-26 16:27:28 +08:00
刘祥超
1d46c446cf 程序退出时关闭sqlite数据库 2022-09-26 16:14:24 +08:00
刘祥超
54b66805f9 将版本修改为0.5.4 2022-09-26 15:17:06 +08:00
刘祥超
f7afcbde92 版本修改为0.5.3.1 2022-09-26 13:02:46 +08:00
刘祥超
8bec1cf68e 优化时钟同步相关代码 2022-09-25 14:26:46 +08:00
刘祥超
2cd1bb7f95 时钟同步错误提示改成警告 2022-09-25 09:40:28 +08:00
刘祥超
19e6329a2b 部分提示支持繁体中文 2022-09-24 20:02:29 +08:00
刘祥超
fce2879567 Websocket支持自定义响应Header 2022-09-23 14:21:53 +08:00
刘祥超
0973765919 增加防盗链拦截时默认提示文字 2022-09-22 17:52:57 +08:00
刘祥超
827679721e 增加防盗链功能 2022-09-22 16:33:53 +08:00
刘祥超
735279bc7a 删除的IP名单不再写入到本地数据库 2022-09-21 17:06:25 +08:00
刘祥超
3eb2ed9897 IP名单数据库增加定时清理 2022-09-21 16:49:48 +08:00
刘祥超
3a913d98c7 优化服务相关错误和警告日志 2022-09-20 14:58:55 +08:00
刘祥超
9bfcd79e36 缩小日志文件尺寸 2022-09-18 16:42:11 +08:00
刘祥超
a81d610302 优化代码 2022-09-18 16:18:31 +08:00
刘祥超
64b1753c4d 自动尝试安装nftables 2022-09-17 21:08:56 +08:00
刘祥超
afcb5c2957 集群设置中增加服务设置/可以设置不记录服务错误日志 2022-09-16 18:41:47 +08:00
刘祥超
7d0b9208a3 访问日志中增加源站状态码 2022-09-16 10:07:40 +08:00
刘祥超
c0f0ec43bb Websocket也支持失败自动重试 2022-09-16 09:37:49 +08:00
刘祥超
30bd66958c 优化NTP时钟自动同步 2022-09-15 15:59:29 +08:00
刘祥超
fafac1a038 优化系统服务管理 2022-09-15 15:01:19 +08:00
刘祥超
f979f9503e 优化服务安装相关代码 2022-09-15 11:45:29 +08:00
刘祥超
b233c3cc7a 优化命令执行相关代码 2022-09-15 11:14:33 +08:00
刘祥超
597ac936f7 检查synflood时忽略IP白名单和局域网连接 2022-09-14 18:52:26 +08:00
刘祥超
a590254eb3 修复有多个网络出口时,可能无法正确转发UDP消息的问题 2022-09-14 17:18:00 +08:00
刘祥超
0498bcf30c 调整GRPC参数 2022-09-12 21:59:52 +08:00
刘祥超
59f9b5c724 优化netdns设置 2022-09-12 17:43:48 +08:00
刘祥超
80729935b6 优化代码 2022-09-11 19:03:53 +08:00
刘祥超
4ca57fb99c 重启时保留-shm,-wal文件 2022-09-09 09:34:00 +08:00
刘祥超
9b35902ad4 访问日志因尺寸过大无法提交到API节点时,自动去除requestBody后再次尝试 2022-09-07 17:34:57 +08:00
刘祥超
3b8bd09190 优化代码 2022-09-07 14:54:36 +08:00
刘祥超
71a5bc0652 可以使用EdgeRecover环境变量指示恢复数据库 2022-09-07 14:44:36 +08:00
刘祥超
ac6a8c4e85 启动时尝试自动修复损坏的缓存索引数据库 2022-09-07 13:55:36 +08:00
刘祥超
f58a808c3a 改进缓存LFU算法 2022-09-07 11:34:26 +08:00
刘祥超
51037be772 将版本修改为0.5.3 2022-09-05 11:04:46 +08:00
刘祥超
443ff9aff7 改进Captcha认证计数 2022-09-05 10:59:02 +08:00
256 changed files with 8944 additions and 2261 deletions

View File

@@ -50,6 +50,7 @@ function build() {
fi fi
cp "$ROOT"/configs/api.template.yaml "$DIST"/configs cp "$ROOT"/configs/api.template.yaml "$DIST"/configs
cp "$ROOT"/configs/cluster.template.yaml "$DIST"/configs
cp -R "$ROOT"/www "$DIST"/ cp -R "$ROOT"/www "$DIST"/
cp -R "$ROOT"/pages "$DIST"/ cp -R "$ROOT"/pages "$DIST"/

View File

@@ -1 +1,2 @@
* `global.yaml` - 全局配置 * `api.template.yaml` - API相关配置模板
* `cluster.template.yaml` - 通过集群自动接入节点模板

View File

@@ -25,7 +25,7 @@ func main() {
Product(teaconst.ProductName). Product(teaconst.ProductName).
Usage(teaconst.ProcessName + " [-v|start|stop|restart|status|quit|test|reload|service|daemon|pprof|accesslog]"). Usage(teaconst.ProcessName + " [-v|start|stop|restart|status|quit|test|reload|service|daemon|pprof|accesslog]").
Usage(teaconst.ProcessName + " [trackers|goman|conns|gc]"). Usage(teaconst.ProcessName + " [trackers|goman|conns|gc]").
Usage(teaconst.ProcessName + " [ip.drop|ip.reject|ip.remove] IP") Usage(teaconst.ProcessName + " [ip.drop|ip.reject|ip.remove|ip.close] IP")
app.On("test", func() { app.On("test", func() {
err := nodes.NewNode().Test() err := nodes.NewNode().Test()
@@ -241,6 +241,38 @@ func main() {
} }
} }
}) })
app.On("ip.close", func() {
var args = os.Args[2:]
if len(args) == 0 {
fmt.Println("Usage: edge-node ip.close IP")
return
}
var ip = args[0]
if len(net.ParseIP(ip)) == 0 {
fmt.Println("IP '" + ip + "' is invalid")
return
}
fmt.Println("close ip '" + ip)
var sock = gosock.NewTmpSock(teaconst.ProcessName)
reply, err := sock.Send(&gosock.Command{
Code: "closeIP",
Params: map[string]any{
"ip": ip,
},
})
if err != nil {
fmt.Println("[ERROR]" + err.Error())
} else {
var errString = maps.NewMap(reply.Params).GetString("error")
if len(errString) > 0 {
fmt.Println("[ERROR]" + errString)
} else {
fmt.Println("ok")
}
}
})
app.On("ip.remove", func() { app.On("ip.remove", func() {
var args = os.Args[2:] var args = os.Args[2:]
if len(args) == 0 { if len(args) == 0 {

View File

@@ -41,7 +41,7 @@ func (this *LogWriter) Init() {
this.c = make(chan string, 1024) this.c = make(chan string, 1024)
// 异步写入文件 // 异步写入文件
var maxFileSize = 2 * sizes.G // 文件最大尺寸,超出此尺寸则清空 var maxFileSize = 128 * sizes.M // 文件最大尺寸,超出此尺寸则清空
if fp != nil { if fp != nil {
goman.New(func() { goman.New(func() {
var totalSize int64 = 0 var totalSize int64 = 0

11
internal/apps/main.go Normal file
View File

@@ -0,0 +1,11 @@
// Copyright 2023 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package apps
import teaconst "github.com/TeaOSLab/EdgeNode/internal/const"
func RunMain(f func()) {
if teaconst.IsMain {
f()
}
}

View File

@@ -3,6 +3,7 @@
package caches package caches
const ( const (
SuffixAll = "@GOEDGE_" // 通用后缀
SuffixWebP = "@GOEDGE_WEBP" // WebP后缀 SuffixWebP = "@GOEDGE_WEBP" // WebP后缀
SuffixCompression = "@GOEDGE_" // 压缩后缀 SuffixCompression + Encoding SuffixCompression = "@GOEDGE_" // 压缩后缀 SuffixCompression + Encoding
SuffixMethod = "@GOEDGE_" // 请求方法后缀 SuffixMethod + RequestMethod SuffixMethod = "@GOEDGE_" // 请求方法后缀 SuffixMethod + RequestMethod

View File

@@ -0,0 +1,11 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package caches
import "github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/shared"
type FileDir struct {
Path string
Capacity *shared.SizeCapacity
IsFull bool
}

View File

@@ -1,7 +1,8 @@
package caches package caches
import ( import (
"github.com/TeaOSLab/EdgeNode/internal/utils" "github.com/TeaOSLab/EdgeNode/internal/utils/fasttime"
"strings"
"time" "time"
) )
@@ -35,7 +36,7 @@ type Item struct {
} }
func (this *Item) IsExpired() bool { func (this *Item) IsExpired() bool {
return this.ExpiredAt < utils.UnixTime() return this.ExpiredAt < fasttime.Now().Unix()
} }
func (this *Item) TotalSize() int64 { func (this *Item) TotalSize() int64 {
@@ -59,3 +60,17 @@ func (this *Item) IncreaseHit(week int32) {
this.Week = week this.Week = week
} }
} }
func (this *Item) RequestURI() string {
var schemeIndex = strings.Index(this.Key, "://")
if schemeIndex <= 0 {
return ""
}
var firstSlashIndex = strings.Index(this.Key[schemeIndex+3:], "/")
if firstSlashIndex <= 0 {
return ""
}
return this.Key[schemeIndex+3+firstSlashIndex:]
}

View File

@@ -81,3 +81,14 @@ func TestItems_Memory2(t *testing.T) {
t.Log(w, len(i)) t.Log(w, len(i))
} }
} }
func TestItem_RequestURI(t *testing.T) {
for _, u := range []string{
"https://goedge.cn/hello/world",
"https://goedge.cn:8080/hello/world",
"https://goedge.cn/hello/world?v=1&t=123",
} {
var item = &Item{Key: u}
t.Log(u, "=>", item.RequestURI())
}
}

View File

@@ -7,9 +7,9 @@ import (
"github.com/TeaOSLab/EdgeNode/internal/goman" "github.com/TeaOSLab/EdgeNode/internal/goman"
"github.com/TeaOSLab/EdgeNode/internal/remotelogs" "github.com/TeaOSLab/EdgeNode/internal/remotelogs"
"github.com/TeaOSLab/EdgeNode/internal/ttlcache" "github.com/TeaOSLab/EdgeNode/internal/ttlcache"
"github.com/TeaOSLab/EdgeNode/internal/utils/dbs"
"github.com/TeaOSLab/EdgeNode/internal/utils/fnv" "github.com/TeaOSLab/EdgeNode/internal/utils/fnv"
"github.com/iwind/TeaGo/types" "github.com/iwind/TeaGo/types"
_ "github.com/mattn/go-sqlite3"
"os" "os"
"sync/atomic" "sync/atomic"
"time" "time"
@@ -160,6 +160,7 @@ func (this *FileList) CleanPrefix(prefix string) error {
} }
defer func() { defer func() {
// TODO 需要优化
this.memoryCache.Clean() this.memoryCache.Clean()
}() }()
@@ -172,6 +173,46 @@ func (this *FileList) CleanPrefix(prefix string) error {
return nil return nil
} }
// CleanMatchKey 清理通配符匹配的缓存数据,类似于 https://*.example.com/hello
func (this *FileList) CleanMatchKey(key string) error {
if len(key) == 0 {
return nil
}
defer func() {
// TODO 需要优化
this.memoryCache.Clean()
}()
for _, db := range this.dbList {
err := db.CleanMatchKey(key)
if err != nil {
return err
}
}
return nil
}
// CleanMatchPrefix 清理通配符匹配的缓存数据,类似于 https://*.example.com/prefix/
func (this *FileList) CleanMatchPrefix(prefix string) error {
if len(prefix) == 0 {
return nil
}
defer func() {
// TODO 需要优化
this.memoryCache.Clean()
}()
for _, db := range this.dbList {
err := db.CleanMatchPrefix(prefix)
if err != nil {
return err
}
}
return nil
}
func (this *FileList) Remove(hash string) error { func (this *FileList) Remove(hash string) error {
_, err := this.remove(hash) _, err := this.remove(hash)
return err return err
@@ -409,7 +450,7 @@ func (this *FileList) UpgradeV3(oldDir string, brokenOnError bool) error {
remotelogs.Println("CACHE", "upgrading local database finished") remotelogs.Println("CACHE", "upgrading local database finished")
}() }()
db, err := sql.Open("sqlite3", "file:"+indexDBPath+"?cache=shared&mode=rwc&_journal_mode=WAL&_sync=OFF") db, err := dbs.OpenWriter("file:" + indexDBPath + "?cache=shared&mode=rwc&_journal_mode=WAL&_sync=OFF&_locking_mode=EXCLUSIVE")
if err != nil { if err != nil {
return err return err
} }

View File

@@ -3,15 +3,20 @@
package caches package caches
import ( import (
"database/sql"
"errors" "errors"
teaconst "github.com/TeaOSLab/EdgeNode/internal/const" teaconst "github.com/TeaOSLab/EdgeNode/internal/const"
"github.com/TeaOSLab/EdgeNode/internal/goman" "github.com/TeaOSLab/EdgeNode/internal/goman"
"github.com/TeaOSLab/EdgeNode/internal/remotelogs" "github.com/TeaOSLab/EdgeNode/internal/remotelogs"
"github.com/TeaOSLab/EdgeNode/internal/utils" "github.com/TeaOSLab/EdgeNode/internal/utils"
"github.com/TeaOSLab/EdgeNode/internal/utils/dbs" "github.com/TeaOSLab/EdgeNode/internal/utils/dbs"
"github.com/TeaOSLab/EdgeNode/internal/utils/fasttime"
"github.com/iwind/TeaGo/logs"
"github.com/iwind/TeaGo/types" "github.com/iwind/TeaGo/types"
timeutil "github.com/iwind/TeaGo/utils/time" timeutil "github.com/iwind/TeaGo/utils/time"
"net"
"net/url"
"os"
"path/filepath"
"runtime" "runtime"
"strings" "strings"
"time" "time"
@@ -52,12 +57,12 @@ type FileListDB struct {
purgeStmt *dbs.Stmt // 清理 purgeStmt *dbs.Stmt // 清理
deleteAllStmt *dbs.Stmt // 删除所有数据 deleteAllStmt *dbs.Stmt // 删除所有数据
listOlderItemsStmt *dbs.Stmt // 读取较早存储的缓存 listOlderItemsStmt *dbs.Stmt // 读取较早存储的缓存
updateAccessWeekSQL string // 修改访问日期
// hits // hits
insertHitSQL string // 写入数据 insertHitSQL string // 写入数据
increaseHitSQL string // 增加点击量 increaseHitSQL string // 增加点击量
deleteHitByHashSQL string // 根据hash删除数据 deleteHitByHashSQL string // 根据hash删除数据
lfuHitsStmt *dbs.Stmt // 读取老的数据
} }
func NewFileListDB() *FileListDB { func NewFileListDB() *FileListDB {
@@ -77,24 +82,37 @@ func (this *FileListDB) Open(dbPath string) error {
} }
// write db // write db
writeDB, err := sql.Open("sqlite3", "file:"+dbPath+"?cache=private&mode=rwc&_journal_mode=WAL&_sync=OFF&_cache_size="+types.String(cacheSize)+"&_secure_delete=FAST") // 这里不能加 EXCLUSIVE 锁,不然异步事务可能会失败
writeDB, err := dbs.OpenWriter("file:" + dbPath + "?cache=private&mode=rwc&_journal_mode=WAL&_sync=OFF&_cache_size=" + types.String(cacheSize) + "&_secure_delete=FAST")
if err != nil { if err != nil {
return errors.New("open write database failed: " + err.Error()) return errors.New("open write database failed: " + err.Error())
} }
writeDB.SetMaxOpenConns(1) writeDB.SetMaxOpenConns(1)
this.writeDB = writeDB
// TODO 耗时过长,暂时不整理数据库 // TODO 耗时过长,暂时不整理数据库
// TODO 需要根据行数来判断是否VACUUM // TODO 需要根据行数来判断是否VACUUM
// TODO 注意VACUUM反而可能让数据库文件变大
/**_, err = db.Exec("VACUUM") /**_, err = db.Exec("VACUUM")
if err != nil { if err != nil {
return err return err
}**/ }**/
this.writeDB = dbs.NewDB(writeDB) // 检查是否损坏
this.writeBatch = dbs.NewBatch(writeDB, 4) // TODO 暂时屏蔽,因为用时过长
var recoverEnv, _ = os.LookupEnv("EdgeRecover")
if len(recoverEnv) > 0 && this.shouldRecover() {
for _, indexName := range []string{"staleAt", "hash"} {
_, _ = this.writeDB.Exec(`REINDEX "` + indexName + `"`)
}
}
this.writeBatch = dbs.NewBatch(writeDB.RawDB(), 4)
this.writeBatch.OnFail(func(err error) { this.writeBatch.OnFail(func(err error) {
remotelogs.Warn("LIST_FILE_DB", "run batch failed: "+err.Error()) remotelogs.Warn("LIST_FILE_DB", "run batch failed: "+err.Error()+" ("+filepath.Base(this.dbPath)+")")
}) })
goman.New(func() { goman.New(func() {
@@ -107,14 +125,14 @@ func (this *FileListDB) Open(dbPath string) error {
} }
// read db // read db
readDB, err := sql.Open("sqlite3", "file:"+dbPath+"?cache=private&mode=ro&_journal_mode=WAL&_sync=OFF&_cache_size="+types.String(cacheSize)) readDB, err := dbs.OpenReader("file:" + dbPath + "?cache=private&mode=ro&_journal_mode=WAL&_sync=OFF&_cache_size=" + types.String(cacheSize))
if err != nil { if err != nil {
return errors.New("open read database failed: " + err.Error()) return errors.New("open read database failed: " + err.Error())
} }
readDB.SetMaxOpenConns(runtime.NumCPU()) readDB.SetMaxOpenConns(runtime.NumCPU())
this.readDB = dbs.NewDB(readDB) this.readDB = readDB
if teaconst.EnableDBStat { if teaconst.EnableDBStat {
this.readDB.EnableStat(true) this.readDB.EnableStat(true)
@@ -151,7 +169,7 @@ func (this *FileListDB) Init() error {
return err return err
} }
this.insertSQL = `INSERT INTO "` + this.itemsTableName + `" ("hash", "key", "headerSize", "bodySize", "metaSize", "expiredAt", "staleAt", "host", "serverId", "createdAt") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)` this.insertSQL = `INSERT INTO "` + this.itemsTableName + `" ("hash", "key", "headerSize", "bodySize", "metaSize", "expiredAt", "staleAt", "host", "serverId", "createdAt", "accessWeek") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
this.insertStmt, err = this.writeDB.Prepare(this.insertSQL) this.insertStmt, err = this.writeDB.Prepare(this.insertSQL)
if err != nil { if err != nil {
return err return err
@@ -163,6 +181,9 @@ func (this *FileListDB) Init() error {
} }
this.selectHashListStmt, err = this.readDB.Prepare(`SELECT "id", "hash" FROM "` + this.itemsTableName + `" WHERE id>:id ORDER BY id ASC LIMIT 2000`) this.selectHashListStmt, err = this.readDB.Prepare(`SELECT "id", "hash" FROM "` + this.itemsTableName + `" WHERE id>:id ORDER BY id ASC LIMIT 2000`)
if err != nil {
return err
}
this.deleteByHashSQL = `DELETE FROM "` + this.itemsTableName + `" WHERE "hash"=?` this.deleteByHashSQL = `DELETE FROM "` + this.itemsTableName + `" WHERE "hash"=?`
this.deleteByHashStmt, err = this.writeDB.Prepare(this.deleteByHashSQL) this.deleteByHashStmt, err = this.writeDB.Prepare(this.deleteByHashSQL)
@@ -185,7 +206,12 @@ func (this *FileListDB) Init() error {
return err return err
} }
this.listOlderItemsStmt, err = this.readDB.Prepare(`SELECT "hash" FROM "` + this.itemsTableName + `" ORDER BY "id" ASC LIMIT ?`) this.listOlderItemsStmt, err = this.readDB.Prepare(`SELECT "hash" FROM "` + this.itemsTableName + `" ORDER BY "accessWeek" ASC, "id" ASC LIMIT ?`)
if err != nil {
return err
}
this.updateAccessWeekSQL = `UPDATE "` + this.itemsTableName + `" SET "accessWeek"=? WHERE "hash"=?`
this.insertHitSQL = `INSERT INTO "` + this.hitsTableName + `" ("hash", "week2Hits", "week") VALUES (?, 1, ?)` this.insertHitSQL = `INSERT INTO "` + this.hitsTableName + `" ("hash", "week2Hits", "week") VALUES (?, 1, ?)`
@@ -193,11 +219,6 @@ func (this *FileListDB) Init() error {
this.deleteHitByHashSQL = `DELETE FROM "` + this.hitsTableName + `" WHERE "hash"=?` this.deleteHitByHashSQL = `DELETE FROM "` + this.hitsTableName + `" WHERE "hash"=?`
this.lfuHitsStmt, err = this.readDB.Prepare(`SELECT "hash" FROM "` + this.hitsTableName + `" ORDER BY "week" ASC, "week1Hits"+"week2Hits" ASC LIMIT ?`)
if err != nil {
return err
}
this.isReady = true this.isReady = true
// 加载HashMap // 加载HashMap
@@ -205,6 +226,20 @@ func (this *FileListDB) Init() error {
err := this.hashMap.Load(this) err := this.hashMap.Load(this)
if err != nil { if err != nil {
remotelogs.Error("LIST_FILE_DB", "load hash map failed: "+err.Error()+"(file: "+this.dbPath+")") remotelogs.Error("LIST_FILE_DB", "load hash map failed: "+err.Error()+"(file: "+this.dbPath+")")
// 自动修复错误
// TODO 将来希望能尽可能恢复以往数据库中的内容
if strings.Contains(err.Error(), "database is closed") || strings.Contains(err.Error(), "database disk image is malformed") {
_ = this.Close()
this.deleteDB()
remotelogs.Println("LIST_FILE_DB", "recreating the database (file:"+this.dbPath+") ...")
err = this.Open(this.dbPath)
if err != nil {
remotelogs.Error("LIST_FILE_DB", "recreate the database failed: "+err.Error()+" (file:"+this.dbPath+")")
} else {
_ = this.Init()
}
}
} }
}() }()
@@ -226,7 +261,7 @@ func (this *FileListDB) AddAsync(hash string, item *Item) error {
item.StaleAt = item.ExpiredAt item.StaleAt = item.ExpiredAt
} }
this.writeBatch.Add(this.insertSQL, hash, item.Key, item.HeaderSize, item.BodySize, item.MetaSize, item.ExpiredAt, item.StaleAt, item.Host, item.ServerId, utils.UnixTime()) this.writeBatch.Add(this.insertSQL, hash, item.Key, item.HeaderSize, item.BodySize, item.MetaSize, item.ExpiredAt, item.StaleAt, item.Host, item.ServerId, fasttime.Now().Unix(), timeutil.Format("YW"))
return nil return nil
} }
@@ -238,7 +273,7 @@ func (this *FileListDB) AddSync(hash string, item *Item) error {
item.StaleAt = item.ExpiredAt item.StaleAt = item.ExpiredAt
} }
_, err := this.insertStmt.Exec(hash, item.Key, item.HeaderSize, item.BodySize, item.MetaSize, item.ExpiredAt, item.StaleAt, item.Host, item.ServerId, utils.UnixTime()) _, err := this.insertStmt.Exec(hash, item.Key, item.HeaderSize, item.BodySize, item.MetaSize, item.ExpiredAt, item.StaleAt, item.Host, item.ServerId, fasttime.Now().Unix(), timeutil.Format("YW"))
if err != nil { if err != nil {
return this.WrapError(err) return this.WrapError(err)
} }
@@ -300,22 +335,23 @@ func (this *FileListDB) ListLFUItems(count int) (hashList []string, err error) {
count = 100 count = 100
} }
hashList, err = this.listLFUItems(count) // 先找过期的
hashList, err = this.ListExpiredItems(count)
if err != nil { if err != nil {
return return
} }
var l = len(hashList)
if len(hashList) > count/2 { // 从旧缓存中补充
return if l < count {
} oldHashList, err := this.listOlderItems(count - l)
// 不足补齐
olderHashList, err := this.listOlderItems(count - len(hashList))
if err != nil { if err != nil {
return nil, err return nil, err
} }
hashList = append(hashList, olderHashList...) hashList = append(hashList, oldHashList...)
return }
return hashList, nil
} }
func (this *FileListDB) ListHashes(lastId int64) (hashList []string, maxId int64, err error) { func (this *FileListDB) ListHashes(lastId int64) (hashList []string, maxId int64, err error) {
@@ -342,6 +378,7 @@ func (this *FileListDB) ListHashes(lastId int64) (hashList []string, maxId int64
func (this *FileListDB) IncreaseHitAsync(hash string) error { func (this *FileListDB) IncreaseHitAsync(hash string) error {
var week = timeutil.Format("YW") var week = timeutil.Format("YW")
this.writeBatch.Add(this.increaseHitSQL, hash, week, week, week, week) this.writeBatch.Add(this.increaseHitSQL, hash, week, week, week, week)
this.writeBatch.Add(this.updateAccessWeekSQL, week, hash)
return nil return nil
} }
@@ -356,7 +393,7 @@ func (this *FileListDB) CleanPrefix(prefix string) error {
} }
var count = int64(10000) var count = int64(10000)
var staleLife = 600 // TODO 需要可以设置 var staleLife = 600 // TODO 需要可以设置
var unixTime = utils.UnixTime() // 只删除当前的,不删除新的 var unixTime = fasttime.Now().Unix() // 只删除当前的,不删除新的
for { for {
result, err := this.writeDB.Exec(`UPDATE "`+this.itemsTableName+`" SET expiredAt=0,staleAt=? WHERE id IN (SELECT id FROM "`+this.itemsTableName+`" WHERE expiredAt>0 AND createdAt<=? AND INSTR("key", ?)=1 LIMIT `+types.String(count)+`)`, unixTime+int64(staleLife), unixTime, prefix) result, err := this.writeDB.Exec(`UPDATE "`+this.itemsTableName+`" SET expiredAt=0,staleAt=? WHERE id IN (SELECT id FROM "`+this.itemsTableName+`" WHERE expiredAt>0 AND createdAt<=? AND INSTR("key", ?)=1 LIMIT `+types.String(count)+`)`, unixTime+int64(staleLife), unixTime, prefix)
if err != nil { if err != nil {
@@ -372,6 +409,85 @@ func (this *FileListDB) CleanPrefix(prefix string) error {
} }
} }
func (this *FileListDB) CleanMatchKey(key string) error {
if !this.isReady {
return nil
}
// 忽略 @GOEDGE_
if strings.Contains(key, SuffixAll) {
return nil
}
u, err := url.Parse(key)
if err != nil {
return nil
}
var host = u.Host
hostPart, _, err := net.SplitHostPort(host)
if err == nil && len(hostPart) > 0 {
host = hostPart
}
if len(host) == 0 {
return nil
}
// 转义
var queryKey = strings.ReplaceAll(key, "%", "\\%")
queryKey = strings.ReplaceAll(queryKey, "_", "\\_")
queryKey = strings.Replace(queryKey, "*", "%", 1)
// TODO 检查大批量数据下的操作性能
var staleLife = 600 // TODO 需要可以设置
var unixTime = fasttime.Now().Unix() // 只删除当前的,不删除新的
_, err = this.writeDB.Exec(`UPDATE "`+this.itemsTableName+`" SET "expiredAt"=0, "staleAt"=? WHERE "host" GLOB ? AND "host" NOT GLOB ? AND "key" LIKE ? ESCAPE '\'`, unixTime+int64(staleLife), host, "*."+host, queryKey)
if err != nil {
return err
}
_, err = this.writeDB.Exec(`UPDATE "`+this.itemsTableName+`" SET "expiredAt"=0, "staleAt"=? WHERE "host" GLOB ? AND "host" NOT GLOB ? AND "key" LIKE ? ESCAPE '\'`, unixTime+int64(staleLife), host, "*."+host, queryKey+SuffixAll+"%")
if err != nil {
return err
}
return nil
}
func (this *FileListDB) CleanMatchPrefix(prefix string) error {
if !this.isReady {
return nil
}
u, err := url.Parse(prefix)
if err != nil {
return nil
}
var host = u.Host
hostPart, _, err := net.SplitHostPort(host)
if err == nil && len(hostPart) > 0 {
host = hostPart
}
if len(host) == 0 {
return nil
}
// 转义
var queryPrefix = strings.ReplaceAll(prefix, "%", "\\%")
queryPrefix = strings.ReplaceAll(queryPrefix, "_", "\\_")
queryPrefix = strings.Replace(queryPrefix, "*", "%", 1)
queryPrefix += "%"
// TODO 检查大批量数据下的操作性能
var staleLife = 600 // TODO 需要可以设置
var unixTime = fasttime.Now().Unix() // 只删除当前的,不删除新的
_, err = this.writeDB.Exec(`UPDATE "`+this.itemsTableName+`" SET "expiredAt"=0, "staleAt"=? WHERE "host" GLOB ? AND "host" NOT GLOB ? AND "key" LIKE ? ESCAPE '\'`, unixTime+int64(staleLife), host, "*."+host, queryPrefix)
return err
}
func (this *FileListDB) CleanAll() error { func (this *FileListDB) CleanAll() error {
if !this.isReady { if !this.isReady {
return nil return nil
@@ -418,8 +534,9 @@ func (this *FileListDB) Close() error {
if this.listOlderItemsStmt != nil { if this.listOlderItemsStmt != nil {
_ = this.listOlderItemsStmt.Close() _ = this.listOlderItemsStmt.Close()
} }
if this.lfuHitsStmt != nil {
_ = this.lfuHitsStmt.Close() if this.writeBatch != nil {
this.writeBatch.Close()
} }
var errStrings []string var errStrings []string
@@ -438,10 +555,6 @@ func (this *FileListDB) Close() error {
} }
} }
if this.writeBatch != nil {
this.writeBatch.Close()
}
if len(errStrings) == 0 { if len(errStrings) == 0 {
return nil return nil
} }
@@ -460,6 +573,7 @@ func (this *FileListDB) initTables(times int) error {
{ {
// expiredAt - 过期时间,用来判断有无过期 // expiredAt - 过期时间,用来判断有无过期
// staleAt - 过时缓存最大时间,用来清理缓存 // staleAt - 过时缓存最大时间,用来清理缓存
// 不对 hash 增加 unique 参数,是尽可能避免产生 malformed 错误
_, err := this.writeDB.Exec(`CREATE TABLE IF NOT EXISTS "` + this.itemsTableName + `" ( _, err := this.writeDB.Exec(`CREATE TABLE IF NOT EXISTS "` + this.itemsTableName + `" (
"id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "id" integer NOT NULL PRIMARY KEY AUTOINCREMENT,
"hash" varchar(32), "hash" varchar(32),
@@ -472,7 +586,8 @@ func (this *FileListDB) initTables(times int) error {
"staleAt" integer DEFAULT 0, "staleAt" integer DEFAULT 0,
"createdAt" integer DEFAULT 0, "createdAt" integer DEFAULT 0,
"host" varchar(128), "host" varchar(128),
"serverId" integer "serverId" integer,
"accessWeek" varchar(6)
); );
DROP INDEX IF EXISTS "createdAt"; DROP INDEX IF EXISTS "createdAt";
@@ -484,14 +599,22 @@ ON "` + this.itemsTableName + `" (
"staleAt" ASC "staleAt" ASC
); );
CREATE UNIQUE INDEX IF NOT EXISTS "hash" CREATE INDEX IF NOT EXISTS "hash"
ON "` + this.itemsTableName + `" ( ON "` + this.itemsTableName + `" (
"hash" ASC "hash" ASC
); );
ALTER TABLE "cacheItems" ADD "accessWeek" varchar(6);
`) `)
if err != nil { if err != nil {
// 忽略可以预期的错误
if strings.Contains(err.Error(), "duplicate column name") {
err = nil
}
// 尝试删除重建 // 尝试删除重建
if err != nil {
if times < 3 { if times < 3 {
_, dropErr := this.writeDB.Exec(`DROP TABLE "` + this.itemsTableName + `"`) _, dropErr := this.writeDB.Exec(`DROP TABLE "` + this.itemsTableName + `"`)
if dropErr == nil { if dropErr == nil {
@@ -503,6 +626,7 @@ ON "` + this.itemsTableName + `" (
return this.WrapError(err) return this.WrapError(err)
} }
} }
}
{ {
_, err := this.writeDB.Exec(`CREATE TABLE IF NOT EXISTS "` + this.hitsTableName + `" ( _, err := this.writeDB.Exec(`CREATE TABLE IF NOT EXISTS "` + this.hitsTableName + `" (
@@ -535,27 +659,6 @@ ON "` + this.hitsTableName + `" (
return nil return nil
} }
func (this *FileListDB) listLFUItems(count int) (hashList []string, err error) {
rows, err := this.lfuHitsStmt.Query(count)
if err != nil {
return nil, err
}
defer func() {
_ = rows.Close()
}()
for rows.Next() {
var hash string
err = rows.Scan(&hash)
if err != nil {
return nil, err
}
hashList = append(hashList, hash)
}
return hashList, nil
}
func (this *FileListDB) listOlderItems(count int) (hashList []string, err error) { func (this *FileListDB) listOlderItems(count int) (hashList []string, err error) {
rows, err := this.listOlderItemsStmt.Query(count) rows, err := this.listOlderItemsStmt.Query(count)
if err != nil { if err != nil {
@@ -576,3 +679,28 @@ func (this *FileListDB) listOlderItems(count int) (hashList []string, err error)
return hashList, nil return hashList, nil
} }
func (this *FileListDB) shouldRecover() bool {
result, err := this.writeDB.Query("pragma integrity_check;")
if err != nil {
logs.Println(result)
}
var errString = ""
var shouldRecover = false
for result.Next() {
err = result.Scan(&errString)
if strings.TrimSpace(errString) != "ok" {
shouldRecover = true
}
break
}
_ = result.Close()
return shouldRecover
}
// 删除数据库文件
func (this *FileListDB) deleteDB() {
_ = os.Remove(this.dbPath)
_ = os.Remove(this.dbPath + "-shm")
_ = os.Remove(this.dbPath + "-wal")
}

View File

@@ -0,0 +1,87 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package caches_test
import (
"github.com/TeaOSLab/EdgeNode/internal/caches"
"github.com/iwind/TeaGo/Tea"
_ "github.com/iwind/TeaGo/bootstrap"
"testing"
"time"
)
func TestFileListDB_ListLFUItems(t *testing.T) {
var db = caches.NewFileListDB()
err := db.Open(Tea.Root + "/data/cache-db-large.db")
//err := db.Open(Tea.Root + "/data/cache-index/p1/db-0.db")
if err != nil {
t.Fatal(err)
}
err = db.Init()
if err != nil {
t.Fatal(err)
}
defer func() {
_ = db.Close()
}()
hashList, err := db.ListLFUItems(100)
if err != nil {
t.Fatal(err)
}
t.Log("[", len(hashList), "]", hashList)
}
func TestFileListDB_IncreaseHitAsync(t *testing.T) {
var db = caches.NewFileListDB()
err := db.Open(Tea.Root + "/data/cache-db-large.db")
if err != nil {
t.Fatal(err)
}
err = db.Init()
err = db.IncreaseHitAsync("4598e5231ba47d6ec7aa9ea640ff2eaf")
if err != nil {
t.Fatal(err)
}
// wait transaction
time.Sleep(1 * time.Second)
}
func TestFileListDB_CleanMatchKey(t *testing.T) {
var db = caches.NewFileListDB()
err := db.Open(Tea.Root + "/data/cache-db-large.db")
if err != nil {
t.Fatal(err)
}
err = db.Init()
err = db.CleanMatchKey("https://*.goedge.cn/large-text")
if err != nil {
t.Fatal(err)
}
err = db.CleanMatchKey("https://*.goedge.cn:1234/large-text?%2B____")
if err != nil {
t.Fatal(err)
}
}
func TestFileListDB_CleanMatchPrefix(t *testing.T) {
var db = caches.NewFileListDB()
err := db.Open(Tea.Root + "/data/cache-db-large.db")
if err != nil {
t.Fatal(err)
}
err = db.Init()
err = db.CleanMatchPrefix("https://*.goedge.cn/large-text")
if err != nil {
t.Fatal(err)
}
err = db.CleanMatchPrefix("https://*.goedge.cn:1234/large-text?%2B____")
if err != nil {
t.Fatal(err)
}
}

View File

@@ -107,6 +107,12 @@ func (this *FileListHashMap) IsReady() bool {
return this.isReady return this.isReady
} }
func (this *FileListHashMap) Len() int {
this.locker.Lock()
defer this.locker.Unlock()
return len(this.m)
}
func (this *FileListHashMap) bigInt(hash string) uint64 { func (this *FileListHashMap) bigInt(hash string) uint64 {
var bigInt = big.NewInt(0) var bigInt = big.NewInt(0)
bigInt.SetString(hash, 16) bigInt.SetString(hash, 16)

View File

@@ -12,6 +12,7 @@ import (
"runtime" "runtime"
"strconv" "strconv"
"testing" "testing"
"time"
) )
func TestFileListHashMap_Memory(t *testing.T) { func TestFileListHashMap_Memory(t *testing.T) {
@@ -68,10 +69,14 @@ func TestFileListHashMap_Load(t *testing.T) {
}() }()
var m = caches.NewFileListHashMap() var m = caches.NewFileListHashMap()
err = m.Load(list.GetDB("abc")) var before = time.Now()
var db = list.GetDB("abc")
err = m.Load(db)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
t.Log(time.Since(before).Seconds()*1000, "ms")
t.Log("count:", m.Len())
m.Add("abc") m.Add("abc")
for _, hash := range []string{"33347bb4441265405347816cad36a0f8", "a", "abc", "123"} { for _, hash := range []string{"33347bb4441265405347816cad36a0f8", "a", "abc", "123"} {

View File

@@ -18,6 +18,12 @@ type ListInterface interface {
// CleanPrefix 清除某个前缀的缓存 // CleanPrefix 清除某个前缀的缓存
CleanPrefix(prefix string) error CleanPrefix(prefix string) error
// CleanMatchKey 清除通配符匹配的Key
CleanMatchKey(key string) error
// CleanMatchPrefix 清除通配符匹配的前缀
CleanMatchPrefix(prefix string) error
// Remove 删除内容 // Remove 删除内容
Remove(hash string) error Remove(hash string) error

View File

@@ -1,8 +1,11 @@
package caches package caches
import ( import (
"github.com/TeaOSLab/EdgeCommon/pkg/configutils"
"github.com/TeaOSLab/EdgeNode/internal/zero" "github.com/TeaOSLab/EdgeNode/internal/zero"
"github.com/iwind/TeaGo/logs" "github.com/iwind/TeaGo/logs"
"net"
"net/url"
"strconv" "strconv"
"strings" "strings"
"sync" "sync"
@@ -146,6 +149,82 @@ func (this *MemoryList) CleanPrefix(prefix string) error {
return nil return nil
} }
// CleanMatchKey 清理通配符匹配的缓存数据,类似于 https://*.example.com/hello
func (this *MemoryList) CleanMatchKey(key string) error {
if strings.Contains(key, SuffixAll) {
return nil
}
u, err := url.Parse(key)
if err != nil {
return nil
}
var host = u.Host
hostPart, _, err := net.SplitHostPort(host)
if err == nil && len(hostPart) > 0 {
host = hostPart
}
if len(host) == 0 {
return nil
}
var requestURI = u.RequestURI()
this.locker.RLock()
defer this.locker.RUnlock()
// TODO 需要优化性能支持千万级数据低于1s的处理速度
for _, itemMap := range this.itemMaps {
for _, item := range itemMap {
if configutils.MatchDomain(host, item.Host) {
var itemRequestURI = item.RequestURI()
if itemRequestURI == requestURI || strings.HasPrefix(itemRequestURI, requestURI+SuffixAll) {
item.ExpiredAt = 0
}
}
}
}
return nil
}
// CleanMatchPrefix 清理通配符匹配的缓存数据,类似于 https://*.example.com/prefix/
func (this *MemoryList) CleanMatchPrefix(prefix string) error {
u, err := url.Parse(prefix)
if err != nil {
return nil
}
var host = u.Host
hostPart, _, err := net.SplitHostPort(host)
if err == nil && len(hostPart) > 0 {
host = hostPart
}
if len(host) == 0 {
return nil
}
var requestURI = u.RequestURI()
var isRootPath = requestURI == "/"
this.locker.RLock()
defer this.locker.RUnlock()
// TODO 需要优化性能支持千万级数据低于1s的处理速度
for _, itemMap := range this.itemMaps {
for _, item := range itemMap {
if configutils.MatchDomain(host, item.Host) {
var itemRequestURI = item.RequestURI()
if isRootPath || strings.HasPrefix(itemRequestURI, requestURI) {
item.ExpiredAt = 0
}
}
}
}
return nil
}
func (this *MemoryList) Remove(hash string) error { func (this *MemoryList) Remove(hash string) error {
this.locker.Lock() this.locker.Lock()

View File

@@ -3,6 +3,7 @@ package caches
import ( import (
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs" "github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/shared" "github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/shared"
teaconst "github.com/TeaOSLab/EdgeNode/internal/const"
"github.com/TeaOSLab/EdgeNode/internal/events" "github.com/TeaOSLab/EdgeNode/internal/events"
"github.com/TeaOSLab/EdgeNode/internal/remotelogs" "github.com/TeaOSLab/EdgeNode/internal/remotelogs"
"github.com/iwind/TeaGo/lists" "github.com/iwind/TeaGo/lists"
@@ -14,6 +15,10 @@ import (
var SharedManager = NewManager() var SharedManager = NewManager()
func init() { func init() {
if !teaconst.IsMain {
return
}
events.On(events.EventQuit, func() { events.On(events.EventQuit, func() {
remotelogs.Println("CACHE", "quiting cache manager") remotelogs.Println("CACHE", "quiting cache manager")
SharedManager.UpdatePolicies([]*serverconfigs.HTTPCachePolicy{}) SharedManager.UpdatePolicies([]*serverconfigs.HTTPCachePolicy{})
@@ -24,7 +29,8 @@ func init() {
type Manager struct { type Manager struct {
// 全局配置 // 全局配置
MaxDiskCapacity *shared.SizeCapacity MaxDiskCapacity *shared.SizeCapacity
DiskDir string MainDiskDir string
SubDiskDirs []*serverconfigs.CacheDir
MaxMemoryCapacity *shared.SizeCapacity MaxMemoryCapacity *shared.SizeCapacity
policyMap map[int64]*serverconfigs.HTTPCachePolicy // policyId => []*Policy policyMap map[int64]*serverconfigs.HTTPCachePolicy // policyId => []*Policy
@@ -47,12 +53,10 @@ func (this *Manager) UpdatePolicies(newPolicies []*serverconfigs.HTTPCachePolicy
this.locker.Lock() this.locker.Lock()
defer this.locker.Unlock() defer this.locker.Unlock()
newPolicyIds := []int64{} var newPolicyIds = []int64{}
for _, policy := range newPolicies { for _, policy := range newPolicies {
// 使用节点单独的缓存目录 // 使用节点单独的缓存目录
if len(this.DiskDir) > 0 { policy.UpdateDiskDir(this.MainDiskDir, this.SubDiskDirs)
policy.UpdateDiskDir(this.DiskDir)
}
newPolicyIds = append(newPolicyIds, policy.Id) newPolicyIds = append(newPolicyIds, policy.Id)
} }
@@ -173,10 +177,15 @@ func (this *Manager) TotalDiskSize() int64 {
this.locker.RLock() this.locker.RLock()
defer this.locker.RUnlock() defer this.locker.RUnlock()
total := int64(0) var total = int64(0)
for _, storage := range this.storageMap { for _, storage := range this.storageMap {
total += storage.TotalDiskSize() total += storage.TotalDiskSize()
} }
if total < 0 {
total = 0
}
return total return total
} }

View File

@@ -19,7 +19,7 @@ type OpenFileCache struct {
poolList *linkedlist.List poolList *linkedlist.List
watcher *fsnotify.Watcher watcher *fsnotify.Watcher
locker sync.Mutex locker sync.RWMutex
maxSize int maxSize int
count int count int
@@ -54,13 +54,18 @@ func NewOpenFileCache(maxSize int) (*OpenFileCache, error) {
} }
func (this *OpenFileCache) Get(filename string) *OpenFile { func (this *OpenFileCache) Get(filename string) *OpenFile {
this.locker.Lock() this.locker.RLock()
defer this.locker.Unlock()
pool, ok := this.poolMap[filename] pool, ok := this.poolMap[filename]
this.locker.RUnlock()
if ok { if ok {
file, consumed := pool.Get() file, consumed := pool.Get()
if consumed { if consumed {
this.locker.Lock()
this.count-- this.count--
// pool如果为空也不需要从列表中删除避免put时需要重新创建
this.locker.Unlock()
} }
return file return file
} }
@@ -124,6 +129,9 @@ func (this *OpenFileCache) Close(filename string) {
pool, ok := this.poolMap[filename] pool, ok := this.poolMap[filename]
if ok { if ok {
// 设置关闭状态
pool.SetClosing()
delete(this.poolMap, filename) delete(this.poolMap, filename)
this.poolList.Remove(pool.linkItem) this.poolList.Remove(pool.linkItem)
_ = this.watcher.Remove(filename) _ = this.watcher.Remove(filename)
@@ -146,6 +154,7 @@ func (this *OpenFileCache) CloseAll() {
this.poolMap = map[string]*OpenFilePool{} this.poolMap = map[string]*OpenFilePool{}
this.poolList.Reset() this.poolList.Reset()
_ = this.watcher.Close() _ = this.watcher.Close()
this.count = 0
this.locker.Unlock() this.locker.Unlock()
} }

View File

@@ -0,0 +1,43 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package caches_test
import (
"github.com/TeaOSLab/EdgeNode/internal/caches"
"testing"
"time"
)
func TestNewOpenFileCache_Close(t *testing.T) {
cache, err := caches.NewOpenFileCache(1024)
if err != nil {
t.Fatal(err)
}
cache.Debug()
cache.Put("a.txt", caches.NewOpenFile(nil, nil, nil, 0))
cache.Put("b.txt", caches.NewOpenFile(nil, nil, nil, 0))
cache.Put("b.txt", caches.NewOpenFile(nil, nil, nil, 0))
cache.Put("b.txt", caches.NewOpenFile(nil, nil, nil, 0))
cache.Put("c.txt", caches.NewOpenFile(nil, nil, nil, 0))
cache.Get("b.txt")
cache.Get("d.txt")
cache.Close("a.txt")
time.Sleep(100 * time.Second)
}
func TestNewOpenFileCache_CloseAll(t *testing.T) {
cache, err := caches.NewOpenFileCache(1024)
if err != nil {
t.Fatal(err)
}
cache.Debug()
cache.Put("a.txt", caches.NewOpenFile(nil, nil, nil, 0))
cache.Put("b.txt", caches.NewOpenFile(nil, nil, nil, 0))
cache.Put("c.txt", caches.NewOpenFile(nil, nil, nil, 0))
cache.Get("b.txt")
cache.Get("d.txt")
cache.CloseAll()
time.Sleep(6 * time.Second)
}

View File

@@ -3,7 +3,7 @@
package caches package caches
import ( import (
"github.com/TeaOSLab/EdgeNode/internal/utils" "github.com/TeaOSLab/EdgeNode/internal/utils/fasttime"
"github.com/TeaOSLab/EdgeNode/internal/utils/linkedlist" "github.com/TeaOSLab/EdgeNode/internal/utils/linkedlist"
) )
@@ -12,13 +12,14 @@ type OpenFilePool struct {
linkItem *linkedlist.Item linkItem *linkedlist.Item
filename string filename string
version int64 version int64
isClosed bool
} }
func NewOpenFilePool(filename string) *OpenFilePool { func NewOpenFilePool(filename string) *OpenFilePool {
var pool = &OpenFilePool{ var pool = &OpenFilePool{
filename: filename, filename: filename,
c: make(chan *OpenFile, 1024), c: make(chan *OpenFile, 1024),
version: utils.UnixTimeMilli(), version: fasttime.Now().UnixMilli(),
} }
pool.linkItem = linkedlist.NewItem(pool) pool.linkItem = linkedlist.NewItem(pool)
return pool return pool
@@ -29,8 +30,14 @@ func (this *OpenFilePool) Filename() string {
} }
func (this *OpenFilePool) Get() (*OpenFile, bool) { func (this *OpenFilePool) Get() (*OpenFile, bool) {
// 如果已经关闭,直接返回
if this.isClosed {
return nil, false
}
select { select {
case file := <-this.c: case file := <-this.c:
if file != nil {
err := file.SeekStart() err := file.SeekStart()
if err != nil { if err != nil {
_ = file.Close() _ = file.Close()
@@ -39,16 +46,27 @@ func (this *OpenFilePool) Get() (*OpenFile, bool) {
file.version = this.version file.version = this.version
return file, true return file, true
}
return nil, false
default: default:
return nil, false return nil, false
} }
} }
func (this *OpenFilePool) Put(file *OpenFile) bool { func (this *OpenFilePool) Put(file *OpenFile) bool {
// 如果已关闭,则不接受新的文件
if this.isClosed {
_ = file.Close()
return false
}
// 检查文件版本号
if this.version > 0 && file.version > 0 && file.version != this.version { if this.version > 0 && file.version > 0 && file.version != this.version {
_ = file.Close() _ = file.Close()
return false return false
} }
// 加入Pool
select { select {
case this.c <- file: case this.c <- file:
return true return true
@@ -63,14 +81,18 @@ func (this *OpenFilePool) Len() int {
return len(this.c) return len(this.c)
} }
func (this *OpenFilePool) SetClosing() {
this.isClosed = true
}
func (this *OpenFilePool) Close() { func (this *OpenFilePool) Close() {
Loop: this.isClosed = true
for { for {
select { select {
case file := <-this.c: case file := <-this.c:
_ = file.Close() _ = file.Close()
default: default:
break Loop return
} }
} }
} }

View File

@@ -4,6 +4,8 @@ package caches_test
import ( import (
"github.com/TeaOSLab/EdgeNode/internal/caches" "github.com/TeaOSLab/EdgeNode/internal/caches"
"github.com/iwind/TeaGo/rands"
"sync"
"testing" "testing"
) )
@@ -15,3 +17,30 @@ func TestOpenFilePool_Get(t *testing.T) {
t.Log(pool.Get()) t.Log(pool.Get())
t.Log(pool.Get()) t.Log(pool.Get())
} }
func TestOpenFilePool_Close(t *testing.T) {
var pool = caches.NewOpenFilePool("a")
pool.Put(caches.NewOpenFile(nil, nil, nil, 0))
pool.Put(caches.NewOpenFile(nil, nil, nil, 0))
pool.Close()
}
func TestOpenFilePool_Concurrent(t *testing.T) {
var pool = caches.NewOpenFilePool("a")
var concurrent = 1000
var wg = &sync.WaitGroup{}
wg.Add(concurrent)
for i := 0; i < concurrent; i++ {
go func() {
defer wg.Done()
if rands.Int(0, 1) == 1 {
pool.Put(caches.NewOpenFile(nil, nil, nil, 0))
}
if rands.Int(0, 1) == 0 {
pool.Get()
}
}()
}
wg.Wait()
}

View File

@@ -3,40 +3,90 @@
package caches package caches
import ( import (
"bytes"
"encoding/json" "encoding/json"
"errors" "github.com/iwind/TeaGo/types"
"os" "os"
"strconv"
) )
// PartialRanges 内容分区范围定义 // PartialRanges 内容分区范围定义
type PartialRanges struct { type PartialRanges struct {
Ranges [][2]int64 `json:"ranges"` Version int `json:"version"` // 版本号
Ranges [][2]int64 `json:"ranges"` // 范围
BodySize int64 `json:"bodySize"` // 总长度
} }
// NewPartialRanges 获取新对象 // NewPartialRanges 获取新对象
func NewPartialRanges() *PartialRanges { func NewPartialRanges(expiresAt int64) *PartialRanges {
return &PartialRanges{Ranges: [][2]int64{}} return &PartialRanges{
Ranges: [][2]int64{},
Version: 1,
}
}
// NewPartialRangesFromData 从数据中解析范围
func NewPartialRangesFromData(data []byte) (*PartialRanges, error) {
var rs = NewPartialRanges(0)
for {
var index = bytes.IndexRune(data, '\n')
if index < 0 {
break
}
var line = data[:index]
var colonIndex = bytes.IndexRune(line, ':')
if colonIndex > 0 {
switch string(line[:colonIndex]) {
case "v": // 版本号
rs.Version = types.Int(line[colonIndex+1:])
case "b": // 总长度
rs.BodySize = types.Int64(line[colonIndex+1:])
case "r": // 范围信息
var commaIndex = bytes.IndexRune(line, ',')
if commaIndex > 0 {
rs.Ranges = append(rs.Ranges, [2]int64{types.Int64(line[colonIndex+1 : commaIndex]), types.Int64(line[commaIndex+1:])})
}
}
}
data = data[index+1:]
if len(data) == 0 {
break
}
}
return rs, nil
} }
// NewPartialRangesFromJSON 从JSON中解析范围 // NewPartialRangesFromJSON 从JSON中解析范围
func NewPartialRangesFromJSON(data []byte) (*PartialRanges, error) { func NewPartialRangesFromJSON(data []byte) (*PartialRanges, error) {
var rs = NewPartialRanges() var rs = NewPartialRanges(0)
err := json.Unmarshal(data, &rs) err := json.Unmarshal(data, &rs)
if err != nil { if err != nil {
return nil, err return nil, err
} }
rs.Version = 0
return rs, nil return rs, nil
} }
// NewPartialRangesFromFile 从文件中加载范围信息
func NewPartialRangesFromFile(path string) (*PartialRanges, error) { func NewPartialRangesFromFile(path string) (*PartialRanges, error) {
data, err := os.ReadFile(path) data, err := os.ReadFile(path)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if len(data) == 0 {
return NewPartialRanges(0), nil
}
// 兼容老的JSON格式
if data[0] == '{' {
return NewPartialRangesFromJSON(data) return NewPartialRangesFromJSON(data)
} }
// 新的格式
return NewPartialRangesFromData(data)
}
// Add 添加新范围 // Add 添加新范围
func (this *PartialRanges) Add(begin int64, end int64) { func (this *PartialRanges) Add(begin int64, end int64) {
if begin > end { if begin > end {
@@ -105,29 +155,27 @@ func (this *PartialRanges) Nearest(begin int64, end int64) (r [2]int64, ok bool)
return return
} }
// AsJSON 转换为JSON // 转换为字符串
func (this *PartialRanges) AsJSON() ([]byte, error) { func (this *PartialRanges) String() string {
return json.Marshal(this) var s = "v:" + strconv.Itoa(this.Version) + "\n" + // version
"b:" + this.formatInt64(this.BodySize) + "\n" // bodySize
for _, r := range this.Ranges {
s += "r:" + this.formatInt64(r[0]) + "," + this.formatInt64(r[1]) + "\n" // range
}
return s
}
// Bytes 将内容转换为字节
func (this *PartialRanges) Bytes() []byte {
return []byte(this.String())
} }
// WriteToFile 写入到文件中 // WriteToFile 写入到文件中
func (this *PartialRanges) WriteToFile(path string) error { func (this *PartialRanges) WriteToFile(path string) error {
data, err := this.AsJSON() return os.WriteFile(path, this.Bytes(), 0666)
if err != nil {
return errors.New("convert to json failed: " + err.Error())
}
return os.WriteFile(path, data, 0666)
}
// ReadFromFile 从文件中读取
func (this *PartialRanges) ReadFromFile(path string) (*PartialRanges, error) {
data, err := os.ReadFile(path)
if err != nil {
return nil, err
}
return NewPartialRangesFromJSON(data)
} }
// Max 获取最大位置
func (this *PartialRanges) Max() int64 { func (this *PartialRanges) Max() int64 {
if len(this.Ranges) > 0 { if len(this.Ranges) > 0 {
return this.Ranges[len(this.Ranges)-1][1] return this.Ranges[len(this.Ranges)-1][1]
@@ -135,6 +183,11 @@ func (this *PartialRanges) Max() int64 {
return 0 return 0
} }
// Reset 重置范围信息
func (this *PartialRanges) Reset() {
this.Ranges = [][2]int64{}
}
func (this *PartialRanges) merge(index int) { func (this *PartialRanges) merge(index int) {
// forward // forward
var lastIndex = index var lastIndex = index
@@ -187,3 +240,7 @@ func (this *PartialRanges) max(n1 int64, n2 int64) int64 {
} }
return n2 return n2
} }
func (this *PartialRanges) formatInt64(i int64) string {
return strconv.FormatInt(i, 10)
}

View File

@@ -3,14 +3,16 @@
package caches_test package caches_test
import ( import (
"encoding/json"
"github.com/TeaOSLab/EdgeNode/internal/caches" "github.com/TeaOSLab/EdgeNode/internal/caches"
"github.com/iwind/TeaGo/assert" "github.com/iwind/TeaGo/assert"
"github.com/iwind/TeaGo/logs" "github.com/iwind/TeaGo/logs"
"testing" "testing"
"time"
) )
func TestNewPartialRanges(t *testing.T) { func TestNewPartialRanges(t *testing.T) {
var r = caches.NewPartialRanges() var r = caches.NewPartialRanges(0)
r.Add(1, 100) r.Add(1, 100)
r.Add(50, 300) r.Add(50, 300)
@@ -28,7 +30,7 @@ func TestNewPartialRanges(t *testing.T) {
func TestNewPartialRanges1(t *testing.T) { func TestNewPartialRanges1(t *testing.T) {
var a = assert.NewAssertion(t) var a = assert.NewAssertion(t)
var r = caches.NewPartialRanges() var r = caches.NewPartialRanges(0)
r.Add(1, 100) r.Add(1, 100)
r.Add(1, 101) r.Add(1, 101)
r.Add(1, 102) r.Add(1, 102)
@@ -47,7 +49,7 @@ func TestNewPartialRanges1(t *testing.T) {
func TestNewPartialRanges2(t *testing.T) { func TestNewPartialRanges2(t *testing.T) {
// low -> high // low -> high
var r = caches.NewPartialRanges() var r = caches.NewPartialRanges(0)
r.Add(1, 100) r.Add(1, 100)
r.Add(1, 101) r.Add(1, 101)
r.Add(1, 102) r.Add(1, 102)
@@ -63,7 +65,7 @@ func TestNewPartialRanges2(t *testing.T) {
func TestNewPartialRanges3(t *testing.T) { func TestNewPartialRanges3(t *testing.T) {
// high -> low // high -> low
var r = caches.NewPartialRanges() var r = caches.NewPartialRanges(0)
r.Add(301, 302) r.Add(301, 302)
r.Add(303, 304) r.Add(303, 304)
r.Add(200, 300) r.Add(200, 300)
@@ -75,7 +77,7 @@ func TestNewPartialRanges3(t *testing.T) {
func TestNewPartialRanges4(t *testing.T) { func TestNewPartialRanges4(t *testing.T) {
// nearby // nearby
var r = caches.NewPartialRanges() var r = caches.NewPartialRanges(0)
r.Add(301, 302) r.Add(301, 302)
r.Add(303, 304) r.Add(303, 304)
r.Add(305, 306) r.Add(305, 306)
@@ -90,7 +92,7 @@ func TestNewPartialRanges4(t *testing.T) {
} }
func TestNewPartialRanges5(t *testing.T) { func TestNewPartialRanges5(t *testing.T) {
var r = caches.NewPartialRanges() var r = caches.NewPartialRanges(0)
for j := 0; j < 1000; j++ { for j := 0; j < 1000; j++ {
r.Add(int64(j), int64(j+100)) r.Add(int64(j), int64(j+100))
} }
@@ -100,7 +102,7 @@ func TestNewPartialRanges5(t *testing.T) {
func TestNewPartialRanges_Nearest(t *testing.T) { func TestNewPartialRanges_Nearest(t *testing.T) {
{ {
// nearby // nearby
var r = caches.NewPartialRanges() var r = caches.NewPartialRanges(0)
r.Add(301, 400) r.Add(301, 400)
r.Add(401, 500) r.Add(401, 500)
r.Add(501, 600) r.Add(501, 600)
@@ -112,7 +114,7 @@ func TestNewPartialRanges_Nearest(t *testing.T) {
{ {
// nearby // nearby
var r = caches.NewPartialRanges() var r = caches.NewPartialRanges(0)
r.Add(301, 400) r.Add(301, 400)
r.Add(450, 500) r.Add(450, 500)
r.Add(550, 600) r.Add(550, 600)
@@ -131,45 +133,100 @@ func TestNewPartialRanges_Large_Range(t *testing.T) {
var largeSize int64 = 10000000000000 var largeSize int64 = 10000000000000
t.Log(largeSize/1024/1024/1024, "G") t.Log(largeSize/1024/1024/1024, "G")
var r = caches.NewPartialRanges() var r = caches.NewPartialRanges(0)
r.Add(1, largeSize) r.Add(1, largeSize)
jsonData, err := r.AsJSON() var s = r.String()
if err != nil { t.Log(s)
t.Fatal(err)
}
t.Log(string(jsonData))
r2, err := caches.NewPartialRangesFromJSON(jsonData) r2, err := caches.NewPartialRangesFromData([]byte(s))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
a.IsTrue(largeSize == r2.Ranges[0][1]) a.IsTrue(largeSize == r2.Ranges[0][1])
logs.PrintAsJSON(r, t)
} }
func TestNewPartialRanges_AsJSON(t *testing.T) { func TestPartialRanges_Encode_JSON(t *testing.T) {
var r = caches.NewPartialRanges() var r = caches.NewPartialRanges(0)
for j := 0; j < 1000; j++ { for i := 0; i < 10; i++ {
r.Add(int64(j), int64(j+100)) r.Ranges = append(r.Ranges, [2]int64{int64(i * 100), int64(i*100 + 100)})
} }
data, err := r.AsJSON() var before = time.Now()
data, err := json.Marshal(r)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
t.Log(string(data)) t.Log(time.Since(before).Seconds()*1000, "ms")
t.Log(len(data))
}
r2, err := caches.NewPartialRangesFromJSON(data) func TestPartialRanges_Encode_String(t *testing.T) {
var r = caches.NewPartialRanges(0)
r.BodySize = 1024
for i := 0; i < 10; i++ {
r.Ranges = append(r.Ranges, [2]int64{int64(i * 100), int64(i*100 + 100)})
}
var before = time.Now()
var data = r.String()
t.Log(time.Since(before).Seconds()*1000, "ms")
t.Log(len(data))
r2, err := caches.NewPartialRangesFromData([]byte(data))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
t.Log(r2.Ranges) logs.PrintAsJSON(r2, t)
}
func TestPartialRanges_Version(t *testing.T) {
{
ranges, err := caches.NewPartialRangesFromData([]byte(`e:1668928495
r:0,1048576
r:1140260864,1140295164`))
if err != nil {
t.Fatal(err)
}
t.Log("version:", ranges.Version)
}
{
ranges, err := caches.NewPartialRangesFromData([]byte(`e:1668928495
r:0,1048576
r:1140260864,1140295164
v:0
`))
if err != nil {
t.Fatal(err)
}
t.Log("version:", ranges.Version)
}
{
ranges, err := caches.NewPartialRangesFromJSON([]byte(`{}`))
if err != nil {
t.Fatal(err)
}
t.Log("version:", ranges.Version)
}
} }
func BenchmarkNewPartialRanges(b *testing.B) { func BenchmarkNewPartialRanges(b *testing.B) {
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
var r = caches.NewPartialRanges() var r = caches.NewPartialRanges(0)
for j := 0; j < 1000; j++ { for j := 0; j < 1000; j++ {
r.Add(int64(j), int64(j+100)) r.Add(int64(j), int64(j+100))
} }
} }
} }
func BenchmarkPartialRanges_String(b *testing.B) {
var r = caches.NewPartialRanges(0)
r.BodySize = 1024
for i := 0; i < 10; i++ {
r.Ranges = append(r.Ranges, [2]int64{int64(i * 100), int64(i*100 + 100)})
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = r.String()
}
}

View File

@@ -42,7 +42,7 @@ func (this *FileReader) InitAutoDiscard(autoDiscard bool) error {
this.header = this.openFile.header this.header = this.openFile.header
} }
isOk := false var isOk = false
if autoDiscard { if autoDiscard {
defer func() { defer func() {
@@ -67,17 +67,17 @@ func (this *FileReader) InitAutoDiscard(autoDiscard bool) error {
this.expiresAt = int64(binary.BigEndian.Uint32(buf[:SizeExpiresAt])) this.expiresAt = int64(binary.BigEndian.Uint32(buf[:SizeExpiresAt]))
status := types.Int(string(buf[OffsetStatus : OffsetStatus+SizeStatus])) var status = types.Int(string(buf[OffsetStatus : OffsetStatus+SizeStatus]))
if status < 100 || status > 999 { if status < 100 || status > 999 {
return errors.New("invalid status") return errors.New("invalid status")
} }
this.status = status this.status = status
// URL // URL
urlLength := binary.BigEndian.Uint32(buf[OffsetURLLength : OffsetURLLength+SizeURLLength]) var urlLength = binary.BigEndian.Uint32(buf[OffsetURLLength : OffsetURLLength+SizeURLLength])
// header // header
headerSize := int(binary.BigEndian.Uint32(buf[OffsetHeaderLength : OffsetHeaderLength+SizeHeaderLength])) var headerSize = int(binary.BigEndian.Uint32(buf[OffsetHeaderLength : OffsetHeaderLength+SizeHeaderLength]))
if headerSize == 0 { if headerSize == 0 {
return nil return nil
} }
@@ -86,7 +86,7 @@ func (this *FileReader) InitAutoDiscard(autoDiscard bool) error {
// body // body
this.bodyOffset = this.headerOffset + int64(headerSize) this.bodyOffset = this.headerOffset + int64(headerSize)
bodySize := int(binary.BigEndian.Uint64(buf[OffsetBodyLength : OffsetBodyLength+SizeBodyLength])) var bodySize = int(binary.BigEndian.Uint64(buf[OffsetBodyLength : OffsetBodyLength+SizeBodyLength]))
if bodySize == 0 { if bodySize == 0 {
isOk = true isOk = true
return nil return nil
@@ -158,7 +158,7 @@ func (this *FileReader) ReadHeader(buf []byte, callback ReaderFunc) error {
return nil return nil
} }
isOk := false var isOk = false
defer func() { defer func() {
if !isOk { if !isOk {
@@ -171,7 +171,7 @@ func (this *FileReader) ReadHeader(buf []byte, callback ReaderFunc) error {
return err return err
} }
headerSize := this.headerSize var headerSize = this.headerSize
for { for {
n, err := this.fp.Read(buf) n, err := this.fp.Read(buf)
@@ -215,7 +215,11 @@ func (this *FileReader) ReadHeader(buf []byte, callback ReaderFunc) error {
} }
func (this *FileReader) ReadBody(buf []byte, callback ReaderFunc) error { func (this *FileReader) ReadBody(buf []byte, callback ReaderFunc) error {
isOk := false if this.bodySize == 0 {
return nil
}
var isOk = false
defer func() { defer func() {
if !isOk { if !isOk {
@@ -257,15 +261,22 @@ func (this *FileReader) ReadBody(buf []byte, callback ReaderFunc) error {
} }
func (this *FileReader) Read(buf []byte) (n int, err error) { func (this *FileReader) Read(buf []byte) (n int, err error) {
if this.bodySize == 0 {
n = 0
err = io.EOF
return
}
n, err = this.fp.Read(buf) n, err = this.fp.Read(buf)
if err != nil && err != io.EOF { if err != nil && err != io.EOF {
_ = this.discard() _ = this.discard()
} }
return return
} }
func (this *FileReader) ReadBodyRange(buf []byte, start int64, end int64, callback ReaderFunc) error { func (this *FileReader) ReadBodyRange(buf []byte, start int64, end int64, callback ReaderFunc) error {
isOk := false var isOk = false
defer func() { defer func() {
if !isOk { if !isOk {
@@ -273,7 +284,7 @@ func (this *FileReader) ReadBodyRange(buf []byte, start int64, end int64, callba
} }
}() }()
offset := start var offset = start
if start < 0 { if start < 0 {
offset = this.bodyOffset + this.bodySize + end offset = this.bodyOffset + this.bodySize + end
end = this.bodyOffset + this.bodySize - 1 end = this.bodyOffset + this.bodySize - 1
@@ -296,7 +307,7 @@ func (this *FileReader) ReadBodyRange(buf []byte, start int64, end int64, callba
for { for {
n, err := this.fp.Read(buf) n, err := this.fp.Read(buf)
if n > 0 { if n > 0 {
n2 := int(end-offset) + 1 var n2 = int(end-offset) + 1
if n2 <= n { if n2 <= n {
_, e := callback(n2) _, e := callback(n2)
if e != nil { if e != nil {
@@ -344,12 +355,12 @@ func (this *FileReader) FP() *os.File {
} }
func (this *FileReader) Close() error { func (this *FileReader) Close() error {
if this.openFileCache != nil {
if this.isClosed { if this.isClosed {
return nil return nil
} }
this.isClosed = true this.isClosed = true
if this.openFileCache != nil {
if this.openFile != nil { if this.openFile != nil {
this.openFileCache.Put(this.fp.Name(), this.openFile) this.openFileCache.Put(this.fp.Name(), this.openFile)
} else { } else {
@@ -359,6 +370,7 @@ func (this *FileReader) Close() error {
} }
return nil return nil
} }
return this.fp.Close() return this.fp.Close()
} }

View File

@@ -19,7 +19,7 @@ func TestFileReader(t *testing.T) {
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
_, path := storage.keyPath("my-key") _, path, _ := storage.keyPath("my-key")
fp, err := os.Open(path) fp, err := os.Open(path)
if err != nil { if err != nil {
@@ -105,7 +105,7 @@ func TestFileReader_Range(t *testing.T) {
} }
_ = writer.Close()**/ _ = writer.Close()**/
_, path := storage.keyPath("my-number") _, path, _ := storage.keyPath("my-number")
fp, err := os.Open(path) fp, err := os.Open(path)
if err != nil { if err != nil {

View File

@@ -117,13 +117,10 @@ func (this *PartialFileReader) ContainsRange(r rangeutils.Range) (r2 rangeutils.
r2, ok = this.ranges.Nearest(r.Start(), r.End()) r2, ok = this.ranges.Nearest(r.Start(), r.End())
if ok && this.bodySize > 0 { if ok && this.bodySize > 0 {
// 考虑可配置 // 考虑可配置
var span int64 = 512 * 1024 const minSpan = 128 << 10
if this.bodySize > 1<<30 {
span = 1 << 20
}
// 这里限制返回的最小缓存,防止因为返回的内容过小而导致请求过多 // 这里限制返回的最小缓存,防止因为返回的内容过小而导致请求过多
if r2.Length() < r.Length() && r2.Length() < span { if r2.Length() < r.Length() && r2.Length() < minSpan {
ok = false ok = false
} }
} }
@@ -138,6 +135,10 @@ func (this *PartialFileReader) MaxLength() int64 {
return this.ranges.Max() + 1 return this.ranges.Max() + 1
} }
func (this *PartialFileReader) Ranges() *PartialRanges {
return this.ranges
}
func (this *PartialFileReader) discard() error { func (this *PartialFileReader) discard() error {
_ = os.Remove(this.rangePath) _ = os.Remove(this.rangePath)
return this.FileReader.discard() return this.FileReader.discard()

View File

@@ -21,6 +21,7 @@ import (
"github.com/iwind/TeaGo/rands" "github.com/iwind/TeaGo/rands"
"github.com/iwind/TeaGo/types" "github.com/iwind/TeaGo/types"
stringutil "github.com/iwind/TeaGo/utils/string" stringutil "github.com/iwind/TeaGo/utils/string"
"golang.org/x/sys/unix"
"golang.org/x/text/language" "golang.org/x/text/language"
"golang.org/x/text/message" "golang.org/x/text/message"
"math" "math"
@@ -49,7 +50,6 @@ const (
OffsetBodyLength = OffsetHeaderLength + SizeHeaderLength OffsetBodyLength = OffsetHeaderLength + SizeHeaderLength
SizeMeta = SizeExpiresAt + SizeStatus + SizeURLLength + SizeHeaderLength + SizeBodyLength SizeMeta = SizeExpiresAt + SizeStatus + SizeURLLength + SizeHeaderLength + SizeBodyLength
OffsetKey = SizeMeta
) )
const ( const (
@@ -58,6 +58,7 @@ const (
HotItemLifeSeconds int64 = 3600 // 热点数据生命周期 HotItemLifeSeconds int64 = 3600 // 热点数据生命周期
FileToMemoryMaxSize = 32 * sizes.M // 可以从文件写入到内存的最大文件尺寸 FileToMemoryMaxSize = 32 * sizes.M // 可以从文件写入到内存的最大文件尺寸
FileTmpSuffix = ".tmp" FileTmpSuffix = ".tmp"
MinDiskSpace = 5 << 30 // 当前磁盘最小剩余空间
) )
var sharedWritingFileKeyMap = map[string]zero.Zero{} // key => bool var sharedWritingFileKeyMap = map[string]zero.Zero{} // key => bool
@@ -90,6 +91,11 @@ type FileStorage struct {
ignoreKeys *setutils.FixedSet ignoreKeys *setutils.FixedSet
openFileCache *OpenFileCache openFileCache *OpenFileCache
mainDir string
mainDiskIsFull bool
subDirs []*FileDir
} }
func NewFileStorage(policy *serverconfigs.HTTPCachePolicy) *FileStorage { func NewFileStorage(policy *serverconfigs.HTTPCachePolicy) *FileStorage {
@@ -153,6 +159,16 @@ func (this *FileStorage) UpdatePolicy(newPolicy *serverconfigs.HTTPCachePolicy)
return return
} }
var subDirs = []*FileDir{}
for _, subDir := range newOptions.SubDirs {
subDirs = append(subDirs, &FileDir{
Path: subDir.Path,
Capacity: subDir.Capacity,
IsFull: false,
})
}
this.checkDiskSpace()
err = newOptions.Init() err = newOptions.Init()
if err != nil { if err != nil {
remotelogs.Error("CACHE", "update policy '"+types.String(this.policy.Id)+"' failed: init options failed: "+err.Error()) remotelogs.Error("CACHE", "update policy '"+types.String(this.policy.Id)+"' failed: init options failed: "+err.Error())
@@ -179,7 +195,7 @@ func (this *FileStorage) UpdatePolicy(newPolicy *serverconfigs.HTTPCachePolicy)
// open cache // open cache
oldOpenFileCacheJSON, _ := json.Marshal(oldOpenFileCache) oldOpenFileCacheJSON, _ := json.Marshal(oldOpenFileCache)
newOpenFileCacheJSON, _ := json.Marshal(this.options.OpenFileCache) newOpenFileCacheJSON, _ := json.Marshal(this.options.OpenFileCache)
if bytes.Compare(oldOpenFileCacheJSON, newOpenFileCacheJSON) != 0 { if !bytes.Equal(oldOpenFileCacheJSON, newOpenFileCacheJSON) {
this.initOpenFileCache() this.initOpenFileCache()
} }
@@ -215,6 +231,19 @@ func (this *FileStorage) Init() error {
this.options.Dir = filepath.Clean(this.options.Dir) this.options.Dir = filepath.Clean(this.options.Dir)
var dir = this.options.Dir var dir = this.options.Dir
var subDirs = []*FileDir{}
for _, subDir := range this.options.SubDirs {
subDirs = append(subDirs, &FileDir{
Path: subDir.Path,
Capacity: subDir.Capacity,
IsFull: false,
})
}
this.subDirs = subDirs
if len(subDirs) > 0 {
this.checkDiskSpace()
}
if len(dir) == 0 { if len(dir) == 0 {
return errors.New("[CACHE]cache storage dir can not be empty") return errors.New("[CACHE]cache storage dir can not be empty")
} }
@@ -287,6 +316,9 @@ func (this *FileStorage) Init() error {
// open file cache // open file cache
this.initOpenFileCache() this.initOpenFileCache()
// 检查磁盘空间
this.checkDiskSpace()
return nil return nil
} }
@@ -314,7 +346,7 @@ func (this *FileStorage) openReader(key string, allowMemory bool, useStale bool,
} }
} }
hash, path := this.keyPath(key) hash, path, _ := this.keyPath(key)
// 检查文件记录是否已过期 // 检查文件记录是否已过期
if !useStale { if !useStale {
@@ -382,16 +414,16 @@ func (this *FileStorage) openReader(key string, allowMemory bool, useStale bool,
} }
// OpenWriter 打开缓存文件等待写入 // OpenWriter 打开缓存文件等待写入
func (this *FileStorage) OpenWriter(key string, expiresAt int64, status int, size int64, maxSize int64, isPartial bool) (Writer, error) { func (this *FileStorage) OpenWriter(key string, expiresAt int64, status int, headerSize int, bodySize int64, maxSize int64, isPartial bool) (Writer, error) {
return this.openWriter(key, expiresAt, status, size, maxSize, isPartial, false) return this.openWriter(key, expiresAt, status, headerSize, bodySize, maxSize, isPartial, false)
} }
// OpenFlushWriter 打开从其他媒介直接刷入的写入器 // OpenFlushWriter 打开从其他媒介直接刷入的写入器
func (this *FileStorage) OpenFlushWriter(key string, expiresAt int64, status int) (Writer, error) { func (this *FileStorage) OpenFlushWriter(key string, expiresAt int64, status int, headerSize int, bodySize int64) (Writer, error) {
return this.openWriter(key, expiresAt, status, -1, -1, false, true) return this.openWriter(key, expiresAt, status, headerSize, bodySize, -1, false, true)
} }
func (this *FileStorage) openWriter(key string, expiredAt int64, status int, size int64, maxSize int64, isPartial bool, isFlushing bool) (Writer, error) { func (this *FileStorage) openWriter(key string, expiredAt int64, status int, headerSize int, bodySize int64, maxSize int64, isPartial bool, isFlushing bool) (Writer, error) {
// 是否正在退出 // 是否正在退出
if teaconst.IsQuiting { if teaconst.IsQuiting {
return nil, ErrWritingUnavailable return nil, ErrWritingUnavailable
@@ -409,8 +441,8 @@ func (this *FileStorage) openWriter(key string, expiredAt int64, status int, siz
maxMemorySize = maxSize maxMemorySize = maxSize
} }
var memoryStorage = this.memoryStorage var memoryStorage = this.memoryStorage
if !isFlushing && !isPartial && memoryStorage != nil && ((size > 0 && size < maxMemorySize) || size < 0) { if !isFlushing && !isPartial && memoryStorage != nil && ((bodySize > 0 && bodySize < maxMemorySize) || bodySize < 0) {
writer, err := memoryStorage.OpenWriter(key, expiredAt, status, size, maxMemorySize, false) writer, err := memoryStorage.OpenWriter(key, expiredAt, status, headerSize, bodySize, maxMemorySize, false)
if err == nil { if err == nil {
return writer, nil return writer, nil
} }
@@ -463,17 +495,9 @@ func (this *FileStorage) openWriter(key string, expiredAt int64, status int, siz
var hash = stringutil.Md5(key) var hash = stringutil.Md5(key)
// TODO 可以只stat一次 dir, diskIsFull := this.subDir(hash)
var dir = this.options.Dir + "/p" + strconv.FormatInt(this.policy.Id, 10) + "/" + hash[:2] + "/" + hash[2:4] if diskIsFull {
_, err = os.Stat(dir) return nil, NewCapacityError("the disk is full")
if err != nil {
if !os.IsNotExist(err) {
return nil, err
}
err = os.MkdirAll(dir, 0777)
if err != nil {
return nil, err
}
} }
// 检查缓存是否已经生成 // 检查缓存是否已经生成
@@ -520,20 +544,39 @@ func (this *FileStorage) openWriter(key string, expiredAt int64, status int, siz
// 从已经存储的内容中读取信息 // 从已经存储的内容中读取信息
var isNewCreated = true var isNewCreated = true
var partialBodyOffset int64 var partialBodyOffset int64
var partialRanges *PartialRanges
if isPartial { if isPartial {
// 数据库中是否存在
existsCacheItem, _ := this.list.Exist(hash)
if existsCacheItem {
readerFp, err := os.OpenFile(tmpPath, os.O_RDONLY, 0444) readerFp, err := os.OpenFile(tmpPath, os.O_RDONLY, 0444)
if err == nil { if err == nil {
var partialReader = NewPartialFileReader(readerFp) var partialReader = NewPartialFileReader(readerFp)
err = partialReader.Init() err = partialReader.Init()
_ = partialReader.Close() _ = partialReader.Close()
if err == nil && partialReader.bodyOffset > 0 { if err == nil && partialReader.bodyOffset > 0 {
partialRanges = partialReader.Ranges()
if bodySize > 0 && partialRanges != nil && partialRanges.BodySize > 0 && bodySize != partialRanges.BodySize {
_ = this.removeCacheFile(tmpPath)
} else {
isNewCreated = false isNewCreated = false
partialBodyOffset = partialReader.bodyOffset partialBodyOffset = partialReader.bodyOffset
}
} else { } else {
_ = this.removeCacheFile(tmpPath) _ = this.removeCacheFile(tmpPath)
} }
} }
} }
if isNewCreated {
err = this.list.Remove(hash)
if err != nil {
return nil, err
}
}
if partialRanges == nil {
partialRanges = NewPartialRanges(expiredAt)
}
}
var flags = os.O_CREATE | os.O_WRONLY var flags = os.O_CREATE | os.O_WRONLY
if isNewCreated && existsFile { if isNewCreated && existsFile {
@@ -541,9 +584,18 @@ func (this *FileStorage) openWriter(key string, expiredAt int64, status int, siz
} }
var before = time.Now() var before = time.Now()
writer, err := os.OpenFile(tmpPath, flags, 0666) writer, err := os.OpenFile(tmpPath, flags, 0666)
if err != nil {
// TODO 检查在各个系统中的稳定性
if os.IsNotExist(err) {
_ = os.MkdirAll(dir, 0777)
// open file again
writer, err = os.OpenFile(tmpPath, flags, 0666)
}
if err != nil { if err != nil {
return nil, err return nil, err
} }
}
if !isFlushing { if !isFlushing {
if time.Since(before) >= maxOpenFilesSlowCost { if time.Since(before) >= maxOpenFilesSlowCost {
maxOpenFiles.Slow() maxOpenFiles.Slow()
@@ -574,9 +626,12 @@ func (this *FileStorage) openWriter(key string, expiredAt int64, status int, siz
return nil, ErrFileIsWriting return nil, ErrFileIsWriting
} }
var metaBodySize int64 = -1
var metaHeaderSize = -1
if isNewCreated { if isNewCreated {
// 写入过期时间 // 写入meta
var metaBytes = make([]byte, SizeMeta+len(key)) // 从v0.5.8开始不再在meta中写入Key
var metaBytes = make([]byte, SizeMeta)
binary.BigEndian.PutUint32(metaBytes[OffsetExpiresAt:], uint32(expiredAt)) binary.BigEndian.PutUint32(metaBytes[OffsetExpiresAt:], uint32(expiredAt))
// 写入状态码 // 写入状态码
@@ -585,17 +640,17 @@ func (this *FileStorage) openWriter(key string, expiredAt int64, status int, siz
} }
copy(metaBytes[OffsetStatus:], strconv.Itoa(status)) copy(metaBytes[OffsetStatus:], strconv.Itoa(status))
// 写入URL长度
binary.BigEndian.PutUint32(metaBytes[OffsetURLLength:], uint32(len(key)))
// 写入Header Length // 写入Header Length
binary.BigEndian.PutUint32(metaBytes[OffsetHeaderLength:], uint32(0)) if headerSize > 0 {
binary.BigEndian.PutUint32(metaBytes[OffsetHeaderLength:], uint32(headerSize))
metaHeaderSize = headerSize
}
// 写入Body Length // 写入Body Length
binary.BigEndian.PutUint64(metaBytes[OffsetBodyLength:], uint64(0)) if bodySize > 0 {
binary.BigEndian.PutUint64(metaBytes[OffsetBodyLength:], uint64(bodySize))
// 写入URL metaBodySize = bodySize
copy(metaBytes[OffsetKey:], key) }
_, err = writer.Write(metaBytes) _, err = writer.Write(metaBytes)
if err != nil { if err != nil {
@@ -605,12 +660,7 @@ func (this *FileStorage) openWriter(key string, expiredAt int64, status int, siz
isOk = true isOk = true
if isPartial { if isPartial {
ranges, err := NewPartialRangesFromFile(cachePathName + "@ranges.cache") return NewPartialFileWriter(writer, key, expiredAt, metaHeaderSize, metaBodySize, isNewCreated, isPartial, partialBodyOffset, partialRanges, func() {
if err != nil {
ranges = NewPartialRanges()
}
return NewPartialFileWriter(writer, key, expiredAt, isNewCreated, isPartial, partialBodyOffset, ranges, func() {
sharedWritingFileKeyLocker.Lock() sharedWritingFileKeyLocker.Lock()
delete(sharedWritingFileKeyMap, key) delete(sharedWritingFileKeyMap, key)
if len(sharedWritingFileKeyMap) == 0 { if len(sharedWritingFileKeyMap) == 0 {
@@ -619,7 +669,7 @@ func (this *FileStorage) openWriter(key string, expiredAt int64, status int, siz
sharedWritingFileKeyLocker.Unlock() sharedWritingFileKeyLocker.Unlock()
}), nil }), nil
} else { } else {
return NewFileWriter(this, writer, key, expiredAt, -1, func() { return NewFileWriter(this, writer, key, expiredAt, metaHeaderSize, metaBodySize, -1, func() {
sharedWritingFileKeyLocker.Lock() sharedWritingFileKeyLocker.Lock()
delete(sharedWritingFileKeyMap, key) delete(sharedWritingFileKeyMap, key)
if len(sharedWritingFileKeyMap) == 0 { if len(sharedWritingFileKeyMap) == 0 {
@@ -646,7 +696,7 @@ func (this *FileStorage) AddToList(item *Item) {
} }
item.MetaSize = SizeMeta + 128 item.MetaSize = SizeMeta + 128
hash := stringutil.Md5(item.Key) var hash = stringutil.Md5(item.Key)
err := this.list.Add(hash, item) err := this.list.Add(hash, item)
if err != nil && !strings.Contains(err.Error(), "UNIQUE constraint failed") { if err != nil && !strings.Contains(err.Error(), "UNIQUE constraint failed") {
remotelogs.Error("CACHE", "add to list failed: "+err.Error()) remotelogs.Error("CACHE", "add to list failed: "+err.Error())
@@ -660,15 +710,12 @@ func (this *FileStorage) Delete(key string) error {
return nil return nil
} }
this.locker.Lock()
defer this.locker.Unlock()
// 先尝试内存缓存 // 先尝试内存缓存
this.runMemoryStorageSafety(func(memoryStorage *MemoryStorage) { this.runMemoryStorageSafety(func(memoryStorage *MemoryStorage) {
_ = memoryStorage.Delete(key) _ = memoryStorage.Delete(key)
}) })
hash, path := this.keyPath(key) hash, path, _ := this.keyPath(key)
err := this.list.Remove(hash) err := this.list.Remove(hash)
if err != nil { if err != nil {
return err return err
@@ -683,9 +730,6 @@ func (this *FileStorage) Delete(key string) error {
// Stat 统计 // Stat 统计
func (this *FileStorage) Stat() (*Stat, error) { func (this *FileStorage) Stat() (*Stat, error) {
this.locker.RLock()
defer this.locker.RUnlock()
return this.list.Stat(func(hash string) bool { return this.list.Stat(func(hash string) bool {
return true return true
}) })
@@ -708,7 +752,19 @@ func (this *FileStorage) CleanAll() error {
// 删除缓存和目录 // 删除缓存和目录
// 不能直接删除子目录,比较危险 // 不能直接删除子目录,比较危险
dir := this.dir()
var rootDirs = []string{this.options.Dir}
var subDirs = this.subDirs // copy slice
if len(subDirs) > 0 {
for _, subDir := range subDirs {
rootDirs = append(rootDirs, subDir.Path)
}
}
var dirNameReg = regexp.MustCompile(`^[0-9a-f]{2}$`)
for _, rootDir := range rootDirs {
var dir = rootDir + "/p" + types.String(this.policy.Id)
err = func(dir string) error {
fp, err := os.Open(dir) fp, err := os.Open(dir)
if err != nil { if err != nil {
return err return err
@@ -735,11 +791,7 @@ func (this *FileStorage) CleanAll() error {
subDir := info.Name() subDir := info.Name()
// 检查目录名 // 检查目录名
ok, err := regexp.MatchString(`^[0-9a-f]{2}$`, subDir) if !dirNameReg.MatchString(subDir) {
if err != nil {
return err
}
if !ok {
continue continue
} }
@@ -759,6 +811,13 @@ func (this *FileStorage) CleanAll() error {
} }
}) })
return nil
}(dir)
if err != nil {
return err
}
}
return nil return nil
} }
@@ -769,9 +828,6 @@ func (this *FileStorage) Purge(keys []string, urlType string) error {
return nil return nil
} }
this.locker.Lock()
defer this.locker.Unlock()
// 先尝试内存缓存 // 先尝试内存缓存
this.runMemoryStorageSafety(func(memoryStorage *MemoryStorage) { this.runMemoryStorageSafety(func(memoryStorage *MemoryStorage) {
_ = memoryStorage.Purge(keys, urlType) _ = memoryStorage.Purge(keys, urlType)
@@ -780,6 +836,19 @@ func (this *FileStorage) Purge(keys []string, urlType string) error {
// 目录 // 目录
if urlType == "dir" { if urlType == "dir" {
for _, key := range keys { for _, key := range keys {
// 检查是否有通配符 http(s)://*.example.com
var schemeIndex = strings.Index(key, "://")
if schemeIndex > 0 {
var keyRight = key[schemeIndex+3:]
if strings.HasPrefix(keyRight, "*.") {
err := this.list.CleanMatchPrefix(key)
if err != nil {
return err
}
continue
}
}
err := this.list.CleanPrefix(key) err := this.list.CleanPrefix(key)
if err != nil { if err != nil {
return err return err
@@ -790,7 +859,21 @@ func (this *FileStorage) Purge(keys []string, urlType string) error {
// URL // URL
for _, key := range keys { for _, key := range keys {
hash, path := this.keyPath(key) // 检查是否有通配符 http(s)://*.example.com
var schemeIndex = strings.Index(key, "://")
if schemeIndex > 0 {
var keyRight = key[schemeIndex+3:]
if strings.HasPrefix(keyRight, "*.") {
err := this.list.CleanMatchKey(key)
if err != nil {
return err
}
continue
}
}
// 普通的Key
hash, path, _ := this.keyPath(key)
err := this.removeCacheFile(path) err := this.removeCacheFile(path)
if err != nil && !os.IsNotExist(err) { if err != nil && !os.IsNotExist(err) {
return err return err
@@ -861,25 +944,22 @@ func (this *FileStorage) CanSendfile() bool {
return this.options.EnableSendfile return this.options.EnableSendfile
} }
// 绝对路径
func (this *FileStorage) dir() string {
return this.options.Dir + "/p" + strconv.FormatInt(this.policy.Id, 10) + "/"
}
// 获取Key对应的文件路径 // 获取Key对应的文件路径
func (this *FileStorage) keyPath(key string) (hash string, path string) { func (this *FileStorage) keyPath(key string) (hash string, path string, diskIsFull bool) {
hash = stringutil.Md5(key) hash = stringutil.Md5(key)
dir := this.options.Dir + "/p" + strconv.FormatInt(this.policy.Id, 10) + "/" + hash[:2] + "/" + hash[2:4] var dir string
dir, diskIsFull = this.subDir(hash)
path = dir + "/" + hash + ".cache" path = dir + "/" + hash + ".cache"
return return
} }
// 获取Hash对应的文件路径 // 获取Hash对应的文件路径
func (this *FileStorage) hashPath(hash string) (path string) { func (this *FileStorage) hashPath(hash string) (path string, diskIsFull bool) {
if len(hash) != 32 { if len(hash) != 32 {
return "" return "", false
} }
dir := this.options.Dir + "/p" + strconv.FormatInt(this.policy.Id, 10) + "/" + hash[:2] + "/" + hash[2:4] var dir string
dir, diskIsFull = this.subDir(hash)
path = dir + "/" + hash + ".cache" path = dir + "/" + hash + ".cache"
return return
} }
@@ -937,15 +1017,34 @@ func (this *FileStorage) initList() error {
} }
// 清理任务 // 清理任务
// TODO purge每个分区
func (this *FileStorage) purgeLoop() { func (this *FileStorage) purgeLoop() {
// 检查磁盘剩余空间
this.checkDiskSpace()
// 计算是否应该开启LFU清理 // 计算是否应该开启LFU清理
var capacityBytes = this.policy.CapacityBytes() var capacityBytes = this.diskCapacityBytes()
var startLFU = false var startLFU = false
var usedPercent = float32(this.TotalDiskSize()*100) / float32(capacityBytes)
var lfuFreePercent = this.policy.PersistenceLFUFreePercent var lfuFreePercent = this.policy.PersistenceLFUFreePercent
if lfuFreePercent <= 0 { if lfuFreePercent <= 0 {
lfuFreePercent = 5 lfuFreePercent = 5
} }
var hasFullDisk = this.mainDiskIsFull
if !hasFullDisk {
var subDirs = this.subDirs // copy slice
for _, subDir := range subDirs {
if subDir.IsFull {
hasFullDisk = true
break
}
}
}
if hasFullDisk {
startLFU = true
} else {
var usedPercent = float32(this.TotalDiskSize()*100) / float32(capacityBytes)
if capacityBytes > 0 { if capacityBytes > 0 {
if lfuFreePercent < 100 { if lfuFreePercent < 100 {
if usedPercent >= 100-lfuFreePercent { if usedPercent >= 100-lfuFreePercent {
@@ -953,6 +1052,7 @@ func (this *FileStorage) purgeLoop() {
} }
} }
} }
}
// 清理过期 // 清理过期
{ {
@@ -974,7 +1074,7 @@ func (this *FileStorage) purgeLoop() {
} }
for i := 0; i < times; i++ { for i := 0; i < times; i++ {
countFound, err := this.list.Purge(purgeCount, func(hash string) error { countFound, err := this.list.Purge(purgeCount, func(hash string) error {
path := this.hashPath(hash) path, _ := this.hashPath(hash)
err := this.removeCacheFile(path) err := this.removeCacheFile(path)
if err != nil && !os.IsNotExist(err) { if err != nil && !os.IsNotExist(err) {
remotelogs.Error("CACHE", "purge '"+path+"' error: "+err.Error()) remotelogs.Error("CACHE", "purge '"+path+"' error: "+err.Error())
@@ -1008,7 +1108,7 @@ func (this *FileStorage) purgeLoop() {
remotelogs.Println("CACHE", "LFU purge policy '"+this.policy.Name+"' id: "+types.String(this.policy.Id)+", count: "+types.String(count)) remotelogs.Println("CACHE", "LFU purge policy '"+this.policy.Name+"' id: "+types.String(this.policy.Id)+", count: "+types.String(count))
err := this.list.PurgeLFU(count, func(hash string) error { err := this.list.PurgeLFU(count, func(hash string) error {
path := this.hashPath(hash) path, _ := this.hashPath(hash)
err := this.removeCacheFile(path) err := this.removeCacheFile(path)
if err != nil && !os.IsNotExist(err) { if err != nil && !os.IsNotExist(err) {
remotelogs.Error("CACHE", "purge '"+path+"' error: "+err.Error()) remotelogs.Error("CACHE", "purge '"+path+"' error: "+err.Error())
@@ -1089,7 +1189,7 @@ func (this *FileStorage) hotLoop() {
expiresAt = bestExpiresAt expiresAt = bestExpiresAt
} }
writer, err := memoryStorage.openWriter(item.Key, expiresAt, reader.Status(), reader.BodySize(), -1, false) writer, err := memoryStorage.openWriter(item.Key, expiresAt, reader.Status(), types.Int(reader.HeaderSize()), reader.BodySize(), -1, false)
if err != nil { if err != nil {
if !CanIgnoreErr(err) { if !CanIgnoreErr(err) {
remotelogs.Error("CACHE", "transfer hot item failed: "+err.Error()) remotelogs.Error("CACHE", "transfer hot item failed: "+err.Error())
@@ -1113,9 +1213,12 @@ func (this *FileStorage) hotLoop() {
} }
err = reader.ReadBody(buf, func(n int) (goNext bool, err error) { err = reader.ReadBody(buf, func(n int) (goNext bool, err error) {
_, err = writer.Write(buf[:n])
if err == nil {
goNext = true goNext = true
if n > 0 {
_, err = writer.Write(buf[:n])
if err != nil {
goNext = false
}
} }
return return
}) })
@@ -1128,6 +1231,7 @@ func (this *FileStorage) hotLoop() {
memoryStorage.AddToList(&Item{ memoryStorage.AddToList(&Item{
Type: writer.ItemType(), Type: writer.ItemType(),
Key: item.Key, Key: item.Key,
Host: ParseHost(item.Key),
ExpiredAt: expiresAt, ExpiredAt: expiresAt,
HeaderSize: writer.HeaderSize(), HeaderSize: writer.HeaderSize(),
BodySize: writer.BodySize(), BodySize: writer.BodySize(),
@@ -1327,3 +1431,63 @@ func (this *FileStorage) runMemoryStorageSafety(f func(memoryStorage *MemoryStor
f(memoryStorage) f(memoryStorage)
} }
} }
// 检查磁盘剩余空间
func (this *FileStorage) checkDiskSpace() {
if this.options != nil && len(this.options.Dir) > 0 {
var stat unix.Statfs_t
err := unix.Statfs(this.options.Dir, &stat)
if err == nil {
var availableBytes = stat.Bavail * uint64(stat.Bsize)
this.mainDiskIsFull = availableBytes < MinDiskSpace
}
}
var subDirs = this.subDirs // copy slice
for _, subDir := range subDirs {
var stat unix.Statfs_t
err := unix.Statfs(subDir.Path, &stat)
if err == nil {
var availableBytes = stat.Bavail * uint64(stat.Bsize)
subDir.IsFull = availableBytes < MinDiskSpace
}
}
}
// 获取目录
func (this *FileStorage) subDir(hash string) (dirPath string, dirIsFull bool) {
var suffix = "/p" + types.String(this.policy.Id) + "/" + hash[:2] + "/" + hash[2:4]
if len(hash) < 4 {
return this.options.Dir + suffix, this.mainDiskIsFull
}
var subDirs = this.subDirs // copy slice
var countSubDirs = len(subDirs)
if countSubDirs == 0 {
return this.options.Dir + suffix, this.mainDiskIsFull
}
countSubDirs++ // add main dir
// 最多只支持16个目录
if countSubDirs > 16 {
countSubDirs = 16
}
var dirIndex = this.charCode(hash[0]) % uint8(countSubDirs)
if dirIndex == 0 {
return this.options.Dir + suffix, this.mainDiskIsFull
}
var subDir = subDirs[dirIndex-1]
return subDir.Path + suffix, subDir.IsFull
}
func (this *FileStorage) charCode(r byte) uint8 {
if r >= '0' && r <= '9' {
return r - '0'
}
if r >= 'a' && r <= 'z' {
return r - 'a' + 10
}
return 0
}

View File

@@ -62,7 +62,7 @@ func TestFileStorage_OpenWriter(t *testing.T) {
header := []byte("Header") header := []byte("Header")
body := []byte("This is Body") body := []byte("This is Body")
writer, err := storage.OpenWriter("my-key", time.Now().Unix()+86400, 200, -1, -1, false) writer, err := storage.OpenWriter("my-key", time.Now().Unix()+86400, 200, -1, -1, -1, false)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@@ -100,7 +100,7 @@ func TestFileStorage_OpenWriter_Partial(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
writer, err := storage.OpenWriter("my-key", time.Now().Unix()+86400, 200, -1, -1, true) writer, err := storage.OpenWriter("my-key", time.Now().Unix()+86400, 200, -1, -1, -1, true)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@@ -139,7 +139,7 @@ func TestFileStorage_OpenWriter_HTTP(t *testing.T) {
t.Log(time.Since(now).Seconds()*1000, "ms") t.Log(time.Since(now).Seconds()*1000, "ms")
}() }()
writer, err := storage.OpenWriter("my-http-response", time.Now().Unix()+86400, 200, -1, -1, false) writer, err := storage.OpenWriter("my-http-response", time.Now().Unix()+86400, 200, -1, -1, -1, false)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@@ -212,7 +212,7 @@ func TestFileStorage_Concurrent_Open_DifferentFile(t *testing.T) {
go func(i int) { go func(i int) {
defer wg.Done() defer wg.Done()
writer, err := storage.OpenWriter("abc"+strconv.Itoa(i), time.Now().Unix()+3600, 200, -1, -1, false) writer, err := storage.OpenWriter("abc"+strconv.Itoa(i), time.Now().Unix()+3600, 200, -1, -1, -1, false)
if err != nil { if err != nil {
if err != ErrFileIsWriting { if err != ErrFileIsWriting {
t.Error(err) t.Error(err)
@@ -267,7 +267,7 @@ func TestFileStorage_Concurrent_Open_SameFile(t *testing.T) {
go func(i int) { go func(i int) {
defer wg.Done() defer wg.Done()
writer, err := storage.OpenWriter("abc"+strconv.Itoa(0), time.Now().Unix()+3600, 200, -1, -1, false) writer, err := storage.OpenWriter("abc"+strconv.Itoa(0), time.Now().Unix()+3600, 200, -1, -1, -1, false)
if err != nil { if err != nil {
if err != ErrFileIsWriting { if err != ErrFileIsWriting {
t.Error(err) t.Error(err)
@@ -522,7 +522,7 @@ func TestFileStorage_DecodeFile(t *testing.T) {
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
_, path := storage.keyPath("my-key") _, path, _ := storage.keyPath("my-key")
t.Log(path) t.Log(path)
} }
@@ -569,6 +569,6 @@ func BenchmarkFileStorage_KeyPath(b *testing.B) {
} }
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
_, _ = storage.keyPath(strconv.Itoa(i)) _, _, _ = storage.keyPath(strconv.Itoa(i))
} }
} }

View File

@@ -14,10 +14,10 @@ type StorageInterface interface {
// OpenWriter 打开缓存写入器等待写入 // OpenWriter 打开缓存写入器等待写入
// size 和 maxSize 可能为-1 // size 和 maxSize 可能为-1
OpenWriter(key string, expiresAt int64, status int, size int64, maxSize int64, isPartial bool) (Writer, error) OpenWriter(key string, expiresAt int64, status int, headerSize int, bodySize int64, maxSize int64, isPartial bool) (Writer, error)
// OpenFlushWriter 打开从其他媒介直接刷入的写入器 // OpenFlushWriter 打开从其他媒介直接刷入的写入器
OpenFlushWriter(key string, expiresAt int64, status int) (Writer, error) OpenFlushWriter(key string, expiresAt int64, status int, headerSize int, bodySize int64) (Writer, error)
// Delete 删除某个键值对应的缓存 // Delete 删除某个键值对应的缓存
Delete(key string) error Delete(key string) error

View File

@@ -1,12 +1,12 @@
package caches package caches
import ( import (
"fmt"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs" "github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
"github.com/TeaOSLab/EdgeNode/internal/goman" "github.com/TeaOSLab/EdgeNode/internal/goman"
"github.com/TeaOSLab/EdgeNode/internal/remotelogs" "github.com/TeaOSLab/EdgeNode/internal/remotelogs"
"github.com/TeaOSLab/EdgeNode/internal/trackers" "github.com/TeaOSLab/EdgeNode/internal/trackers"
"github.com/TeaOSLab/EdgeNode/internal/utils" "github.com/TeaOSLab/EdgeNode/internal/utils"
"github.com/TeaOSLab/EdgeNode/internal/utils/fasttime"
setutils "github.com/TeaOSLab/EdgeNode/internal/utils/sets" setutils "github.com/TeaOSLab/EdgeNode/internal/utils/sets"
"github.com/TeaOSLab/EdgeNode/internal/utils/sizes" "github.com/TeaOSLab/EdgeNode/internal/utils/sizes"
"github.com/TeaOSLab/EdgeNode/internal/zero" "github.com/TeaOSLab/EdgeNode/internal/zero"
@@ -17,6 +17,7 @@ import (
"math" "math"
"runtime" "runtime"
"strconv" "strconv"
"strings"
"sync" "sync"
"sync/atomic" "sync/atomic"
"time" "time"
@@ -32,7 +33,7 @@ type MemoryItem struct {
} }
func (this *MemoryItem) IsExpired() bool { func (this *MemoryItem) IsExpired() bool {
return this.ExpiresAt < utils.UnixTime() return this.ExpiresAt < fasttime.Now().Unix()
} }
type MemoryStorage struct { type MemoryStorage struct {
@@ -119,7 +120,7 @@ func (this *MemoryStorage) OpenReader(key string, useStale bool, isPartial bool)
return nil, ErrNotFound return nil, ErrNotFound
} }
if useStale || (item.ExpiresAt > utils.UnixTime()) { if useStale || (item.ExpiresAt > fasttime.Now().Unix()) {
reader := NewMemoryReader(item) reader := NewMemoryReader(item)
err := reader.Init() err := reader.Init()
if err != nil { if err != nil {
@@ -149,7 +150,7 @@ func (this *MemoryStorage) OpenReader(key string, useStale bool, isPartial bool)
} }
// OpenWriter 打开缓存写入器等待写入 // OpenWriter 打开缓存写入器等待写入
func (this *MemoryStorage) OpenWriter(key string, expiredAt int64, status int, size int64, maxSize int64, isPartial bool) (Writer, error) { func (this *MemoryStorage) OpenWriter(key string, expiredAt int64, status int, headerSize int, bodySize int64, maxSize int64, isPartial bool) (Writer, error) {
if this.ignoreKeys.Has(key) { if this.ignoreKeys.Has(key) {
return nil, ErrEntityTooLarge return nil, ErrEntityTooLarge
} }
@@ -158,15 +159,15 @@ func (this *MemoryStorage) OpenWriter(key string, expiredAt int64, status int, s
if isPartial { if isPartial {
return nil, ErrFileIsWriting return nil, ErrFileIsWriting
} }
return this.openWriter(key, expiredAt, status, size, maxSize, true) return this.openWriter(key, expiredAt, status, headerSize, bodySize, maxSize, true)
} }
// OpenFlushWriter 打开从其他媒介直接刷入的写入器 // OpenFlushWriter 打开从其他媒介直接刷入的写入器
func (this *MemoryStorage) OpenFlushWriter(key string, expiresAt int64, status int) (Writer, error) { func (this *MemoryStorage) OpenFlushWriter(key string, expiresAt int64, status int, headerSize int, bodySize int64) (Writer, error) {
return this.openWriter(key, expiresAt, status, -1, -1, true) return this.openWriter(key, expiresAt, status, headerSize, bodySize, -1, true)
} }
func (this *MemoryStorage) openWriter(key string, expiresAt int64, status int, size int64, maxSize int64, isDirty bool) (Writer, error) { func (this *MemoryStorage) openWriter(key string, expiresAt int64, status int, headerSize int, bodySize int64, maxSize int64, isDirty bool) (Writer, error) {
// 待写入队列是否已满 // 待写入队列是否已满
if isDirty && if isDirty &&
this.parentStorage != nil && this.parentStorage != nil &&
@@ -207,10 +208,10 @@ func (this *MemoryStorage) openWriter(key string, expiresAt int64, status int, s
return nil, NewCapacityError("write memory cache failed: too many keys in cache storage") return nil, NewCapacityError("write memory cache failed: too many keys in cache storage")
} }
capacityBytes := this.memoryCapacityBytes() capacityBytes := this.memoryCapacityBytes()
if size < 0 { if bodySize < 0 {
size = 0 bodySize = 0
} }
if capacityBytes > 0 && capacityBytes <= this.totalSize+size { if capacityBytes > 0 && capacityBytes <= this.totalSize+bodySize {
return nil, NewCapacityError("write memory cache failed: over memory size: " + strconv.FormatInt(capacityBytes, 10) + ", current size: " + strconv.FormatInt(this.totalSize, 10) + " bytes") return nil, NewCapacityError("write memory cache failed: over memory size: " + strconv.FormatInt(capacityBytes, 10) + ", current size: " + strconv.FormatInt(this.totalSize, 10) + " bytes")
} }
@@ -230,10 +231,10 @@ func (this *MemoryStorage) openWriter(key string, expiresAt int64, status int, s
// Delete 删除某个键值对应的缓存 // Delete 删除某个键值对应的缓存
func (this *MemoryStorage) Delete(key string) error { func (this *MemoryStorage) Delete(key string) error {
hash := this.hash(key) var hash = this.hash(key)
this.locker.Lock() this.locker.Lock()
delete(this.valuesMap, hash) delete(this.valuesMap, hash)
_ = this.list.Remove(fmt.Sprintf("%d", hash)) _ = this.list.Remove(types.String(hash))
this.locker.Unlock() this.locker.Unlock()
return nil return nil
} }
@@ -263,6 +264,19 @@ func (this *MemoryStorage) Purge(keys []string, urlType string) error {
// 目录 // 目录
if urlType == "dir" { if urlType == "dir" {
for _, key := range keys { for _, key := range keys {
// 检查是否有通配符 http(s)://*.example.com
var schemeIndex = strings.Index(key, "://")
if schemeIndex > 0 {
var keyRight = key[schemeIndex+3:]
if strings.HasPrefix(keyRight, "*.") {
err := this.list.CleanMatchPrefix(key)
if err != nil {
return err
}
continue
}
}
err := this.list.CleanPrefix(key) err := this.list.CleanPrefix(key)
if err != nil { if err != nil {
return err return err
@@ -273,6 +287,19 @@ func (this *MemoryStorage) Purge(keys []string, urlType string) error {
// URL // URL
for _, key := range keys { for _, key := range keys {
// 检查是否有通配符 http(s)://*.example.com
var schemeIndex = strings.Index(key, "://")
if schemeIndex > 0 {
var keyRight = key[schemeIndex+3:]
if strings.HasPrefix(keyRight, "*.") {
err := this.list.CleanMatchKey(key)
if err != nil {
return err
}
continue
}
}
err := this.Delete(key) err := this.Delete(key)
if err != nil { if err != nil {
return err return err
@@ -336,7 +363,12 @@ func (this *MemoryStorage) CanUpdatePolicy(newPolicy *serverconfigs.HTTPCachePol
// AddToList 将缓存添加到列表 // AddToList 将缓存添加到列表
func (this *MemoryStorage) AddToList(item *Item) { func (this *MemoryStorage) AddToList(item *Item) {
item.MetaSize = int64(len(item.Key)) + 128 /** 128是我们评估的数据结构的长度 **/ item.MetaSize = int64(len(item.Key)) + 128 /** 128是我们评估的数据结构的长度 **/
hash := fmt.Sprintf("%d", this.hash(item.Key)) var hash = types.String(this.hash(item.Key))
if len(item.Host) == 0 {
item.Host = ParseHost(item.Key)
}
_ = this.list.Add(hash, item) _ = this.list.Add(hash, item)
} }
@@ -433,7 +465,7 @@ func (this *MemoryStorage) startFlush() {
var statCount = 0 var statCount = 0
var writeDelayMS float64 = 0 var writeDelayMS float64 = 0
for hash := range this.dirtyChan { for key := range this.dirtyChan {
statCount++ statCount++
if statCount == 100 { if statCount == 100 {
@@ -455,7 +487,7 @@ func (this *MemoryStorage) startFlush() {
} }
} }
this.flushItem(hash) this.flushItem(key)
if writeDelayMS > 0 { if writeDelayMS > 0 {
time.Sleep(time.Duration(writeDelayMS) * time.Millisecond) time.Sleep(time.Duration(writeDelayMS) * time.Millisecond)
@@ -477,11 +509,15 @@ func (this *MemoryStorage) flushItem(key string) {
if !ok { if !ok {
return return
} }
if !item.IsDone || item.IsExpired() { if !item.IsDone {
remotelogs.Error("CACHE", "flush items failed: open writer failed: item has not been done")
return
}
if item.IsExpired() {
return return
} }
writer, err := this.parentStorage.OpenFlushWriter(key, item.ExpiresAt, item.Status) writer, err := this.parentStorage.OpenFlushWriter(key, item.ExpiresAt, item.Status, len(item.HeaderValue), int64(len(item.BodyValue)))
if err != nil { if err != nil {
if !CanIgnoreErr(err) { if !CanIgnoreErr(err) {
remotelogs.Error("CACHE", "flush items failed: open writer failed: "+err.Error()) remotelogs.Error("CACHE", "flush items failed: open writer failed: "+err.Error())
@@ -513,6 +549,7 @@ func (this *MemoryStorage) flushItem(key string) {
this.parentStorage.AddToList(&Item{ this.parentStorage.AddToList(&Item{
Type: writer.ItemType(), Type: writer.ItemType(),
Key: key, Key: key,
Host: ParseHost(key),
ExpiredAt: item.ExpiresAt, ExpiredAt: item.ExpiresAt,
HeaderSize: writer.HeaderSize(), HeaderSize: writer.HeaderSize(),
BodySize: writer.BodySize(), BodySize: writer.BodySize(),
@@ -520,8 +557,6 @@ func (this *MemoryStorage) flushItem(key string) {
// 从内存中移除 // 从内存中移除
_ = this.Delete(key) _ = this.Delete(key)
return
} }
func (this *MemoryStorage) memoryCapacityBytes() int64 { func (this *MemoryStorage) memoryCapacityBytes() int64 {
@@ -544,7 +579,7 @@ func (this *MemoryStorage) memoryCapacityBytes() int64 {
func (this *MemoryStorage) deleteWithoutLocker(key string) error { func (this *MemoryStorage) deleteWithoutLocker(key string) error {
hash := this.hash(key) hash := this.hash(key)
delete(this.valuesMap, hash) delete(this.valuesMap, hash)
_ = this.list.Remove(fmt.Sprintf("%d", hash)) _ = this.list.Remove(types.String(hash))
return nil return nil
} }

View File

@@ -14,15 +14,22 @@ import (
) )
func TestMemoryStorage_OpenWriter(t *testing.T) { func TestMemoryStorage_OpenWriter(t *testing.T) {
storage := NewMemoryStorage(&serverconfigs.HTTPCachePolicy{}, nil) var storage = NewMemoryStorage(&serverconfigs.HTTPCachePolicy{}, nil)
writer, err := storage.OpenWriter("abc", time.Now().Unix()+60, 200, -1, -1, false) writer, err := storage.OpenWriter("abc", time.Now().Unix()+60, 200, -1, -1, -1, false)
if err != nil {
t.Fatal(err)
}
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
_, _ = writer.WriteHeader([]byte("Header")) _, _ = writer.WriteHeader([]byte("Header"))
_, _ = writer.Write([]byte("Hello")) _, _ = writer.Write([]byte("Hello"))
_, _ = writer.Write([]byte(", World")) _, _ = writer.Write([]byte(", World"))
err = writer.Close()
if err != nil {
t.Fatal(err)
}
t.Log(storage.valuesMap) t.Log(storage.valuesMap)
{ {
@@ -30,6 +37,7 @@ func TestMemoryStorage_OpenWriter(t *testing.T) {
if err != nil { if err != nil {
if err == ErrNotFound { if err == ErrNotFound {
t.Log("not found: abc") t.Log("not found: abc")
return
} else { } else {
t.Fatal(err) t.Fatal(err)
} }
@@ -63,7 +71,7 @@ func TestMemoryStorage_OpenWriter(t *testing.T) {
} }
} }
writer, err = storage.OpenWriter("abc", time.Now().Unix()+60, 200, -1, -1, false) writer, err = storage.OpenWriter("abc", time.Now().Unix()+60, 200, -1, -1, -1, false)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@@ -102,21 +110,29 @@ func TestMemoryStorage_OpenReaderLock(t *testing.T) {
} }
func TestMemoryStorage_Delete(t *testing.T) { func TestMemoryStorage_Delete(t *testing.T) {
storage := NewMemoryStorage(&serverconfigs.HTTPCachePolicy{}, nil) var storage = NewMemoryStorage(&serverconfigs.HTTPCachePolicy{}, nil)
{ {
writer, err := storage.OpenWriter("abc", time.Now().Unix()+60, 200, -1, -1, false) writer, err := storage.OpenWriter("abc", time.Now().Unix()+60, 200, -1, -1, -1, false)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
_, _ = writer.Write([]byte("Hello")) _, _ = writer.Write([]byte("Hello"))
err = writer.Close()
if err != nil {
t.Fatal(err)
}
t.Log(len(storage.valuesMap)) t.Log(len(storage.valuesMap))
} }
{ {
writer, err := storage.OpenWriter("abc1", time.Now().Unix()+60, 200, -1, -1, false) writer, err := storage.OpenWriter("abc1", time.Now().Unix()+60, 200, -1, -1, -1, false)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
_, _ = writer.Write([]byte("Hello")) _, _ = writer.Write([]byte("Hello"))
err = writer.Close()
if err != nil {
t.Fatal(err)
}
t.Log(len(storage.valuesMap)) t.Log(len(storage.valuesMap))
} }
_ = storage.Delete("abc1") _ = storage.Delete("abc1")
@@ -124,14 +140,18 @@ func TestMemoryStorage_Delete(t *testing.T) {
} }
func TestMemoryStorage_Stat(t *testing.T) { func TestMemoryStorage_Stat(t *testing.T) {
storage := NewMemoryStorage(&serverconfigs.HTTPCachePolicy{}, nil) var storage = NewMemoryStorage(&serverconfigs.HTTPCachePolicy{}, nil)
expiredAt := time.Now().Unix() + 60 expiredAt := time.Now().Unix() + 60
{ {
writer, err := storage.OpenWriter("abc", expiredAt, 200, -1, -1, false) writer, err := storage.OpenWriter("abc", expiredAt, 200, -1, -1, -1, false)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
_, _ = writer.Write([]byte("Hello")) _, _ = writer.Write([]byte("Hello"))
err = writer.Close()
if err != nil {
t.Fatal(err)
}
t.Log(len(storage.valuesMap)) t.Log(len(storage.valuesMap))
storage.AddToList(&Item{ storage.AddToList(&Item{
Key: "abc", Key: "abc",
@@ -140,11 +160,15 @@ func TestMemoryStorage_Stat(t *testing.T) {
}) })
} }
{ {
writer, err := storage.OpenWriter("abc1", expiredAt, 200, -1, -1, false) writer, err := storage.OpenWriter("abc1", expiredAt, 200, -1, -1, -1, false)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
_, _ = writer.Write([]byte("Hello")) _, _ = writer.Write([]byte("Hello"))
err = writer.Close()
if err != nil {
t.Fatal(err)
}
t.Log(len(storage.valuesMap)) t.Log(len(storage.valuesMap))
storage.AddToList(&Item{ storage.AddToList(&Item{
Key: "abc1", Key: "abc1",
@@ -161,14 +185,18 @@ func TestMemoryStorage_Stat(t *testing.T) {
} }
func TestMemoryStorage_CleanAll(t *testing.T) { func TestMemoryStorage_CleanAll(t *testing.T) {
storage := NewMemoryStorage(&serverconfigs.HTTPCachePolicy{}, nil) var storage = NewMemoryStorage(&serverconfigs.HTTPCachePolicy{}, nil)
expiredAt := time.Now().Unix() + 60 var expiredAt = time.Now().Unix() + 60
{ {
writer, err := storage.OpenWriter("abc", expiredAt, 200, -1, -1, false) writer, err := storage.OpenWriter("abc", expiredAt, 200, -1, -1, -1, false)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
_, _ = writer.Write([]byte("Hello")) _, _ = writer.Write([]byte("Hello"))
err = writer.Close()
if err != nil {
t.Fatal(err)
}
storage.AddToList(&Item{ storage.AddToList(&Item{
Key: "abc", Key: "abc",
BodySize: 5, BodySize: 5,
@@ -176,11 +204,15 @@ func TestMemoryStorage_CleanAll(t *testing.T) {
}) })
} }
{ {
writer, err := storage.OpenWriter("abc1", expiredAt, 200, -1, -1, false) writer, err := storage.OpenWriter("abc1", expiredAt, 200, -1, -1, -1, false)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
_, _ = writer.Write([]byte("Hello")) _, _ = writer.Write([]byte("Hello"))
err = writer.Close()
if err != nil {
t.Fatal(err)
}
storage.AddToList(&Item{ storage.AddToList(&Item{
Key: "abc1", Key: "abc1",
BodySize: 5, BodySize: 5,
@@ -199,11 +231,15 @@ func TestMemoryStorage_Purge(t *testing.T) {
storage := NewMemoryStorage(&serverconfigs.HTTPCachePolicy{}, nil) storage := NewMemoryStorage(&serverconfigs.HTTPCachePolicy{}, nil)
expiredAt := time.Now().Unix() + 60 expiredAt := time.Now().Unix() + 60
{ {
writer, err := storage.OpenWriter("abc", expiredAt, 200, -1, -1, false) writer, err := storage.OpenWriter("abc", expiredAt, 200, -1, -1, -1, false)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
_, _ = writer.Write([]byte("Hello")) _, _ = writer.Write([]byte("Hello"))
err = writer.Close()
if err != nil {
t.Fatal(err)
}
storage.AddToList(&Item{ storage.AddToList(&Item{
Key: "abc", Key: "abc",
BodySize: 5, BodySize: 5,
@@ -211,11 +247,15 @@ func TestMemoryStorage_Purge(t *testing.T) {
}) })
} }
{ {
writer, err := storage.OpenWriter("abc1", expiredAt, 200, -1, -1, false) writer, err := storage.OpenWriter("abc1", expiredAt, 200, -1, -1, -1, false)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
_, _ = writer.Write([]byte("Hello")) _, _ = writer.Write([]byte("Hello"))
err = writer.Close()
if err != nil {
t.Fatal(err)
}
storage.AddToList(&Item{ storage.AddToList(&Item{
Key: "abc1", Key: "abc1",
BodySize: 5, BodySize: 5,
@@ -231,7 +271,7 @@ func TestMemoryStorage_Purge(t *testing.T) {
} }
func TestMemoryStorage_Expire(t *testing.T) { func TestMemoryStorage_Expire(t *testing.T) {
storage := NewMemoryStorage(&serverconfigs.HTTPCachePolicy{ var storage = NewMemoryStorage(&serverconfigs.HTTPCachePolicy{
MemoryAutoPurgeInterval: 5, MemoryAutoPurgeInterval: 5,
}, nil) }, nil)
err := storage.Init() err := storage.Init()
@@ -242,11 +282,15 @@ func TestMemoryStorage_Expire(t *testing.T) {
for i := 0; i < 1000; i++ { for i := 0; i < 1000; i++ {
expiredAt := time.Now().Unix() + int64(rands.Int(0, 60)) expiredAt := time.Now().Unix() + int64(rands.Int(0, 60))
key := "abc" + strconv.Itoa(i) key := "abc" + strconv.Itoa(i)
writer, err := storage.OpenWriter(key, expiredAt, 200, -1, -1, false) writer, err := storage.OpenWriter(key, expiredAt, 200, -1, -1, -1, false)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
_, _ = writer.Write([]byte("Hello")) _, _ = writer.Write([]byte("Hello"))
err = writer.Close()
if err != nil {
t.Fatal(err)
}
storage.AddToList(&Item{ storage.AddToList(&Item{
Key: key, Key: key,
BodySize: 5, BodySize: 5,
@@ -257,7 +301,7 @@ func TestMemoryStorage_Expire(t *testing.T) {
} }
func TestMemoryStorage_Locker(t *testing.T) { func TestMemoryStorage_Locker(t *testing.T) {
storage := NewMemoryStorage(&serverconfigs.HTTPCachePolicy{}, nil) var storage = NewMemoryStorage(&serverconfigs.HTTPCachePolicy{}, nil)
err := storage.Init() err := storage.Init()
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)

30
internal/caches/utils.go Normal file
View File

@@ -0,0 +1,30 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package caches
import (
"github.com/TeaOSLab/EdgeCommon/pkg/configutils"
"net"
"strings"
)
func ParseHost(key string) string {
var schemeIndex = strings.Index(key, "://")
if schemeIndex <= 0 {
return ""
}
var firstSlashIndex = strings.Index(key[schemeIndex+3:], "/")
if firstSlashIndex <= 0 {
return ""
}
var host = key[schemeIndex+3 : schemeIndex+3+firstSlashIndex]
hostPart, _, err := net.SplitHostPort(host)
if err == nil && len(hostPart) > 0 {
host = configutils.QuoteIP(hostPart)
}
return host
}

View File

@@ -0,0 +1,51 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package caches_test
import (
"fmt"
"github.com/TeaOSLab/EdgeNode/internal/caches"
"github.com/cespare/xxhash"
"github.com/iwind/TeaGo/types"
"strconv"
"testing"
)
func TestParseHost(t *testing.T) {
for _, u := range []string{
"https://goedge.cn/hello/world",
"https://goedge.cn:8080/hello/world",
"https://goedge.cn/hello/world?v=1&t=123",
"https://[::1]:1234/hello/world?v=1&t=123",
"https://[::1]/hello/world?v=1&t=123",
"https://127.0.0.1/hello/world?v=1&t=123",
"https:/hello/world?v=1&t=123",
"123456",
} {
t.Log(u, "=>", caches.ParseHost(u))
}
}
func TestUintString(t *testing.T) {
t.Log(strconv.FormatUint(xxhash.Sum64String("https://goedge.cn/"), 10))
t.Log(strconv.FormatUint(123456789, 10))
t.Log(fmt.Sprintf("%d", 1234567890123))
}
func BenchmarkUint_String(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = strconv.FormatUint(1234567890123, 10)
}
}
func BenchmarkUint_String2(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = types.String(1234567890123)
}
}
func BenchmarkUint_String3(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = fmt.Sprintf("%d", 1234567890123)
}
}

View File

@@ -14,15 +14,20 @@ type FileWriter struct {
storage StorageInterface storage StorageInterface
rawWriter *os.File rawWriter *os.File
key string key string
metaHeaderSize int
headerSize int64 headerSize int64
metaBodySize int64 // 写入前的内容长度
bodySize int64 bodySize int64
expiredAt int64 expiredAt int64
maxSize int64 maxSize int64
endFunc func() endFunc func()
once sync.Once once sync.Once
} }
func NewFileWriter(storage StorageInterface, rawWriter *os.File, key string, expiredAt int64, maxSize int64, endFunc func()) *FileWriter { func NewFileWriter(storage StorageInterface, rawWriter *os.File, key string, expiredAt int64, metaHeaderSize int, metaBodySize int64, maxSize int64, endFunc func()) *FileWriter {
return &FileWriter{ return &FileWriter{
storage: storage, storage: storage,
key: key, key: key,
@@ -30,6 +35,8 @@ func NewFileWriter(storage StorageInterface, rawWriter *os.File, key string, exp
expiredAt: expiredAt, expiredAt: expiredAt,
maxSize: maxSize, maxSize: maxSize,
endFunc: endFunc, endFunc: endFunc,
metaHeaderSize: metaHeaderSize,
metaBodySize: metaBodySize,
} }
} }
@@ -45,7 +52,10 @@ func (this *FileWriter) WriteHeader(data []byte) (n int, err error) {
// WriteHeaderLength 写入Header长度数据 // WriteHeaderLength 写入Header长度数据
func (this *FileWriter) WriteHeaderLength(headerLength int) error { func (this *FileWriter) WriteHeaderLength(headerLength int) error {
bytes4 := make([]byte, 4) if this.metaHeaderSize > 0 && this.metaHeaderSize == headerLength {
return nil
}
var bytes4 = make([]byte, 4)
binary.BigEndian.PutUint32(bytes4, uint32(headerLength)) binary.BigEndian.PutUint32(bytes4, uint32(headerLength))
_, err := this.rawWriter.Seek(SizeExpiresAt+SizeStatus+SizeURLLength, io.SeekStart) _, err := this.rawWriter.Seek(SizeExpiresAt+SizeStatus+SizeURLLength, io.SeekStart)
if err != nil { if err != nil {
@@ -88,7 +98,10 @@ func (this *FileWriter) WriteAt(offset int64, data []byte) error {
// WriteBodyLength 写入Body长度数据 // WriteBodyLength 写入Body长度数据
func (this *FileWriter) WriteBodyLength(bodyLength int64) error { func (this *FileWriter) WriteBodyLength(bodyLength int64) error {
bytes8 := make([]byte, 8) if this.metaBodySize >= 0 && bodyLength == this.metaBodySize {
return nil
}
var bytes8 = make([]byte, 8)
binary.BigEndian.PutUint64(bytes8, uint64(bodyLength)) binary.BigEndian.PutUint64(bytes8, uint64(bodyLength))
_, err := this.rawWriter.Seek(SizeExpiresAt+SizeStatus+SizeURLLength+SizeHeaderLength, io.SeekStart) _, err := this.rawWriter.Seek(SizeExpiresAt+SizeStatus+SizeURLLength+SizeHeaderLength, io.SeekStart)
if err != nil { if err != nil {
@@ -109,7 +122,7 @@ func (this *FileWriter) Close() error {
this.endFunc() this.endFunc()
}) })
path := this.rawWriter.Name() var path = this.rawWriter.Name()
err := this.WriteHeaderLength(types.Int(this.headerSize)) err := this.WriteHeaderLength(types.Int(this.headerSize))
if err != nil { if err != nil {

View File

@@ -13,8 +13,13 @@ import (
type PartialFileWriter struct { type PartialFileWriter struct {
rawWriter *os.File rawWriter *os.File
key string key string
metaHeaderSize int
headerSize int64 headerSize int64
metaBodySize int64
bodySize int64 bodySize int64
expiredAt int64 expiredAt int64
endFunc func() endFunc func()
once sync.Once once sync.Once
@@ -27,7 +32,7 @@ type PartialFileWriter struct {
rangePath string rangePath string
} }
func NewPartialFileWriter(rawWriter *os.File, key string, expiredAt int64, isNew bool, isPartial bool, bodyOffset int64, ranges *PartialRanges, endFunc func()) *PartialFileWriter { func NewPartialFileWriter(rawWriter *os.File, key string, expiredAt int64, metaHeaderSize int, metaBodySize int64, isNew bool, isPartial bool, bodyOffset int64, ranges *PartialRanges, endFunc func()) *PartialFileWriter {
return &PartialFileWriter{ return &PartialFileWriter{
key: key, key: key,
rawWriter: rawWriter, rawWriter: rawWriter,
@@ -38,6 +43,8 @@ func NewPartialFileWriter(rawWriter *os.File, key string, expiredAt int64, isNew
bodyOffset: bodyOffset, bodyOffset: bodyOffset,
ranges: ranges, ranges: ranges,
rangePath: partialRangesFilePath(rawWriter.Name()), rangePath: partialRangesFilePath(rawWriter.Name()),
metaHeaderSize: metaHeaderSize,
metaBodySize: metaBodySize,
} }
} }
@@ -71,7 +78,11 @@ func (this *PartialFileWriter) AppendHeader(data []byte) error {
// WriteHeaderLength 写入Header长度数据 // WriteHeaderLength 写入Header长度数据
func (this *PartialFileWriter) WriteHeaderLength(headerLength int) error { func (this *PartialFileWriter) WriteHeaderLength(headerLength int) error {
bytes4 := make([]byte, 4) if this.metaHeaderSize > 0 && this.metaHeaderSize == headerLength {
return nil
}
var bytes4 = make([]byte, 4)
binary.BigEndian.PutUint32(bytes4, uint32(headerLength)) binary.BigEndian.PutUint32(bytes4, uint32(headerLength))
_, err := this.rawWriter.Seek(SizeExpiresAt+SizeStatus+SizeURLLength, io.SeekStart) _, err := this.rawWriter.Seek(SizeExpiresAt+SizeStatus+SizeURLLength, io.SeekStart)
if err != nil { if err != nil {
@@ -110,8 +121,13 @@ func (this *PartialFileWriter) WriteAt(offset int64, data []byte) error {
} }
if this.bodyOffset == 0 { if this.bodyOffset == 0 {
this.bodyOffset = SizeMeta + int64(len(this.key)) + this.headerSize var keyLength = 0
if this.ranges.Version == 0 { // 以往的版本包含有Key
keyLength = len(this.key)
} }
this.bodyOffset = SizeMeta + int64(keyLength) + this.headerSize
}
_, err := this.rawWriter.WriteAt(data, this.bodyOffset+offset) _, err := this.rawWriter.WriteAt(data, this.bodyOffset+offset)
if err != nil { if err != nil {
return err return err
@@ -129,7 +145,10 @@ func (this *PartialFileWriter) SetBodyLength(bodyLength int64) {
// WriteBodyLength 写入Body长度数据 // WriteBodyLength 写入Body长度数据
func (this *PartialFileWriter) WriteBodyLength(bodyLength int64) error { func (this *PartialFileWriter) WriteBodyLength(bodyLength int64) error {
bytes8 := make([]byte, 8) if this.metaBodySize > 0 && this.metaBodySize == bodyLength {
return nil
}
var bytes8 = make([]byte, 8)
binary.BigEndian.PutUint64(bytes8, uint64(bodyLength)) binary.BigEndian.PutUint64(bytes8, uint64(bodyLength))
_, err := this.rawWriter.Seek(SizeExpiresAt+SizeStatus+SizeURLLength+SizeHeaderLength, io.SeekStart) _, err := this.rawWriter.Seek(SizeExpiresAt+SizeStatus+SizeURLLength+SizeHeaderLength, io.SeekStart)
if err != nil { if err != nil {
@@ -150,8 +169,11 @@ func (this *PartialFileWriter) Close() error {
this.endFunc() this.endFunc()
}) })
this.ranges.BodySize = this.bodySize
err := this.ranges.WriteToFile(this.rangePath) err := this.ranges.WriteToFile(this.rangePath)
if err != nil { if err != nil {
_ = this.rawWriter.Close()
this.remove()
return err return err
} }

View File

@@ -26,8 +26,8 @@ func TestPartialFileWriter_Write(t *testing.T) {
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
var ranges = caches.NewPartialRanges() var ranges = caches.NewPartialRanges(0)
var writer = caches.NewPartialFileWriter(fp, "test", time.Now().Unix()+86500, true, true, 0, ranges, func() { var writer = caches.NewPartialFileWriter(fp, "test", time.Now().Unix()+86500, -1, -1, true, true, 0, ranges, func() {
t.Log("end") t.Log("end")
}) })
_, err = writer.WriteHeader([]byte("header")) _, err = writer.WriteHeader([]byte("header"))

View File

@@ -11,7 +11,7 @@ import (
var sharedBrotliReaderPool *ReaderPool var sharedBrotliReaderPool *ReaderPool
func init() { func init() {
if teaconst.IsDaemon { if !teaconst.IsMain {
return return
} }

View File

@@ -11,7 +11,7 @@ import (
var sharedDeflateReaderPool *ReaderPool var sharedDeflateReaderPool *ReaderPool
func init() { func init() {
if teaconst.IsDaemon { if !teaconst.IsMain {
return return
} }

View File

@@ -11,7 +11,7 @@ import (
var sharedGzipReaderPool *ReaderPool var sharedGzipReaderPool *ReaderPool
func init() { func init() {
if teaconst.IsDaemon { if !teaconst.IsMain {
return return
} }

View File

@@ -11,7 +11,7 @@ import (
var sharedZSTDReaderPool *ReaderPool var sharedZSTDReaderPool *ReaderPool
func init() { func init() {
if teaconst.IsDaemon { if !teaconst.IsMain {
return return
} }

View File

@@ -12,7 +12,7 @@ import (
var sharedBrotliWriterPool *WriterPool var sharedBrotliWriterPool *WriterPool
func init() { func init() {
if teaconst.IsDaemon { if !teaconst.IsMain {
return return
} }

View File

@@ -12,7 +12,7 @@ import (
var sharedDeflateWriterPool *WriterPool var sharedDeflateWriterPool *WriterPool
func init() { func init() {
if teaconst.IsDaemon { if !teaconst.IsMain {
return return
} }

View File

@@ -12,7 +12,7 @@ import (
var sharedGzipWriterPool *WriterPool var sharedGzipWriterPool *WriterPool
func init() { func init() {
if teaconst.IsDaemon { if !teaconst.IsMain {
return return
} }

View File

@@ -12,7 +12,7 @@ import (
var sharedZSTDWriterPool *WriterPool var sharedZSTDWriterPool *WriterPool
func init() { func init() {
if teaconst.IsDaemon { if !teaconst.IsMain {
return return
} }

View File

@@ -9,11 +9,15 @@ import (
// APIConfig 节点API配置 // APIConfig 节点API配置
type APIConfig struct { type APIConfig struct {
RPC struct { RPC struct {
Endpoints []string `yaml:"endpoints"` Endpoints []string `yaml:"endpoints" json:"endpoints"`
DisableUpdate bool `yaml:"disableUpdate"` DisableUpdate bool `yaml:"disableUpdate" json:"disableUpdate"`
} `yaml:"rpc"` } `yaml:"rpc" json:"rpc"`
NodeId string `yaml:"nodeId"` NodeId string `yaml:"nodeId" json:"nodeId"`
Secret string `yaml:"secret"` Secret string `yaml:"secret" json:"secret"`
}
func NewAPIConfig() *APIConfig {
return &APIConfig{}
} }
func LoadAPIConfig() (*APIConfig, error) { func LoadAPIConfig() (*APIConfig, error) {

View File

@@ -3,9 +3,9 @@ package configs
// ClusterConfig 集群配置 // ClusterConfig 集群配置
type ClusterConfig struct { type ClusterConfig struct {
RPC struct { RPC struct {
Endpoints []string `yaml:"endpoints"` Endpoints []string `yaml:"endpoints" json:"endpoints"`
DisableUpdate bool `yaml:"disableUpdate"` DisableUpdate bool `yaml:"disableUpdate" json:"disableUpdate"`
} `yaml:"rpc"` } `yaml:"rpc" json:"rpc"`
ClusterId string `yaml:"clusterId"` ClusterId string `yaml:"clusterId" json:"clusterId"`
Secret string `yaml:"secret"` Secret string `yaml:"secret" json:"secret"`
} }

View File

@@ -1,5 +0,0 @@
package configs
import "sync"
var sharedLocker = &sync.RWMutex{}

7
internal/conns/linger.go Normal file
View File

@@ -0,0 +1,7 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package conns
type LingerConn interface {
SetLinger(sec int) error
}

View File

@@ -37,9 +37,7 @@ func (this *Map) Add(conn net.Conn) {
defer this.locker.Unlock() defer this.locker.Unlock()
connMap, ok := this.m[ip] connMap, ok := this.m[ip]
if !ok { if !ok {
this.m[ip] = map[int]net.Conn{ this.m[ip] = map[int]net.Conn{port: conn}
port: conn,
}
} else { } else {
connMap[port] = conn connMap[port] = conn
} }
@@ -96,6 +94,13 @@ func (this *Map) CloseIPConns(ip string) {
if ok { if ok {
for _, conn := range conns { for _, conn := range conns {
// 设置Linger
lingerConn, isLingerConn := conn.(LingerConn)
if isLingerConn {
_ = lingerConn.SetLinger(0)
}
// 关闭
_ = conn.Close() _ = conn.Close()
} }
@@ -109,9 +114,10 @@ func (this *Map) AllConns() []net.Conn {
var result = []net.Conn{} var result = []net.Conn{}
for _, m := range this.m { for _, m := range this.m {
for _, conn := range m { for _, connInfo := range m {
result = append(result, conn) result = append(result, connInfo)
} }
} }
return result return result
} }

View File

@@ -1,7 +1,7 @@
package teaconst package teaconst
const ( const (
Version = "0.5.2" Version = "1.0.4"
ProductName = "Edge Node" ProductName = "Edge Node"
ProcessName = "edge-node" ProcessName = "edge-node"

View File

@@ -5,6 +5,7 @@ package teaconst
import ( import (
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs" "github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
"os" "os"
"strings"
) )
var ( var (
@@ -15,7 +16,7 @@ var (
NodeId int64 = 0 NodeId int64 = 0
NodeIdString = "" NodeIdString = ""
IsDaemon = len(os.Args) > 1 && os.Args[1] == "daemon" IsMain = checkMain()
GlobalProductName = nodeconfigs.DefaultProductName GlobalProductName = nodeconfigs.DefaultProductName
@@ -24,3 +25,15 @@ var (
DiskIsFast = false // 是否为高速硬盘 DiskIsFast = false // 是否为高速硬盘
) )
// 检查是否为主程序
func checkMain() bool {
if len(os.Args) == 1 ||
(len(os.Args) >= 2 && os.Args[1] == "pprof") {
return true
}
exe, _ := os.Executable()
return strings.HasSuffix(exe, ".test") ||
strings.HasSuffix(exe, ".test.exe") ||
strings.Contains(exe, "___")
}

View File

@@ -1,6 +1,5 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. // Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
//go:build linux //go:build linux
// +build linux
package firewalls package firewalls
@@ -10,22 +9,29 @@ import (
"errors" "errors"
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs" "github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/ddosconfigs" "github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/ddosconfigs"
teaconst "github.com/TeaOSLab/EdgeNode/internal/const"
"github.com/TeaOSLab/EdgeNode/internal/events" "github.com/TeaOSLab/EdgeNode/internal/events"
"github.com/TeaOSLab/EdgeNode/internal/firewalls/nftables" "github.com/TeaOSLab/EdgeNode/internal/firewalls/nftables"
"github.com/TeaOSLab/EdgeNode/internal/remotelogs" "github.com/TeaOSLab/EdgeNode/internal/remotelogs"
"github.com/TeaOSLab/EdgeNode/internal/utils" "github.com/TeaOSLab/EdgeNode/internal/utils"
executils "github.com/TeaOSLab/EdgeNode/internal/utils/exec"
"github.com/TeaOSLab/EdgeNode/internal/zero" "github.com/TeaOSLab/EdgeNode/internal/zero"
"github.com/iwind/TeaGo/lists" "github.com/iwind/TeaGo/lists"
"github.com/iwind/TeaGo/types" "github.com/iwind/TeaGo/types"
stringutil "github.com/iwind/TeaGo/utils/string" stringutil "github.com/iwind/TeaGo/utils/string"
"net" "net"
"os/exec"
"strings" "strings"
"sync"
"time"
) )
var SharedDDoSProtectionManager = NewDDoSProtectionManager() var SharedDDoSProtectionManager = NewDDoSProtectionManager()
func init() { func init() {
if !teaconst.IsMain {
return
}
events.On(events.EventReload, func() { events.On(events.EventReload, func() {
if nftablesInstance == nil { if nftablesInstance == nil {
return return
@@ -53,29 +59,31 @@ func init() {
// DDoSProtectionManager DDoS防护 // DDoSProtectionManager DDoS防护
type DDoSProtectionManager struct { type DDoSProtectionManager struct {
nftPath string
lastAllowIPList []string lastAllowIPList []string
lastConfig []byte lastConfig []byte
locker sync.Mutex
} }
// NewDDoSProtectionManager 获取新对象 // NewDDoSProtectionManager 获取新对象
func NewDDoSProtectionManager() *DDoSProtectionManager { func NewDDoSProtectionManager() *DDoSProtectionManager {
nftPath, _ := exec.LookPath("nft") return &DDoSProtectionManager{}
return &DDoSProtectionManager{
nftPath: nftPath,
}
} }
// Apply 应用配置 // Apply 应用配置
func (this *DDoSProtectionManager) Apply(config *ddosconfigs.ProtectionConfig) error { func (this *DDoSProtectionManager) Apply(config *ddosconfigs.ProtectionConfig) error {
// 加锁防止并发更改
if !this.locker.TryLock() {
return nil
}
defer this.locker.Unlock()
// 同集群节点IP白名单 // 同集群节点IP白名单
var allowIPListChanged = false var allowIPListChanged = false
nodeConfig, _ := nodeconfigs.SharedNodeConfig() nodeConfig, _ := nodeconfigs.SharedNodeConfig()
if nodeConfig != nil { if nodeConfig != nil {
var allowIPList = nodeConfig.AllowedIPs var allowIPList = nodeConfig.AllowedIPs
if !utils.ContainsSameStrings(allowIPList, this.lastAllowIPList) { if !utils.EqualStrings(allowIPList, this.lastAllowIPList) {
allowIPListChanged = true allowIPListChanged = true
this.lastAllowIPList = allowIPList this.lastAllowIPList = allowIPList
} }
@@ -91,11 +99,14 @@ func (this *DDoSProtectionManager) Apply(config *ddosconfigs.ProtectionConfig) e
} }
remotelogs.Println("FIREWALL", "change DDoS protection config") remotelogs.Println("FIREWALL", "change DDoS protection config")
if len(this.nftPath) == 0 { if len(nftables.NftExePath()) == 0 {
return errors.New("can not find nft command") return errors.New("can not find nft command")
} }
if nftablesInstance == nil { if nftablesInstance == nil {
if config == nil || !config.IsOn() {
return nil
}
return errors.New("nftables instance should not be nil") return errors.New("nftables instance should not be nil")
} }
@@ -154,6 +165,11 @@ func (this *DDoSProtectionManager) Apply(config *ddosconfigs.ProtectionConfig) e
// 添加TCP规则 // 添加TCP规则
func (this *DDoSProtectionManager) addTCPRules(tcpConfig *ddosconfigs.TCPConfig) error { func (this *DDoSProtectionManager) addTCPRules(tcpConfig *ddosconfigs.TCPConfig) error {
var nftExe = nftables.NftExePath()
if len(nftExe) == 0 {
return nil
}
// 检查nft版本不能小于0.9 // 检查nft版本不能小于0.9
if len(nftablesInstance.version) > 0 && stringutil.VersionCompare("0.9", nftablesInstance.version) > 0 { if len(nftablesInstance.version) > 0 && stringutil.VersionCompare("0.9", nftablesInstance.version) > 0 {
return nil return nil
@@ -263,23 +279,21 @@ func (this *DDoSProtectionManager) addTCPRules(tcpConfig *ddosconfigs.TCPConfig)
// 添加新规则 // 添加新规则
for _, port := range ports { for _, port := range ports {
if maxConnections > 0 { if maxConnections > 0 {
var cmd = exec.Command(this.nftPath, "add", "rule", protocol, filter.Name, nftablesChainName, "tcp", "dport", types.String(port), "ct", "count", "over", types.String(maxConnections), "counter", "drop", "comment", this.encodeUserData([]string{"tcp", types.String(port), "maxConnections", types.String(maxConnections)})) var cmd = executils.NewTimeoutCmd(10*time.Second, nftExe, "add", "rule", protocol, filter.Name, nftablesChainName, "tcp", "dport", types.String(port), "ct", "count", "over", types.String(maxConnections), "counter", "drop", "comment", this.encodeUserData([]string{"tcp", types.String(port), "maxConnections", types.String(maxConnections)}))
var stderr = &bytes.Buffer{} cmd.WithStderr()
cmd.Stderr = stderr
err := cmd.Run() err := cmd.Run()
if err != nil { if err != nil {
return errors.New("add nftables rule '" + cmd.String() + "' failed: " + err.Error() + " (" + stderr.String() + ")") return errors.New("add nftables rule '" + cmd.String() + "' failed: " + err.Error() + " (" + cmd.Stderr() + ")")
} }
} }
// TODO 让用户选择是drop还是reject // TODO 让用户选择是drop还是reject
if maxConnectionsPerIP > 0 { if maxConnectionsPerIP > 0 {
var cmd = exec.Command(this.nftPath, "add", "rule", protocol, filter.Name, nftablesChainName, "tcp", "dport", types.String(port), "meter", "meter-"+protocol+"-"+types.String(port)+"-max-connections", "{ "+protocol+" saddr ct count over "+types.String(maxConnectionsPerIP)+" }", "counter", "drop", "comment", this.encodeUserData([]string{"tcp", types.String(port), "maxConnectionsPerIP", types.String(maxConnectionsPerIP)})) var cmd = executils.NewTimeoutCmd(10*time.Second, nftExe, "add", "rule", protocol, filter.Name, nftablesChainName, "tcp", "dport", types.String(port), "meter", "meter-"+protocol+"-"+types.String(port)+"-max-connections", "{ "+protocol+" saddr ct count over "+types.String(maxConnectionsPerIP)+" }", "counter", "drop", "comment", this.encodeUserData([]string{"tcp", types.String(port), "maxConnectionsPerIP", types.String(maxConnectionsPerIP)}))
var stderr = &bytes.Buffer{} cmd.WithStderr()
cmd.Stderr = stderr
err := cmd.Run() err := cmd.Run()
if err != nil { if err != nil {
return errors.New("add nftables rule '" + cmd.String() + "' failed: " + err.Error() + " (" + stderr.String() + ")") return errors.New("add nftables rule '" + cmd.String() + "' failed: " + err.Error() + " (" + cmd.Stderr() + ")")
} }
} }
@@ -287,20 +301,18 @@ func (this *DDoSProtectionManager) addTCPRules(tcpConfig *ddosconfigs.TCPConfig)
// TODO 让用户选择是drop还是reject // TODO 让用户选择是drop还是reject
if newConnectionsMinutelyRate > 0 { if newConnectionsMinutelyRate > 0 {
if newConnectionsMinutelyRateBlockTimeout > 0 { if newConnectionsMinutelyRateBlockTimeout > 0 {
var cmd = exec.Command(this.nftPath, "add", "rule", protocol, filter.Name, nftablesChainName, "tcp", "dport", types.String(port), "ct", "state", "new", "meter", "meter-"+protocol+"-"+types.String(port)+"-new-connections-rate", "{ "+protocol+" saddr limit rate over "+types.String(newConnectionsMinutelyRate)+"/minute burst "+types.String(newConnectionsMinutelyRate+3)+" packets }", "add", "@deny_set", "{"+protocol+" saddr timeout "+types.String(newConnectionsMinutelyRateBlockTimeout)+"s}", "comment", this.encodeUserData([]string{"tcp", types.String(port), "newConnectionsRate", types.String(newConnectionsMinutelyRate), types.String(newConnectionsMinutelyRateBlockTimeout)})) var cmd = executils.NewTimeoutCmd(10*time.Second, nftExe, "add", "rule", protocol, filter.Name, nftablesChainName, "tcp", "dport", types.String(port), "ct", "state", "new", "meter", "meter-"+protocol+"-"+types.String(port)+"-new-connections-rate", "{ "+protocol+" saddr limit rate over "+types.String(newConnectionsMinutelyRate)+"/minute burst "+types.String(newConnectionsMinutelyRate+3)+" packets }", "add", "@deny_set", "{"+protocol+" saddr timeout "+types.String(newConnectionsMinutelyRateBlockTimeout)+"s}", "comment", this.encodeUserData([]string{"tcp", types.String(port), "newConnectionsRate", types.String(newConnectionsMinutelyRate), types.String(newConnectionsMinutelyRateBlockTimeout)}))
var stderr = &bytes.Buffer{} cmd.WithStderr()
cmd.Stderr = stderr
err := cmd.Run() err := cmd.Run()
if err != nil { if err != nil {
return errors.New("add nftables rule '" + cmd.String() + "' failed: " + err.Error() + " (" + stderr.String() + ")") return errors.New("add nftables rule '" + cmd.String() + "' failed: " + err.Error() + " (" + cmd.Stderr() + ")")
} }
} else { } else {
var cmd = exec.Command(this.nftPath, "add", "rule", protocol, filter.Name, nftablesChainName, "tcp", "dport", types.String(port), "ct", "state", "new", "meter", "meter-"+protocol+"-"+types.String(port)+"-new-connections-rate", "{ "+protocol+" saddr limit rate over "+types.String(newConnectionsMinutelyRate)+"/minute burst "+types.String(newConnectionsMinutelyRate+3)+" packets }" /**"add", "@deny_set", "{"+protocol+" saddr}",**/, "counter", "drop", "comment", this.encodeUserData([]string{"tcp", types.String(port), "newConnectionsRate", "0"})) var cmd = executils.NewTimeoutCmd(10*time.Second, nftExe, "add", "rule", protocol, filter.Name, nftablesChainName, "tcp", "dport", types.String(port), "ct", "state", "new", "meter", "meter-"+protocol+"-"+types.String(port)+"-new-connections-rate", "{ "+protocol+" saddr limit rate over "+types.String(newConnectionsMinutelyRate)+"/minute burst "+types.String(newConnectionsMinutelyRate+3)+" packets }" /**"add", "@deny_set", "{"+protocol+" saddr}",**/, "counter", "drop", "comment", this.encodeUserData([]string{"tcp", types.String(port), "newConnectionsRate", "0"}))
var stderr = &bytes.Buffer{} cmd.WithStderr()
cmd.Stderr = stderr
err := cmd.Run() err := cmd.Run()
if err != nil { if err != nil {
return errors.New("add nftables rule '" + cmd.String() + "' failed: " + err.Error() + " (" + stderr.String() + ")") return errors.New("add nftables rule '" + cmd.String() + "' failed: " + err.Error() + " (" + cmd.Stderr() + ")")
} }
} }
} }
@@ -309,20 +321,18 @@ func (this *DDoSProtectionManager) addTCPRules(tcpConfig *ddosconfigs.TCPConfig)
// TODO 让用户选择是drop还是reject // TODO 让用户选择是drop还是reject
if newConnectionsSecondlyRate > 0 { if newConnectionsSecondlyRate > 0 {
if newConnectionsSecondlyRateBlockTimeout > 0 { if newConnectionsSecondlyRateBlockTimeout > 0 {
var cmd = exec.Command(this.nftPath, "add", "rule", protocol, filter.Name, nftablesChainName, "tcp", "dport", types.String(port), "ct", "state", "new", "meter", "meter-"+protocol+"-"+types.String(port)+"-new-connections-secondly-rate", "{ "+protocol+" saddr limit rate over "+types.String(newConnectionsSecondlyRate)+"/second burst "+types.String(newConnectionsSecondlyRate+3)+" packets }", "add", "@deny_set", "{"+protocol+" saddr timeout "+types.String(newConnectionsSecondlyRateBlockTimeout)+"s}", "comment", this.encodeUserData([]string{"tcp", types.String(port), "newConnectionsSecondlyRate", types.String(newConnectionsSecondlyRate), types.String(newConnectionsSecondlyRateBlockTimeout)})) var cmd = executils.NewTimeoutCmd(10*time.Second, nftExe, "add", "rule", protocol, filter.Name, nftablesChainName, "tcp", "dport", types.String(port), "ct", "state", "new", "meter", "meter-"+protocol+"-"+types.String(port)+"-new-connections-secondly-rate", "{ "+protocol+" saddr limit rate over "+types.String(newConnectionsSecondlyRate)+"/second burst "+types.String(newConnectionsSecondlyRate+3)+" packets }", "add", "@deny_set", "{"+protocol+" saddr timeout "+types.String(newConnectionsSecondlyRateBlockTimeout)+"s}", "comment", this.encodeUserData([]string{"tcp", types.String(port), "newConnectionsSecondlyRate", types.String(newConnectionsSecondlyRate), types.String(newConnectionsSecondlyRateBlockTimeout)}))
var stderr = &bytes.Buffer{} cmd.WithStderr()
cmd.Stderr = stderr
err := cmd.Run() err := cmd.Run()
if err != nil { if err != nil {
return errors.New("add nftables rule '" + cmd.String() + "' failed: " + err.Error() + " (" + stderr.String() + ")") return errors.New("add nftables rule '" + cmd.String() + "' failed: " + err.Error() + " (" + cmd.Stderr() + ")")
} }
} else { } else {
var cmd = exec.Command(this.nftPath, "add", "rule", protocol, filter.Name, nftablesChainName, "tcp", "dport", types.String(port), "ct", "state", "new", "meter", "meter-"+protocol+"-"+types.String(port)+"-new-connections-secondly-rate", "{ "+protocol+" saddr limit rate over "+types.String(newConnectionsSecondlyRate)+"/second burst "+types.String(newConnectionsSecondlyRate+3)+" packets }" /**"add", "@deny_set", "{"+protocol+" saddr}",**/, "counter", "drop", "comment", this.encodeUserData([]string{"tcp", types.String(port), "newConnectionsSecondlyRate", "0"})) var cmd = executils.NewTimeoutCmd(10*time.Second, nftExe, "add", "rule", protocol, filter.Name, nftablesChainName, "tcp", "dport", types.String(port), "ct", "state", "new", "meter", "meter-"+protocol+"-"+types.String(port)+"-new-connections-secondly-rate", "{ "+protocol+" saddr limit rate over "+types.String(newConnectionsSecondlyRate)+"/second burst "+types.String(newConnectionsSecondlyRate+3)+" packets }" /**"add", "@deny_set", "{"+protocol+" saddr}",**/, "counter", "drop", "comment", this.encodeUserData([]string{"tcp", types.String(port), "newConnectionsSecondlyRate", "0"}))
var stderr = &bytes.Buffer{} cmd.WithStderr()
cmd.Stderr = stderr
err := cmd.Run() err := cmd.Run()
if err != nil { if err != nil {
return errors.New("add nftables rule '" + cmd.String() + "' failed: " + err.Error() + " (" + stderr.String() + ")") return errors.New("add nftables rule '" + cmd.String() + "' failed: " + err.Error() + " (" + cmd.Stderr() + ")")
} }
} }
} }
@@ -544,7 +554,7 @@ func (this *DDoSProtectionManager) updateAllowIPList(allIPList []string) error {
_, ok := oldMap[ip] _, ok := oldMap[ip]
if !ok { if !ok {
// 不存在则添加 // 不存在则添加
err = set.AddIPElement(ip, nil) err = set.AddIPElement(ip, nil, false)
if err != nil { if err != nil {
return errors.New("add ip '" + ip + "' failed: " + err.Error()) return errors.New("add ip '" + ip + "' failed: " + err.Error())
} }

View File

@@ -11,7 +11,6 @@ import (
var SharedDDoSProtectionManager = NewDDoSProtectionManager() var SharedDDoSProtectionManager = NewDDoSProtectionManager()
type DDoSProtectionManager struct { type DDoSProtectionManager struct {
nftPath string
} }
func NewDDoSProtectionManager() *DDoSProtectionManager { func NewDDoSProtectionManager() *DDoSProtectionManager {

View File

@@ -3,6 +3,7 @@
package firewalls package firewalls
import ( import (
teaconst "github.com/TeaOSLab/EdgeNode/internal/const"
"github.com/TeaOSLab/EdgeNode/internal/events" "github.com/TeaOSLab/EdgeNode/internal/events"
"github.com/TeaOSLab/EdgeNode/internal/remotelogs" "github.com/TeaOSLab/EdgeNode/internal/remotelogs"
"runtime" "runtime"
@@ -14,6 +15,10 @@ var firewallLocker = &sync.Mutex{}
// 初始化 // 初始化
func init() { func init() {
if !teaconst.IsMain {
return
}
events.On(events.EventLoaded, func() { events.On(events.EventLoaded, func() {
var firewall = Firewall() var firewall = Firewall()
if firewall.Name() != "mock" { if firewall.Name() != "mock" {

View File

@@ -7,13 +7,15 @@ import (
"github.com/TeaOSLab/EdgeNode/internal/conns" "github.com/TeaOSLab/EdgeNode/internal/conns"
"github.com/TeaOSLab/EdgeNode/internal/goman" "github.com/TeaOSLab/EdgeNode/internal/goman"
"github.com/TeaOSLab/EdgeNode/internal/remotelogs" "github.com/TeaOSLab/EdgeNode/internal/remotelogs"
executils "github.com/TeaOSLab/EdgeNode/internal/utils/exec"
"github.com/iwind/TeaGo/types" "github.com/iwind/TeaGo/types"
"os/exec" "os/exec"
"strings" "strings"
"time"
) )
type firewalldCmd struct { type firewalldCmd struct {
cmd *exec.Cmd cmd *executils.Cmd
denyIP string denyIP string
} }
@@ -32,7 +34,7 @@ func NewFirewalld() *Firewalld {
path, err := exec.LookPath("firewall-cmd") path, err := exec.LookPath("firewall-cmd")
if err == nil && len(path) > 0 { if err == nil && len(path) > 0 {
var cmd = exec.Command(path, "--state") var cmd = executils.NewTimeoutCmd(3*time.Second, path, "--state")
err := cmd.Run() err := cmd.Run()
if err == nil { if err == nil {
firewalld.exe = path firewalld.exe = path
@@ -85,7 +87,7 @@ func (this *Firewalld) AllowPort(port int, protocol string) error {
if !this.isReady { if !this.isReady {
return nil return nil
} }
var cmd = exec.Command(this.exe, "--add-port="+types.String(port)+"/"+protocol) var cmd = executils.NewTimeoutCmd(10*time.Second, this.exe, "--add-port="+types.String(port)+"/"+protocol)
this.pushCmd(cmd, "") this.pushCmd(cmd, "")
return nil return nil
} }
@@ -95,12 +97,12 @@ func (this *Firewalld) AllowPortRangesPermanently(portRanges [][2]int, protocol
var port = this.PortRangeString(portRange, protocol) var port = this.PortRangeString(portRange, protocol)
{ {
var cmd = exec.Command(this.exe, "--add-port="+port, "--permanent") var cmd = executils.NewTimeoutCmd(10*time.Second, this.exe, "--add-port="+port, "--permanent")
this.pushCmd(cmd, "") this.pushCmd(cmd, "")
} }
{ {
var cmd = exec.Command(this.exe, "--add-port="+port) var cmd = executils.NewTimeoutCmd(10*time.Second, this.exe, "--add-port="+port)
this.pushCmd(cmd, "") this.pushCmd(cmd, "")
} }
} }
@@ -112,7 +114,7 @@ func (this *Firewalld) RemovePort(port int, protocol string) error {
if !this.isReady { if !this.isReady {
return nil return nil
} }
var cmd = exec.Command(this.exe, "--remove-port="+types.String(port)+"/"+protocol) var cmd = executils.NewTimeoutCmd(10*time.Second, this.exe, "--remove-port="+types.String(port)+"/"+protocol)
this.pushCmd(cmd, "") this.pushCmd(cmd, "")
return nil return nil
} }
@@ -121,12 +123,12 @@ func (this *Firewalld) RemovePortRangePermanently(portRange [2]int, protocol str
var port = this.PortRangeString(portRange, protocol) var port = this.PortRangeString(portRange, protocol)
{ {
var cmd = exec.Command(this.exe, "--remove-port="+port, "--permanent") var cmd = executils.NewTimeoutCmd(10*time.Second, this.exe, "--remove-port="+port, "--permanent")
this.pushCmd(cmd, "") this.pushCmd(cmd, "")
} }
{ {
var cmd = exec.Command(this.exe, "--remove-port="+port) var cmd = executils.NewTimeoutCmd(10*time.Second, this.exe, "--remove-port="+port)
this.pushCmd(cmd, "") this.pushCmd(cmd, "")
} }
@@ -159,7 +161,7 @@ func (this *Firewalld) RejectSourceIP(ip string, timeoutSeconds int) error {
if timeoutSeconds > 0 { if timeoutSeconds > 0 {
args = append(args, "--timeout="+types.String(timeoutSeconds)+"s") args = append(args, "--timeout="+types.String(timeoutSeconds)+"s")
} }
var cmd = exec.Command(this.exe, args...) var cmd = executils.NewTimeoutCmd(10*time.Second, this.exe, args...)
this.pushCmd(cmd, ip) this.pushCmd(cmd, ip)
return nil return nil
} }
@@ -182,7 +184,7 @@ func (this *Firewalld) DropSourceIP(ip string, timeoutSeconds int, async bool) e
if timeoutSeconds > 0 { if timeoutSeconds > 0 {
args = append(args, "--timeout="+types.String(timeoutSeconds)+"s") args = append(args, "--timeout="+types.String(timeoutSeconds)+"s")
} }
var cmd = exec.Command(this.exe, args...) var cmd = executils.NewTimeoutCmd(10*time.Second, this.exe, args...)
if async { if async {
this.pushCmd(cmd, ip) this.pushCmd(cmd, ip)
return nil return nil
@@ -209,13 +211,13 @@ func (this *Firewalld) RemoveSourceIP(ip string) error {
} }
for _, action := range []string{"reject", "drop"} { for _, action := range []string{"reject", "drop"} {
var args = []string{"--remove-rich-rule=rule family='" + family + "' source address='" + ip + "' " + action} var args = []string{"--remove-rich-rule=rule family='" + family + "' source address='" + ip + "' " + action}
var cmd = exec.Command(this.exe, args...) var cmd = executils.NewTimeoutCmd(10*time.Second, this.exe, args...)
this.pushCmd(cmd, "") this.pushCmd(cmd, "")
} }
return nil return nil
} }
func (this *Firewalld) pushCmd(cmd *exec.Cmd, denyIP string) { func (this *Firewalld) pushCmd(cmd *executils.Cmd, denyIP string) {
select { select {
case this.cmdQueue <- &firewalldCmd{cmd: cmd, denyIP: denyIP}: case this.cmdQueue <- &firewalldCmd{cmd: cmd, denyIP: denyIP}:
default: default:

View File

@@ -1,21 +1,21 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. // Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
//go:build linux //go:build linux
// +build linux
package firewalls package firewalls
import ( import (
"bytes"
"errors" "errors"
"github.com/TeaOSLab/EdgeCommon/pkg/configutils"
"github.com/TeaOSLab/EdgeNode/internal/conns" "github.com/TeaOSLab/EdgeNode/internal/conns"
teaconst "github.com/TeaOSLab/EdgeNode/internal/const" teaconst "github.com/TeaOSLab/EdgeNode/internal/const"
"github.com/TeaOSLab/EdgeNode/internal/events" "github.com/TeaOSLab/EdgeNode/internal/events"
"github.com/TeaOSLab/EdgeNode/internal/firewalls/nftables" "github.com/TeaOSLab/EdgeNode/internal/firewalls/nftables"
"github.com/TeaOSLab/EdgeNode/internal/goman" "github.com/TeaOSLab/EdgeNode/internal/goman"
"github.com/TeaOSLab/EdgeNode/internal/remotelogs" "github.com/TeaOSLab/EdgeNode/internal/remotelogs"
executils "github.com/TeaOSLab/EdgeNode/internal/utils/exec"
"github.com/google/nftables/expr"
"github.com/iwind/TeaGo/types" "github.com/iwind/TeaGo/types"
"net" "net"
"os/exec"
"regexp" "regexp"
"runtime" "runtime"
"strings" "strings"
@@ -24,7 +24,7 @@ import (
// check nft status, if being enabled we load it automatically // check nft status, if being enabled we load it automatically
func init() { func init() {
if teaconst.IsDaemon { if !teaconst.IsMain {
return return
} }
@@ -37,8 +37,8 @@ func init() {
ticker.Stop() ticker.Stop()
break break
} }
_, err := exec.LookPath("nft") var nftExe = nftables.NftExePath()
if err == nil { if len(nftExe) > 0 {
nftablesFirewall, err := NewNFTablesFirewall() nftablesFirewall, err := NewNFTablesFirewall()
if err != nil { if err != nil {
continue continue
@@ -88,11 +88,15 @@ type blockIPItem struct {
} }
func NewNFTablesFirewall() (*NFTablesFirewall, error) { func NewNFTablesFirewall() (*NFTablesFirewall, error) {
conn, err := nftables.NewConn()
if err != nil {
return nil, err
}
var firewall = &NFTablesFirewall{ var firewall = &NFTablesFirewall{
conn: nftables.NewConn(), conn: conn,
dropIPQueue: make(chan *blockIPItem, 4096), dropIPQueue: make(chan *blockIPItem, 4096),
} }
err := firewall.init() err = firewall.init()
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -110,8 +114,8 @@ type NFTablesFirewall struct {
allowIPv4Set *nftables.Set allowIPv4Set *nftables.Set
allowIPv6Set *nftables.Set allowIPv6Set *nftables.Set
denyIPv4Set *nftables.Set denyIPv4Sets []*nftables.Set
denyIPv6Set *nftables.Set denyIPv6Sets []*nftables.Set
firewalld *Firewalld firewalld *Firewalld
@@ -120,9 +124,9 @@ type NFTablesFirewall struct {
func (this *NFTablesFirewall) init() error { func (this *NFTablesFirewall) init() error {
// check nft // check nft
nftPath, err := exec.LookPath("nft") var nftPath = nftables.NftExePath()
if err != nil { if len(nftPath) == 0 {
return errors.New("nft not found") return errors.New("'nft' not found")
} }
this.version = this.readVersion(nftPath) this.version = this.readVersion(nftPath)
@@ -186,7 +190,7 @@ func (this *NFTablesFirewall) init() error {
// allow set // allow set
// "allow" should be always first // "allow" should be always first
for _, setAction := range []string{"allow", "deny"} { for _, setAction := range []string{"allow", "deny", "deny1", "deny2", "deny3", "deny4"} {
var setName = setAction + "_set" var setName = setAction + "_set"
set, err := table.GetSet(setName) set, err := table.GetSet(setName)
@@ -216,32 +220,42 @@ func (this *NFTablesFirewall) init() error {
if setAction == "allow" { if setAction == "allow" {
this.allowIPv4Set = set this.allowIPv4Set = set
} else { } else {
this.denyIPv4Set = set this.denyIPv4Sets = append(this.denyIPv4Sets, set)
} }
} else if tableDef.IsIPv6 { } else if tableDef.IsIPv6 {
if setAction == "allow" { if setAction == "allow" {
this.allowIPv6Set = set this.allowIPv6Set = set
} else { } else {
this.denyIPv6Set = set this.denyIPv6Sets = append(this.denyIPv6Sets, set)
} }
} }
// rule // rule
var ruleName = []byte(setAction) var ruleName = []byte(setAction)
rule, err := chain.GetRuleWithUserData(ruleName) rule, err := chain.GetRuleWithUserData(ruleName)
// 将以前的drop规则删掉替换成后面的reject
if err == nil && setAction != "allow" && rule != nil && rule.VerDict() == expr.VerdictDrop {
deleteErr := chain.DeleteRule(rule)
if deleteErr == nil {
err = nftables.ErrRuleNotFound
rule = nil
}
}
if err != nil { if err != nil {
if nftables.IsNotFound(err) { if nftables.IsNotFound(err) {
if tableDef.IsIPv4 { if tableDef.IsIPv4 {
if setAction == "allow" { if setAction == "allow" {
rule, err = chain.AddAcceptIPv4SetRule(setName, ruleName) rule, err = chain.AddAcceptIPv4SetRule(setName, ruleName)
} else { } else {
rule, err = chain.AddDropIPv4SetRule(setName, ruleName) rule, err = chain.AddRejectIPv4SetRule(setName, ruleName)
} }
} else if tableDef.IsIPv6 { } else if tableDef.IsIPv6 {
if setAction == "allow" { if setAction == "allow" {
rule, err = chain.AddAcceptIPv6SetRule(setName, ruleName) rule, err = chain.AddAcceptIPv6SetRule(setName, ruleName)
} else { } else {
rule, err = chain.AddDropIPv6SetRule(setName, ruleName) rule, err = chain.AddRejectIPv6SetRule(setName, ruleName)
} }
} }
if err != nil { if err != nil {
@@ -265,7 +279,7 @@ func (this *NFTablesFirewall) init() error {
for ipItem := range this.dropIPQueue { for ipItem := range this.dropIPQueue {
switch ipItem.action { switch ipItem.action {
case "drop": case "drop":
err = this.DropSourceIP(ipItem.ip, ipItem.timeoutSeconds, false) err := this.DropSourceIP(ipItem.ip, ipItem.timeoutSeconds, false)
if err != nil { if err != nil {
remotelogs.Warn("NFTABLES", "drop ip '"+ipItem.ip+"' failed: "+err.Error()) remotelogs.Warn("NFTABLES", "drop ip '"+ipItem.ip+"' failed: "+err.Error())
} }
@@ -324,14 +338,14 @@ func (this *NFTablesFirewall) AllowSourceIP(ip string) error {
if this.allowIPv6Set == nil { if this.allowIPv6Set == nil {
return errors.New("ipv6 ip set is nil") return errors.New("ipv6 ip set is nil")
} }
return this.allowIPv6Set.AddElement(data.To16(), nil) return this.allowIPv6Set.AddElement(data.To16(), nil, false)
} }
// ipv4 // ipv4
if this.allowIPv4Set == nil { if this.allowIPv4Set == nil {
return errors.New("ipv4 ip set is nil") return errors.New("ipv4 ip set is nil")
} }
return this.allowIPv4Set.AddElement(data.To4(), nil) return this.allowIPv4Set.AddElement(data.To4(), nil, false)
} }
// RejectSourceIP 拒绝某个源IP连接 // RejectSourceIP 拒绝某个源IP连接
@@ -371,22 +385,23 @@ func (this *NFTablesFirewall) DropSourceIP(ip string, timeoutSeconds int, async
// 再次尝试关闭连接 // 再次尝试关闭连接
defer conns.SharedMap.CloseIPConns(ip) defer conns.SharedMap.CloseIPConns(ip)
var ipLong = configutils.IPString2Long(ip)
if strings.Contains(ip, ":") { // ipv6 if strings.Contains(ip, ":") { // ipv6
if this.denyIPv6Set == nil { if len(this.denyIPv6Sets) == 0 {
return errors.New("ipv6 ip set is nil") return errors.New("ipv6 ip set not found")
} }
return this.denyIPv6Set.AddElement(data.To16(), &nftables.ElementOptions{ return this.denyIPv6Sets[ipLong%uint64(len(this.denyIPv6Sets))].AddElement(data.To16(), &nftables.ElementOptions{
Timeout: time.Duration(timeoutSeconds) * time.Second, Timeout: time.Duration(timeoutSeconds) * time.Second,
}) }, false)
} }
// ipv4 // ipv4
if this.denyIPv4Set == nil { if len(this.denyIPv4Sets) == 0 {
return errors.New("ipv4 ip set is nil") return errors.New("ipv4 ip set not found")
} }
return this.denyIPv4Set.AddElement(data.To4(), &nftables.ElementOptions{ return this.denyIPv4Sets[ipLong%uint64(len(this.denyIPv4Sets))].AddElement(data.To4(), &nftables.ElementOptions{
Timeout: time.Duration(timeoutSeconds) * time.Second, Timeout: time.Duration(timeoutSeconds) * time.Second,
}) }, false)
} }
// RemoveSourceIP 删除某个源IP // RemoveSourceIP 删除某个源IP
@@ -396,9 +411,10 @@ func (this *NFTablesFirewall) RemoveSourceIP(ip string) error {
return errors.New("invalid ip '" + ip + "'") return errors.New("invalid ip '" + ip + "'")
} }
var ipLong = configutils.IPString2Long(ip)
if strings.Contains(ip, ":") { // ipv6 if strings.Contains(ip, ":") { // ipv6
if this.denyIPv6Set != nil { if len(this.denyIPv6Sets) > 0 {
err := this.denyIPv6Set.DeleteElement(data.To16()) err := this.denyIPv6Sets[ipLong%uint64(len(this.denyIPv6Sets))].DeleteElement(data.To16())
if err != nil { if err != nil {
return err return err
} }
@@ -415,13 +431,14 @@ func (this *NFTablesFirewall) RemoveSourceIP(ip string) error {
} }
// ipv4 // ipv4
if this.allowIPv4Set != nil { if len(this.denyIPv4Sets) > 0 {
err := this.denyIPv4Set.DeleteElement(data.To4()) err := this.denyIPv4Sets[ipLong%uint64(len(this.denyIPv4Sets))].DeleteElement(data.To4())
if err != nil { if err != nil {
return err return err
} }
}
err = this.allowIPv4Set.DeleteElement(data.To4()) if this.allowIPv4Set != nil {
err := this.allowIPv4Set.DeleteElement(data.To4())
if err != nil { if err != nil {
return err return err
} }
@@ -432,15 +449,14 @@ func (this *NFTablesFirewall) RemoveSourceIP(ip string) error {
// 读取版本号 // 读取版本号
func (this *NFTablesFirewall) readVersion(nftPath string) string { func (this *NFTablesFirewall) readVersion(nftPath string) string {
var cmd = exec.Command(nftPath, "--version") var cmd = executils.NewTimeoutCmd(10*time.Second, nftPath, "--version")
var output = &bytes.Buffer{} cmd.WithStdout()
cmd.Stdout = output
err := cmd.Run() err := cmd.Run()
if err != nil { if err != nil {
return "" return ""
} }
var outputString = output.String() var outputString = cmd.Stdout()
var versionMatches = regexp.MustCompile(`nftables v([\d.]+)`).FindStringSubmatch(outputString) var versionMatches = regexp.MustCompile(`nftables v([\d.]+)`).FindStringSubmatch(outputString)
if len(versionMatches) <= 1 { if len(versionMatches) <= 1 {
return "" return ""

View File

@@ -1,6 +1,5 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. // Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
//go:build linux //go:build linux
// +build linux
package nftables package nftables

View File

@@ -1,4 +1,5 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. // Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
//go:build linux
package nftables package nftables

View File

@@ -1,6 +1,5 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. // Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
//go:build linux //go:build linux
// +build linux
package nftables_test package nftables_test
@@ -11,7 +10,10 @@ import (
) )
func getIPv4Chain(t *testing.T) *nftables.Chain { func getIPv4Chain(t *testing.T) *nftables.Chain {
var conn = nftables.NewConn() conn, err := nftables.NewConn()
if err != nil {
t.Fatal(err)
}
table, err := conn.GetTable("test_ipv4", nftables.TableFamilyIPv4) table, err := conn.GetTable("test_ipv4", nftables.TableFamilyIPv4)
if err != nil { if err != nil {
if err == nftables.ErrTableNotFound { if err == nftables.ErrTableNotFound {

View File

@@ -1,6 +1,5 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. // Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
//go:build linux //go:build linux
// +build linux
package nftables package nftables
@@ -16,10 +15,14 @@ type Conn struct {
rawConn *nft.Conn rawConn *nft.Conn
} }
func NewConn() *Conn { func NewConn() (*Conn, error) {
return &Conn{ conn, err := nft.New()
rawConn: &nft.Conn{}, if err != nil {
return nil, err
} }
return &Conn{
rawConn: conn,
}, nil
} }
func (this *Conn) Raw() *nft.Conn { func (this *Conn) Raw() *nft.Conn {

View File

@@ -4,7 +4,10 @@
package nftables package nftables
import "errors" import (
"errors"
"strings"
)
var ErrTableNotFound = errors.New("table not found") var ErrTableNotFound = errors.New("table not found")
var ErrChainNotFound = errors.New("chain not found") var ErrChainNotFound = errors.New("chain not found")
@@ -15,5 +18,5 @@ func IsNotFound(err error) bool {
if err == nil { if err == nil {
return false return false
} }
return err == ErrTableNotFound || err == ErrChainNotFound || err == ErrSetNotFound || err == ErrRuleNotFound return err == ErrTableNotFound || err == ErrChainNotFound || err == ErrSetNotFound || err == ErrRuleNotFound || strings.Contains(err.Error(), "no such file or directory")
} }

View File

@@ -0,0 +1,65 @@
// Copyright 2023 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package nftables
import (
"sync"
"time"
)
type Expiration struct {
m map[string]time.Time // key => expires time
lastGCAt int64
locker sync.RWMutex
}
func NewExpiration() *Expiration {
return &Expiration{
m: map[string]time.Time{},
}
}
func (this *Expiration) AddUnsafe(key []byte, expires time.Time) {
this.m[string(key)] = expires
}
func (this *Expiration) Add(key []byte, expires time.Time) {
this.locker.Lock()
this.m[string(key)] = expires
this.gc()
this.locker.Unlock()
}
func (this *Expiration) Remove(key []byte) {
this.locker.Lock()
delete(this.m, string(key))
this.locker.Unlock()
}
func (this *Expiration) Contains(key []byte) bool {
this.locker.RLock()
expires, ok := this.m[string(key)]
if ok && expires.Year() > 2000 && time.Now().After(expires) {
ok = false
}
this.locker.RUnlock()
return ok
}
func (this *Expiration) gc() {
// we won't gc too frequently
var currentTime = time.Now().Unix()
if this.lastGCAt >= currentTime {
return
}
this.lastGCAt = currentTime
var now = time.Now().Add(-10 * time.Second) // gc elements expired before 10 seconds ago
for key, expires := range this.m {
if expires.Year() > 2000 && now.After(expires) {
delete(this.m, key)
}
}
}

View File

@@ -0,0 +1,59 @@
// Copyright 2023 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package nftables_test
import (
"github.com/TeaOSLab/EdgeNode/internal/firewalls/nftables"
"github.com/iwind/TeaGo/rands"
"github.com/iwind/TeaGo/types"
"net"
"testing"
"time"
)
func TestExpiration_Add(t *testing.T) {
var expiration = nftables.NewExpiration()
{
expiration.Add([]byte{'a', 'b', 'c'}, time.Now())
t.Log(expiration.Contains([]byte{'a', 'b', 'c'}))
}
{
expiration.Add([]byte{'a', 'b', 'c'}, time.Now().Add(1*time.Second))
t.Log(expiration.Contains([]byte{'a', 'b', 'c'}))
}
{
expiration.Add([]byte{'a', 'b', 'c'}, time.Time{})
t.Log(expiration.Contains([]byte{'a', 'b', 'c'}))
}
{
expiration.Add([]byte{'a', 'b', 'c'}, time.Now().Add(-1*time.Second))
t.Log(expiration.Contains([]byte{'a', 'b', 'c'}))
}
{
expiration.Add([]byte{'a', 'b', 'c'}, time.Now().Add(-10*time.Second))
t.Log(expiration.Contains([]byte{'a', 'b', 'c'}))
}
{
expiration.Add([]byte{'a', 'b', 'c'}, time.Now().Add(1*time.Second))
expiration.Remove([]byte{'a', 'b', 'c'})
t.Log(expiration.Contains([]byte{'a', 'b', 'c'}))
}
{
expiration.Add(net.ParseIP("10.254.0.75").To4(), time.Now())
t.Log(expiration.Contains(net.ParseIP("10.254.0.75").To4()))
}
}
func BenchmarkNewExpiration(b *testing.B) {
var expiration = nftables.NewExpiration()
for i := 0; i < 10_000; i++ {
expiration.Add([]byte(types.String(types.String(rands.Int(0, 255))+"."+types.String(rands.Int(0, 255))+"."+types.String(rands.Int(0, 255))+"."+types.String(rands.Int(0, 255)))), time.Now().Add(3600*time.Second))
}
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
expiration.Add([]byte(types.String(types.String(rands.Int(0, 255))+"."+types.String(rands.Int(0, 255))+"."+types.String(rands.Int(0, 255))+"."+types.String(rands.Int(0, 255)))), time.Now().Add(3600*time.Second))
}
})
}

View File

@@ -1,4 +1,5 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. // Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
//go:build linux
package nftables package nftables

View File

@@ -0,0 +1,131 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
//go:build linux
package nftables
import (
"errors"
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
teaconst "github.com/TeaOSLab/EdgeNode/internal/const"
"github.com/TeaOSLab/EdgeNode/internal/events"
"github.com/TeaOSLab/EdgeNode/internal/goman"
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
executils "github.com/TeaOSLab/EdgeNode/internal/utils/exec"
"github.com/iwind/TeaGo/logs"
"os"
"os/exec"
"runtime"
"time"
)
func init() {
if !teaconst.IsMain {
return
}
events.On(events.EventReload, func() {
// linux only
if runtime.GOOS != "linux" {
return
}
nodeConfig, err := nodeconfigs.SharedNodeConfig()
if err != nil {
return
}
if nodeConfig == nil || !nodeConfig.AutoInstallNftables {
return
}
if os.Getgid() == 0 { // root user only
if len(NftExePath()) > 0 {
return
}
goman.New(func() {
err := NewInstaller().Install()
if err != nil {
// 不需要传到API节点
logs.Println("[NFTABLES]install nftables failed: " + err.Error())
}
})
}
})
}
// NftExePath 查找nftables可执行文件路径
func NftExePath() string {
path, _ := exec.LookPath("nft")
if len(path) > 0 {
return path
}
for _, possiblePath := range []string{
"/usr/sbin/nft",
} {
_, err := os.Stat(possiblePath)
if err == nil {
return possiblePath
}
}
return ""
}
type Installer struct {
}
func NewInstaller() *Installer {
return &Installer{}
}
func (this *Installer) Install() error {
// linux only
if runtime.GOOS != "linux" {
return nil
}
// 检查是否已经存在
if len(NftExePath()) > 0 {
return nil
}
var cmd *executils.Cmd
// check dnf
dnfExe, err := exec.LookPath("dnf")
if err == nil {
cmd = executils.NewCmd(dnfExe, "-y", "install", "nftables")
}
// check apt
if cmd == nil {
aptExe, err := exec.LookPath("apt")
if err == nil {
cmd = executils.NewCmd(aptExe, "install", "nftables")
}
}
// check yum
if cmd == nil {
yumExe, err := exec.LookPath("yum")
if err == nil {
cmd = executils.NewCmd(yumExe, "-y", "install", "nftables")
}
}
if cmd == nil {
return nil
}
cmd.WithTimeout(10 * time.Minute)
cmd.WithStderr()
err = cmd.Run()
if err != nil {
return errors.New(err.Error() + ": " + cmd.Stderr())
}
remotelogs.Println("NFTABLES", "installed nftables with command '"+cmd.String()+"' successfully")
return nil
}

View File

@@ -1,4 +1,5 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. // Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
//go:build linux
package nftables package nftables

View File

@@ -1,6 +1,5 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. // Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
//go:build linux //go:build linux
// +build linux
package nftables package nftables
@@ -35,17 +34,25 @@ type Set struct {
conn *Conn conn *Conn
rawSet *nft.Set rawSet *nft.Set
batch *SetBatch batch *SetBatch
expiration *Expiration
} }
func NewSet(conn *Conn, rawSet *nft.Set) *Set { func NewSet(conn *Conn, rawSet *nft.Set) *Set {
return &Set{ var set = &Set{
conn: conn, conn: conn,
rawSet: rawSet, rawSet: rawSet,
expiration: nil,
batch: &SetBatch{ batch: &SetBatch{
conn: conn, conn: conn,
rawSet: rawSet, rawSet: rawSet,
}, },
} }
// retrieve set elements to improve "delete" speed
set.initElements()
return set
} }
func (this *Set) Raw() *nft.Set { func (this *Set) Raw() *nft.Set {
@@ -56,12 +63,22 @@ func (this *Set) Name() string {
return this.rawSet.Name return this.rawSet.Name
} }
func (this *Set) AddElement(key []byte, options *ElementOptions) error { func (this *Set) AddElement(key []byte, options *ElementOptions, overwrite bool) error {
// check if already exists
if this.expiration != nil && !overwrite && this.expiration.Contains(key) {
return nil
}
var expiresTime = time.Time{}
var rawElement = nft.SetElement{ var rawElement = nft.SetElement{
Key: key, Key: key,
} }
if options != nil { if options != nil {
rawElement.Timeout = options.Timeout rawElement.Timeout = options.Timeout
if options.Timeout > 0 {
expiresTime = time.UnixMilli(time.Now().UnixMilli() + options.Timeout.Milliseconds())
}
} }
err := this.conn.Raw().SetAddElements(this.rawSet, []nft.SetElement{ err := this.conn.Raw().SetAddElements(this.rawSet, []nft.SetElement{
rawElement, rawElement,
@@ -71,9 +88,19 @@ func (this *Set) AddElement(key []byte, options *ElementOptions) error {
} }
err = this.conn.Commit() err = this.conn.Commit()
if err != nil { if err == nil {
if this.expiration != nil {
this.expiration.Add(key, expiresTime)
}
} else {
var isFileExistsErr = strings.Contains(err.Error(), "file exists")
if !overwrite && isFileExistsErr {
// ignore file exists error
return nil
}
// retry if exists // retry if exists
if strings.Contains(err.Error(), "file exists") { if overwrite && isFileExistsErr {
deleteErr := this.conn.Raw().SetDeleteElements(this.rawSet, []nft.SetElement{ deleteErr := this.conn.Raw().SetDeleteElements(this.rawSet, []nft.SetElement{
{ {
Key: key, Key: key,
@@ -85,6 +112,11 @@ func (this *Set) AddElement(key []byte, options *ElementOptions) error {
}) })
if err == nil { if err == nil {
err = this.conn.Commit() err = this.conn.Commit()
if err == nil {
if this.expiration != nil {
this.expiration.Add(key, expiresTime)
}
}
} }
} }
} }
@@ -93,20 +125,25 @@ func (this *Set) AddElement(key []byte, options *ElementOptions) error {
return err return err
} }
func (this *Set) AddIPElement(ip string, options *ElementOptions) error { func (this *Set) AddIPElement(ip string, options *ElementOptions, overwrite bool) error {
var ipObj = net.ParseIP(ip) var ipObj = net.ParseIP(ip)
if ipObj == nil { if ipObj == nil {
return errors.New("invalid ip '" + ip + "'") return errors.New("invalid ip '" + ip + "'")
} }
if utils.IsIPv4(ip) { if utils.IsIPv4(ip) {
return this.AddElement(ipObj.To4(), options) return this.AddElement(ipObj.To4(), options, overwrite)
} else { } else {
return this.AddElement(ipObj.To16(), options) return this.AddElement(ipObj.To16(), options, overwrite)
} }
} }
func (this *Set) DeleteElement(key []byte) error { func (this *Set) DeleteElement(key []byte) error {
// if set element does not exist, we return immediately
if this.expiration != nil && !this.expiration.Contains(key) {
return nil
}
err := this.conn.Raw().SetDeleteElements(this.rawSet, []nft.SetElement{ err := this.conn.Raw().SetDeleteElements(this.rawSet, []nft.SetElement{
{ {
Key: key, Key: key,
@@ -116,9 +153,17 @@ func (this *Set) DeleteElement(key []byte) error {
return err return err
} }
err = this.conn.Commit() err = this.conn.Commit()
if err != nil { if err == nil {
if this.expiration != nil {
this.expiration.Remove(key)
}
} else {
if strings.Contains(err.Error(), "no such file or directory") { if strings.Contains(err.Error(), "no such file or directory") {
err = nil err = nil
if this.expiration != nil {
this.expiration.Remove(key)
}
} }
} }
return err return err

View File

@@ -1,4 +1,5 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. // Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
//go:build linux
package nftables package nftables

View File

@@ -1,4 +1,5 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. // Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
//go:build linux
package nftables package nftables

View File

@@ -0,0 +1,8 @@
// Copyright 2023 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
//go:build linux && !plus
package nftables
func (this *Set) initElements() {
// NOT IMPLEMENTED
}

View File

@@ -1,4 +1,5 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. // Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
//go:build linux
package nftables_test package nftables_test
@@ -33,7 +34,7 @@ func getIPv4Set(t *testing.T) *nftables.Set {
func TestSet_AddElement(t *testing.T) { func TestSet_AddElement(t *testing.T) {
var set = getIPv4Set(t) var set = getIPv4Set(t)
err := set.AddElement(net.ParseIP("192.168.2.31").To4(), &nftables.ElementOptions{Timeout: 86400 * time.Second}) err := set.AddElement(net.ParseIP("192.168.2.31").To4(), &nftables.ElementOptions{Timeout: 86400 * time.Second}, false)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }

View File

@@ -1,6 +1,5 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. // Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
//go:build linux //go:build linux
// +build linux
package nftables package nftables

View File

@@ -1,6 +1,5 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. // Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
//go:build linux //go:build linux
// +build linux
package nftables_test package nftables_test
@@ -10,7 +9,10 @@ import (
) )
func getIPv4Table(t *testing.T) *nftables.Table { func getIPv4Table(t *testing.T) *nftables.Table {
var conn = nftables.NewConn() conn, err := nftables.NewConn()
if err != nil {
t.Fatal(err)
}
table, err := conn.GetTable("test_ipv4", nftables.TableFamilyIPv4) table, err := conn.GetTable("test_ipv4", nftables.TableFamilyIPv4)
if err != nil { if err != nil {
if err == nftables.ErrTableNotFound { if err == nftables.ErrTableNotFound {

View File

@@ -0,0 +1,30 @@
// Copyright 2023 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package firewalls
import (
"time"
)
// DropTemporaryTo 使用本地防火墙临时拦截IP数据包
func DropTemporaryTo(ip string, expiresAt int64) {
// 如果为0则表示是长期有效
if expiresAt <= 0 {
expiresAt = time.Now().Unix() + 3600
}
var timeout = expiresAt - time.Now().Unix()
if timeout < 1 {
return
}
if timeout > 3600 {
timeout = 3600
}
// 使用本地防火墙延长封禁
var fw = Firewall()
if fw != nil && !fw.IsMock() {
// 这里 int(int64) 转换的前提是限制了 timeout <= 3600否则将有整型溢出的风险
_ = fw.DropSourceIP(ip, int(timeout), true)
}
}

View File

@@ -15,7 +15,7 @@ var instanceId = uint64(0)
// New 新创建goroutine // New 新创建goroutine
func New(f func()) { func New(f func()) {
if teaconst.IsDaemon { if !teaconst.IsMain {
return return
} }
@@ -47,7 +47,7 @@ func New(f func()) {
// NewWithArgs 创建带有参数的goroutine // NewWithArgs 创建带有参数的goroutine
func NewWithArgs(f func(args ...interface{}), args ...interface{}) { func NewWithArgs(f func(args ...interface{}), args ...interface{}) {
if teaconst.IsDaemon { if !teaconst.IsMain {
return return
} }

View File

@@ -1,11 +1,11 @@
package iplibrary package iplibrary
import ( import (
"bytes"
"errors" "errors"
"fmt" "fmt"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb" "github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs" "github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs"
executils "github.com/TeaOSLab/EdgeNode/internal/utils/exec"
"os/exec" "os/exec"
"runtime" "runtime"
"time" "time"
@@ -144,12 +144,11 @@ func (this *FirewalldAction) runActionSingleIP(action string, listType IPListTyp
// MAC OS直接返回 // MAC OS直接返回
return nil return nil
} }
cmd := exec.Command(path, args...) cmd := executils.NewTimeoutCmd(30*time.Second, path, args...)
stderr := bytes.NewBuffer([]byte{}) cmd.WithStderr()
cmd.Stderr = stderr
err = cmd.Run() err = cmd.Run()
if err != nil { if err != nil {
return errors.New(err.Error() + ", output: " + string(stderr.Bytes())) return errors.New(err.Error() + ", output: " + cmd.Stderr())
} }
return nil return nil
} }

View File

@@ -1,10 +1,10 @@
package iplibrary package iplibrary
import ( import (
"bytes"
"errors" "errors"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb" "github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs" "github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs"
executils "github.com/TeaOSLab/EdgeNode/internal/utils/exec"
"github.com/iwind/TeaGo/types" "github.com/iwind/TeaGo/types"
"os/exec" "os/exec"
"runtime" "runtime"
@@ -31,15 +31,12 @@ type IPSetAction struct {
BaseAction BaseAction
config *firewallconfigs.FirewallActionIPSetConfig config *firewallconfigs.FirewallActionIPSetConfig
errorBuf *bytes.Buffer
ipsetNotfound bool ipsetNotfound bool
} }
func NewIPSetAction() *IPSetAction { func NewIPSetAction() *IPSetAction {
return &IPSetAction{ return &IPSetAction{}
errorBuf: &bytes.Buffer{},
}
} }
func (this *IPSetAction) Init(config *firewallconfigs.FirewallActionConfig) error { func (this *IPSetAction) Init(config *firewallconfigs.FirewallActionConfig) error {
@@ -68,14 +65,13 @@ func (this *IPSetAction) Init(config *firewallconfigs.FirewallActionConfig) erro
if len(listName) == 0 { if len(listName) == 0 {
continue continue
} }
var cmd = exec.Command(path, "create", listName, "hash:ip", "timeout", "0", "maxelem", "1000000") var cmd = executils.NewTimeoutCmd(30*time.Second, path, "create", listName, "hash:ip", "timeout", "0", "maxelem", "1000000")
var stderr = bytes.NewBuffer([]byte{}) cmd.WithStderr()
cmd.Stderr = stderr
err := cmd.Run() err := cmd.Run()
if err != nil { if err != nil {
var output = stderr.Bytes() var output = cmd.Stderr()
if !bytes.Contains(output, []byte("already exists")) { if !strings.Contains(output, "already exists") {
return errors.New("create ipset '" + listName + "': " + err.Error() + ", output: " + string(output)) return errors.New("create ipset '" + listName + "': " + err.Error() + ", output: " + output)
} else { } else {
err = nil err = nil
} }
@@ -87,14 +83,13 @@ func (this *IPSetAction) Init(config *firewallconfigs.FirewallActionConfig) erro
if len(listName) == 0 { if len(listName) == 0 {
continue continue
} }
var cmd = exec.Command(path, "create", listName, "hash:ip", "family", "inet6", "timeout", "0", "maxelem", "1000000") var cmd = executils.NewTimeoutCmd(30*time.Second, path, "create", listName, "hash:ip", "family", "inet6", "timeout", "0", "maxelem", "1000000")
var stderr = bytes.NewBuffer([]byte{}) cmd.WithStderr()
cmd.Stderr = stderr
err := cmd.Run() err := cmd.Run()
if err != nil { if err != nil {
var output = stderr.Bytes() var output = cmd.Stderr()
if !bytes.Contains(output, []byte("already exists")) { if !strings.Contains(output, "already exists") {
return errors.New("create ipset '" + listName + "': " + err.Error() + ", output: " + string(output)) return errors.New("create ipset '" + listName + "': " + err.Error() + ", output: " + output)
} else { } else {
err = nil err = nil
} }
@@ -114,16 +109,15 @@ func (this *IPSetAction) Init(config *firewallconfigs.FirewallActionConfig) erro
if len(listName) == 0 { if len(listName) == 0 {
continue continue
} }
cmd := exec.Command(path, "--permanent", "--new-ipset="+listName, "--type=hash:ip", "--option=timeout=0", "--option=maxelem=1000000") var cmd = executils.NewTimeoutCmd(30*time.Second, path, "--permanent", "--new-ipset="+listName, "--type=hash:ip", "--option=timeout=0", "--option=maxelem=1000000")
stderr := bytes.NewBuffer([]byte{}) cmd.WithStderr()
cmd.Stderr = stderr
err := cmd.Run() err := cmd.Run()
if err != nil { if err != nil {
output := stderr.Bytes() var output = cmd.Stderr()
if bytes.Contains(output, []byte("NAME_CONFLICT")) { if strings.Contains(output, "NAME_CONFLICT") {
err = nil err = nil
} else { } else {
return errors.New("firewall-cmd add ipset '" + listName + "': " + err.Error() + ", output: " + string(output)) return errors.New("firewall-cmd add ipset '" + listName + "': " + err.Error() + ", output: " + output)
} }
} }
} }
@@ -133,16 +127,15 @@ func (this *IPSetAction) Init(config *firewallconfigs.FirewallActionConfig) erro
if len(listName) == 0 { if len(listName) == 0 {
continue continue
} }
cmd := exec.Command(path, "--permanent", "--new-ipset="+listName, "--type=hash:ip", "--option=family=inet6", "--option=timeout=0", "--option=maxelem=1000000") var cmd = executils.NewTimeoutCmd(30*time.Second, path, "--permanent", "--new-ipset="+listName, "--type=hash:ip", "--option=family=inet6", "--option=timeout=0", "--option=maxelem=1000000")
stderr := bytes.NewBuffer([]byte{}) cmd.WithStderr()
cmd.Stderr = stderr
err := cmd.Run() err := cmd.Run()
if err != nil { if err != nil {
var output = stderr.Bytes() var output = cmd.Stderr()
if bytes.Contains(output, []byte("NAME_CONFLICT")) { if strings.Contains(output, "NAME_CONFLICT") {
err = nil err = nil
} else { } else {
return errors.New("firewall-cmd add ipset '" + listName + "': " + err.Error() + ", output: " + string(output)) return errors.New("firewall-cmd add ipset '" + listName + "': " + err.Error() + ", output: " + output)
} }
} }
} }
@@ -152,13 +145,11 @@ func (this *IPSetAction) Init(config *firewallconfigs.FirewallActionConfig) erro
if len(listName) == 0 { if len(listName) == 0 {
continue continue
} }
var cmd = exec.Command(path, "--permanent", "--add-rich-rule=rule source ipset='"+listName+"' accept") var cmd = executils.NewTimeoutCmd(30*time.Second, path, "--permanent", "--add-rich-rule=rule source ipset='"+listName+"' accept")
var stderr = bytes.NewBuffer([]byte{}) cmd.WithStderr()
cmd.Stderr = stderr
err := cmd.Run() err := cmd.Run()
if err != nil { if err != nil {
var output = stderr.Bytes() return errors.New("firewall-cmd add rich rule '" + listName + "': " + err.Error() + ", output: " + cmd.Stderr())
return errors.New("firewall-cmd add rich rule '" + listName + "': " + err.Error() + ", output: " + string(output))
} }
} }
@@ -167,25 +158,21 @@ func (this *IPSetAction) Init(config *firewallconfigs.FirewallActionConfig) erro
if len(listName) == 0 { if len(listName) == 0 {
continue continue
} }
var cmd = exec.Command(path, "--permanent", "--add-rich-rule=rule source ipset='"+listName+"' reject") var cmd = executils.NewTimeoutCmd(30*time.Second, path, "--permanent", "--add-rich-rule=rule source ipset='"+listName+"' reject")
var stderr = bytes.NewBuffer([]byte{}) cmd.WithStderr()
cmd.Stderr = stderr
err := cmd.Run() err := cmd.Run()
if err != nil { if err != nil {
var output = stderr.Bytes() return errors.New("firewall-cmd add rich rule '" + listName + "': " + err.Error() + ", output: " + cmd.Stderr())
return errors.New("firewall-cmd add rich rule '" + listName + "': " + err.Error() + ", output: " + string(output))
} }
} }
// reload // reload
{ {
cmd := exec.Command(path, "--reload") var cmd = executils.NewTimeoutCmd(30*time.Second, path, "--reload")
stderr := bytes.NewBuffer([]byte{}) cmd.WithStderr()
cmd.Stderr = stderr
err := cmd.Run() err := cmd.Run()
if err != nil { if err != nil {
var output = stderr.Bytes() return errors.New("firewall-cmd reload: " + err.Error() + ", output: " + cmd.Stderr())
return errors.New("firewall-cmd reload: " + err.Error() + ", output: " + string(output))
} }
} }
} }
@@ -204,19 +191,17 @@ func (this *IPSetAction) Init(config *firewallconfigs.FirewallActionConfig) erro
} }
// 检查规则是否存在 // 检查规则是否存在
var cmd = exec.Command(path, "-C", "INPUT", "-m", "set", "--match-set", listName, "src", "-j", "ACCEPT") var cmd = executils.NewTimeoutCmd(30*time.Second, path, "-C", "INPUT", "-m", "set", "--match-set", listName, "src", "-j", "ACCEPT")
err := cmd.Run() err := cmd.Run()
var exists = err == nil var exists = err == nil
// 添加规则 // 添加规则
if !exists { if !exists {
var cmd = exec.Command(path, "-A", "INPUT", "-m", "set", "--match-set", listName, "src", "-j", "ACCEPT") var cmd = executils.NewTimeoutCmd(30*time.Second, path, "-A", "INPUT", "-m", "set", "--match-set", listName, "src", "-j", "ACCEPT")
var stderr = bytes.NewBuffer([]byte{}) cmd.WithStderr()
cmd.Stderr = stderr
err := cmd.Run() err := cmd.Run()
if err != nil { if err != nil {
var output = stderr.Bytes() return errors.New("iptables add rule: " + err.Error() + ", output: " + cmd.Stderr())
return errors.New("iptables add rule: " + err.Error() + ", output: " + string(output))
} }
} }
} }
@@ -228,18 +213,16 @@ func (this *IPSetAction) Init(config *firewallconfigs.FirewallActionConfig) erro
} }
// 检查规则是否存在 // 检查规则是否存在
var cmd = exec.Command(path, "-C", "INPUT", "-m", "set", "--match-set", listName, "src", "-j", "REJECT") var cmd = executils.NewTimeoutCmd(30*time.Second, path, "-C", "INPUT", "-m", "set", "--match-set", listName, "src", "-j", "REJECT")
err := cmd.Run() err := cmd.Run()
var exists = err == nil var exists = err == nil
if !exists { if !exists {
var cmd = exec.Command(path, "-A", "INPUT", "-m", "set", "--match-set", listName, "src", "-j", "REJECT") var cmd = executils.NewTimeoutCmd(30*time.Second, path, "-A", "INPUT", "-m", "set", "--match-set", listName, "src", "-j", "REJECT")
var stderr = bytes.NewBuffer([]byte{}) cmd.WithStderr()
cmd.Stderr = stderr
err := cmd.Run() err := cmd.Run()
if err != nil { if err != nil {
var output = stderr.Bytes() return errors.New("iptables add rule: " + err.Error() + ", output: " + cmd.Stderr())
return errors.New("iptables add rule: " + err.Error() + ", output: " + string(output))
} }
} }
} }
@@ -361,12 +344,11 @@ func (this *IPSetAction) runActionSingleIP(action string, listType IPListType, i
return nil return nil
} }
this.errorBuf.Reset() var cmd = executils.NewTimeoutCmd(30*time.Second, path, args...)
var cmd = exec.Command(path, args...) cmd.WithStderr()
cmd.Stderr = this.errorBuf
err = cmd.Run() err = cmd.Run()
if err != nil { if err != nil {
var errString = this.errorBuf.String() var errString = cmd.Stderr()
if action == "deleteItem" && strings.Contains(errString, "not added") { if action == "deleteItem" && strings.Contains(errString, "not added") {
return nil return nil
} }

View File

@@ -1,16 +1,20 @@
package iplibrary package iplibrary
import ( import (
"bytes"
"errors" "errors"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb" "github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs" "github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs"
"github.com/TeaOSLab/EdgeNode/internal/utils"
executils "github.com/TeaOSLab/EdgeNode/internal/utils/exec"
"os/exec" "os/exec"
"runtime" "runtime"
"strings"
"time"
) )
// IPTablesAction IPTables动作 // IPTablesAction IPTables动作
// 相关命令: // 相关命令:
//
// iptables -A INPUT -s "192.168.2.32" -j ACCEPT // iptables -A INPUT -s "192.168.2.32" -j ACCEPT
// iptables -A INPUT -s "192.168.2.32" -j REJECT // iptables -A INPUT -s "192.168.2.32" -j REJECT
// iptables -D INPUT ... // iptables -D INPUT ...
@@ -71,10 +75,16 @@ func (this *IPTablesAction) runAction(action string, listType IPListType, item *
} }
func (this *IPTablesAction) runActionSingleIP(action string, listType IPListType, item *pb.IPItem) error { func (this *IPTablesAction) runActionSingleIP(action string, listType IPListType, item *pb.IPItem) error {
// 暂时不支持ipv6
// TODO 将来支持ipv6
if utils.IsIPv6(item.IpFrom) {
return nil
}
if item.Type == "all" { if item.Type == "all" {
return nil return nil
} }
path := this.config.Path var path = this.config.Path
var err error var err error
if len(path) == 0 { if len(path) == 0 {
path, err = exec.LookPath("iptables") path, err = exec.LookPath("iptables")
@@ -85,6 +95,7 @@ func (this *IPTablesAction) runActionSingleIP(action string, listType IPListType
this.iptablesNotFound = true this.iptablesNotFound = true
return err return err
} }
this.config.Path = path
} }
iptablesAction := "" iptablesAction := ""
switch action { switch action {
@@ -110,16 +121,15 @@ func (this *IPTablesAction) runActionSingleIP(action string, listType IPListType
return nil return nil
} }
cmd := exec.Command(path, args...) var cmd = executils.NewTimeoutCmd(30*time.Second, path, args...)
stderr := bytes.NewBuffer([]byte{}) cmd.WithStderr()
cmd.Stderr = stderr
err = cmd.Run() err = cmd.Run()
if err != nil { if err != nil {
output := stderr.Bytes() var output = cmd.Stderr()
if bytes.Contains(output, []byte("No chain/target/match")) { if strings.Contains(output, "No chain/target/match") {
err = nil err = nil
} else { } else {
return errors.New(err.Error() + ", output: " + string(output)) return errors.New(err.Error() + ", output: " + output)
} }
} }
return nil return nil

View File

@@ -68,7 +68,7 @@ func (this *ActionManager) UpdateActions(actions []*firewallconfigs.FirewallActi
remotelogs.Error("IPLIBRARY/ACTION_MANAGER", "action "+strconv.FormatInt(newAction.Id, 10)+", type:"+newAction.Type+": "+err.Error()) remotelogs.Error("IPLIBRARY/ACTION_MANAGER", "action "+strconv.FormatInt(newAction.Id, 10)+", type:"+newAction.Type+": "+err.Error())
continue continue
} }
if bytes.Compare(newConfigJSON, oldConfigJSON) != 0 { if !bytes.Equal(newConfigJSON, oldConfigJSON) {
_ = oldInstance.Close() _ = oldInstance.Close()
// 重新创建 // 重新创建

View File

@@ -1,13 +1,13 @@
package iplibrary package iplibrary
import ( import (
"bytes"
"errors" "errors"
"fmt" "fmt"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb" "github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs" "github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs"
"os/exec" executils "github.com/TeaOSLab/EdgeNode/internal/utils/exec"
"path/filepath" "path/filepath"
"time"
) )
// ScriptAction 脚本命令动作 // ScriptAction 脚本命令动作
@@ -45,25 +45,24 @@ func (this *ScriptAction) DeleteItem(listType IPListType, item *pb.IPItem) error
func (this *ScriptAction) runAction(action string, listType IPListType, item *pb.IPItem) error { func (this *ScriptAction) runAction(action string, listType IPListType, item *pb.IPItem) error {
// TODO 智能支持 .sh 脚本文件 // TODO 智能支持 .sh 脚本文件
cmd := exec.Command(this.config.Path) var cmd = executils.NewTimeoutCmd(30*time.Second, this.config.Path)
cmd.Env = []string{ cmd.WithEnv([]string{
"ACTION=" + action, "ACTION=" + action,
"TYPE=" + item.Type, "TYPE=" + item.Type,
"IP_FROM=" + item.IpFrom, "IP_FROM=" + item.IpFrom,
"IP_TO=" + item.IpTo, "IP_TO=" + item.IpTo,
"EXPIRED_AT=" + fmt.Sprintf("%d", item.ExpiredAt), "EXPIRED_AT=" + fmt.Sprintf("%d", item.ExpiredAt),
"LIST_TYPE=" + listType, "LIST_TYPE=" + listType,
} })
if len(this.config.Cwd) > 0 { if len(this.config.Cwd) > 0 {
cmd.Dir = this.config.Cwd cmd.WithDir(this.config.Cwd)
} else { } else {
cmd.Dir = filepath.Dir(this.config.Path) cmd.WithDir(filepath.Dir(this.config.Path))
} }
stderr := bytes.NewBuffer([]byte{}) cmd.WithStderr()
cmd.Stderr = stderr
err := cmd.Run() err := cmd.Run()
if err != nil { if err != nil {
return errors.New(err.Error() + ", output: " + string(stderr.Bytes())) return errors.New(err.Error() + ", output: " + cmd.Stderr())
} }
return nil return nil
} }

View File

@@ -1,6 +1,8 @@
package iplibrary package iplibrary
import "github.com/TeaOSLab/EdgeNode/internal/utils" import (
"github.com/TeaOSLab/EdgeNode/internal/utils/fasttime"
)
type IPItemType = string type IPItemType = string
@@ -45,7 +47,7 @@ func (this *IPItem) containsIPv4(ip uint64) bool {
return false return false
} }
} }
if this.ExpiredAt > 0 && this.ExpiredAt < utils.UnixTime() { if this.ExpiredAt > 0 && this.ExpiredAt < fasttime.Now().Unix() {
return false return false
} }
return true return true
@@ -56,7 +58,7 @@ func (this *IPItem) containsIPv6(ip uint64) bool {
if this.IPFrom != ip { if this.IPFrom != ip {
return false return false
} }
if this.ExpiredAt > 0 && this.ExpiredAt < utils.UnixTime() { if this.ExpiredAt > 0 && this.ExpiredAt < fasttime.Now().Unix() {
return false return false
} }
return true return true
@@ -64,7 +66,7 @@ func (this *IPItem) containsIPv6(ip uint64) bool {
// 检查是否包所有IP // 检查是否包所有IP
func (this *IPItem) containsAll() bool { func (this *IPItem) containsAll() bool {
if this.ExpiredAt > 0 && this.ExpiredAt < utils.UnixTime() { if this.ExpiredAt > 0 && this.ExpiredAt < fasttime.Now().Unix() {
return false return false
} }
return true return true

View File

@@ -3,6 +3,7 @@ package iplibrary
import ( import (
"github.com/TeaOSLab/EdgeNode/internal/utils" "github.com/TeaOSLab/EdgeNode/internal/utils"
"github.com/TeaOSLab/EdgeNode/internal/utils/expires" "github.com/TeaOSLab/EdgeNode/internal/utils/expires"
"github.com/TeaOSLab/EdgeNode/internal/utils/fasttime"
"sort" "sort"
"sync" "sync"
) )
@@ -72,6 +73,25 @@ func (this *IPList) Contains(ip uint64) bool {
return item != nil return item != nil
} }
// ContainsExpires 判断是否包含某个IP
func (this *IPList) ContainsExpires(ip uint64) (expiresAt int64, ok bool) {
this.locker.RLock()
if len(this.allItemsMap) > 0 {
this.locker.RUnlock()
return 0, true
}
var item = this.lookupIP(ip)
this.locker.RUnlock()
if item == nil {
return
}
return item.ExpiredAt, true
}
// ContainsIPStrings 是否包含一组IP中的任意一个并返回匹配的第一个Item // ContainsIPStrings 是否包含一组IP中的任意一个并返回匹配的第一个Item
func (this *IPList) ContainsIPStrings(ipStrings []string) (item *IPItem, found bool) { func (this *IPList) ContainsIPStrings(ipStrings []string) (item *IPItem, found bool) {
if len(ipStrings) == 0 { if len(ipStrings) == 0 {
@@ -110,7 +130,7 @@ func (this *IPList) addItem(item *IPItem, sortable bool) {
return return
} }
if item.ExpiredAt > 0 && item.ExpiredAt < utils.UnixTime() { if item.ExpiredAt > 0 && item.ExpiredAt < fasttime.Now().Unix() {
return return
} }

View File

@@ -3,28 +3,31 @@
package iplibrary package iplibrary
import ( import (
"database/sql"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb" "github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeNode/internal/events" "github.com/TeaOSLab/EdgeNode/internal/events"
"github.com/TeaOSLab/EdgeNode/internal/goman" "github.com/TeaOSLab/EdgeNode/internal/goman"
"github.com/TeaOSLab/EdgeNode/internal/remotelogs" "github.com/TeaOSLab/EdgeNode/internal/remotelogs"
"github.com/TeaOSLab/EdgeNode/internal/utils/dbs"
"github.com/iwind/TeaGo/Tea" "github.com/iwind/TeaGo/Tea"
_ "github.com/mattn/go-sqlite3"
"os" "os"
"path/filepath" "path/filepath"
"time" "time"
) )
type IPListDB struct { type IPListDB struct {
db *sql.DB db *dbs.DB
itemTableName string itemTableName string
versionTableName string
deleteExpiredItemsStmt *sql.Stmt deleteExpiredItemsStmt *dbs.Stmt
deleteItemStmt *sql.Stmt deleteItemStmt *dbs.Stmt
insertItemStmt *sql.Stmt insertItemStmt *dbs.Stmt
selectItemsStmt *sql.Stmt selectItemsStmt *dbs.Stmt
selectMaxVersionStmt *sql.Stmt selectMaxItemVersionStmt *dbs.Stmt
selectVersionStmt *dbs.Stmt
updateVersionStmt *dbs.Stmt
cleanTicker *time.Ticker cleanTicker *time.Ticker
@@ -36,6 +39,7 @@ type IPListDB struct {
func NewIPListDB() (*IPListDB, error) { func NewIPListDB() (*IPListDB, error) {
var db = &IPListDB{ var db = &IPListDB{
itemTableName: "ipItems", itemTableName: "ipItems",
versionTableName: "versions",
dir: filepath.Clean(Tea.Root + "/data"), dir: filepath.Clean(Tea.Root + "/data"),
cleanTicker: time.NewTicker(24 * time.Hour), cleanTicker: time.NewTicker(24 * time.Hour),
} }
@@ -55,10 +59,8 @@ func (this *IPListDB) init() error {
} }
var path = this.dir + "/ip_list.db" var path = this.dir + "/ip_list.db"
_ = os.Remove(path + "-shm")
_ = os.Remove(path + "-wal")
db, err := sql.Open("sqlite3", "file:"+path+"?cache=shared&mode=rwc&_journal_mode=WAL&_sync=OFF") db, err := dbs.OpenWriter("file:" + path + "?cache=shared&mode=rwc&_journal_mode=WAL&_sync=OFF&_locking_mode=EXCLUSIVE")
if err != nil { if err != nil {
return err return err
} }
@@ -71,6 +73,14 @@ func (this *IPListDB) init() error {
this.db = db this.db = db
// 恢复数据库
var recoverEnv, _ = os.LookupEnv("EdgeRecover")
if len(recoverEnv) > 0 {
for _, indexName := range []string{"ip_list_itemId", "ip_list_expiredAt"} {
_, _ = db.Exec(`REINDEX "` + indexName + `"`)
}
}
// 初始化数据库 // 初始化数据库
_, err = db.Exec(`CREATE TABLE IF NOT EXISTS "` + this.itemTableName + `" ( _, err = db.Exec(`CREATE TABLE IF NOT EXISTS "` + this.itemTableName + `" (
"id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "id" integer NOT NULL PRIMARY KEY AUTOINCREMENT,
@@ -103,6 +113,15 @@ ON "` + this.itemTableName + `" (
return err return err
} }
_, err = db.Exec(`CREATE TABLE IF NOT EXISTS "` + this.versionTableName + `" (
"id" integer NOT NULL PRIMARY KEY AUTOINCREMENT,
"version" integer DEFAULT 0
);
`)
if err != nil {
return err
}
// 初始化SQL语句 // 初始化SQL语句
this.deleteExpiredItemsStmt, err = this.db.Prepare(`DELETE FROM "` + this.itemTableName + `" WHERE "expiredAt">0 AND "expiredAt"<?`) this.deleteExpiredItemsStmt, err = this.db.Prepare(`DELETE FROM "` + this.itemTableName + `" WHERE "expiredAt">0 AND "expiredAt"<?`)
if err != nil { if err != nil {
@@ -124,7 +143,20 @@ ON "` + this.itemTableName + `" (
return err return err
} }
this.selectMaxVersionStmt, err = this.db.Prepare(`SELECT "version" FROM "` + this.itemTableName + `" ORDER BY "id" DESC LIMIT 1`) this.selectMaxItemVersionStmt, err = this.db.Prepare(`SELECT "version" FROM "` + this.itemTableName + `" ORDER BY "id" DESC LIMIT 1`)
if err != nil {
return err
}
this.selectVersionStmt, err = this.db.Prepare(`SELECT "version" FROM "` + this.versionTableName + `" LIMIT 1`)
if err != nil {
return err
}
this.updateVersionStmt, err = this.db.Prepare(`REPLACE INTO "` + this.versionTableName + `" ("id", "version") VALUES (1, ?)`)
if err != nil {
return err
}
this.db = db this.db = db
@@ -164,10 +196,20 @@ func (this *IPListDB) AddItem(item *pb.IPItem) error {
if err != nil { if err != nil {
return err return err
} }
// 如果是删除,则不再创建新记录
if item.IsDeleted {
return this.UpdateMaxVersion(item.Version)
}
_, err = this.insertItemStmt.Exec(item.ListId, item.ListType, item.IsGlobal, item.Type, item.Id, item.IpFrom, item.IpTo, item.ExpiredAt, item.EventLevel, item.IsDeleted, item.Version, item.NodeId, item.ServerId) _, err = this.insertItemStmt.Exec(item.ListId, item.ListType, item.IsGlobal, item.Type, item.Id, item.IpFrom, item.IpTo, item.ExpiredAt, item.EventLevel, item.IsDeleted, item.Version, item.NodeId, item.ServerId)
if err != nil {
return err return err
} }
return this.UpdateMaxVersion(item.Version)
}
func (this *IPListDB) ReadItems(offset int64, size int64) (items []*pb.IPItem, err error) { func (this *IPListDB) ReadItems(offset int64, size int64) (items []*pb.IPItem, err error) {
if this.isClosed { if this.isClosed {
return return
@@ -199,7 +241,22 @@ func (this *IPListDB) ReadMaxVersion() int64 {
return 0 return 0
} }
var row = this.selectMaxVersionStmt.QueryRow() // from version table
{
var row = this.selectVersionStmt.QueryRow()
if row == nil {
return 0
}
var version int64
err := row.Scan(&version)
if err == nil {
return version
}
}
// from items table
{
var row = this.selectMaxItemVersionStmt.QueryRow()
if row == nil { if row == nil {
return 0 return 0
} }
@@ -208,18 +265,39 @@ func (this *IPListDB) ReadMaxVersion() int64 {
if err != nil { if err != nil {
return 0 return 0
} }
return version return version
} }
}
// UpdateMaxVersion 修改版本号
func (this *IPListDB) UpdateMaxVersion(version int64) error {
if this.isClosed {
return nil
}
_, err := this.updateVersionStmt.Exec(version)
return err
}
func (this *IPListDB) Close() error { func (this *IPListDB) Close() error {
this.isClosed = true this.isClosed = true
if this.db != nil { if this.db != nil {
_ = this.deleteExpiredItemsStmt.Close() for _, stmt := range []*dbs.Stmt{
_ = this.deleteItemStmt.Close() this.deleteExpiredItemsStmt,
_ = this.insertItemStmt.Close() this.deleteItemStmt,
_ = this.selectItemsStmt.Close() this.insertItemStmt,
_ = this.selectMaxVersionStmt.Close() this.selectItemsStmt,
this.selectMaxItemVersionStmt, // ipItems table
this.selectVersionStmt, // versions table
this.updateVersionStmt,
} {
if stmt != nil {
_ = stmt.Close()
}
}
return this.db.Close() return this.db.Close()
} }

View File

@@ -79,3 +79,15 @@ func TestIPListDB_ReadMaxVersion(t *testing.T) {
} }
t.Log(db.ReadMaxVersion()) t.Log(db.ReadMaxVersion())
} }
func TestIPListDB_UpdateMaxVersion(t *testing.T) {
db, err := iplibrary.NewIPListDB()
if err != nil {
t.Fatal(err)
}
err = db.UpdateMaxVersion(1027)
if err != nil {
t.Fatal(err)
}
t.Log(db.ReadMaxVersion())
}

View File

@@ -10,50 +10,54 @@ import (
// AllowIP 检查IP是否被允许访问 // AllowIP 检查IP是否被允许访问
// 如果一个IP不在任何名单中则允许访问 // 如果一个IP不在任何名单中则允许访问
func AllowIP(ip string, serverId int64) (canGoNext bool, inAllowList bool) { func AllowIP(ip string, serverId int64) (canGoNext bool, inAllowList bool, expiresAt int64) {
if !Tea.IsTesting() { // 如果在测试环境,我们不加入一些白名单,以便于可以在本地和局域网正常测试 if !Tea.IsTesting() { // 如果在测试环境,我们不加入一些白名单,以便于可以在本地和局域网正常测试
// 放行lo // 放行lo
if ip == "127.0.0.1" || ip == "::1" { if ip == "127.0.0.1" || ip == "::1" {
return true, true return true, true, 0
} }
// check node // check node
nodeConfig, err := nodeconfigs.SharedNodeConfig() nodeConfig, err := nodeconfigs.SharedNodeConfig()
if err == nil && nodeConfig.IPIsAutoAllowed(ip) { if err == nil && nodeConfig.IPIsAutoAllowed(ip) {
return true, true return true, true, 0
} }
} }
var ipLong = utils.IP2Long(ip) var ipLong = utils.IP2Long(ip)
if ipLong == 0 { if ipLong == 0 {
return false, false return false, false, 0
} }
// check white lists // check white lists
if GlobalWhiteIPList.Contains(ipLong) { if GlobalWhiteIPList.Contains(ipLong) {
return true, true return true, true, 0
} }
if serverId > 0 { if serverId > 0 {
var list = SharedServerListManager.FindWhiteList(serverId, false) var list = SharedServerListManager.FindWhiteList(serverId, false)
if list != nil && list.Contains(ipLong) { if list != nil && list.Contains(ipLong) {
return true, true return true, true, 0
} }
} }
// check black lists // check black lists
if GlobalBlackIPList.Contains(ipLong) { expiresAt, ok := GlobalBlackIPList.ContainsExpires(ipLong)
return false, false if ok {
return false, false, expiresAt
} }
if serverId > 0 { if serverId > 0 {
var list = SharedServerListManager.FindBlackList(serverId, false) var list = SharedServerListManager.FindBlackList(serverId, false)
if list != nil && list.Contains(ipLong) { if list != nil {
return false, false expiresAt, ok = list.ContainsExpires(ipLong)
if ok {
return false, false, expiresAt
}
} }
} }
return true, false return true, false, 0
} }
// IsInWhiteList 检查IP是否在白名单中 // IsInWhiteList 检查IP是否在白名单中
@@ -73,7 +77,7 @@ func AllowIPStrings(ipStrings []string, serverId int64) bool {
return true return true
} }
for _, ip := range ipStrings { for _, ip := range ipStrings {
isAllowed, _ := AllowIP(ip, serverId) isAllowed, _, _ := AllowIP(ip, serverId)
if !isAllowed { if !isAllowed {
return false return false
} }

View File

@@ -1,7 +1,9 @@
package iplibrary package iplibrary
import ( import (
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb" "github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
teaconst "github.com/TeaOSLab/EdgeNode/internal/const"
"github.com/TeaOSLab/EdgeNode/internal/events" "github.com/TeaOSLab/EdgeNode/internal/events"
"github.com/TeaOSLab/EdgeNode/internal/goman" "github.com/TeaOSLab/EdgeNode/internal/goman"
"github.com/TeaOSLab/EdgeNode/internal/remotelogs" "github.com/TeaOSLab/EdgeNode/internal/remotelogs"
@@ -18,6 +20,10 @@ var SharedIPListManager = NewIPListManager()
var IPListUpdateNotify = make(chan bool, 1) var IPListUpdateNotify = make(chan bool, 1)
func init() { func init() {
if !teaconst.IsMain {
return
}
events.On(events.EventLoaded, func() { events.On(events.EventLoaded, func() {
goman.New(func() { goman.New(func() {
SharedIPListManager.Start() SharedIPListManager.Start()
@@ -26,6 +32,13 @@ func init() {
events.On(events.EventQuit, func() { events.On(events.EventQuit, func() {
SharedIPListManager.Stop() SharedIPListManager.Stop()
}) })
var ticker = time.NewTicker(24 * time.Hour)
goman.New(func() {
for range ticker.C {
SharedIPListManager.DeleteExpiredItems()
}
})
} }
// IPListManager IP名单管理 // IPListManager IP名单管理
@@ -34,17 +47,20 @@ type IPListManager struct {
db *IPListDB db *IPListDB
version int64 lastVersion int64
pageSize int64 fetchPageSize int64
listMap map[int64]*IPList listMap map[int64]*IPList
locker sync.Mutex locker sync.Mutex
isFirstTime bool
} }
func NewIPListManager() *IPListManager { func NewIPListManager() *IPListManager {
return &IPListManager{ return &IPListManager{
pageSize: 1000, fetchPageSize: 5_000,
listMap: map[int64]*IPList{}, listMap: map[int64]*IPList{},
isFirstTime: true,
} }
} }
@@ -104,11 +120,11 @@ func (this *IPListManager) init() {
_ = db.DeleteExpiredItems() _ = db.DeleteExpiredItems()
// 本地数据库中最大版本号 // 本地数据库中最大版本号
this.version = db.ReadMaxVersion() this.lastVersion = db.ReadMaxVersion()
// 从本地数据库中加载 // 从本地数据库中加载
var offset int64 = 0 var offset int64 = 0
var size int64 = 1000 var size int64 = 2_000
for { for {
items, err := db.ReadItems(offset, size) items, err := db.ReadItems(offset, size)
var l = len(items) var l = len(items)
@@ -129,6 +145,17 @@ func (this *IPListManager) init() {
} }
func (this *IPListManager) loop() error { func (this *IPListManager) loop() error {
// 是否同步IP名单
nodeConfig, _ := nodeconfigs.SharedNodeConfig()
if nodeConfig != nil && !nodeConfig.EnableIPLists {
return nil
}
// 第一次同步则打印信息
if this.isFirstTime {
remotelogs.Println("IP_LIST_MANAGER", "initializing ip items ...")
}
for { for {
hasNext, err := this.fetch() hasNext, err := this.fetch()
if err != nil { if err != nil {
@@ -140,6 +167,12 @@ func (this *IPListManager) loop() error {
time.Sleep(1 * time.Second) time.Sleep(1 * time.Second)
} }
// 第一次同步则打印信息
if this.isFirstTime {
this.isFirstTime = false
remotelogs.Println("IP_LIST_MANAGER", "finished initializing ip items")
}
return nil return nil
} }
@@ -149,8 +182,8 @@ func (this *IPListManager) fetch() (hasNext bool, err error) {
return false, err return false, err
} }
itemsResp, err := rpcClient.IPItemRPC.ListIPItemsAfterVersion(rpcClient.Context(), &pb.ListIPItemsAfterVersionRequest{ itemsResp, err := rpcClient.IPItemRPC.ListIPItemsAfterVersion(rpcClient.Context(), &pb.ListIPItemsAfterVersionRequest{
Version: this.version, Version: this.lastVersion,
Size: this.pageSize, Size: this.fetchPageSize,
}) })
if err != nil { if err != nil {
if rpc.IsConnError(err) { if rpc.IsConnError(err) {
@@ -186,6 +219,13 @@ func (this *IPListManager) FindList(listId int64) *IPList {
return list return list
} }
func (this *IPListManager) DeleteExpiredItems() {
if this.db != nil {
_ = this.db.DeleteExpiredItems()
}
}
// 处理IP条目
func (this *IPListManager) processItems(items []*pb.IPItem, fromRemote bool) { func (this *IPListManager) processItems(items []*pb.IPItem, fromRemote bool) {
var changedLists = map[*IPList]zero.Zero{} var changedLists = map[*IPList]zero.Zero{}
for _, item := range items { for _, item := range items {
@@ -255,8 +295,8 @@ func (this *IPListManager) processItems(items []*pb.IPItem, fromRemote bool) {
if fromRemote { if fromRemote {
var latestVersion = items[len(items)-1].Version var latestVersion = items[len(items)-1].Version
if latestVersion > this.version { if latestVersion > this.lastVersion {
this.version = latestVersion this.lastVersion = latestVersion
} }
} }
} }

View File

@@ -4,6 +4,7 @@ package metrics
import ( import (
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs" "github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
teaconst "github.com/TeaOSLab/EdgeNode/internal/const"
"github.com/TeaOSLab/EdgeNode/internal/events" "github.com/TeaOSLab/EdgeNode/internal/events"
"github.com/TeaOSLab/EdgeNode/internal/remotelogs" "github.com/TeaOSLab/EdgeNode/internal/remotelogs"
"strconv" "strconv"
@@ -13,6 +14,10 @@ import (
var SharedManager = NewManager() var SharedManager = NewManager()
func init() { func init() {
if !teaconst.IsMain {
return
}
events.On(events.EventQuit, func() { events.On(events.EventQuit, func() {
SharedManager.Quit() SharedManager.Quit()
}) })

View File

@@ -3,7 +3,6 @@
package metrics package metrics
import ( import (
"database/sql"
"encoding/json" "encoding/json"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb" "github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs" "github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
@@ -17,7 +16,6 @@ import (
"github.com/TeaOSLab/EdgeNode/internal/zero" "github.com/TeaOSLab/EdgeNode/internal/zero"
"github.com/iwind/TeaGo/Tea" "github.com/iwind/TeaGo/Tea"
"github.com/iwind/TeaGo/types" "github.com/iwind/TeaGo/types"
_ "github.com/mattn/go-sqlite3"
"os" "os"
"strconv" "strconv"
"sync" "sync"
@@ -50,11 +48,11 @@ type Task struct {
cleanVersion int32 cleanVersion int32
insertStatStmt *sql.Stmt insertStatStmt *dbs.Stmt
deleteByVersionStmt *sql.Stmt deleteByVersionStmt *dbs.Stmt
deleteByExpiresTimeStmt *sql.Stmt deleteByExpiresTimeStmt *dbs.Stmt
selectTopStmt *sql.Stmt selectTopStmt *dbs.Stmt
sumStmt *sql.Stmt sumStmt *dbs.Stmt
serverIdMap map[int64]zero.Zero // 所有的服务Ids serverIdMap map[int64]zero.Zero // 所有的服务Ids
timeMap map[string]zero.Zero // time => bool timeMap map[string]zero.Zero // time => bool
@@ -91,15 +89,21 @@ func (this *Task) Init() error {
} }
var path = dir + "/metric." + types.String(this.item.Id) + ".db" var path = dir + "/metric." + types.String(this.item.Id) + ".db"
_ = os.Remove(path + "-shm")
_ = os.Remove(path + "-wal")
db, err := sql.Open("sqlite3", "file:"+path+"?cache=shared&mode=rwc&_journal_mode=WAL&_sync=OFF") db, err := dbs.OpenWriter("file:" + path + "?cache=shared&mode=rwc&_journal_mode=WAL&_sync=OFF&_locking_mode=EXCLUSIVE")
if err != nil { if err != nil {
return err return err
} }
db.SetMaxOpenConns(1) db.SetMaxOpenConns(1)
this.db = dbs.NewDB(db) this.db = db
// 恢复数据库
var recoverEnv, _ = os.LookupEnv("EdgeRecover")
if len(recoverEnv) > 0 {
for _, indexName := range []string{"serverId", "hash"} {
_, _ = db.Exec(`REINDEX "` + indexName + `"`)
}
}
if teaconst.EnableDBStat { if teaconst.EnableDBStat {
this.db.EnableStat(true) this.db.EnableStat(true)

View File

@@ -5,6 +5,7 @@ package monitor
import ( import (
"encoding/json" "encoding/json"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb" "github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
teaconst "github.com/TeaOSLab/EdgeNode/internal/const"
"github.com/TeaOSLab/EdgeNode/internal/events" "github.com/TeaOSLab/EdgeNode/internal/events"
"github.com/TeaOSLab/EdgeNode/internal/goman" "github.com/TeaOSLab/EdgeNode/internal/goman"
"github.com/TeaOSLab/EdgeNode/internal/remotelogs" "github.com/TeaOSLab/EdgeNode/internal/remotelogs"
@@ -16,6 +17,10 @@ import (
var SharedValueQueue = NewValueQueue() var SharedValueQueue = NewValueQueue()
func init() { func init() {
if !teaconst.IsMain {
return
}
events.On(events.EventLoaded, func() { events.On(events.EventLoaded, func() {
goman.New(func() { goman.New(func() {
SharedValueQueue.Start() SharedValueQueue.Start()

View File

@@ -1,7 +1,6 @@
package nodes package nodes
import ( import (
"bytes"
"context" "context"
"encoding/json" "encoding/json"
"fmt" "fmt"
@@ -17,7 +16,7 @@ import (
"github.com/TeaOSLab/EdgeNode/internal/goman" "github.com/TeaOSLab/EdgeNode/internal/goman"
"github.com/TeaOSLab/EdgeNode/internal/remotelogs" "github.com/TeaOSLab/EdgeNode/internal/remotelogs"
"github.com/TeaOSLab/EdgeNode/internal/rpc" "github.com/TeaOSLab/EdgeNode/internal/rpc"
"github.com/TeaOSLab/EdgeNode/internal/utils" executils "github.com/TeaOSLab/EdgeNode/internal/utils/exec"
"github.com/iwind/TeaGo/Tea" "github.com/iwind/TeaGo/Tea"
"github.com/iwind/TeaGo/maps" "github.com/iwind/TeaGo/maps"
"net/url" "net/url"
@@ -179,7 +178,7 @@ func (this *APIStream) handleWriteCache(message *pb.NodeStreamMessage) error {
} }
expiredAt := time.Now().Unix() + msg.LifeSeconds expiredAt := time.Now().Unix() + msg.LifeSeconds
writer, err := storage.OpenWriter(msg.Key, expiredAt, 200, int64(len(msg.Value)), -1, false) writer, err := storage.OpenWriter(msg.Key, expiredAt, 200, -1, int64(len(msg.Value)), -1, false)
if err != nil { if err != nil {
this.replyFail(message.RequestId, "prepare writing failed: "+err.Error()) this.replyFail(message.RequestId, "prepare writing failed: "+err.Error())
return err return err
@@ -347,15 +346,15 @@ func (this *APIStream) handleCheckSystemdService(message *pb.NodeStreamMessage)
return nil return nil
} }
var cmd = utils.NewCommandExecutor() var shortName = teaconst.SystemdServiceName
shortName := teaconst.SystemdServiceName var cmd = executils.NewTimeoutCmd(10*time.Second, systemctl, "is-enabled", shortName)
cmd.Add(systemctl, "is-enabled", shortName) cmd.WithStdout()
output, err := cmd.Run() err = cmd.Run()
if err != nil { if err != nil {
this.replyFail(message.RequestId, "'systemctl' command error: "+err.Error()) this.replyFail(message.RequestId, "'systemctl' command error: "+err.Error())
return nil return nil
} }
if output == "enabled" { if cmd.Stdout() == "enabled" {
this.replyOk(message.RequestId, "ok") this.replyOk(message.RequestId, "ok")
} else { } else {
this.replyFail(message.RequestId, "not installed") this.replyFail(message.RequestId, "not installed")
@@ -385,16 +384,15 @@ func (this *APIStream) handleCheckLocalFirewall(message *pb.NodeStreamMessage) e
return nil return nil
} }
var cmd = exec.Command(nft, "--version") var cmd = executils.NewTimeoutCmd(10*time.Second, nft, "--version")
var output = &bytes.Buffer{} cmd.WithStdout()
cmd.Stdout = output
err = cmd.Run() err = cmd.Run()
if err != nil { if err != nil {
this.replyFail(message.RequestId, "get version failed: "+err.Error()) this.replyFail(message.RequestId, "get version failed: "+err.Error())
return nil return nil
} }
var outputString = output.String() var outputString = cmd.Stdout()
var versionMatches = regexp.MustCompile(`nftables v([\d.]+)`).FindStringSubmatch(outputString) var versionMatches = regexp.MustCompile(`nftables v([\d.]+)`).FindStringSubmatch(outputString)
if len(versionMatches) <= 1 { if len(versionMatches) <= 1 {
this.replyFail(message.RequestId, "can not get nft version") this.replyFail(message.RequestId, "can not get nft version")

View File

@@ -3,6 +3,7 @@
package nodes package nodes
import ( import (
"errors"
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs" "github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs" "github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs"
"github.com/TeaOSLab/EdgeNode/internal/conns" "github.com/TeaOSLab/EdgeNode/internal/conns"
@@ -11,13 +12,14 @@ import (
"github.com/TeaOSLab/EdgeNode/internal/stats" "github.com/TeaOSLab/EdgeNode/internal/stats"
"github.com/TeaOSLab/EdgeNode/internal/ttlcache" "github.com/TeaOSLab/EdgeNode/internal/ttlcache"
"github.com/TeaOSLab/EdgeNode/internal/utils" "github.com/TeaOSLab/EdgeNode/internal/utils"
connutils "github.com/TeaOSLab/EdgeNode/internal/utils/conns"
"github.com/TeaOSLab/EdgeNode/internal/utils/fasttime"
"github.com/TeaOSLab/EdgeNode/internal/waf" "github.com/TeaOSLab/EdgeNode/internal/waf"
"github.com/iwind/TeaGo/Tea" "github.com/iwind/TeaGo/Tea"
"github.com/iwind/TeaGo/types" "github.com/iwind/TeaGo/types"
"net" "net"
"os" "os"
"strings" "strings"
"sync"
"sync/atomic" "sync/atomic"
"time" "time"
) )
@@ -26,29 +28,53 @@ import (
type ClientConn struct { type ClientConn struct {
BaseClientConn BaseClientConn
once sync.Once createdAt int64
isTLS bool isTLS bool
hasDeadline bool isHTTP bool
hasRead bool hasRead bool
isLO bool // 是否为环路 isLO bool // 是否为环路
isNoStat bool // 是否不统计带宽
isInAllowList bool
hasResetSYNFlood bool hasResetSYNFlood bool
lastReadAt int64
lastWriteAt int64
lastErr error
readDeadlineTime int64
isShortReading bool // reading header or tls handshake
isDebugging bool
autoReadTimeout bool
autoWriteTimeout bool
} }
func NewClientConn(rawConn net.Conn, isTLS bool, quickClose bool) net.Conn { func NewClientConn(rawConn net.Conn, isHTTP bool, isTLS bool, isInAllowList bool) net.Conn {
// 是否为环路 // 是否为环路
var remoteAddr = rawConn.RemoteAddr().String() var remoteAddr = rawConn.RemoteAddr().String()
var isLO = strings.HasPrefix(remoteAddr, "127.0.0.1:") || strings.HasPrefix(remoteAddr, "[::1]:")
var conn = &ClientConn{ var conn = &ClientConn{
BaseClientConn: BaseClientConn{rawConn: rawConn}, BaseClientConn: BaseClientConn{rawConn: rawConn},
isTLS: isTLS, isTLS: isTLS,
isLO: isLO, isHTTP: isHTTP,
isLO: strings.HasPrefix(remoteAddr, "127.0.0.1:") || strings.HasPrefix(remoteAddr, "[::1]:"),
isNoStat: connutils.IsNoStatConn(rawConn.RemoteAddr().String()),
isInAllowList: isInAllowList,
createdAt: fasttime.Now().Unix(),
} }
if quickClose { var globalServerConfig = sharedNodeConfig.GlobalServerConfig
if globalServerConfig != nil {
var performanceConfig = globalServerConfig.Performance
conn.isDebugging = performanceConfig.Debug
conn.autoReadTimeout = performanceConfig.AutoReadTimeout
conn.autoWriteTimeout = performanceConfig.AutoWriteTimeout
}
if isHTTP {
// TODO 可以在配置中设置此值 // TODO 可以在配置中设置此值
_ = conn.SetLinger(nodeconfigs.DefaultTCPLinger) _ = conn.SetLinger(nodeconfigs.DefaultTCPLinger)
} }
@@ -60,6 +86,18 @@ func NewClientConn(rawConn net.Conn, isTLS bool, quickClose bool) net.Conn {
} }
func (this *ClientConn) Read(b []byte) (n int, err error) { func (this *ClientConn) Read(b []byte) (n int, err error) {
if this.isDebugging {
this.lastReadAt = fasttime.Now().Unix()
defer func() {
if err != nil {
this.lastErr = errors.New("read error: " + err.Error())
} else {
this.lastErr = nil
}
}()
}
// 环路直接读取 // 环路直接读取
if this.isLO { if this.isLO {
n, err = this.rawConn.Read(b) n, err = this.rawConn.Read(b)
@@ -69,31 +107,29 @@ func (this *ClientConn) Read(b []byte) (n int, err error) {
return return
} }
// TLS // 设置读超时时间
if this.isTLS { if this.isHTTP && !this.isPersistent && !this.isShortReading && this.autoReadTimeout {
if !this.hasDeadline { this.setHTTPReadTimeout()
_ = this.rawConn.SetReadDeadline(time.Now().Add(time.Duration(nodeconfigs.DefaultTLSHandshakeTimeout) * time.Second)) // TODO 握手超时时间可以设置
this.hasDeadline = true
defer func() {
_ = this.rawConn.SetReadDeadline(time.Time{})
}()
}
} }
// 开始读取 // 开始读取
n, err = this.rawConn.Read(b) n, err = this.rawConn.Read(b)
if n > 0 { if n > 0 {
atomic.AddUint64(&teaconst.InTrafficBytes, uint64(n)) atomic.AddUint64(&teaconst.InTrafficBytes, uint64(n))
if !this.hasRead {
this.hasRead = true this.hasRead = true
} }
}
var isHandshakeError = err != nil && os.IsTimeout(err) && !this.hasRead // 检测是否为超时错误
if isHandshakeError { var isTimeout = err != nil && os.IsTimeout(err)
var isHandshakeError = isTimeout && !this.hasRead
if isTimeout {
_ = this.SetLinger(0) _ = this.SetLinger(0)
} else {
_ = this.SetLinger(nodeconfigs.DefaultTCPLinger)
} }
// 忽略白名单和局域网
if this.isHTTP && !this.isInAllowList && !utils.IsLocalIP(this.RawIP()) {
// SYN Flood检测 // SYN Flood检测
if this.serverId == 0 || !this.hasResetSYNFlood { if this.serverId == 0 || !this.hasResetSYNFlood {
var synFloodConfig = sharedNodeConfig.SYNFloodConfig() var synFloodConfig = sharedNodeConfig.SYNFloodConfig()
@@ -106,21 +142,73 @@ func (this *ClientConn) Read(b []byte) (n int, err error) {
} }
} }
} }
}
return return
} }
func (this *ClientConn) Write(b []byte) (n int, err error) { func (this *ClientConn) Write(b []byte) (n int, err error) {
if len(b) == 0 {
return 0, nil
}
if this.isDebugging {
this.lastWriteAt = fasttime.Now().Unix()
defer func() {
if err != nil {
this.lastErr = errors.New("write error: " + err.Error())
} else {
this.lastErr = nil
}
}()
}
// 设置写超时时间
if this.autoWriteTimeout {
// TODO L2 -> L1 写入时不限制时间
var timeoutSeconds = len(b) / 1024
if timeoutSeconds < 3 {
timeoutSeconds = 3
}
_ = this.rawConn.SetWriteDeadline(time.Now().Add(time.Duration(timeoutSeconds) * time.Second)) // TODO 时间可以设置
}
// 延长读超时时间
if this.isHTTP && !this.isPersistent && this.autoReadTimeout {
this.setHTTPReadTimeout()
}
// 开始写入
var before = time.Now()
n, err = this.rawConn.Write(b) n, err = this.rawConn.Write(b)
if n > 0 { if n > 0 {
// 统计当前服务带宽 // 统计当前服务带宽
if this.serverId > 0 { if this.serverId > 0 {
if !this.isLO || Tea.IsTesting() { // 环路不统计带宽,避免缓存预热等行为产生带宽 // TODO 需要加入在serverId绑定之前的带宽
if !this.isNoStat || Tea.IsTesting() { // 环路不统计带宽,避免缓存预热等行为产生带宽
atomic.AddUint64(&teaconst.OutTrafficBytes, uint64(n)) atomic.AddUint64(&teaconst.OutTrafficBytes, uint64(n))
stats.SharedBandwidthStatManager.Add(this.userId, this.serverId, int64(n))
var cost = time.Since(before).Seconds()
if cost > 1 {
stats.SharedBandwidthStatManager.AddBandwidth(this.userId, this.serverId, int64(float64(n)/cost), int64(n))
} else {
stats.SharedBandwidthStatManager.AddBandwidth(this.userId, this.serverId, int64(n), int64(n))
} }
} }
} }
}
// 如果是写入超时,则立即关闭连接
if err != nil && os.IsTimeout(err) {
// TODO 考虑对多次慢连接的IP做出惩罚
conn, ok := this.rawConn.(LingerConn)
if ok {
_ = conn.SetLinger(0)
}
_ = this.Close()
}
return return
} }
@@ -153,6 +241,26 @@ func (this *ClientConn) SetDeadline(t time.Time) error {
} }
func (this *ClientConn) SetReadDeadline(t time.Time) error { func (this *ClientConn) SetReadDeadline(t time.Time) error {
// 如果开启了HTTP自动读超时选项则自动控制超时时间
if this.isHTTP && !this.isPersistent && this.autoReadTimeout {
this.isShortReading = false
var unixTime = t.Unix()
if unixTime < 10 {
return nil
}
if unixTime == this.readDeadlineTime {
return nil
}
this.readDeadlineTime = unixTime
var seconds = -time.Since(t)
if seconds <= 0 || seconds > HTTPIdleTimeout {
return nil
}
if seconds < HTTPIdleTimeout-1*time.Second {
this.isShortReading = true
}
}
return this.rawConn.SetReadDeadline(t) return this.rawConn.SetReadDeadline(t)
} }
@@ -160,6 +268,22 @@ func (this *ClientConn) SetWriteDeadline(t time.Time) error {
return this.rawConn.SetWriteDeadline(t) return this.rawConn.SetWriteDeadline(t)
} }
func (this *ClientConn) CreatedAt() int64 {
return this.createdAt
}
func (this *ClientConn) LastReadAt() int64 {
return this.lastReadAt
}
func (this *ClientConn) LastWriteAt() int64 {
return this.lastWriteAt
}
func (this *ClientConn) LastErr() error {
return this.lastErr
}
func (this *ClientConn) resetSYNFlood() { func (this *ClientConn) resetSYNFlood() {
ttlcache.SharedCache.Delete("SYN_FLOOD:" + this.RawIP()) ttlcache.SharedCache.Delete("SYN_FLOOD:" + this.RawIP())
} }
@@ -167,7 +291,7 @@ func (this *ClientConn) resetSYNFlood() {
func (this *ClientConn) increaseSYNFlood(synFloodConfig *firewallconfigs.SYNFloodConfig) { func (this *ClientConn) increaseSYNFlood(synFloodConfig *firewallconfigs.SYNFloodConfig) {
var ip = this.RawIP() var ip = this.RawIP()
if len(ip) > 0 && !iplibrary.IsInWhiteList(ip) && (!synFloodConfig.IgnoreLocal || !utils.IsLocalIP(ip)) { if len(ip) > 0 && !iplibrary.IsInWhiteList(ip) && (!synFloodConfig.IgnoreLocal || !utils.IsLocalIP(ip)) {
var timestamp = utils.NextMinuteUnixTime() var timestamp = fasttime.Now().UnixNextMinute()
var result = ttlcache.SharedCache.IncreaseInt64("SYN_FLOOD:"+ip, 1, timestamp, true) var result = ttlcache.SharedCache.IncreaseInt64("SYN_FLOOD:"+ip, 1, timestamp, true)
var minAttempts = synFloodConfig.MinAttempts var minAttempts = synFloodConfig.MinAttempts
if minAttempts < 5 { if minAttempts < 5 {
@@ -187,7 +311,12 @@ func (this *ClientConn) increaseSYNFlood(synFloodConfig *firewallconfigs.SYNFloo
_ = this.SetLinger(0) _ = this.SetLinger(0)
_ = this.Close() _ = this.Close()
waf.SharedIPBlackList.RecordIP(waf.IPTypeAll, firewallconfigs.FirewallScopeGlobal, 0, ip, time.Now().Unix()+int64(timeout), 0, true, 0, 0, "疑似SYN Flood攻击当前1分钟"+types.String(result)+"次空连接") waf.SharedIPBlackList.RecordIP(waf.IPTypeAll, firewallconfigs.FirewallScopeGlobal, 0, ip, fasttime.Now().Unix()+int64(timeout), 0, true, 0, 0, "疑似SYN Flood攻击当前1分钟"+types.String(result)+"次空连接")
} }
} }
} }
// 设置读超时时间
func (this *ClientConn) setHTTPReadTimeout() {
_ = this.SetReadDeadline(time.Now().Add(HTTPIdleTimeout))
}

View File

@@ -4,6 +4,8 @@ package nodes
import ( import (
"crypto/tls" "crypto/tls"
"github.com/TeaOSLab/EdgeNode/internal/firewalls"
"github.com/TeaOSLab/EdgeNode/internal/iplibrary"
"net" "net"
) )
@@ -16,7 +18,12 @@ type BaseClientConn struct {
remoteAddr string remoteAddr string
hasLimit bool hasLimit bool
isPersistent bool // 是否为持久化连接
fingerprint []byte
isClosed bool isClosed bool
rawIP string
} }
func (this *BaseClientConn) IsClosed() bool { func (this *BaseClientConn) IsClosed() bool {
@@ -43,7 +50,20 @@ func (this *BaseClientConn) Bind(serverId int64, remoteAddr string, maxConnsPerS
} }
// SetServerId 设置服务ID // SetServerId 设置服务ID
func (this *BaseClientConn) SetServerId(serverId int64) { func (this *BaseClientConn) SetServerId(serverId int64) (goNext bool) {
goNext = true
// 检查服务相关IP黑名单
if serverId > 0 && len(this.rawIP) > 0 {
// 是否在白名单中
ok, _, expiresAt := iplibrary.AllowIP(this.rawIP, serverId)
if !ok {
_ = this.rawConn.Close()
firewalls.DropTemporaryTo(this.rawIP, expiresAt)
return false
}
}
this.serverId = serverId this.serverId = serverId
// 设置包装前连接 // 设置包装前连接
@@ -56,6 +76,8 @@ func (this *BaseClientConn) SetServerId(serverId int64) {
case *ClientConn: case *ClientConn:
conn.SetServerId(serverId) conn.SetServerId(serverId)
} }
return true
} }
// ServerId 读取当前连接绑定的服务ID // ServerId 读取当前连接绑定的服务ID
@@ -86,7 +108,12 @@ func (this *BaseClientConn) UserId() int64 {
// RawIP 原本IP // RawIP 原本IP
func (this *BaseClientConn) RawIP() string { func (this *BaseClientConn) RawIP() string {
if len(this.rawIP) > 0 {
return this.rawIP
}
ip, _, _ := net.SplitHostPort(this.rawConn.RemoteAddr().String()) ip, _, _ := net.SplitHostPort(this.rawConn.RemoteAddr().String())
this.rawIP = ip
return ip return ip
} }
@@ -115,3 +142,17 @@ func (this *BaseClientConn) SetLinger(seconds int) error {
} }
return nil return nil
} }
func (this *BaseClientConn) SetIsPersistent(isPersistent bool) {
this.isPersistent = isPersistent
}
// SetFingerprint 设置指纹信息
func (this *BaseClientConn) SetFingerprint(fingerprint []byte) {
this.fingerprint = fingerprint
}
// Fingerprint 读取指纹信息
func (this *BaseClientConn) Fingerprint() []byte {
return this.fingerprint
}

View File

@@ -16,11 +16,20 @@ type ClientConnInterface interface {
ServerId() int64 ServerId() int64
// SetServerId 设置服务ID // SetServerId 设置服务ID
SetServerId(serverId int64) SetServerId(serverId int64) (goNext bool)
// SetUserId 设置所属服务的用户ID // SetUserId 设置所属服务的用户ID
SetUserId(userId int64) SetUserId(userId int64)
// UserId 获取当前连接所属服务的用户ID // UserId 获取当前连接所属服务的用户ID
UserId() int64 UserId() int64
// SetIsPersistent 设置是否为持久化
SetIsPersistent(isPersistent bool)
// SetFingerprint 设置指纹信息
SetFingerprint(fingerprint []byte)
// Fingerprint 读取指纹信息
Fingerprint() []byte
} }

View File

@@ -15,6 +15,10 @@ import (
// 发送监控流量 // 发送监控流量
func init() { func init() {
if !teaconst.IsMain {
return
}
events.On(events.EventStart, func() { events.On(events.EventStart, func() {
var ticker = time.NewTicker(1 * time.Minute) var ticker = time.NewTicker(1 * time.Minute)
goman.New(func() { goman.New(func() {

View File

@@ -8,20 +8,19 @@ import (
"github.com/TeaOSLab/EdgeNode/internal/iplibrary" "github.com/TeaOSLab/EdgeNode/internal/iplibrary"
"github.com/TeaOSLab/EdgeNode/internal/waf" "github.com/TeaOSLab/EdgeNode/internal/waf"
"net" "net"
"time"
) )
// ClientListener 客户端网络监听 // ClientListener 客户端网络监听
type ClientListener struct { type ClientListener struct {
rawListener net.Listener rawListener net.Listener
isHTTP bool
isTLS bool isTLS bool
quickClose bool
} }
func NewClientListener(listener net.Listener, quickClose bool) *ClientListener { func NewClientListener(listener net.Listener, isHTTP bool) *ClientListener {
return &ClientListener{ return &ClientListener{
rawListener: listener, rawListener: listener,
quickClose: quickClose, isHTTP: isHTTP,
} }
} }
@@ -41,25 +40,19 @@ func (this *ClientListener) Accept() (net.Conn, error) {
// 是否在WAF名单中 // 是否在WAF名单中
ip, _, err := net.SplitHostPort(conn.RemoteAddr().String()) ip, _, err := net.SplitHostPort(conn.RemoteAddr().String())
var isInAllowList = false
if err == nil { if err == nil {
canGoNext, _ := iplibrary.AllowIP(ip, 0) canGoNext, inAllowList, expiresAt := iplibrary.AllowIP(ip, 0)
isInAllowList = inAllowList
if !canGoNext {
firewalls.DropTemporaryTo(ip, expiresAt)
} else {
if !waf.SharedIPWhiteList.Contains(waf.IPTypeAll, firewallconfigs.FirewallScopeGlobal, 0, ip) { if !waf.SharedIPWhiteList.Contains(waf.IPTypeAll, firewallconfigs.FirewallScopeGlobal, 0, ip) {
expiresAt, ok := waf.SharedIPBlackList.ContainsExpires(waf.IPTypeAll, firewallconfigs.FirewallScopeGlobal, 0, ip) var ok = false
expiresAt, ok = waf.SharedIPBlackList.ContainsExpires(waf.IPTypeAll, firewallconfigs.FirewallScopeGlobal, 0, ip)
if ok { if ok {
var timeout = expiresAt - time.Now().Unix()
if timeout > 0 {
canGoNext = false canGoNext = false
firewalls.DropTemporaryTo(ip, expiresAt)
if timeout > 3600 {
timeout = 3600
}
// 使用本地防火墙延长封禁
var fw = firewalls.Firewall()
if fw != nil && !fw.IsMock() {
// 这里 int(int64) 转换的前提是限制了 timeout <= 3600否则将有整型溢出的风险
_ = fw.DropSourceIP(ip, int(timeout), true)
}
} }
} }
} }
@@ -76,7 +69,7 @@ func (this *ClientListener) Accept() (net.Conn, error) {
} }
} }
return NewClientConn(conn, this.isTLS, this.quickClose), nil return NewClientConn(conn, this.isHTTP, this.isTLS, isInAllowList), nil
} }
func (this *ClientListener) Close() error { func (this *ClientListener) Close() error {

View File

@@ -55,3 +55,30 @@ func (this *ClientTLSConn) SetReadDeadline(t time.Time) error {
func (this *ClientTLSConn) SetWriteDeadline(t time.Time) error { func (this *ClientTLSConn) SetWriteDeadline(t time.Time) error {
return this.rawConn.SetWriteDeadline(t) return this.rawConn.SetWriteDeadline(t)
} }
func (this *ClientTLSConn) SetIsPersistent(isPersistent bool) {
tlsConn, ok := this.rawConn.(*tls.Conn)
if ok {
var rawConn = tlsConn.NetConn()
if rawConn != nil {
clientConn, ok := rawConn.(*ClientConn)
if ok {
clientConn.SetIsPersistent(isPersistent)
}
}
}
}
func (this *ClientTLSConn) Fingerprint() []byte {
tlsConn, ok := this.rawConn.(*tls.Conn)
if ok {
var rawConn = tlsConn.NetConn()
if rawConn != nil {
clientConn, ok := rawConn.(*ClientConn)
if ok {
return clientConn.fingerprint
}
}
}
return nil
}

View File

@@ -7,8 +7,11 @@ import (
"github.com/TeaOSLab/EdgeNode/internal/remotelogs" "github.com/TeaOSLab/EdgeNode/internal/remotelogs"
"github.com/TeaOSLab/EdgeNode/internal/rpc" "github.com/TeaOSLab/EdgeNode/internal/rpc"
"github.com/TeaOSLab/EdgeNode/internal/utils" "github.com/TeaOSLab/EdgeNode/internal/utils"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"strings" "strings"
"time" "time"
"unicode/utf8"
) )
var sharedHTTPAccessLogQueue = NewHTTPAccessLogQueue() var sharedHTTPAccessLogQueue = NewHTTPAccessLogQueue()
@@ -23,9 +26,12 @@ type HTTPAccessLogQueue struct {
// NewHTTPAccessLogQueue 获取新对象 // NewHTTPAccessLogQueue 获取新对象
func NewHTTPAccessLogQueue() *HTTPAccessLogQueue { func NewHTTPAccessLogQueue() *HTTPAccessLogQueue {
// 队列中最大的值,超出此数量的访问日志会被丢弃 // 队列中最大的值,超出此数量的访问日志会被丢弃
// TODO 需要可以在界面中设置 var maxSize = 2_000 * (1 + utils.SystemMemoryGB()/2)
maxSize := 20000 if maxSize > 20_000 {
queue := &HTTPAccessLogQueue{ maxSize = 20_000
}
var queue = &HTTPAccessLogQueue{
queue: make(chan *pb.HTTPAccessLog, maxSize), queue: make(chan *pb.HTTPAccessLog, maxSize),
} }
goman.New(func() { goman.New(func() {
@@ -41,10 +47,14 @@ func (this *HTTPAccessLogQueue) Start() {
for range ticker.C { for range ticker.C {
err := this.loop() err := this.loop()
if err != nil { if err != nil {
if rpc.IsConnError(err) {
remotelogs.Debug("ACCESS_LOG_QUEUE", err.Error())
} else {
remotelogs.Error("ACCESS_LOG_QUEUE", err.Error()) remotelogs.Error("ACCESS_LOG_QUEUE", err.Error())
} }
} }
} }
}
// Push 加入新访问日志 // Push 加入新访问日志
func (this *HTTPAccessLogQueue) Push(accessLog *pb.HTTPAccessLog) { func (this *HTTPAccessLogQueue) Push(accessLog *pb.HTTPAccessLog) {
@@ -109,20 +119,42 @@ Loop:
return err return err
} }
// 是否请求内容过大
statusCode, ok := status.FromError(err)
if ok && statusCode.Code() == codes.ResourceExhausted {
// 去除Body
for _, accessLog := range accessLogs {
accessLog.RequestBody = nil
}
// 重新提交
_, err = this.rpcClient.HTTPAccessLogRPC.CreateHTTPAccessLogs(this.rpcClient.Context(), &pb.CreateHTTPAccessLogsRequest{HttpAccessLogs: accessLogs})
return err
}
return err return err
} }
return nil return nil
} }
// ToValidUTF8 处理访问日志中的非UTF-8字节
func (this *HTTPAccessLogQueue) ToValidUTF8(accessLog *pb.HTTPAccessLog) { func (this *HTTPAccessLogQueue) ToValidUTF8(accessLog *pb.HTTPAccessLog) {
accessLog.RemoteAddr = utils.ToValidUTF8string(accessLog.RemoteAddr)
accessLog.RemoteUser = utils.ToValidUTF8string(accessLog.RemoteUser) accessLog.RemoteUser = utils.ToValidUTF8string(accessLog.RemoteUser)
accessLog.RequestURI = utils.ToValidUTF8string(accessLog.RequestURI) accessLog.RequestURI = utils.ToValidUTF8string(accessLog.RequestURI)
accessLog.RequestPath = utils.ToValidUTF8string(accessLog.RequestPath) accessLog.RequestPath = utils.ToValidUTF8string(accessLog.RequestPath)
accessLog.RequestFilename = utils.ToValidUTF8string(accessLog.RequestFilename) accessLog.RequestFilename = utils.ToValidUTF8string(accessLog.RequestFilename)
accessLog.RequestBody = bytes.ToValidUTF8(accessLog.RequestBody, []byte{}) accessLog.RequestBody = bytes.ToValidUTF8(accessLog.RequestBody, []byte{})
accessLog.Host = utils.ToValidUTF8string(accessLog.Host)
accessLog.Hostname = utils.ToValidUTF8string(accessLog.Hostname)
for k, v := range accessLog.SentHeader {
if !utf8.ValidString(k) {
delete(accessLog.SentHeader, k)
continue
}
for _, v := range accessLog.SentHeader {
for index, s := range v.Values { for index, s := range v.Values {
v.Values[index] = utils.ToValidUTF8string(s) v.Values[index] = utils.ToValidUTF8string(s)
} }
@@ -134,15 +166,27 @@ func (this *HTTPAccessLogQueue) ToValidUTF8(accessLog *pb.HTTPAccessLog) {
accessLog.ContentType = utils.ToValidUTF8string(accessLog.ContentType) accessLog.ContentType = utils.ToValidUTF8string(accessLog.ContentType)
for k, c := range accessLog.Cookie { for k, c := range accessLog.Cookie {
if !utf8.ValidString(k) {
delete(accessLog.Cookie, k)
continue
}
accessLog.Cookie[k] = utils.ToValidUTF8string(c) accessLog.Cookie[k] = utils.ToValidUTF8string(c)
} }
accessLog.Args = utils.ToValidUTF8string(accessLog.Args) accessLog.Args = utils.ToValidUTF8string(accessLog.Args)
accessLog.QueryString = utils.ToValidUTF8string(accessLog.QueryString) accessLog.QueryString = utils.ToValidUTF8string(accessLog.QueryString)
for _, v := range accessLog.Header { for k, v := range accessLog.Header {
if !utf8.ValidString(k) {
delete(accessLog.Header, k)
continue
}
for index, s := range v.Values { for index, s := range v.Values {
v.Values[index] = utils.ToValidUTF8string(s) v.Values[index] = utils.ToValidUTF8string(s)
} }
} }
for k, v := range accessLog.Errors {
accessLog.Errors[k] = utils.ToValidUTF8string(v)
}
} }

View File

@@ -17,6 +17,7 @@ import (
"strings" "strings"
"testing" "testing"
"time" "time"
"unicode/utf8"
) )
func TestHTTPAccessLogQueue_Push(t *testing.T) { func TestHTTPAccessLogQueue_Push(t *testing.T) {
@@ -135,6 +136,16 @@ func TestHTTPAccessLogQueue_Memory(t *testing.T) {
time.Sleep(5 * time.Second) time.Sleep(5 * time.Second)
} }
func TestUTF8_IsValid(t *testing.T) {
t.Log(utf8.ValidString("abc"))
var noneUTF8Bytes = []byte{}
for i := 0; i < 254; i++ {
noneUTF8Bytes = append(noneUTF8Bytes, uint8(i))
}
t.Log(utf8.ValidString(string(noneUTF8Bytes)))
}
func BenchmarkHTTPAccessLogQueue_ToValidUTF8(b *testing.B) { func BenchmarkHTTPAccessLogQueue_ToValidUTF8(b *testing.B) {
runtime.GOMAXPROCS(1) runtime.GOMAXPROCS(1)

View File

@@ -9,10 +9,12 @@ import (
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb" "github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeNode/internal/caches" "github.com/TeaOSLab/EdgeNode/internal/caches"
"github.com/TeaOSLab/EdgeNode/internal/compressions" "github.com/TeaOSLab/EdgeNode/internal/compressions"
teaconst "github.com/TeaOSLab/EdgeNode/internal/const"
"github.com/TeaOSLab/EdgeNode/internal/events" "github.com/TeaOSLab/EdgeNode/internal/events"
"github.com/TeaOSLab/EdgeNode/internal/goman" "github.com/TeaOSLab/EdgeNode/internal/goman"
"github.com/TeaOSLab/EdgeNode/internal/remotelogs" "github.com/TeaOSLab/EdgeNode/internal/remotelogs"
"github.com/TeaOSLab/EdgeNode/internal/rpc" "github.com/TeaOSLab/EdgeNode/internal/rpc"
connutils "github.com/TeaOSLab/EdgeNode/internal/utils/conns"
"github.com/iwind/TeaGo/Tea" "github.com/iwind/TeaGo/Tea"
"io" "io"
"net" "net"
@@ -23,6 +25,10 @@ import (
) )
func init() { func init() {
if !teaconst.IsMain {
return
}
events.On(events.EventStart, func() { events.On(events.EventStart, func() {
goman.New(func() { goman.New(func() {
SharedHTTPCacheTaskManager.Start() SharedHTTPCacheTaskManager.Start()
@@ -56,7 +62,12 @@ func NewHTTPCacheTaskManager() *HTTPCacheTaskManager {
if err != nil { if err != nil {
return nil, err return nil, err
} }
return net.Dial(network, "127.0.0.1:"+port) conn, err := net.Dial(network, "127.0.0.1:"+port)
if err != nil {
return nil, err
}
return connutils.NewNoStat(conn), nil
}, },
MaxIdleConns: 128, MaxIdleConns: 128,
MaxIdleConnsPerHost: 32, MaxIdleConnsPerHost: 32,

View File

@@ -1,7 +1,7 @@
package nodes package nodes
import ( import (
"github.com/TeaOSLab/EdgeNode/internal/utils" "github.com/TeaOSLab/EdgeNode/internal/utils/fasttime"
"net/http" "net/http"
) )
@@ -15,7 +15,7 @@ type HTTPClient struct {
func NewHTTPClient(rawClient *http.Client) *HTTPClient { func NewHTTPClient(rawClient *http.Client) *HTTPClient {
return &HTTPClient{ return &HTTPClient{
rawClient: rawClient, rawClient: rawClient,
accessAt: utils.UnixTime(), accessAt: fasttime.Now().Unix(),
} }
} }
@@ -26,7 +26,7 @@ func (this *HTTPClient) RawClient() *http.Client {
// UpdateAccessTime 更新访问时间 // UpdateAccessTime 更新访问时间
func (this *HTTPClient) UpdateAccessTime() { func (this *HTTPClient) UpdateAccessTime() {
this.accessAt = utils.UnixTime() this.accessAt = fasttime.Now().Unix()
} }
// AccessTime 获取访问时间 // AccessTime 获取访问时间

Some files were not shown because too many files have changed in this diff Show More