Compare commits

...

150 Commits

Author SHA1 Message Date
刘祥超
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
179 changed files with 6300 additions and 1635 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

@@ -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

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

@@ -2,6 +2,7 @@ package caches
import ( import (
"github.com/TeaOSLab/EdgeNode/internal/utils" "github.com/TeaOSLab/EdgeNode/internal/utils"
"strings"
"time" "time"
) )
@@ -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

@@ -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

View File

@@ -10,8 +10,13 @@ import (
"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/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"
@@ -48,16 +53,16 @@ type FileListDB struct {
deleteByHashStmt *dbs.Stmt // 根据hash删除数据 deleteByHashStmt *dbs.Stmt // 根据hash删除数据
deleteByHashSQL string deleteByHashSQL string
statStmt *dbs.Stmt // 统计 statStmt *dbs.Stmt // 统计
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 {
@@ -84,17 +89,29 @@ func (this *FileListDB) Open(dbPath string) error {
writeDB.SetMaxOpenConns(1) writeDB.SetMaxOpenConns(1)
this.writeDB = dbs.NewDB(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) // 检查是否损坏
// 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, 4) this.writeBatch = dbs.NewBatch(writeDB, 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() {
@@ -151,7 +168,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 +180,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 +205,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 +218,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
@@ -226,7 +246,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, utils.UnixTime(), timeutil.Format("YW"))
return nil return nil
} }
@@ -238,7 +258,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, utils.UnixTime(), timeutil.Format("YW"))
if err != nil { if err != nil {
return this.WrapError(err) return this.WrapError(err)
} }
@@ -300,22 +320,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)
if err != nil {
return nil, err
}
hashList = append(hashList, oldHashList...)
} }
// 不足补齐 return hashList, nil
olderHashList, err := this.listOlderItems(count - len(hashList))
if err != nil {
return nil, err
}
hashList = append(hashList, olderHashList...)
return
} }
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 +363,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
} }
@@ -372,6 +394,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 = utils.UnixTime() // 只删除当前的,不删除新的
_, 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 = utils.UnixTime() // 只删除当前的,不删除新的
_, 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 +519,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 +540,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 +558,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 +571,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,23 +584,32 @@ 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 times < 3 { if strings.Contains(err.Error(), "duplicate column name") {
_, dropErr := this.writeDB.Exec(`DROP TABLE "` + this.itemsTableName + `"`) err = nil
if dropErr == nil {
return this.initTables(times + 1)
}
return this.WrapError(err)
} }
return this.WrapError(err) // 尝试删除重建
if err != nil {
if times < 3 {
_, dropErr := this.writeDB.Exec(`DROP TABLE "` + this.itemsTableName + `"`)
if dropErr == nil {
return this.initTables(times + 1)
}
return this.WrapError(err)
}
return this.WrapError(err)
}
} }
} }
@@ -535,27 +644,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 +664,21 @@ 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
}

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

@@ -24,7 +24,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 +48,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)
} }

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

@@ -12,6 +12,7 @@ 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 {
@@ -29,26 +30,43 @@ 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:
err := file.SeekStart() if file != nil {
if err != nil { err := file.SeekStart()
_ = file.Close() if err != nil {
return nil, true _ = file.Close()
} return nil, true
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,38 +3,88 @@
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
} }
return NewPartialRangesFromJSON(data) if len(data) == 0 {
return NewPartialRanges(0), nil
}
// 兼容老的JSON格式
if data[0] == '{' {
return NewPartialRangesFromJSON(data)
}
// 新的格式
return NewPartialRangesFromData(data)
} }
// Add 添加新范围 // Add 添加新范围
@@ -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"
@@ -48,8 +49,7 @@ const (
SizeBodyLength = 8 SizeBodyLength = 8
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,19 +544,38 @@ 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 {
readerFp, err := os.OpenFile(tmpPath, os.O_RDONLY, 0444) // 数据库中是否存在
if err == nil { existsCacheItem, _ := this.list.Exist(hash)
var partialReader = NewPartialFileReader(readerFp) if existsCacheItem {
err = partialReader.Init() readerFp, err := os.OpenFile(tmpPath, os.O_RDONLY, 0444)
_ = partialReader.Close() if err == nil {
if err == nil && partialReader.bodyOffset > 0 { var partialReader = NewPartialFileReader(readerFp)
isNewCreated = false err = partialReader.Init()
partialBodyOffset = partialReader.bodyOffset _ = partialReader.Close()
} else { if err == nil && partialReader.bodyOffset > 0 {
_ = this.removeCacheFile(tmpPath) partialRanges = partialReader.Ranges()
if bodySize > 0 && partialRanges != nil && partialRanges.BodySize > 0 && bodySize != partialRanges.BodySize {
_ = this.removeCacheFile(tmpPath)
} else {
isNewCreated = false
partialBodyOffset = partialReader.bodyOffset
}
} else {
_ = 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
@@ -542,7 +585,16 @@ 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 { if err != nil {
return nil, err // TODO 检查在各个系统中的稳定性
if os.IsNotExist(err) {
_ = os.MkdirAll(dir, 0777)
// open file again
writer, err = os.OpenFile(tmpPath, flags, 0666)
}
if err != nil {
return nil, err
}
} }
if !isFlushing { if !isFlushing {
if time.Since(before) >= maxOpenFilesSlowCost { if time.Since(before) >= maxOpenFilesSlowCost {
@@ -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,57 +752,72 @@ func (this *FileStorage) CleanAll() error {
// 删除缓存和目录 // 删除缓存和目录
// 不能直接删除子目录,比较危险 // 不能直接删除子目录,比较危险
dir := this.dir()
fp, err := os.Open(dir)
if err != nil {
return err
}
defer func() {
_ = fp.Close()
}()
stat, err := fp.Stat() var rootDirs = []string{this.options.Dir}
if err != nil { var subDirs = this.subDirs // copy slice
return err if len(subDirs) > 0 {
} for _, subDir := range subDirs {
rootDirs = append(rootDirs, subDir.Path)
if !stat.IsDir() {
return nil
}
// 改成待删除
subDirs, err := fp.Readdir(-1)
if err != nil {
return err
}
for _, info := range subDirs {
subDir := info.Name()
// 检查目录名
ok, err := regexp.MatchString(`^[0-9a-f]{2}$`, subDir)
if err != nil {
return err
}
if !ok {
continue
} }
}
// 修改目录名 var dirNameReg = regexp.MustCompile(`^[0-9a-f]{2}$`)
tmpDir := dir + "/" + subDir + "-deleted" for _, rootDir := range rootDirs {
err = os.Rename(dir+"/"+subDir, tmpDir) var dir = rootDir + "/p" + types.String(this.policy.Id)
err = func(dir string) error {
fp, err := os.Open(dir)
if err != nil {
return err
}
defer func() {
_ = fp.Close()
}()
stat, err := fp.Stat()
if err != nil {
return err
}
if !stat.IsDir() {
return nil
}
// 改成待删除
subDirs, err := fp.Readdir(-1)
if err != nil {
return err
}
for _, info := range subDirs {
subDir := info.Name()
// 检查目录名
if !dirNameReg.MatchString(subDir) {
continue
}
// 修改目录名
tmpDir := dir + "/" + subDir + "-deleted"
err = os.Rename(dir+"/"+subDir, tmpDir)
if err != nil {
return err
}
}
// 重新遍历待删除
goman.New(func() {
err = this.cleanDeletedDirs(dir)
if err != nil {
remotelogs.Warn("CACHE", "delete '*-deleted' dirs failed: "+err.Error())
}
})
return nil
}(dir)
if err != nil { if err != nil {
return err return err
} }
} }
// 重新遍历待删除
goman.New(func() {
err = this.cleanDeletedDirs(dir)
if err != nil {
remotelogs.Warn("CACHE", "delete '*-deleted' dirs failed: "+err.Error())
}
})
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,19 +1017,39 @@ 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
} }
if capacityBytes > 0 {
if lfuFreePercent < 100 { var hasFullDisk = this.mainDiskIsFull
if usedPercent >= 100-lfuFreePercent { if !hasFullDisk {
startLFU = true 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 lfuFreePercent < 100 {
if usedPercent >= 100-lfuFreePercent {
startLFU = true
}
} }
} }
} }
@@ -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]) goNext = true
if err == nil { if n > 0 {
goNext = true _, 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,7 +1,6 @@
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"
@@ -17,6 +16,7 @@ import (
"math" "math"
"runtime" "runtime"
"strconv" "strconv"
"strings"
"sync" "sync"
"sync/atomic" "sync/atomic"
"time" "time"
@@ -149,7 +149,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 +158,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 +207,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 +230,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 +263,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 +286,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 +362,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 +464,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 +486,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)
@@ -481,7 +512,7 @@ func (this *MemoryStorage) flushItem(key string) {
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 +544,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 +552,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 +574,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

@@ -11,25 +11,32 @@ import (
) )
type FileWriter struct { type FileWriter struct {
storage StorageInterface storage StorageInterface
rawWriter *os.File rawWriter *os.File
key string key string
headerSize int64
bodySize int64 metaHeaderSize int
expiredAt int64 headerSize int64
maxSize int64
endFunc func() metaBodySize int64 // 写入前的内容长度
once sync.Once bodySize int64
expiredAt int64
maxSize int64
endFunc func()
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,
rawWriter: rawWriter, rawWriter: rawWriter,
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

@@ -11,13 +11,18 @@ import (
) )
type PartialFileWriter struct { type PartialFileWriter struct {
rawWriter *os.File rawWriter *os.File
key string key string
headerSize int64
bodySize int64 metaHeaderSize int
expiredAt int64 headerSize int64
endFunc func()
once sync.Once metaBodySize int64
bodySize int64
expiredAt int64
endFunc func()
once sync.Once
isNew bool isNew bool
isPartial bool isPartial bool
@@ -27,17 +32,19 @@ 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,
expiredAt: expiredAt, expiredAt: expiredAt,
endFunc: endFunc, endFunc: endFunc,
isNew: isNew, isNew: isNew,
isPartial: isPartial, isPartial: isPartial,
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

@@ -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

@@ -10,7 +10,7 @@ import (
var SharedMap = NewMap() var SharedMap = NewMap()
type Map struct { type Map struct {
m map[string]map[int]net.Conn // ip => { port => Conn } m map[string]map[int]net.Conn // ip => { port => Conn }
locker sync.RWMutex locker sync.RWMutex
} }
@@ -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 = "0.6.2"
ProductName = "Edge Node" ProductName = "Edge Node"
ProcessName = "edge-node" ProcessName = "edge-node"

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
@@ -14,6 +13,7 @@ import (
"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"
@@ -21,6 +21,7 @@ import (
"net" "net"
"os/exec" "os/exec"
"strings" "strings"
"time"
) )
var SharedDDoSProtectionManager = NewDDoSProtectionManager() var SharedDDoSProtectionManager = NewDDoSProtectionManager()
@@ -53,19 +54,13 @@ func init() {
// DDoSProtectionManager DDoS防护 // DDoSProtectionManager DDoS防护
type DDoSProtectionManager struct { type DDoSProtectionManager struct {
nftPath string
lastAllowIPList []string lastAllowIPList []string
lastConfig []byte lastConfig []byte
} }
// NewDDoSProtectionManager 获取新对象 // NewDDoSProtectionManager 获取新对象
func NewDDoSProtectionManager() *DDoSProtectionManager { func NewDDoSProtectionManager() *DDoSProtectionManager {
nftPath, _ := exec.LookPath("nft") return &DDoSProtectionManager{}
return &DDoSProtectionManager{
nftPath: nftPath,
}
} }
// Apply 应用配置 // Apply 应用配置
@@ -75,7 +70,7 @@ func (this *DDoSProtectionManager) Apply(config *ddosconfigs.ProtectionConfig) e
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 +86,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(this.nftExe()) == 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 +152,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 = this.nftExe()
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 +266,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 +288,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 +308,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() + ")")
} }
} }
} }
@@ -555,3 +552,8 @@ func (this *DDoSProtectionManager) updateAllowIPList(allIPList []string) error {
return nil return nil
} }
func (this *DDoSProtectionManager) nftExe() string {
path, _ := exec.LookPath("nft")
return path
}

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

@@ -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

@@ -5,7 +5,6 @@
package firewalls package firewalls
import ( import (
"bytes"
"errors" "errors"
"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"
@@ -13,6 +12,7 @@ import (
"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/iwind/TeaGo/types" "github.com/iwind/TeaGo/types"
"net" "net"
"os/exec" "os/exec"
@@ -432,15 +432,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

@@ -0,0 +1,108 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package nftables
import (
"errors"
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
"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() {
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
_, err := exec.LookPath("nft")
if err == nil {
return
}
goman.New(func() {
err := NewInstaller().Install()
if err != nil {
// 不需要传到API节点
logs.Println("[NFTABLES]install nftables failed: " + err.Error())
}
})
}
})
}
type Installer struct {
}
func NewInstaller() *Installer {
return &Installer{}
}
func (this *Installer) Install() error {
// linux only
if runtime.GOOS != "linux" {
return nil
}
// 检查是否已经存在
_, err := exec.LookPath("nft")
if err == nil {
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,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

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"
@@ -13,9 +13,9 @@ import (
// FirewalldAction Firewalld动作管理 // FirewalldAction Firewalld动作管理
// 常用命令: // 常用命令:
// - 查询列表: firewall-cmd --list-all // - 查询列表: firewall-cmd --list-all
// - 添加IPfirewall-cmd --add-rich-rule="rule family='ipv4' source address='192.168.2.32' reject" --timeout=30s // - 添加IPfirewall-cmd --add-rich-rule="rule family='ipv4' source address='192.168.2.32' reject" --timeout=30s
// - 删除IPfirewall-cmd --remove-rich-rule="rule family='ipv4' source address='192.168.2.32' reject" --timeout=30s // - 删除IPfirewall-cmd --remove-rich-rule="rule family='ipv4' source address='192.168.2.32' reject" --timeout=30s
type FirewalldAction struct { type FirewalldAction struct {
BaseAction BaseAction
@@ -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"
@@ -16,12 +16,12 @@ import (
// IPSetAction IPSet动作 // IPSetAction IPSet动作
// 相关命令: // 相关命令:
// - 利用Firewalld管理set // - 利用Firewalld管理set
// - 添加firewall-cmd --permanent --new-ipset=edge_ip_list --type=hash:ip --option="timeout=0" // - 添加firewall-cmd --permanent --new-ipset=edge_ip_list --type=hash:ip --option="timeout=0"
// - 删除firewall-cmd --permanent --delete-ipset=edge_ip_list // - 删除firewall-cmd --permanent --delete-ipset=edge_ip_list
// - 重载firewall-cmd --reload // - 重载firewall-cmd --reload
// - firewalld+ipset: firewall-cmd --permanent --add-rich-rule="rule source ipset='edge_ip_list' reject" // - firewalld+ipset: firewall-cmd --permanent --add-rich-rule="rule source ipset='edge_ip_list' reject"
// - 利用IPTables管理set // - 利用IPTables管理set
// - 添加iptables -A INPUT -m set --match-set edge_ip_list src -j REJECT // - 添加iptables -A INPUT -m set --match-set edge_ip_list src -j REJECT
// - 添加Itemipset add edge_ip_list 192.168.2.32 timeout 30 // - 添加Itemipset add edge_ip_list 192.168.2.32 timeout 30
// - 删除Item: ipset del edge_ip_list 192.168.2.32 // - 删除Item: ipset del edge_ip_list 192.168.2.32
// - 创建setipset create edge_ip_list hash:ip timeout 0 // - 创建setipset create edge_ip_list hash:ip timeout 0
@@ -30,16 +30,13 @@ import (
type IPSetAction struct { 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,20 +1,24 @@
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 REJECT // iptables -A INPUT -s "192.168.2.32" -j ACCEPT
// iptables -D INPUT ... // iptables -A INPUT -s "192.168.2.32" -j REJECT
// iptables -F INPUT // iptables -D INPUT ...
// iptables -F INPUT
type IPTablesAction struct { type IPTablesAction struct {
BaseAction BaseAction
@@ -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

@@ -55,8 +55,6 @@ 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 := sql.Open("sqlite3", "file:"+path+"?cache=shared&mode=rwc&_journal_mode=WAL&_sync=OFF")
if err != nil { if err != nil {
@@ -71,6 +69,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,
@@ -164,6 +170,12 @@ func (this *IPListDB) AddItem(item *pb.IPItem) error {
if err != nil { if err != nil {
return err return err
} }
// 如果是删除,则不再创建新记录
if item.IsDeleted {
return nil
}
_, 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)
return err return err
} }

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.IsDaemon {
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名单管理
@@ -129,6 +142,12 @@ 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
}
for { for {
hasNext, err := this.fetch() hasNext, err := this.fetch()
if err != nil { if err != nil {
@@ -186,6 +205,12 @@ func (this *IPListManager) FindList(listId int64) *IPList {
return list return list
} }
func (this *IPListManager) DeleteExpiredItems() {
if this.db != nil {
_ = this.db.DeleteExpiredItems()
}
}
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 {

View File

@@ -91,8 +91,6 @@ 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 := sql.Open("sqlite3", "file:"+path+"?cache=shared&mode=rwc&_journal_mode=WAL&_sync=OFF")
if err != nil { if err != nil {
@@ -101,6 +99,14 @@ func (this *Task) Init() error {
db.SetMaxOpenConns(1) db.SetMaxOpenConns(1)
this.db = dbs.NewDB(db) this.db = dbs.NewDB(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

@@ -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")
@@ -409,7 +407,7 @@ func (this *APIStream) handleCheckLocalFirewall(message *pb.NodeStreamMessage) e
var protectionConfig = sharedNodeConfig.DDoSProtection var protectionConfig = sharedNodeConfig.DDoSProtection
err = firewalls.SharedDDoSProtectionManager.Apply(protectionConfig) err = firewalls.SharedDDoSProtectionManager.Apply(protectionConfig)
if err != nil { if err != nil {
this.replyFail(message.RequestId, dataMessage.Name+"was installed, but apply DDoS protection config failed: "+err.Error()) this.replyFail(message.RequestId, dataMessage.Name+" was installed, but apply DDoS protection config failed: "+err.Error())
} else { } else {
this.replyOk(message.RequestId, string(result.AsJSON())) this.replyOk(message.RequestId, string(result.AsJSON()))
} }

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"
@@ -17,7 +18,6 @@ import (
"net" "net"
"os" "os"
"strings" "strings"
"sync"
"sync/atomic" "sync/atomic"
"time" "time"
) )
@@ -26,18 +26,30 @@ 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 // 是否为环路
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 isLO = strings.HasPrefix(remoteAddr, "127.0.0.1:") || strings.HasPrefix(remoteAddr, "[::1]:")
@@ -45,10 +57,21 @@ func NewClientConn(rawConn net.Conn, isTLS bool, quickClose bool) net.Conn {
var conn = &ClientConn{ var conn = &ClientConn{
BaseClientConn: BaseClientConn{rawConn: rawConn}, BaseClientConn: BaseClientConn{rawConn: rawConn},
isTLS: isTLS, isTLS: isTLS,
isHTTP: isHTTP,
isLO: isLO, isLO: isLO,
isInAllowList: isInAllowList,
createdAt: time.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 +83,16 @@ 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 = time.Now().Unix()
defer func() {
if err != nil {
this.lastErr = errors.New("read error: " + err.Error())
}
}()
}
// 环路直接读取 // 环路直接读取
if this.isLO { if this.isLO {
n, err = this.rawConn.Read(b) n, err = this.rawConn.Read(b)
@@ -69,40 +102,39 @@ func (this *ClientConn) Read(b []byte) (n int, err error) {
return return
} }
// TLS // 设置读超时时间
if this.isTLS { if this.isHTTP && !this.isWebsocket && !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)
} }
// SYN Flood检测 // 忽略白名单和局域网
if this.serverId == 0 || !this.hasResetSYNFlood { if this.isHTTP && !this.isInAllowList && !utils.IsLocalIP(this.RawIP()) {
var synFloodConfig = sharedNodeConfig.SYNFloodConfig() // SYN Flood检测
if synFloodConfig != nil && synFloodConfig.IsOn { if this.serverId == 0 || !this.hasResetSYNFlood {
if isHandshakeError { var synFloodConfig = sharedNodeConfig.SYNFloodConfig()
this.increaseSYNFlood(synFloodConfig) if synFloodConfig != nil && synFloodConfig.IsOn {
} else if err == nil && !this.hasResetSYNFlood { if isHandshakeError {
this.hasResetSYNFlood = true this.increaseSYNFlood(synFloodConfig)
this.resetSYNFlood() } else if err == nil && !this.hasResetSYNFlood {
this.hasResetSYNFlood = true
this.resetSYNFlood()
}
} }
} }
} }
@@ -111,6 +143,32 @@ func (this *ClientConn) Read(b []byte) (n int, err error) {
} }
func (this *ClientConn) Write(b []byte) (n int, err error) { func (this *ClientConn) Write(b []byte) (n int, err error) {
if this.isDebugging {
this.lastWriteAt = time.Now().Unix()
defer func() {
if err != nil {
this.lastErr = errors.New("write error: " + err.Error())
}
}()
}
// 设置写超时时间
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.isWebsocket && this.autoReadTimeout {
this.setHTTPReadTimeout()
}
// 开始写入
n, err = this.rawConn.Write(b) n, err = this.rawConn.Write(b)
if n > 0 { if n > 0 {
// 统计当前服务带宽 // 统计当前服务带宽
@@ -122,6 +180,17 @@ func (this *ClientConn) Write(b []byte) (n int, err error) {
} }
} }
// 如果是写入超时,则立即关闭连接
if err != nil && os.IsTimeout(err) {
// TODO 考虑对多次慢连接的IP做出惩罚
conn, ok := this.rawConn.(LingerConn)
if ok {
_ = conn.SetLinger(0)
}
_ = this.Close()
}
return return
} }
@@ -153,6 +222,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.isWebsocket && 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 +249,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())
} }
@@ -191,3 +296,8 @@ func (this *ClientConn) increaseSYNFlood(synFloodConfig *firewallconfigs.SYNFloo
} }
} }
} }
// 设置读超时时间
func (this *ClientConn) setHTTPReadTimeout() {
_ = this.SetReadDeadline(time.Now().Add(HTTPIdleTimeout))
}

View File

@@ -16,7 +16,11 @@ type BaseClientConn struct {
remoteAddr string remoteAddr string
hasLimit bool hasLimit bool
isWebsocket bool
isClosed bool isClosed bool
rawIP string
} }
func (this *BaseClientConn) IsClosed() bool { func (this *BaseClientConn) IsClosed() bool {
@@ -86,7 +90,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 +124,7 @@ func (this *BaseClientConn) SetLinger(seconds int) error {
} }
return nil return nil
} }
func (this *BaseClientConn) SetIsWebsocket(isWebsocket bool) {
this.isWebsocket = isWebsocket
}

View File

@@ -23,4 +23,7 @@ type ClientConnInterface interface {
// UserId 获取当前连接所属服务的用户ID // UserId 获取当前连接所属服务的用户ID
UserId() int64 UserId() int64
// SetIsWebsocket 设置是否为Websocket
SetIsWebsocket(isWebsocket bool)
} }

View File

@@ -14,14 +14,14 @@ import (
// 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,8 +41,10 @@ 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 := iplibrary.AllowIP(ip, 0)
isInAllowList = inAllowList
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) expiresAt, ok := waf.SharedIPBlackList.ContainsExpires(waf.IPTypeAll, firewallconfigs.FirewallScopeGlobal, 0, ip)
if ok { if ok {
@@ -76,7 +78,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,16 @@ 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) SetIsWebsocket(isWebsocket bool) {
tlsConn, ok := this.rawConn.(*tls.Conn)
if ok {
var rawConn = tlsConn.NetConn()
if rawConn != nil {
clientConn, ok := rawConn.(*ClientConn)
if ok {
clientConn.SetIsWebsocket(isWebsocket)
}
}
}
}

View File

@@ -7,6 +7,8 @@ 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"
) )
@@ -41,7 +43,11 @@ func (this *HTTPAccessLogQueue) Start() {
for range ticker.C { for range ticker.C {
err := this.loop() err := this.loop()
if err != nil { if err != nil {
remotelogs.Error("ACCESS_LOG_QUEUE", err.Error()) if rpc.IsConnError(err) {
remotelogs.Debug("ACCESS_LOG_QUEUE", err.Error())
} else {
remotelogs.Error("ACCESS_LOG_QUEUE", err.Error())
}
} }
} }
} }
@@ -109,6 +115,19 @@ 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
} }

View File

@@ -95,11 +95,11 @@ func (this *HTTPClientPool) Client(req *HTTPRequest,
numberCPU = 8 numberCPU = 8
} }
if maxConnections <= 0 { if maxConnections <= 0 {
maxConnections = numberCPU * 32 maxConnections = numberCPU * 64
} }
if idleConns <= 0 { if idleConns <= 0 {
idleConns = numberCPU * 8 idleConns = numberCPU * 16
} }
// 可以判断为Ln节点请求 // 可以判断为Ln节点请求

View File

@@ -68,6 +68,7 @@ type HTTPRequest struct {
filePath string // 请求的文件名仅在读取Root目录下的内容时不为空 filePath string // 请求的文件名仅在读取Root目录下的内容时不为空
origin *serverconfigs.OriginConfig // 源站 origin *serverconfigs.OriginConfig // 源站
originAddr string // 源站实际地址 originAddr string // 源站实际地址
originStatus int32 // 源站响应代码
errors []string // 错误信息 errors []string // 错误信息
rewriteRule *serverconfigs.HTTPRewriteRule // 匹配到的重写规则 rewriteRule *serverconfigs.HTTPRewriteRule // 匹配到的重写规则
rewriteReplace string // 重写规则的目标 rewriteReplace string // 重写规则的目标
@@ -228,6 +229,22 @@ func (this *HTTPRequest) Do() {
} }
} }
// 防盗链
if !this.isSubRequest && this.web.Referers != nil && this.web.Referers.IsOn {
if this.doCheckReferers() {
this.doEnd()
return
}
}
// UA名单
if !this.isSubRequest && this.web.UserAgent != nil && this.web.UserAgent.IsOn {
if this.doCheckUserAgent() {
this.doEnd()
return
}
}
// 访问控制 // 访问控制
if !this.isSubRequest && this.web.Auth != nil && this.web.Auth.IsOn { if !this.isSubRequest && this.web.Auth != nil && this.web.Auth.IsOn {
if this.doAuth() { if this.doAuth() {
@@ -512,6 +529,16 @@ func (this *HTTPRequest) configureWeb(web *serverconfigs.HTTPWebConfig, isTop bo
this.web.Auth = web.Auth this.web.Auth = web.Auth
} }
// referers
if web.Referers != nil && (web.Referers.IsPrior || isTop) {
this.web.Referers = web.Referers
}
// user agent
if web.UserAgent != nil && (web.UserAgent.IsPrior || isTop) {
this.web.UserAgent = web.UserAgent
}
// request limit // request limit
if web.RequestLimit != nil && (web.RequestLimit.IsPrior || isTop) { if web.RequestLimit != nil && (web.RequestLimit.IsPrior || isTop) {
this.web.RequestLimit = web.RequestLimit this.web.RequestLimit = web.RequestLimit
@@ -744,6 +771,8 @@ func (this *HTTPRequest) Format(source string) string {
return strconv.FormatInt(this.requestFromTime.Unix(), 10) return strconv.FormatInt(this.requestFromTime.Unix(), 10)
case "host": case "host":
return this.ReqHost return this.ReqHost
case "cname":
return this.ReqServer.CNameDomain
case "referer": case "referer":
return this.RawReq.Referer() return this.RawReq.Referer()
case "referer.host": case "referer.host":
@@ -1117,6 +1146,8 @@ func (this *HTTPRequest) requestRemoteAddr(supportVar bool) string {
// 获取请求的客户端地址列表 // 获取请求的客户端地址列表
func (this *HTTPRequest) requestRemoteAddrs() (result []string) { func (this *HTTPRequest) requestRemoteAddrs() (result []string) {
result = append(result, this.requestRemoteAddr(true))
// X-Forwarded-For // X-Forwarded-For
var forwardedFor = this.RawReq.Header.Get("X-Forwarded-For") var forwardedFor = this.RawReq.Header.Get("X-Forwarded-For")
if len(forwardedFor) > 0 { if len(forwardedFor) > 0 {
@@ -1443,8 +1474,6 @@ func (this *HTTPRequest) Close() {
_ = conn.Close() _ = conn.Close()
return return
} }
return
} }
// Allow 放行 // Allow 放行
@@ -1540,7 +1569,7 @@ func (this *HTTPRequest) processRequestHeaders(reqHeader http.Header) {
} }
// 是否已删除 // 是否已删除
if this.web.ResponseHeaderPolicy.ContainsDeletedHeader(header.Name) { if this.web.RequestHeaderPolicy.ContainsDeletedHeader(header.Name) {
continue continue
} }
@@ -1591,17 +1620,31 @@ func (this *HTTPRequest) fixRequestHeader(header http.Header) {
header.Del(k) header.Del(k)
k = strings.ReplaceAll(k, "-Websocket-", "-WebSocket-") k = strings.ReplaceAll(k, "-Websocket-", "-WebSocket-")
header[k] = v header[k] = v
} else if k == "Www-Authenticate" { } else if strings.HasPrefix(k, "Sec-Ch") {
header.Del(k) header.Del(k)
header["WWW-Authenticate"] = v k = strings.ReplaceAll(k, "Sec-Ch-Ua", "Sec-CH-UA")
header[k] = v
} else {
switch k {
case "Www-Authenticate":
header.Del(k)
header["WWW-Authenticate"] = v
case "A-Im":
header.Del(k)
header["A-IM"] = v
case "Content-Md5":
header.Del(k)
header["Content-MD5"] = v
case "Sec-Gpc":
header.Del(k)
header["Content-GPC"] = v
}
} }
} }
} }
// 处理自定义Response Header // 处理自定义Response Header
func (this *HTTPRequest) processResponseHeaders(statusCode int) { func (this *HTTPRequest) processResponseHeaders(responseHeader http.Header, statusCode int) {
var responseHeader = this.writer.Header()
// 删除/添加/替换Header // 删除/添加/替换Header
// TODO 实现AddTrailers // TODO 实现AddTrailers
if this.web.ResponseHeaderPolicy != nil && this.web.ResponseHeaderPolicy.IsOn { if this.web.ResponseHeaderPolicy != nil && this.web.ResponseHeaderPolicy.IsOn {
@@ -1664,6 +1707,36 @@ func (this *HTTPRequest) processResponseHeaders(statusCode int) {
responseHeader[header.Name] = []string{headerValue} responseHeader[header.Name] = []string{headerValue}
} }
} }
// CORS
if this.web.ResponseHeaderPolicy.CORS != nil && this.web.ResponseHeaderPolicy.CORS.IsOn {
var corsConfig = this.web.ResponseHeaderPolicy.CORS
// Allow-Origin
if len(corsConfig.AllowOrigin) == 0 {
var origin = this.RawReq.Header.Get("Origin")
if len(origin) > 0 {
responseHeader.Set("Access-Control-Allow-Origin", origin)
}
} else {
responseHeader.Set("Access-Control-Allow-Origin", corsConfig.AllowOrigin)
}
// Allow-Methods
if len(corsConfig.AllowMethods) == 0 {
responseHeader.Set("Access-Control-Allow-Methods", "PUT, GET, POST, DELETE, HEAD, OPTIONS")
} else {
responseHeader.Set("Access-Control-Allow-Methods", strings.Join(corsConfig.AllowMethods, ", "))
}
// Max-Age
if corsConfig.MaxAge > 0 {
responseHeader.Set("Access-Control-Max-Age", types.String(corsConfig.MaxAge))
}
// Allow-Credentials
responseHeader.Set("Access-Control-Allow-Credentials", "true")
}
} }
// HSTS // HSTS
@@ -1695,10 +1768,10 @@ func (this *HTTPRequest) bytePool(contentLength int64) *utils.BytePool {
return utils.BytePool1k return utils.BytePool1k
} }
if contentLength < 32768 { // 32K if contentLength < 32768 { // 32K
return utils.BytePool4k return utils.BytePool16k
} }
if contentLength < 131072 { // 128K if contentLength < 131072 { // 128K
return utils.BytePool16k return utils.BytePool32k
} }
return utils.BytePool32k return utils.BytePool32k
} }

View File

@@ -19,6 +19,11 @@ import (
// 读取缓存 // 读取缓存
func (this *HTTPRequest) doCacheRead(useStale bool) (shouldStop bool) { func (this *HTTPRequest) doCacheRead(useStale bool) (shouldStop bool) {
// 需要动态Upgrade的不缓存
if len(this.RawReq.Header.Get("Upgrade")) > 0 {
return
}
this.cacheCanTryStale = false this.cacheCanTryStale = false
var cachePolicy = this.ReqServer.HTTPCachePolicy var cachePolicy = this.ReqServer.HTTPCachePolicy
@@ -155,7 +160,7 @@ func (this *HTTPRequest) doCacheRead(useStale bool) (shouldStop bool) {
for _, subKey := range subKeys { for _, subKey := range subKeys {
err := storage.Delete(subKey) err := storage.Delete(subKey)
if err != nil { if err != nil {
remotelogs.Error("HTTP_REQUEST_CACHE", "purge failed: "+err.Error()) remotelogs.ErrorServer("HTTP_REQUEST_CACHE", "purge failed: "+err.Error())
} }
} }
@@ -260,7 +265,7 @@ func (this *HTTPRequest) doCacheRead(useStale bool) (shouldStop bool) {
} }
if !this.canIgnore(err) { if !this.canIgnore(err) {
remotelogs.Warn("HTTP_REQUEST_CACHE", this.URL()+": read from cache failed: open cache failed: "+err.Error()) remotelogs.WarnServer("HTTP_REQUEST_CACHE", this.URL()+": read from cache failed: open cache failed: "+err.Error())
} }
return return
} }
@@ -290,37 +295,34 @@ func (this *HTTPRequest) doCacheRead(useStale bool) (shouldStop bool) {
} }
} }
var pool = this.bytePool(fileSize)
var buf = pool.Get()
defer func() {
pool.Put(buf)
}()
// 读取Header // 读取Header
var headerBuf = []byte{} var headerData = []byte{}
this.writer.SetSentHeaderBytes(reader.HeaderSize()) this.writer.SetSentHeaderBytes(reader.HeaderSize())
err = reader.ReadHeader(buf, func(n int) (goNext bool, err error) { var headerPool = this.bytePool(reader.HeaderSize())
headerBuf = append(headerBuf, buf[:n]...) var headerBuf = headerPool.Get()
err = reader.ReadHeader(headerBuf, func(n int) (goNext bool, err error) {
headerData = append(headerData, headerBuf[:n]...)
for { for {
nIndex := bytes.Index(headerBuf, []byte{'\n'}) nIndex := bytes.Index(headerData, []byte{'\n'})
if nIndex >= 0 { if nIndex >= 0 {
row := headerBuf[:nIndex] row := headerData[:nIndex]
spaceIndex := bytes.Index(row, []byte{':'}) spaceIndex := bytes.Index(row, []byte{':'})
if spaceIndex <= 0 { if spaceIndex <= 0 {
return false, errors.New("invalid header '" + string(row) + "'") return false, errors.New("invalid header '" + string(row) + "'")
} }
this.writer.Header().Set(string(row[:spaceIndex]), string(row[spaceIndex+1:])) this.writer.Header().Set(string(row[:spaceIndex]), string(row[spaceIndex+1:]))
headerBuf = headerBuf[nIndex+1:] headerData = headerData[nIndex+1:]
} else { } else {
break break
} }
} }
return true, nil return true, nil
}) })
headerPool.Put(headerBuf)
if err != nil { if err != nil {
if !this.canIgnore(err) { if !this.canIgnore(err) {
remotelogs.Warn("HTTP_REQUEST_CACHE", this.URL()+": read from cache failed: read header failed: "+err.Error()) remotelogs.WarnServer("HTTP_REQUEST_CACHE", this.URL()+": read from cache failed: read header failed: "+err.Error())
} }
return return
} }
@@ -372,7 +374,7 @@ func (this *HTTPRequest) doCacheRead(useStale bool) (shouldStop bool) {
// 支持 If-None-Match // 支持 If-None-Match
if !this.isLnRequest && !isPartialCache && len(eTag) > 0 && this.requestHeader("If-None-Match") == eTag { if !this.isLnRequest && !isPartialCache && len(eTag) > 0 && this.requestHeader("If-None-Match") == eTag {
// 自定义Header // 自定义Header
this.processResponseHeaders(http.StatusNotModified) this.processResponseHeaders(this.writer.Header(), http.StatusNotModified)
this.addExpiresHeader(reader.ExpiresAt()) this.addExpiresHeader(reader.ExpiresAt())
this.writer.WriteHeader(http.StatusNotModified) this.writer.WriteHeader(http.StatusNotModified)
this.isCached = true this.isCached = true
@@ -384,7 +386,7 @@ func (this *HTTPRequest) doCacheRead(useStale bool) (shouldStop bool) {
// 支持 If-Modified-Since // 支持 If-Modified-Since
if !this.isLnRequest && !isPartialCache && len(modifiedTime) > 0 && this.requestHeader("If-Modified-Since") == modifiedTime { if !this.isLnRequest && !isPartialCache && len(modifiedTime) > 0 && this.requestHeader("If-Modified-Since") == modifiedTime {
// 自定义Header // 自定义Header
this.processResponseHeaders(http.StatusNotModified) this.processResponseHeaders(this.writer.Header(), http.StatusNotModified)
this.addExpiresHeader(reader.ExpiresAt()) this.addExpiresHeader(reader.ExpiresAt())
this.writer.WriteHeader(http.StatusNotModified) this.writer.WriteHeader(http.StatusNotModified)
this.isCached = true this.isCached = true
@@ -393,7 +395,7 @@ func (this *HTTPRequest) doCacheRead(useStale bool) (shouldStop bool) {
return true return true
} }
this.processResponseHeaders(reader.Status()) this.processResponseHeaders(this.writer.Header(), reader.Status())
this.addExpiresHeader(reader.ExpiresAt()) this.addExpiresHeader(reader.ExpiresAt())
// 返回上级节点过期时间 // 返回上级节点过期时间
@@ -422,7 +424,7 @@ func (this *HTTPRequest) doCacheRead(useStale bool) (shouldStop bool) {
if supportRange { if supportRange {
if len(rangeHeader) > 0 { if len(rangeHeader) > 0 {
if fileSize == 0 { if fileSize == 0 {
this.processResponseHeaders(http.StatusRequestedRangeNotSatisfiable) this.processResponseHeaders(this.writer.Header(), http.StatusRequestedRangeNotSatisfiable)
this.writer.WriteHeader(http.StatusRequestedRangeNotSatisfiable) this.writer.WriteHeader(http.StatusRequestedRangeNotSatisfiable)
return true return true
} }
@@ -430,7 +432,7 @@ func (this *HTTPRequest) doCacheRead(useStale bool) (shouldStop bool) {
if len(ranges) == 0 { if len(ranges) == 0 {
ranges, ok = httpRequestParseRangeHeader(rangeHeader) ranges, ok = httpRequestParseRangeHeader(rangeHeader)
if !ok { if !ok {
this.processResponseHeaders(http.StatusRequestedRangeNotSatisfiable) this.processResponseHeaders(this.writer.Header(), http.StatusRequestedRangeNotSatisfiable)
this.writer.WriteHeader(http.StatusRequestedRangeNotSatisfiable) this.writer.WriteHeader(http.StatusRequestedRangeNotSatisfiable)
return true return true
} }
@@ -439,7 +441,7 @@ func (this *HTTPRequest) doCacheRead(useStale bool) (shouldStop bool) {
for k, r := range ranges { for k, r := range ranges {
r2, ok := r.Convert(fileSize) r2, ok := r.Convert(fileSize)
if !ok { if !ok {
this.processResponseHeaders(http.StatusRequestedRangeNotSatisfiable) this.processResponseHeaders(this.writer.Header(), http.StatusRequestedRangeNotSatisfiable)
this.writer.WriteHeader(http.StatusRequestedRangeNotSatisfiable) this.writer.WriteHeader(http.StatusRequestedRangeNotSatisfiable)
return true return true
} }
@@ -455,23 +457,26 @@ func (this *HTTPRequest) doCacheRead(useStale bool) (shouldStop bool) {
respHeader.Set("Content-Length", strconv.FormatInt(ranges[0].Length(), 10)) respHeader.Set("Content-Length", strconv.FormatInt(ranges[0].Length(), 10))
this.writer.WriteHeader(http.StatusPartialContent) this.writer.WriteHeader(http.StatusPartialContent)
err = reader.ReadBodyRange(buf, ranges[0].Start(), ranges[0].End(), func(n int) (goNext bool, err error) { var pool = this.bytePool(fileSize)
_, err = this.writer.Write(buf[:n]) var bodyBuf = pool.Get()
err = reader.ReadBodyRange(bodyBuf, ranges[0].Start(), ranges[0].End(), func(n int) (goNext bool, err error) {
_, err = this.writer.Write(bodyBuf[:n])
if err != nil { if err != nil {
return false, errWritingToClient return false, errWritingToClient
} }
return true, nil return true, nil
}) })
pool.Put(bodyBuf)
if err != nil { if err != nil {
this.varMapping["cache.status"] = "MISS" this.varMapping["cache.status"] = "MISS"
if err == caches.ErrInvalidRange { if err == caches.ErrInvalidRange {
this.processResponseHeaders(http.StatusRequestedRangeNotSatisfiable) this.processResponseHeaders(this.writer.Header(), http.StatusRequestedRangeNotSatisfiable)
this.writer.WriteHeader(http.StatusRequestedRangeNotSatisfiable) this.writer.WriteHeader(http.StatusRequestedRangeNotSatisfiable)
return true return true
} }
if !this.canIgnore(err) { if !this.canIgnore(err) {
remotelogs.Warn("HTTP_REQUEST_CACHE", this.URL()+": read from cache failed: "+err.Error()) remotelogs.WarnServer("HTTP_REQUEST_CACHE", this.URL()+": read from cache failed: "+err.Error())
} }
return return
} }
@@ -508,16 +513,19 @@ func (this *HTTPRequest) doCacheRead(useStale bool) (shouldStop bool) {
} }
} }
err := reader.ReadBodyRange(buf, r.Start(), r.End(), func(n int) (goNext bool, err error) { var pool = this.bytePool(fileSize)
_, err = this.writer.Write(buf[:n]) var bodyBuf = pool.Get()
err := reader.ReadBodyRange(bodyBuf, r.Start(), r.End(), func(n int) (goNext bool, err error) {
_, err = this.writer.Write(bodyBuf[:n])
if err != nil { if err != nil {
return false, errWritingToClient return false, errWritingToClient
} }
return true, nil return true, nil
}) })
pool.Put(bodyBuf)
if err != nil { if err != nil {
if !this.canIgnore(err) { if !this.canIgnore(err) {
remotelogs.Warn("HTTP_REQUEST_CACHE", this.URL()+": read from cache failed: "+err.Error()) remotelogs.WarnServer("HTTP_REQUEST_CACHE", this.URL()+": read from cache failed: "+err.Error())
} }
return true return true
} }
@@ -538,15 +546,18 @@ func (this *HTTPRequest) doCacheRead(useStale bool) (shouldStop bool) {
this.writer.Prepare(resp, fileSize, reader.Status(), false) this.writer.Prepare(resp, fileSize, reader.Status(), false)
this.writer.WriteHeader(reader.Status()) this.writer.WriteHeader(reader.Status())
var pool = this.bytePool(fileSize)
var bodyBuf = pool.Get()
if storage.CanSendfile() { if storage.CanSendfile() {
if fp, canSendFile := this.writer.canSendfile(); canSendFile { if fp, canSendFile := this.writer.canSendfile(); canSendFile {
this.writer.sentBodyBytes, err = io.CopyBuffer(this.writer.rawWriter, fp, buf) this.writer.sentBodyBytes, err = io.CopyBuffer(this.writer.rawWriter, fp, bodyBuf)
} else { } else {
_, err = io.CopyBuffer(this.writer, resp.Body, buf) _, err = io.CopyBuffer(this.writer, resp.Body, bodyBuf)
} }
} else { } else {
_, err = io.CopyBuffer(this.writer, resp.Body, buf) _, err = io.CopyBuffer(this.writer, resp.Body, bodyBuf)
} }
pool.Put(bodyBuf)
if err == io.EOF { if err == io.EOF {
err = nil err = nil
} }
@@ -554,7 +565,7 @@ func (this *HTTPRequest) doCacheRead(useStale bool) (shouldStop bool) {
this.varMapping["cache.status"] = "MISS" this.varMapping["cache.status"] = "MISS"
if !this.canIgnore(err) { if !this.canIgnore(err) {
remotelogs.Warn("HTTP_REQUEST_CACHE", this.URL()+": read from cache failed: read body failed: "+err.Error()) remotelogs.WarnServer("HTTP_REQUEST_CACHE", this.URL()+": read from cache failed: read body failed: "+err.Error())
} }
return return
} }
@@ -617,7 +628,14 @@ func (this *HTTPRequest) tryPartialReader(storage caches.StorageInterface, key s
}() }()
// 检查范围 // 检查范围
//const maxFirstSpan = 16 << 20 // TODO 可以在缓存策略中设置此值
for index, r := range ranges { for index, r := range ranges {
// 没有指定结束位置时,自动指定一个
/**if r.Start() >= 0 && r.End() == -1 {
if partialReader.MaxLength() > r.Start()+maxFirstSpan {
r[1] = r.Start() + maxFirstSpan
}
}**/
r1, ok := r.Convert(partialReader.MaxLength()) r1, ok := r.Convert(partialReader.MaxLength())
if !ok { if !ok {
return nil, nil return nil, nil

View File

@@ -25,10 +25,10 @@ const httpStatusPageTemplate = `<!DOCTYPE html>
</html>` </html>`
func (this *HTTPRequest) write404() { func (this *HTTPRequest) write404() {
this.writeCode(http.StatusNotFound) this.writeCode(http.StatusNotFound, "", "")
} }
func (this *HTTPRequest) writeCode(statusCode int) { func (this *HTTPRequest) writeCode(statusCode int, enMessage string, zhMessage string) {
if this.doPage(statusCode) { if this.doPage(statusCode) {
return return
} }
@@ -42,12 +42,22 @@ func (this *HTTPRequest) writeCode(statusCode int) {
case "requestId": case "requestId":
return this.requestId return this.requestId
case "message": case "message":
return "" // 空 var acceptLanguages = this.RawReq.Header.Get("Accept-Language")
if len(acceptLanguages) > 0 {
var index = strings.Index(acceptLanguages, ",")
if index > 0 {
var firstLanguage = acceptLanguages[:index]
if firstLanguage == "zh-CN" {
return zhMessage
}
}
}
return enMessage
} }
return "${" + varName + "}" return "${" + varName + "}"
}) })
this.processResponseHeaders(statusCode) this.processResponseHeaders(this.writer.Header(), statusCode)
this.writer.WriteHeader(statusCode) this.writer.WriteHeader(statusCode)
_, _ = this.writer.Write([]byte(pageContent)) _, _ = this.writer.Write([]byte(pageContent))
@@ -100,7 +110,7 @@ func (this *HTTPRequest) write50x(err error, statusCode int, enMessage string, z
return "${" + varName + "}" return "${" + varName + "}"
}) })
this.processResponseHeaders(statusCode) this.processResponseHeaders(this.writer.Header(), statusCode)
this.writer.WriteHeader(statusCode) this.writer.WriteHeader(statusCode)
_, _ = this.writer.Write([]byte(pageContent)) _, _ = this.writer.Write([]byte(pageContent))

View File

@@ -187,7 +187,7 @@ func (this *HTTPRequest) doFastcgi() (shouldStop bool) {
// 响应Header // 响应Header
this.writer.AddHeaders(resp.Header) this.writer.AddHeaders(resp.Header)
this.processResponseHeaders(resp.StatusCode) this.processResponseHeaders(this.writer.Header(), resp.StatusCode)
// 准备 // 准备
this.writer.Prepare(resp, resp.ContentLength, resp.StatusCode, true) this.writer.Prepare(resp, resp.ContentLength, resp.StatusCode, true)

View File

@@ -1,7 +1,11 @@
package nodes package nodes
import ( import (
"github.com/TeaOSLab/EdgeCommon/pkg/configutils"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
"github.com/TeaOSLab/EdgeNode/internal/utils" "github.com/TeaOSLab/EdgeNode/internal/utils"
"github.com/iwind/TeaGo/types"
"net"
"net/http" "net/http"
"strconv" "strconv"
"strings" "strings"
@@ -13,7 +17,7 @@ func (this *HTTPRequest) doHostRedirect() (blocked bool) {
if this.web.MergeSlashes { if this.web.MergeSlashes {
urlPath = utils.CleanPath(urlPath) urlPath = utils.CleanPath(urlPath)
} }
fullURL := this.requestScheme() + "://" + this.ReqHost + urlPath var fullURL = this.requestScheme() + "://" + this.ReqHost + urlPath
for _, u := range this.web.HostRedirects { for _, u := range this.web.HostRedirects {
if !u.IsOn { if !u.IsOn {
continue continue
@@ -21,11 +25,50 @@ func (this *HTTPRequest) doHostRedirect() (blocked bool) {
if !u.MatchRequest(this.Format) { if !u.MatchRequest(this.Format) {
continue continue
} }
if u.MatchPrefix { // 匹配前缀 if len(u.Type) == 0 || u.Type == serverconfigs.HTTPHostRedirectTypeURL {
if strings.HasPrefix(fullURL, u.BeforeURL) { if u.MatchPrefix { // 匹配前缀
afterURL := u.AfterURL if strings.HasPrefix(fullURL, u.BeforeURL) {
if u.KeepRequestURI { afterURL := u.AfterURL
afterURL += this.RawReq.URL.RequestURI() if u.KeepRequestURI {
afterURL += this.RawReq.URL.RequestURI()
}
// 前后是否一致
if fullURL == afterURL {
return false
}
if u.Status <= 0 {
u.Status = http.StatusTemporaryRedirect
}
this.processResponseHeaders(this.writer.Header(), u.Status)
http.Redirect(this.RawWriter, this.RawReq, afterURL, u.Status)
return true
}
} else if u.MatchRegexp { // 正则匹配
var reg = u.BeforeURLRegexp()
if reg == nil {
continue
}
var matches = reg.FindStringSubmatch(fullURL)
if len(matches) == 0 {
continue
}
var afterURL = u.AfterURL
for i, match := range matches {
afterURL = strings.ReplaceAll(afterURL, "${"+strconv.Itoa(i)+"}", match)
}
var subNames = reg.SubexpNames()
if len(subNames) > 0 {
for _, subName := range subNames {
if len(subName) > 0 {
index := reg.SubexpIndex(subName)
if index > -1 {
afterURL = strings.ReplaceAll(afterURL, "${"+subName+"}", matches[index])
}
}
}
} }
// 前后是否一致 // 前后是否一致
@@ -33,69 +76,6 @@ func (this *HTTPRequest) doHostRedirect() (blocked bool) {
return false return false
} }
if u.Status <= 0 {
this.processResponseHeaders(http.StatusTemporaryRedirect)
http.Redirect(this.RawWriter, this.RawReq, afterURL, http.StatusTemporaryRedirect)
} else {
this.processResponseHeaders(u.Status)
http.Redirect(this.RawWriter, this.RawReq, afterURL, u.Status)
}
return true
}
} else if u.MatchRegexp { // 正则匹配
reg := u.BeforeURLRegexp()
if reg == nil {
continue
}
matches := reg.FindStringSubmatch(fullURL)
if len(matches) == 0 {
continue
}
afterURL := u.AfterURL
for i, match := range matches {
afterURL = strings.ReplaceAll(afterURL, "${"+strconv.Itoa(i)+"}", match)
}
subNames := reg.SubexpNames()
if len(subNames) > 0 {
for _, subName := range subNames {
if len(subName) > 0 {
index := reg.SubexpIndex(subName)
if index > -1 {
afterURL = strings.ReplaceAll(afterURL, "${"+subName+"}", matches[index])
}
}
}
}
// 前后是否一致
if fullURL == afterURL {
return false
}
if u.KeepArgs {
var qIndex = strings.Index(this.uri, "?")
if qIndex >= 0 {
afterURL += this.uri[qIndex:]
}
}
if u.Status <= 0 {
this.processResponseHeaders(http.StatusTemporaryRedirect)
http.Redirect(this.RawWriter, this.RawReq, afterURL, http.StatusTemporaryRedirect)
} else {
this.processResponseHeaders(u.Status)
http.Redirect(this.RawWriter, this.RawReq, afterURL, u.Status)
}
return true
} else { // 精准匹配
if fullURL == u.RealBeforeURL() {
// 前后是否一致
if fullURL == u.AfterURL {
return false
}
var afterURL = u.AfterURL
if u.KeepArgs { if u.KeepArgs {
var qIndex = strings.Index(this.uri, "?") var qIndex = strings.Index(this.uri, "?")
if qIndex >= 0 { if qIndex >= 0 {
@@ -104,12 +84,127 @@ func (this *HTTPRequest) doHostRedirect() (blocked bool) {
} }
if u.Status <= 0 { if u.Status <= 0 {
this.processResponseHeaders(http.StatusTemporaryRedirect) u.Status = http.StatusTemporaryRedirect
http.Redirect(this.RawWriter, this.RawReq, afterURL, http.StatusTemporaryRedirect)
} else {
this.processResponseHeaders(u.Status)
http.Redirect(this.RawWriter, this.RawReq, afterURL, u.Status)
} }
this.processResponseHeaders(this.writer.Header(), u.Status)
http.Redirect(this.RawWriter, this.RawReq, afterURL, u.Status)
return true
} else { // 精准匹配
if fullURL == u.RealBeforeURL() {
// 前后是否一致
if fullURL == u.AfterURL {
return false
}
var afterURL = u.AfterURL
if u.KeepArgs {
var qIndex = strings.Index(this.uri, "?")
if qIndex >= 0 {
afterURL += this.uri[qIndex:]
}
}
if u.Status <= 0 {
u.Status = http.StatusTemporaryRedirect
}
this.processResponseHeaders(this.writer.Header(), u.Status)
http.Redirect(this.RawWriter, this.RawReq, afterURL, u.Status)
return true
}
}
} else if u.Type == serverconfigs.HTTPHostRedirectTypeDomain {
if len(u.DomainAfter) == 0 {
continue
}
var reqHost = this.ReqHost
// 忽略跳转前端口
if u.DomainBeforeIgnorePorts {
h, _, err := net.SplitHostPort(reqHost)
if err == nil && len(h) > 0 {
reqHost = h
}
}
// 如果跳转前后域名一致,则终止
if u.DomainAfter == reqHost {
return false
}
var scheme = u.DomainAfterScheme
if len(scheme) == 0 {
scheme = this.requestScheme()
}
if u.DomainsAll || configutils.MatchDomains(u.DomainsBefore, reqHost) {
var afterURL = scheme + "://" + u.DomainAfter + urlPath
if fullURL == afterURL {
// 终止匹配
return false
}
if u.Status <= 0 {
u.Status = http.StatusTemporaryRedirect
}
this.processResponseHeaders(this.writer.Header(), u.Status)
// 参数
var qIndex = strings.Index(this.uri, "?")
if qIndex >= 0 {
afterURL += this.uri[qIndex:]
}
http.Redirect(this.RawWriter, this.RawReq, afterURL, u.Status)
return true
}
} else if u.Type == serverconfigs.HTTPHostRedirectTypePort {
if u.PortAfter <= 0 {
continue
}
var scheme = u.PortAfterScheme
if len(scheme) == 0 {
scheme = this.requestScheme()
}
reqHost, reqPort, _ := net.SplitHostPort(this.ReqHost)
if len(reqHost) == 0 {
reqHost = this.ReqHost
}
if len(reqPort) == 0 {
switch this.requestScheme() {
case "http":
reqPort = "80"
case "https":
reqPort = "443"
}
}
// 如果跳转前后端口一致,则终止
if reqPort == types.String(u.PortAfter) {
return false
}
var containsPort = false
if u.PortsAll {
containsPort = true
} else {
containsPort = u.ContainsPort(types.Int(reqPort))
}
if containsPort {
var newReqHost = reqHost
if !((scheme == "http" && u.PortAfter == 80) || (scheme == "https" && u.PortAfter == 443)) {
newReqHost += ":" + types.String(u.PortAfter)
}
var afterURL = scheme + "://" + newReqHost + urlPath
if fullURL == afterURL {
// 终止匹配
return false
}
if u.Status <= 0 {
u.Status = http.StatusTemporaryRedirect
}
this.processResponseHeaders(this.writer.Header(), u.Status)
http.Redirect(this.RawWriter, this.RawReq, afterURL, u.Status)
return true return true
} }
} }

View File

@@ -18,7 +18,7 @@ func (this *HTTPRequest) doRequestLimit() (shouldStop bool) {
// TODO 处理分片提交的内容 // TODO 处理分片提交的内容
if this.web.RequestLimit.MaxBodyBytes() > 0 && if this.web.RequestLimit.MaxBodyBytes() > 0 &&
this.RawReq.ContentLength > this.web.RequestLimit.MaxBodyBytes() { this.RawReq.ContentLength > this.web.RequestLimit.MaxBodyBytes() {
this.writeCode(http.StatusRequestEntityTooLarge) this.writeCode(http.StatusRequestEntityTooLarge, "", "")
return true return true
} }
@@ -29,7 +29,7 @@ func (this *HTTPRequest) doRequestLimit() (shouldStop bool) {
clientConn, ok := requestConn.(ClientConnInterface) clientConn, ok := requestConn.(ClientConnInterface)
if ok && !clientConn.IsBound() { if ok && !clientConn.IsBound() {
if !clientConn.Bind(this.ReqServer.Id, this.requestRemoteAddr(true), this.web.RequestLimit.MaxConns, this.web.RequestLimit.MaxConnsPerIP) { if !clientConn.Bind(this.ReqServer.Id, this.requestRemoteAddr(true), this.web.RequestLimit.MaxConns, this.web.RequestLimit.MaxConnsPerIP) {
this.writeCode(http.StatusTooManyRequests) this.writeCode(http.StatusTooManyRequests, "", "")
this.Close() this.Close()
return true return true
} }

View File

@@ -51,27 +51,46 @@ func (this *HTTPRequest) log() {
addr = addr[:index] addr = addr[:index]
} }
var serverGlobalConfig = this.nodeConfig.GlobalServerConfig
// 请求Cookie // 请求Cookie
var cookies = map[string]string{} var cookies = map[string]string{}
if ref == nil || ref.ContainsField(serverconfigs.HTTPAccessLogFieldCookie) { var enableCookies = false
for _, cookie := range this.RawReq.Cookies() { if serverGlobalConfig == nil || serverGlobalConfig.HTTPAccessLog.EnableCookies {
cookies[cookie.Name] = cookie.Value enableCookies = true
if ref == nil || ref.ContainsField(serverconfigs.HTTPAccessLogFieldCookie) {
for _, cookie := range this.RawReq.Cookies() {
cookies[cookie.Name] = cookie.Value
}
} }
} }
// 请求Header // 请求Header
var pbReqHeader = map[string]*pb.Strings{} var pbReqHeader = map[string]*pb.Strings{}
if ref == nil || ref.ContainsField(serverconfigs.HTTPAccessLogFieldHeader) { if serverGlobalConfig == nil || serverGlobalConfig.HTTPAccessLog.EnableRequestHeaders {
for k, v := range this.RawReq.Header { if ref == nil || ref.ContainsField(serverconfigs.HTTPAccessLogFieldHeader) {
pbReqHeader[k] = &pb.Strings{Values: v} // 是否只记录通用Header
var commonHeadersOnly = serverGlobalConfig != nil && serverGlobalConfig.HTTPAccessLog.CommonRequestHeadersOnly
for k, v := range this.RawReq.Header {
if commonHeadersOnly && !serverconfigs.IsCommonRequestHeader(k) {
continue
}
if !enableCookies && k == "Cookie" {
continue
}
pbReqHeader[k] = &pb.Strings{Values: v}
}
} }
} }
// 响应Header // 响应Header
var pbResHeader = map[string]*pb.Strings{} var pbResHeader = map[string]*pb.Strings{}
if ref == nil || ref.ContainsField(serverconfigs.HTTPAccessLogFieldSentHeader) { if serverGlobalConfig == nil || serverGlobalConfig.HTTPAccessLog.EnableResponseHeaders {
for k, v := range this.writer.Header() { if ref == nil || ref.ContainsField(serverconfigs.HTTPAccessLogFieldSentHeader) {
pbResHeader[k] = &pb.Strings{Values: v} for k, v := range this.writer.Header() {
pbResHeader[k] = &pb.Strings{Values: v}
}
} }
} }
@@ -146,6 +165,7 @@ func (this *HTTPRequest) log() {
if this.origin != nil { if this.origin != nil {
accessLog.OriginId = this.origin.Id accessLog.OriginId = this.origin.Id
accessLog.OriginAddress = this.originAddr accessLog.OriginAddress = this.originAddr
accessLog.OriginStatus = this.originStatus
} }
// 请求Body // 请求Body

View File

@@ -32,7 +32,7 @@ func (this *HTTPRequest) doMismatch() {
} }
// 根据配置进行相应的处理 // 根据配置进行相应的处理
if sharedNodeConfig.GlobalConfig != nil && sharedNodeConfig.GlobalConfig.HTTPAll.MatchDomainStrictly { if sharedNodeConfig.GlobalServerConfig != nil && sharedNodeConfig.GlobalServerConfig.HTTPAll.MatchDomainStrictly {
// 检查cc // 检查cc
// TODO 可以在管理端配置是否开启以及最多尝试次数 // TODO 可以在管理端配置是否开启以及最多尝试次数
if len(remoteIP) > 0 { if len(remoteIP) > 0 {
@@ -46,7 +46,7 @@ func (this *HTTPRequest) doMismatch() {
} }
// 处理当前连接 // 处理当前连接
var httpAllConfig = sharedNodeConfig.GlobalConfig.HTTPAll var httpAllConfig = sharedNodeConfig.GlobalServerConfig.HTTPAll
var mismatchAction = httpAllConfig.DomainMismatchAction var mismatchAction = httpAllConfig.DomainMismatchAction
if mismatchAction != nil && mismatchAction.Code == "page" { if mismatchAction != nil && mismatchAction.Code == "page" {
if mismatchAction.Options != nil { if mismatchAction.Options != nil {

View File

@@ -60,11 +60,11 @@ func (this *HTTPRequest) doPage(status int) (shouldStop bool) {
// 修改状态码 // 修改状态码
if page.NewStatus > 0 { if page.NewStatus > 0 {
// 自定义响应Headers // 自定义响应Headers
this.processResponseHeaders(page.NewStatus) this.processResponseHeaders(this.writer.Header(), page.NewStatus)
this.writer.Prepare(nil, stat.Size(), page.NewStatus, true) this.writer.Prepare(nil, stat.Size(), page.NewStatus, true)
this.writer.WriteHeader(page.NewStatus) this.writer.WriteHeader(page.NewStatus)
} else { } else {
this.processResponseHeaders(status) this.processResponseHeaders(this.writer.Header(), status)
this.writer.Prepare(nil, stat.Size(), status, true) this.writer.Prepare(nil, stat.Size(), status, true)
this.writer.WriteHeader(status) this.writer.WriteHeader(status)
} }
@@ -99,11 +99,11 @@ func (this *HTTPRequest) doPage(status int) (shouldStop bool) {
// 修改状态码 // 修改状态码
if page.NewStatus > 0 { if page.NewStatus > 0 {
// 自定义响应Headers // 自定义响应Headers
this.processResponseHeaders(page.NewStatus) this.processResponseHeaders(this.writer.Header(), page.NewStatus)
this.writer.Prepare(nil, int64(len(content)), page.NewStatus, true) this.writer.Prepare(nil, int64(len(content)), page.NewStatus, true)
this.writer.WriteHeader(page.NewStatus) this.writer.WriteHeader(page.NewStatus)
} else { } else {
this.processResponseHeaders(status) this.processResponseHeaders(this.writer.Header(), status)
this.writer.Prepare(nil, int64(len(content)), status, true) this.writer.Prepare(nil, int64(len(content)), status, true)
this.writer.WriteHeader(status) this.writer.WriteHeader(status)
} }

View File

@@ -12,7 +12,7 @@ func (this *HTTPRequest) doPlanExpires() {
this.tags = append(this.tags, "plan") this.tags = append(this.tags, "plan")
var statusCode = http.StatusNotFound var statusCode = http.StatusNotFound
this.processResponseHeaders(statusCode) this.processResponseHeaders(this.writer.Header(), statusCode)
this.writer.WriteHeader(statusCode) this.writer.WriteHeader(statusCode)
_, _ = this.writer.WriteString(this.Format(serverconfigs.DefaultPlanExpireNoticePageBody)) _, _ = this.writer.WriteString(this.Format(serverconfigs.DefaultPlanExpireNoticePageBody))

View File

@@ -42,7 +42,7 @@ func (this *HTTPRequest) doRedirectToHTTPS(redirectToHTTPSConfig *serverconfigs.
} }
newURL := "https://" + host + this.RawReq.RequestURI newURL := "https://" + host + this.RawReq.RequestURI
this.processResponseHeaders(statusCode) this.processResponseHeaders(this.writer.Header(), statusCode)
http.Redirect(this.writer, this.RawReq, newURL, statusCode) http.Redirect(this.writer, this.RawReq, newURL, statusCode)
return true return true

View File

@@ -0,0 +1,50 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package nodes
import (
"net/http"
"net/url"
)
func (this *HTTPRequest) doCheckReferers() (shouldStop bool) {
if this.web.Referers == nil {
return
}
const cacheSeconds = "3600" // 时间不能过长,防止修改设置后长期无法生效
var refererURL = this.RawReq.Header.Get("Referer")
if len(refererURL) == 0 {
if this.web.Referers.MatchDomain(this.ReqHost, "") {
return
}
this.tags = append(this.tags, "refererCheck")
this.writer.Header().Set("Cache-Control", "max-age="+cacheSeconds)
this.writeCode(http.StatusForbidden, "The referer has been blocked.", "当前访问已被防盗链系统拦截。")
return true
}
u, err := url.Parse(refererURL)
if err != nil {
if this.web.Referers.MatchDomain(this.ReqHost, "") {
return
}
this.tags = append(this.tags, "refererCheck")
this.writer.Header().Set("Cache-Control", "max-age="+cacheSeconds)
this.writeCode(http.StatusForbidden, "The referer has been blocked.", "当前访问已被防盗链系统拦截。")
return true
}
if !this.web.Referers.MatchDomain(this.ReqHost, u.Host) {
this.tags = append(this.tags, "refererCheck")
this.writer.Header().Set("Cache-Control", "max-age="+cacheSeconds)
this.writeCode(http.StatusForbidden, "The referer has been blocked.", "当前访问已被防盗链系统拦截。")
return true
}
return
}

View File

@@ -116,7 +116,7 @@ func (this *HTTPRequest) doOriginRequest(failedOriginIds []int64, failedLnNodeId
// 处理Scheme // 处理Scheme
if origin.Addr == nil { if origin.Addr == nil {
err := errors.New(this.URL() + ": Origin '" + strconv.FormatInt(origin.Id, 10) + "' does not has a address") err := errors.New(this.URL() + ": Origin '" + strconv.FormatInt(origin.Id, 10) + "' does not has a address")
remotelogs.Error("HTTP_REQUEST_REVERSE_PROXY", err.Error()) remotelogs.ErrorServer("HTTP_REQUEST_REVERSE_PROXY", err.Error())
this.write50x(err, http.StatusBadGateway, "Origin site did not has a valid address", "源站尚未配置地址", true) this.write50x(err, http.StatusBadGateway, "Origin site did not has a valid address", "源站尚未配置地址", true)
return return
} }
@@ -168,7 +168,7 @@ func (this *HTTPRequest) doOriginRequest(failedOriginIds []int64, failedLnNodeId
var originHostIndex = strings.Index(originAddr, ":") var originHostIndex = strings.Index(originAddr, ":")
if originHostIndex < 0 { if originHostIndex < 0 {
var originErr = errors.New(this.URL() + ": Invalid origin address '" + originAddr + "', lacking port") var originErr = errors.New(this.URL() + ": Invalid origin address '" + originAddr + "', lacking port")
remotelogs.Error("HTTP_REQUEST_REVERSE_PROXY", originErr.Error()) remotelogs.ErrorServer("HTTP_REQUEST_REVERSE_PROXY", originErr.Error())
this.write50x(originErr, http.StatusBadGateway, "No port in origin site address", "源站地址中没有配置端口", true) this.write50x(originErr, http.StatusBadGateway, "No port in origin site address", "源站地址中没有配置端口", true)
return return
} }
@@ -240,14 +240,14 @@ func (this *HTTPRequest) doOriginRequest(failedOriginIds []int64, failedLnNodeId
// 判断是否为Websocket请求 // 判断是否为Websocket请求
if this.RawReq.Header.Get("Upgrade") == "websocket" { if this.RawReq.Header.Get("Upgrade") == "websocket" {
this.doWebsocket(requestHost) shouldRetry = this.doWebsocket(requestHost, isLastRetry)
return return
} }
// 获取请求客户端 // 获取请求客户端
client, err := SharedHTTPClientPool.Client(this, origin, originAddr, this.reverseProxy.ProxyProtocol, this.reverseProxy.FollowRedirects) client, err := SharedHTTPClientPool.Client(this, origin, originAddr, this.reverseProxy.ProxyProtocol, this.reverseProxy.FollowRedirects)
if err != nil { if err != nil {
remotelogs.Error("HTTP_REQUEST_REVERSE_PROXY", this.URL()+": Create client failed: "+err.Error()) remotelogs.ErrorServer("HTTP_REQUEST_REVERSE_PROXY", this.URL()+": Create client failed: "+err.Error())
this.write50x(err, http.StatusBadGateway, "Failed to create origin site client", "构造源站客户端失败", true) this.write50x(err, http.StatusBadGateway, "Failed to create origin site client", "构造源站客户端失败", true)
return return
} }
@@ -262,7 +262,7 @@ func (this *HTTPRequest) doOriginRequest(failedOriginIds []int64, failedLnNodeId
this.reverseProxy.ResetScheduling() this.reverseProxy.ResetScheduling()
}) })
this.write50x(err, http.StatusBadGateway, "Failed to read origin site", "源站读取失败", true) this.write50x(err, http.StatusBadGateway, "Failed to read origin site", "源站读取失败", true)
remotelogs.Warn("HTTP_REQUEST_REVERSE_PROXY", this.RawReq.URL.String()+": Request origin server failed: "+err.Error()) remotelogs.WarnServer("HTTP_REQUEST_REVERSE_PROXY", this.RawReq.URL.String()+": Request origin server failed: "+err.Error())
} else if httpErr.Err != context.Canceled { } else if httpErr.Err != context.Canceled {
SharedOriginStateManager.Fail(origin, requestHost, this.reverseProxy, func() { SharedOriginStateManager.Fail(origin, requestHost, this.reverseProxy, func() {
this.reverseProxy.ResetScheduling() this.reverseProxy.ResetScheduling()
@@ -278,7 +278,7 @@ func (this *HTTPRequest) doOriginRequest(failedOriginIds []int64, failedLnNodeId
} }
if httpErr.Err != io.EOF { if httpErr.Err != io.EOF {
remotelogs.Warn("HTTP_REQUEST_REVERSE_PROXY", this.URL()+": Request origin server failed: "+err.Error()) remotelogs.WarnServer("HTTP_REQUEST_REVERSE_PROXY", this.URL()+": Request origin server failed: "+err.Error())
} }
return return
@@ -292,7 +292,7 @@ func (this *HTTPRequest) doOriginRequest(failedOriginIds []int64, failedLnNodeId
this.write50x(err, http.StatusBadGateway, "Failed to read origin site", "源站读取失败", true) this.write50x(err, http.StatusBadGateway, "Failed to read origin site", "源站读取失败", true)
} }
if httpErr.Err != io.EOF { if httpErr.Err != io.EOF {
remotelogs.Warn("HTTP_REQUEST_REVERSE_PROXY", this.URL()+": Request origin server failed: "+err.Error()) remotelogs.WarnServer("HTTP_REQUEST_REVERSE_PROXY", this.URL()+": Request origin server failed: "+err.Error())
} }
} else { } else {
// 是否为客户端方面的错误 // 是否为客户端方面的错误
@@ -320,6 +320,11 @@ func (this *HTTPRequest) doOriginRequest(failedOriginIds []int64, failedLnNodeId
} }
return return
} }
// 记录相关数据
this.originStatus = int32(resp.StatusCode)
// 恢复源站状态
if !origin.IsOk { if !origin.IsOk {
SharedOriginStateManager.Success(origin, func() { SharedOriginStateManager.Success(origin, func() {
this.reverseProxy.ResetScheduling() this.reverseProxy.ResetScheduling()
@@ -331,7 +336,7 @@ func (this *HTTPRequest) doOriginRequest(failedOriginIds []int64, failedLnNodeId
if this.doWAFResponse(resp) { if this.doWAFResponse(resp) {
err = resp.Body.Close() err = resp.Body.Close()
if err != nil { if err != nil {
remotelogs.Warn("HTTP_REQUEST_REVERSE_PROXY", this.URL()+": Closing Error (WAF): "+err.Error()) remotelogs.WarnServer("HTTP_REQUEST_REVERSE_PROXY", this.URL()+": Closing Error (WAF): "+err.Error())
} }
return return
} }
@@ -341,7 +346,7 @@ func (this *HTTPRequest) doOriginRequest(failedOriginIds []int64, failedLnNodeId
if len(this.web.Pages) > 0 && this.doPage(resp.StatusCode) { if len(this.web.Pages) > 0 && this.doPage(resp.StatusCode) {
err = resp.Body.Close() err = resp.Body.Close()
if err != nil { if err != nil {
remotelogs.Warn("HTTP_REQUEST_REVERSE_PROXY", this.URL()+": Closing error (Page): "+err.Error()) remotelogs.WarnServer("HTTP_REQUEST_REVERSE_PROXY", this.URL()+": Closing error (Page): "+err.Error())
} }
return return
} }
@@ -392,7 +397,7 @@ func (this *HTTPRequest) doOriginRequest(failedOriginIds []int64, failedLnNodeId
// 响应Header // 响应Header
this.writer.AddHeaders(resp.Header) this.writer.AddHeaders(resp.Header)
this.processResponseHeaders(resp.StatusCode) this.processResponseHeaders(this.writer.Header(), resp.StatusCode)
// 是否需要刷新 // 是否需要刷新
var shouldAutoFlush = this.reverseProxy.AutoFlush || this.RawReq.Header.Get("Accept") == "text/event-stream" var shouldAutoFlush = this.reverseProxy.AutoFlush || this.RawReq.Header.Get("Accept") == "text/event-stream"
@@ -443,13 +448,13 @@ func (this *HTTPRequest) doOriginRequest(failedOriginIds []int64, failedLnNodeId
var closeErr = resp.Body.Close() var closeErr = resp.Body.Close()
if closeErr != nil { if closeErr != nil {
if !this.canIgnore(closeErr) { if !this.canIgnore(closeErr) {
remotelogs.Warn("HTTP_REQUEST_REVERSE_PROXY", this.URL()+": Closing error: "+closeErr.Error()) remotelogs.WarnServer("HTTP_REQUEST_REVERSE_PROXY", this.URL()+": Closing error: "+closeErr.Error())
} }
} }
if err != nil && err != io.EOF { if err != nil && err != io.EOF {
if !this.canIgnore(err) { if !this.canIgnore(err) {
remotelogs.Warn("HTTP_REQUEST_REVERSE_PROXY", this.URL()+": Writing error: "+err.Error()) remotelogs.WarnServer("HTTP_REQUEST_REVERSE_PROXY", this.URL()+": Writing error: "+err.Error())
this.addError(err) this.addError(err)
} }
} }

View File

@@ -30,10 +30,10 @@ func (this *HTTPRequest) doRewrite() (shouldShop bool) {
// 跳转 // 跳转
if this.rewriteRule.Mode == serverconfigs.HTTPRewriteModeRedirect { if this.rewriteRule.Mode == serverconfigs.HTTPRewriteModeRedirect {
if this.rewriteRule.RedirectStatus > 0 { if this.rewriteRule.RedirectStatus > 0 {
this.processResponseHeaders(this.rewriteRule.RedirectStatus) this.processResponseHeaders(this.writer.Header(), this.rewriteRule.RedirectStatus)
http.Redirect(this.writer, this.RawReq, this.rewriteReplace, this.rewriteRule.RedirectStatus) http.Redirect(this.writer, this.RawReq, this.rewriteReplace, this.rewriteRule.RedirectStatus)
} else { } else {
this.processResponseHeaders(http.StatusTemporaryRedirect) this.processResponseHeaders(this.writer.Header(), http.StatusTemporaryRedirect)
http.Redirect(this.writer, this.RawReq, this.rewriteReplace, http.StatusTemporaryRedirect) http.Redirect(this.writer, this.RawReq, this.rewriteReplace, http.StatusTemporaryRedirect)
} }
return true return true

View File

@@ -217,7 +217,7 @@ func (this *HTTPRequest) doRoot() (isBreak bool) {
// 支持 If-None-Match // 支持 If-None-Match
if this.requestHeader("If-None-Match") == eTag { if this.requestHeader("If-None-Match") == eTag {
// 自定义Header // 自定义Header
this.processResponseHeaders(http.StatusNotModified) this.processResponseHeaders(this.writer.Header(), http.StatusNotModified)
this.writer.WriteHeader(http.StatusNotModified) this.writer.WriteHeader(http.StatusNotModified)
return true return true
} }
@@ -225,7 +225,7 @@ func (this *HTTPRequest) doRoot() (isBreak bool) {
// 支持 If-Modified-Since // 支持 If-Modified-Since
if this.requestHeader("If-Modified-Since") == modifiedTime { if this.requestHeader("If-Modified-Since") == modifiedTime {
// 自定义Header // 自定义Header
this.processResponseHeaders(http.StatusNotModified) this.processResponseHeaders(this.writer.Header(), http.StatusNotModified)
this.writer.WriteHeader(http.StatusNotModified) this.writer.WriteHeader(http.StatusNotModified)
return true return true
} }
@@ -253,14 +253,14 @@ func (this *HTTPRequest) doRoot() (isBreak bool) {
var contentRange = this.RawReq.Header.Get("Range") var contentRange = this.RawReq.Header.Get("Range")
if len(contentRange) > 0 { if len(contentRange) > 0 {
if fileSize == 0 { if fileSize == 0 {
this.processResponseHeaders(http.StatusRequestedRangeNotSatisfiable) this.processResponseHeaders(this.writer.Header(), http.StatusRequestedRangeNotSatisfiable)
this.writer.WriteHeader(http.StatusRequestedRangeNotSatisfiable) this.writer.WriteHeader(http.StatusRequestedRangeNotSatisfiable)
return true return true
} }
set, ok := httpRequestParseRangeHeader(contentRange) set, ok := httpRequestParseRangeHeader(contentRange)
if !ok { if !ok {
this.processResponseHeaders(http.StatusRequestedRangeNotSatisfiable) this.processResponseHeaders(this.writer.Header(), http.StatusRequestedRangeNotSatisfiable)
this.writer.WriteHeader(http.StatusRequestedRangeNotSatisfiable) this.writer.WriteHeader(http.StatusRequestedRangeNotSatisfiable)
return true return true
} }
@@ -269,7 +269,7 @@ func (this *HTTPRequest) doRoot() (isBreak bool) {
for k, r := range ranges { for k, r := range ranges {
r2, ok := r.Convert(fileSize) r2, ok := r.Convert(fileSize)
if !ok { if !ok {
this.processResponseHeaders(http.StatusRequestedRangeNotSatisfiable) this.processResponseHeaders(this.writer.Header(), http.StatusRequestedRangeNotSatisfiable)
this.writer.WriteHeader(http.StatusRequestedRangeNotSatisfiable) this.writer.WriteHeader(http.StatusRequestedRangeNotSatisfiable)
return true return true
} }
@@ -290,7 +290,7 @@ func (this *HTTPRequest) doRoot() (isBreak bool) {
} }
// 自定义Header // 自定义Header
this.processResponseHeaders(http.StatusOK) this.processResponseHeaders(this.writer.Header(), http.StatusOK)
// 在Range请求中不能缓存 // 在Range请求中不能缓存
if len(ranges) > 0 { if len(ranges) > 0 {
@@ -325,7 +325,7 @@ func (this *HTTPRequest) doRoot() (isBreak bool) {
return true return true
} }
if !ok { if !ok {
this.processResponseHeaders(http.StatusRequestedRangeNotSatisfiable) this.processResponseHeaders(this.writer.Header(), http.StatusRequestedRangeNotSatisfiable)
this.writer.WriteHeader(http.StatusRequestedRangeNotSatisfiable) this.writer.WriteHeader(http.StatusRequestedRangeNotSatisfiable)
return true return true
} }
@@ -377,7 +377,7 @@ func (this *HTTPRequest) doRoot() (isBreak bool) {
return true return true
} }
if !ok { if !ok {
this.processResponseHeaders(http.StatusRequestedRangeNotSatisfiable) this.processResponseHeaders(this.writer.Header(), http.StatusRequestedRangeNotSatisfiable)
this.writer.WriteHeader(http.StatusRequestedRangeNotSatisfiable) this.writer.WriteHeader(http.StatusRequestedRangeNotSatisfiable)
return true return true
} }

View File

@@ -28,10 +28,10 @@ func (this *HTTPRequest) doShutdown() {
if len(shutdown.URL) == 0 { if len(shutdown.URL) == 0 {
// 自定义响应Headers // 自定义响应Headers
if shutdown.Status > 0 { if shutdown.Status > 0 {
this.processResponseHeaders(shutdown.Status) this.processResponseHeaders(this.writer.Header(), shutdown.Status)
this.writer.WriteHeader(shutdown.Status) this.writer.WriteHeader(shutdown.Status)
} else { } else {
this.processResponseHeaders(http.StatusOK) this.processResponseHeaders(this.writer.Header(), http.StatusOK)
this.writer.WriteHeader(http.StatusOK) this.writer.WriteHeader(http.StatusOK)
} }
_, err := this.writer.WriteString("The site have been shutdown.") _, err := this.writer.WriteString("The site have been shutdown.")
@@ -59,10 +59,10 @@ func (this *HTTPRequest) doShutdown() {
// 自定义响应Headers // 自定义响应Headers
if shutdown.Status > 0 { if shutdown.Status > 0 {
this.processResponseHeaders(shutdown.Status) this.processResponseHeaders(this.writer.Header(), shutdown.Status)
this.writer.WriteHeader(shutdown.Status) this.writer.WriteHeader(shutdown.Status)
} else { } else {
this.processResponseHeaders(http.StatusOK) this.processResponseHeaders(this.writer.Header(), http.StatusOK)
this.writer.WriteHeader(http.StatusOK) this.writer.WriteHeader(http.StatusOK)
} }
buf := utils.BytePool1k.Get() buf := utils.BytePool1k.Get()
@@ -85,10 +85,10 @@ func (this *HTTPRequest) doShutdown() {
} else if shutdown.BodyType == shared.BodyTypeHTML { } else if shutdown.BodyType == shared.BodyTypeHTML {
// 自定义响应Headers // 自定义响应Headers
if shutdown.Status > 0 { if shutdown.Status > 0 {
this.processResponseHeaders(shutdown.Status) this.processResponseHeaders(this.writer.Header(), shutdown.Status)
this.writer.WriteHeader(shutdown.Status) this.writer.WriteHeader(shutdown.Status)
} else { } else {
this.processResponseHeaders(http.StatusOK) this.processResponseHeaders(this.writer.Header(), http.StatusOK)
this.writer.WriteHeader(http.StatusOK) this.writer.WriteHeader(http.StatusOK)
} }

View File

@@ -12,5 +12,5 @@ func (this *HTTPRequest) doStat() {
// 内置的统计 // 内置的统计
stats.SharedHTTPRequestStatManager.AddRemoteAddr(this.ReqServer.Id, this.requestRemoteAddr(true), this.writer.SentBodyBytes(), this.isAttack) stats.SharedHTTPRequestStatManager.AddRemoteAddr(this.ReqServer.Id, this.requestRemoteAddr(true), this.writer.SentBodyBytes(), this.isAttack)
stats.SharedHTTPRequestStatManager.AddUserAgent(this.ReqServer.Id, this.requestHeader("User-Agent")) stats.SharedHTTPRequestStatManager.AddUserAgent(this.ReqServer.Id, this.requestHeader("User-Agent"), this.remoteAddr)
} }

View File

@@ -13,7 +13,7 @@ func (this *HTTPRequest) doTrafficLimit() {
this.tags = append(this.tags, "bandwidth") this.tags = append(this.tags, "bandwidth")
var statusCode = 509 var statusCode = 509
this.processResponseHeaders(statusCode) this.processResponseHeaders(this.writer.Header(), statusCode)
this.writer.WriteHeader(statusCode) this.writer.WriteHeader(statusCode)
if len(config.NoticePageBody) != 0 { if len(config.NoticePageBody) != 0 {

View File

@@ -44,9 +44,9 @@ func (this *HTTPRequest) doURL(method string, url string, host string, statusCod
// Header // Header
if statusCode <= 0 { if statusCode <= 0 {
this.processResponseHeaders(resp.StatusCode) this.processResponseHeaders(this.writer.Header(), resp.StatusCode)
} else { } else {
this.processResponseHeaders(statusCode) this.processResponseHeaders(this.writer.Header(), statusCode)
} }
if supportVariables { if supportVariables {

View File

@@ -0,0 +1,24 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package nodes
import (
"net/http"
)
func (this *HTTPRequest) doCheckUserAgent() (shouldStop bool) {
if this.web.UserAgent == nil {
return
}
const cacheSeconds = "3600" // 时间不能过长,防止修改设置后长期无法生效
if !this.web.UserAgent.AllowRequest(this.RawReq) {
this.tags = append(this.tags, "userAgentCheck")
this.writer.Header().Set("Cache-Control", "max-age="+cacheSeconds)
this.writeCode(http.StatusForbidden, "The User-Agent has been blocked.", "当前访问已被UA名单拦截。")
return true
}
return
}

View File

@@ -173,7 +173,7 @@ func (this *HTTPRequest) checkWAFRequest(firewallPolicy *firewallconfigs.HTTPFir
if countryId > 0 && lists.ContainsInt64(regionConfig.DenyCountryIds, countryId) { if countryId > 0 && lists.ContainsInt64(regionConfig.DenyCountryIds, countryId) {
this.firewallPolicyId = firewallPolicy.Id this.firewallPolicyId = firewallPolicy.Id
this.writeCode(http.StatusForbidden) this.writeCode(http.StatusForbidden, "", "")
this.writer.Flush() this.writer.Flush()
this.writer.Close() this.writer.Close()
@@ -192,7 +192,7 @@ func (this *HTTPRequest) checkWAFRequest(firewallPolicy *firewallconfigs.HTTPFir
if provinceId > 0 && lists.ContainsInt64(regionConfig.DenyProvinceIds, provinceId) { if provinceId > 0 && lists.ContainsInt64(regionConfig.DenyProvinceIds, provinceId) {
this.firewallPolicyId = firewallPolicy.Id this.firewallPolicyId = firewallPolicy.Id
this.writeCode(http.StatusForbidden) this.writeCode(http.StatusForbidden, "", "")
this.writer.Flush() this.writer.Flush()
this.writer.Close() this.writer.Close()
@@ -218,7 +218,7 @@ func (this *HTTPRequest) checkWAFRequest(firewallPolicy *firewallconfigs.HTTPFir
} }
goNext, hasRequestBody, ruleGroup, ruleSet, err := w.MatchRequest(this, this.writer) goNext, hasRequestBody, ruleGroup, ruleSet, err := w.MatchRequest(this, this.writer)
if forceLog && logRequestBody && hasRequestBody { if forceLog && logRequestBody && hasRequestBody && ruleSet != nil && ruleSet.HasAttackActions() {
this.wafHasRequestBody = true this.wafHasRequestBody = true
} }
if err != nil { if err != nil {
@@ -294,7 +294,7 @@ func (this *HTTPRequest) checkWAFResponse(firewallPolicy *firewallconfigs.HTTPFi
} }
goNext, hasRequestBody, ruleGroup, ruleSet, err := w.MatchResponse(this, resp, this.writer) goNext, hasRequestBody, ruleGroup, ruleSet, err := w.MatchResponse(this, resp, this.writer)
if forceLog && logRequestBody && hasRequestBody { if forceLog && logRequestBody && hasRequestBody && ruleSet != nil && ruleSet.HasAttackActions() {
this.wafHasRequestBody = true this.wafHasRequestBody = true
} }
if err != nil { if err != nil {

View File

@@ -1,6 +1,8 @@
package nodes package nodes
import ( import (
"bufio"
"bytes"
"errors" "errors"
"github.com/TeaOSLab/EdgeNode/internal/utils" "github.com/TeaOSLab/EdgeNode/internal/utils"
"io" "io"
@@ -8,8 +10,36 @@ import (
"net/url" "net/url"
) )
// WebsocketResponseReader Websocket响应Reader
type WebsocketResponseReader struct {
rawReader io.Reader
buf []byte
}
func NewWebsocketResponseReader(rawReader io.Reader) *WebsocketResponseReader {
return &WebsocketResponseReader{
rawReader: rawReader,
}
}
func (this *WebsocketResponseReader) Read(p []byte) (n int, err error) {
n, err = this.rawReader.Read(p)
if n > 0 {
if len(this.buf) == 0 {
this.buf = make([]byte, n)
copy(this.buf, p[:n])
} else {
this.buf = append(this.buf, p[:n]...)
}
}
return
}
// 处理Websocket请求 // 处理Websocket请求
func (this *HTTPRequest) doWebsocket(requestHost string) { func (this *HTTPRequest) doWebsocket(requestHost string, isLastRetry bool) (shouldRetry bool) {
// 设置不缓存
this.web.Cache = nil
if this.web.WebsocketRef == nil || !this.web.WebsocketRef.IsOn || this.web.Websocket == nil || !this.web.Websocket.IsOn { if this.web.WebsocketRef == nil || !this.web.WebsocketRef.IsOn || this.web.Websocket == nil || !this.web.Websocket.IsOn {
this.writer.WriteHeader(http.StatusForbidden) this.writer.WriteHeader(http.StatusForbidden)
this.addError(errors.New("websocket have not been enabled yet")) this.addError(errors.New("websocket have not been enabled yet"))
@@ -40,16 +70,26 @@ func (this *HTTPRequest) doWebsocket(requestHost string) {
this.RawReq.Header.Set("Origin", newRequestOrigin) this.RawReq.Header.Set("Origin", newRequestOrigin)
} }
// 获取当前连接
var requestConn = this.RawReq.Context().Value(HTTPConnContextKey)
if requestConn == nil {
return
}
// 连接源站
// TODO 增加N次错误重试重试的时候需要尝试不同的源站 // TODO 增加N次错误重试重试的时候需要尝试不同的源站
originConn, _, err := OriginConnect(this.origin, this.requestServerPort(), this.RawReq.RemoteAddr, requestHost) originConn, _, err := OriginConnect(this.origin, this.requestServerPort(), this.RawReq.RemoteAddr, requestHost)
if err != nil { if err != nil {
this.write50x(err, http.StatusBadGateway, "Failed to connect origin site", "源站连接失败", false) if isLastRetry {
this.write50x(err, http.StatusBadGateway, "Failed to connect origin site", "源站连接失败", false)
}
// 增加失败次数 // 增加失败次数
SharedOriginStateManager.Fail(this.origin, requestHost, this.reverseProxy, func() { SharedOriginStateManager.Fail(this.origin, requestHost, this.reverseProxy, func() {
this.reverseProxy.ResetScheduling() this.reverseProxy.ResetScheduling()
}) })
shouldRetry = true
return return
} }
@@ -69,6 +109,11 @@ func (this *HTTPRequest) doWebsocket(requestHost string) {
return return
} }
requestClientConn, ok := requestConn.(ClientConnInterface)
if ok {
requestClientConn.SetIsWebsocket(true)
}
clientConn, _, err := this.writer.Hijack() clientConn, _, err := this.writer.Hijack()
if err != nil || clientConn == nil { if err != nil || clientConn == nil {
this.write50x(err, http.StatusInternalServerError, "Failed to get origin site connection", "获取源站连接失败", false) this.write50x(err, http.StatusInternalServerError, "Failed to get origin site connection", "获取源站连接失败", false)
@@ -79,6 +124,58 @@ func (this *HTTPRequest) doWebsocket(requestHost string) {
}() }()
go func() { go func() {
// 读取第一个响应
var respReader = NewWebsocketResponseReader(originConn)
resp, err := http.ReadResponse(bufio.NewReader(respReader), this.RawReq)
if err != nil || resp == nil {
if resp != nil && resp.Body != nil {
_ = resp.Body.Close()
}
_ = clientConn.Close()
_ = originConn.Close()
return
}
this.processResponseHeaders(resp.Header, resp.StatusCode)
this.writer.statusCode = resp.StatusCode
// 将响应写回客户端
err = resp.Write(clientConn)
if err != nil {
if resp.Body != nil {
_ = resp.Body.Close()
}
_ = clientConn.Close()
_ = originConn.Close()
return
}
// 剩余已经从源站读取的内容
var headerBytes = respReader.buf
var headerIndex = bytes.Index(headerBytes, []byte{'\r', '\n', '\r', '\n'}) // CRLF
if headerIndex > 0 {
var leftBytes = headerBytes[headerIndex+4:]
if len(leftBytes) > 0 {
_, err = clientConn.Write(leftBytes)
if err != nil {
if resp.Body != nil {
_ = resp.Body.Close()
}
_ = clientConn.Close()
_ = originConn.Close()
return
}
}
}
if resp.Body != nil {
_ = resp.Body.Close()
}
// 复制剩余的数据
var buf = utils.BytePool4k.Get() var buf = utils.BytePool4k.Get()
defer utils.BytePool4k.Put(buf) defer utils.BytePool4k.Put(buf)
for { for {
@@ -98,4 +195,6 @@ func (this *HTTPRequest) doWebsocket(requestHost string) {
_ = originConn.Close() _ = originConn.Close()
}() }()
_, _ = io.Copy(originConn, clientConn) _, _ = io.Copy(originConn, clientConn)
return
} }

View File

@@ -132,7 +132,7 @@ func (this *HTTPWriter) Prepare(resp *http.Response, size int64, status int, ena
this.req.web.RequestLimit != nil && this.req.web.RequestLimit != nil &&
this.req.web.RequestLimit.IsOn && this.req.web.RequestLimit.IsOn &&
this.req.web.RequestLimit.OutBandwidthPerConnBytes() > 0 { this.req.web.RequestLimit.OutBandwidthPerConnBytes() > 0 {
this.writer = writers.NewRateLimitWriter(this.writer, this.req.web.RequestLimit.OutBandwidthPerConnBytes()) this.writer = writers.NewRateLimitWriter(this.req.RawReq.Context(), this.writer, this.req.web.RequestLimit.OutBandwidthPerConnBytes())
} }
return return
@@ -303,7 +303,19 @@ func (this *HTTPWriter) PrepareCache(resp *http.Response, size int64) {
if this.isPartial { if this.isPartial {
cacheKey += caches.SuffixPartial cacheKey += caches.SuffixPartial
} }
cacheWriter, err := storage.OpenWriter(cacheKey, expiresAt, this.StatusCode(), size, cacheRef.MaxSizeBytes(), this.isPartial)
// 待写入尺寸
var totalSize = size
if totalSize < 0 && this.isPartial {
var contentRange = resp.Header.Get("Content-Range")
if len(contentRange) > 0 {
_, partialTotalSize := httpRequestParseContentRangeHeader(contentRange)
if partialTotalSize > 0 {
totalSize = partialTotalSize
}
}
}
cacheWriter, err := storage.OpenWriter(cacheKey, expiresAt, this.StatusCode(), this.calculateHeaderLength(), totalSize, cacheRef.MaxSizeBytes(), this.isPartial)
if err != nil { if err != nil {
if err == caches.ErrEntityTooLarge && addStatusHeader { if err == caches.ErrEntityTooLarge && addStatusHeader {
this.Header().Set("X-Cache", "BYPASS, entity too large") this.Header().Set("X-Cache", "BYPASS, entity too large")
@@ -324,13 +336,19 @@ func (this *HTTPWriter) PrepareCache(resp *http.Response, size int64) {
} }
// 写入Header // 写入Header
var headerBuf = utils.SharedBufferPool.Get()
for k, v := range this.Header() { for k, v := range this.Header() {
if k == "Set-Cookie" || (this.isPartial && k == "Content-Range") {
continue
}
for _, v1 := range v { for _, v1 := range v {
if this.isPartial && k == "Content-Type" && strings.Contains(v1, "multipart/byteranges") { if this.isPartial && k == "Content-Type" && strings.Contains(v1, "multipart/byteranges") {
continue continue
} }
_, err = cacheWriter.WriteHeader([]byte(k + ":" + v1 + "\n")) _, err = headerBuf.Write([]byte(k + ":" + v1 + "\n"))
if err != nil { if err != nil {
utils.SharedBufferPool.Put(headerBuf)
remotelogs.Error("HTTP_WRITER", "write cache failed: "+err.Error()) remotelogs.Error("HTTP_WRITER", "write cache failed: "+err.Error())
_ = this.cacheWriter.Discard() _ = this.cacheWriter.Discard()
this.cacheWriter = nil this.cacheWriter = nil
@@ -338,6 +356,14 @@ func (this *HTTPWriter) PrepareCache(resp *http.Response, size int64) {
} }
} }
} }
_, err = cacheWriter.WriteHeader(headerBuf.Bytes())
utils.SharedBufferPool.Put(headerBuf)
if err != nil {
remotelogs.Error("HTTP_WRITER", "write cache failed: "+err.Error())
_ = this.cacheWriter.Discard()
this.cacheWriter = nil
return
}
if this.isPartial { if this.isPartial {
// content-range // content-range
@@ -558,6 +584,11 @@ func (this *HTTPWriter) PrepareCompression(resp *http.Response, size int64) {
return return
} }
// 分区内容不压缩,防止读取失败
if !this.compressionConfig.EnablePartialContent && this.StatusCode() == http.StatusPartialContent {
return
}
if this.compressionConfig.Level <= 0 { if this.compressionConfig.Level <= 0 {
return return
} }
@@ -627,16 +658,21 @@ func (this *HTTPWriter) PrepareCompression(resp *http.Response, size int64) {
cacheKey += this.cacheReaderSuffix cacheKey += this.cacheReaderSuffix
} }
compressionCacheWriter, err := this.cacheStorage.OpenWriter(cacheKey+caches.SuffixCompression+compressionEncoding, expiredAt, this.StatusCode(), -1, cacheRef.MaxSizeBytes(), false) compressionCacheWriter, err := this.cacheStorage.OpenWriter(cacheKey+caches.SuffixCompression+compressionEncoding, expiredAt, this.StatusCode(), this.calculateHeaderLength(), -1, cacheRef.MaxSizeBytes(), false)
if err != nil { if err != nil {
return return
} }
// 写入Header // 写入Header
var headerBuffer = utils.SharedBufferPool.Get()
for k, v := range this.Header() { for k, v := range this.Header() {
if k == "Set-Cookie" || (this.isPartial && k == "Content-Range") {
continue
}
for _, v1 := range v { for _, v1 := range v {
_, err = compressionCacheWriter.WriteHeader([]byte(k + ":" + v1 + "\n")) _, err = headerBuffer.Write([]byte(k + ":" + v1 + "\n"))
if err != nil { if err != nil {
utils.SharedBufferPool.Put(headerBuffer)
remotelogs.Error("HTTP_WRITER", "write compression cache failed: "+err.Error()) remotelogs.Error("HTTP_WRITER", "write compression cache failed: "+err.Error())
_ = compressionCacheWriter.Discard() _ = compressionCacheWriter.Discard()
compressionCacheWriter = nil compressionCacheWriter = nil
@@ -645,6 +681,15 @@ func (this *HTTPWriter) PrepareCompression(resp *http.Response, size int64) {
} }
} }
_, err = compressionCacheWriter.WriteHeader(headerBuffer.Bytes())
utils.SharedBufferPool.Put(headerBuffer)
if err != nil {
remotelogs.Error("HTTP_WRITER", "write compression cache failed: "+err.Error())
_ = compressionCacheWriter.Discard()
compressionCacheWriter = nil
return
}
if compressionCacheWriter != nil { if compressionCacheWriter != nil {
this.compressionCacheWriter = compressionCacheWriter this.compressionCacheWriter = compressionCacheWriter
var teeWriter = writers.NewTeeWriterCloser(this.writer, compressionCacheWriter) var teeWriter = writers.NewTeeWriterCloser(this.writer, compressionCacheWriter)
@@ -942,10 +987,14 @@ func (this *HTTPWriter) finishWebP() {
expiredAt = this.cacheWriter.ExpiredAt() expiredAt = this.cacheWriter.ExpiredAt()
} }
webpCacheWriter, _ = this.cacheStorage.OpenWriter(cacheKey, expiredAt, this.StatusCode(), -1, -1, false) webpCacheWriter, _ = this.cacheStorage.OpenWriter(cacheKey, expiredAt, this.StatusCode(), -1, -1, -1, false)
if webpCacheWriter != nil { if webpCacheWriter != nil {
// 写入Header // 写入Header
for k, v := range this.Header() { for k, v := range this.Header() {
if k == "Set-Cookie" {
continue
}
// 这里是原始的数据,不需要内容编码 // 这里是原始的数据,不需要内容编码
if k == "Content-Encoding" || k == "Transfer-Encoding" { if k == "Content-Encoding" || k == "Transfer-Encoding" {
continue continue
@@ -1157,3 +1206,16 @@ func (this *HTTPWriter) finishRequest() {
_ = this.rawReader.Close() _ = this.rawReader.Close()
} }
} }
// 计算Header长度
func (this *HTTPWriter) calculateHeaderLength() (result int) {
for k, v := range this.Header() {
if k == "Set-Cookie" || (this.isPartial && k == "Content-Range") {
continue
}
for _, v1 := range v {
result += len(k) + 1 /**:**/ + len(v1) + 1 /**\n**/
}
}
return
}

View File

@@ -7,14 +7,16 @@ import (
"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"
"golang.org/x/net/ipv4"
"golang.org/x/net/ipv6"
"net" "net"
"strings"
"sync" "sync"
) )
type Listener struct { type Listener struct {
group *serverconfigs.ServerAddressGroup group *serverconfigs.ServerAddressGroup
isListening bool listener ListenerInterface // 监听器
listener ListenerInterface // 监听器
locker sync.RWMutex locker sync.RWMutex
} }
@@ -118,18 +120,64 @@ func (this *Listener) listenTCP() error {
} }
func (this *Listener) listenUDP() error { func (this *Listener) listenUDP() error {
listener, err := this.createUDPListener() var addr = this.group.Addr()
var ipv4PacketListener *ipv4.PacketConn
var ipv6PacketListener *ipv6.PacketConn
host, _, err := net.SplitHostPort(addr)
if err != nil { if err != nil {
return err return err
} }
if len(host) == 0 {
// ipv4
ipv4Listener, err := this.createUDPIPv4Listener()
if err == nil {
ipv4PacketListener = ipv4.NewPacketConn(ipv4Listener)
} else {
remotelogs.Error("LISTENER", "create udp ipv4 listener '"+addr+"': "+err.Error())
}
// ipv6
ipv6Listener, err := this.createUDPIPv6Listener()
if err == nil {
ipv6PacketListener = ipv6.NewPacketConn(ipv6Listener)
} else {
remotelogs.Error("LISTENER", "create udp ipv6 listener '"+addr+"': "+err.Error())
}
} else if strings.Contains(host, ":") { // ipv6
ipv6Listener, err := this.createUDPIPv6Listener()
if err == nil {
ipv6PacketListener = ipv6.NewPacketConn(ipv6Listener)
} else {
remotelogs.Error("LISTENER", "create udp ipv6 listener '"+addr+"': "+err.Error())
}
} else { // ipv4
ipv4Listener, err := this.createUDPIPv4Listener()
if err == nil {
ipv4PacketListener = ipv4.NewPacketConn(ipv4Listener)
} else {
remotelogs.Error("LISTENER", "create udp ipv4 listener '"+addr+"': "+err.Error())
}
}
events.OnKey(events.EventQuit, this, func() { events.OnKey(events.EventQuit, this, func() {
remotelogs.Println("LISTENER", "quit "+this.group.FullAddr()) remotelogs.Println("LISTENER", "quit "+this.group.FullAddr())
_ = listener.Close()
if ipv4PacketListener != nil {
_ = ipv4PacketListener.Close()
}
if ipv6PacketListener != nil {
_ = ipv6PacketListener.Close()
}
}) })
this.listener = &UDPListener{ this.listener = &UDPListener{
BaseListener: BaseListener{Group: this.group}, BaseListener: BaseListener{Group: this.group},
Listener: listener, IPv4Listener: ipv4PacketListener,
IPv6Listener: ipv6PacketListener,
} }
goman.New(func() { goman.New(func() {
@@ -168,12 +216,20 @@ func (this *Listener) createTCPListener() (net.Listener, error) {
return listenConfig.Listen(context.Background(), "tcp", this.group.Addr()) return listenConfig.Listen(context.Background(), "tcp", this.group.Addr())
} }
// 创建UDP监听器 // 创建UDP IPv4监听器
func (this *Listener) createUDPListener() (*net.UDPConn, error) { func (this *Listener) createUDPIPv4Listener() (*net.UDPConn, error) {
// TODO 将来支持udp4/udp6
addr, err := net.ResolveUDPAddr("udp", this.group.Addr()) addr, err := net.ResolveUDPAddr("udp", this.group.Addr())
if err != nil { if err != nil {
return nil, err return nil, err
} }
return net.ListenUDP("udp", addr) return net.ListenUDP("udp4", addr)
}
// 创建UDP监听器
func (this *Listener) createUDPIPv6Listener() (*net.UDPConn, error) {
addr, err := net.ResolveUDPAddr("udp", this.group.Addr())
if err != nil {
return nil, err
}
return net.ListenUDP("udp6", addr)
} }

View File

@@ -3,11 +3,12 @@ package nodes
import ( import (
"crypto/tls" "crypto/tls"
"errors" "errors"
"github.com/TeaOSLab/EdgeCommon/pkg/configutils"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs" "github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/sslconfigs" "github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/sslconfigs"
"github.com/TeaOSLab/EdgeNode/internal/remotelogs" "github.com/TeaOSLab/EdgeNode/internal/remotelogs"
"github.com/iwind/TeaGo/lists"
"github.com/iwind/TeaGo/types" "github.com/iwind/TeaGo/types"
"net"
) )
type BaseListener struct { type BaseListener struct {
@@ -35,7 +36,7 @@ func (this *BaseListener) buildTLSConfig() *tls.Config {
return &tls.Config{ return &tls.Config{
Certificates: nil, Certificates: nil,
GetConfigForClient: func(clientInfo *tls.ClientHelloInfo) (config *tls.Config, e error) { GetConfigForClient: func(clientInfo *tls.ClientHelloInfo) (config *tls.Config, e error) {
tlsPolicy, _, err := this.matchSSL(clientInfo.ServerName) tlsPolicy, _, err := this.matchSSL(this.helloServerName(clientInfo))
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -49,7 +50,7 @@ func (this *BaseListener) buildTLSConfig() *tls.Config {
return tlsPolicy.TLSConfig(), nil return tlsPolicy.TLSConfig(), nil
}, },
GetCertificate: func(clientInfo *tls.ClientHelloInfo) (certificate *tls.Certificate, e error) { GetCertificate: func(clientInfo *tls.ClientHelloInfo) (certificate *tls.Certificate, e error) {
tlsPolicy, cert, err := this.matchSSL(clientInfo.ServerName) tlsPolicy, cert, err := this.matchSSL(this.helloServerName(clientInfo))
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -75,7 +76,7 @@ func (this *BaseListener) matchSSL(domain string) (*sslconfigs.SSLPolicy, *tls.C
// 如果域名为空,则取第一个 // 如果域名为空,则取第一个
// 通常域名为空是因为是直接通过IP访问的 // 通常域名为空是因为是直接通过IP访问的
if len(domain) == 0 { if len(domain) == 0 {
if group.IsHTTPS() && sharedNodeConfig.GlobalConfig != nil && sharedNodeConfig.GlobalConfig.HTTPAll.MatchDomainStrictly { if group.IsHTTPS() && sharedNodeConfig.GlobalServerConfig != nil && sharedNodeConfig.GlobalServerConfig.HTTPAll.MatchDomainStrictly {
return nil, nil, errors.New("no tls server name matched") return nil, nil, errors.New("no tls server name matched")
} }
@@ -131,19 +132,19 @@ func (this *BaseListener) findNamedServer(name string) (serverConfig *serverconf
return return
} }
var matchDomainStrictly = sharedNodeConfig.GlobalConfig != nil && sharedNodeConfig.GlobalConfig.HTTPAll.MatchDomainStrictly var matchDomainStrictly = sharedNodeConfig.GlobalServerConfig != nil && sharedNodeConfig.GlobalServerConfig.HTTPAll.MatchDomainStrictly
if sharedNodeConfig.GlobalConfig != nil && if sharedNodeConfig.GlobalServerConfig != nil &&
len(sharedNodeConfig.GlobalConfig.HTTPAll.DefaultDomain) > 0 && len(sharedNodeConfig.GlobalServerConfig.HTTPAll.DefaultDomain) > 0 &&
(!matchDomainStrictly || lists.ContainsString(sharedNodeConfig.GlobalConfig.HTTPAll.AllowMismatchDomains, name)) { (!matchDomainStrictly || configutils.MatchDomains(sharedNodeConfig.GlobalServerConfig.HTTPAll.AllowMismatchDomains, name) || (sharedNodeConfig.GlobalServerConfig.HTTPAll.AllowNodeIP && net.ParseIP(name) != nil)) {
defaultDomain := sharedNodeConfig.GlobalConfig.HTTPAll.DefaultDomain var defaultDomain = sharedNodeConfig.GlobalServerConfig.HTTPAll.DefaultDomain
serverConfig, serverName = this.findNamedServerMatched(defaultDomain) serverConfig, serverName = this.findNamedServerMatched(defaultDomain)
if serverConfig != nil { if serverConfig != nil {
return return
} }
} }
if matchDomainStrictly && !lists.ContainsString(sharedNodeConfig.GlobalConfig.HTTPAll.AllowMismatchDomains, name) { if matchDomainStrictly && !configutils.MatchDomains(sharedNodeConfig.GlobalServerConfig.HTTPAll.AllowMismatchDomains, name) && (!sharedNodeConfig.GlobalServerConfig.HTTPAll.AllowNodeIP || net.ParseIP(name) == nil) {
return return
} }
@@ -159,7 +160,7 @@ func (this *BaseListener) findNamedServer(name string) (serverConfig *serverconf
// 严格查找域名 // 严格查找域名
func (this *BaseListener) findNamedServerMatched(name string) (serverConfig *serverconfigs.ServerConfig, serverName string) { func (this *BaseListener) findNamedServerMatched(name string) (serverConfig *serverconfigs.ServerConfig, serverName string) {
group := this.Group var group = this.Group
if group == nil { if group == nil {
return nil, "" return nil, ""
} }
@@ -170,7 +171,7 @@ func (this *BaseListener) findNamedServerMatched(name string) (serverConfig *ser
} }
// 是否严格匹配域名 // 是否严格匹配域名
matchDomainStrictly := sharedNodeConfig.GlobalConfig != nil && sharedNodeConfig.GlobalConfig.HTTPAll.MatchDomainStrictly var matchDomainStrictly = sharedNodeConfig.GlobalServerConfig != nil && sharedNodeConfig.GlobalServerConfig.HTTPAll.MatchDomainStrictly
// 如果只有一个server则默认为这个 // 如果只有一个server则默认为这个
var currentServers = group.Servers() var currentServers = group.Servers()
@@ -182,22 +183,17 @@ func (this *BaseListener) findNamedServerMatched(name string) (serverConfig *ser
return nil, name return nil, name
} }
// 使用CNAME来查找服务 // 从Hello信息中获取服务名称
// TODO 防止单IP随机生成域名攻击 func (this *BaseListener) helloServerName(clientInfo *tls.ClientHelloInfo) string {
func (this *BaseListener) findServerWithCNAME(domain string) *serverconfigs.ServerConfig { var serverName = clientInfo.ServerName
if !sharedNodeConfig.SupportCNAME { if len(serverName) == 0 {
return nil var localAddr = clientInfo.Conn.LocalAddr()
if localAddr != nil {
tcpAddr, ok := localAddr.(*net.TCPAddr)
if ok {
serverName = tcpAddr.IP.String()
}
}
} }
return serverName
var realName = sharedCNAMEManager.Lookup(domain)
if len(realName) == 0 {
return nil
}
group := this.Group
if group == nil {
return nil
}
return group.MatchServerCNAME(realName)
} }

View File

@@ -14,7 +14,7 @@ func TestBaseListener_FindServer(t *testing.T) {
sharedNodeConfig = &nodeconfigs.NodeConfig{} sharedNodeConfig = &nodeconfigs.NodeConfig{}
var listener = &BaseListener{} var listener = &BaseListener{}
listener.Group = &serverconfigs.ServerAddressGroup{} listener.Group = serverconfigs.NewServerAddressGroup("https://*:443")
for i := 0; i < 1_000_000; i++ { for i := 0; i < 1_000_000; i++ {
var server = &serverconfigs.ServerConfig{ var server = &serverconfigs.ServerConfig{
IsOn: true, IsOn: true,

View File

@@ -18,6 +18,8 @@ import (
var httpErrorLogger = log.New(io.Discard, "", 0) var httpErrorLogger = log.New(io.Discard, "", 0)
const HTTPIdleTimeout = 75 * time.Second
type contextKey struct { type contextKey struct {
key string key string
} }
@@ -43,16 +45,12 @@ func (this *HTTPListener) Serve() error {
this.httpServer = &http.Server{ this.httpServer = &http.Server{
Addr: this.addr, Addr: this.addr,
Handler: this, Handler: this,
ReadTimeout: 1 * time.Hour, // TODO 改成可以配置 ReadHeaderTimeout: 3 * time.Second, // TODO 改成可以配置
ReadHeaderTimeout: 3 * time.Second, // TODO 改成可以配置 IdleTimeout: HTTPIdleTimeout, // TODO 改成可以配置
WriteTimeout: 1 * time.Hour, // TODO 改成可以配置
IdleTimeout: 75 * time.Second, // TODO 改成可以配置
ConnState: func(conn net.Conn, state http.ConnState) { ConnState: func(conn net.Conn, state http.ConnState) {
switch state { switch state {
case http.StateNew: case http.StateNew:
atomic.AddInt64(&this.countActiveConnections, 1) atomic.AddInt64(&this.countActiveConnections, 1)
case http.StateActive, http.StateIdle, http.StateHijacked:
// Nothing to do
case http.StateClosed: case http.StateClosed:
atomic.AddInt64(&this.countActiveConnections, -1) atomic.AddInt64(&this.countActiveConnections, -1)
} }
@@ -116,8 +114,14 @@ func (this *HTTPListener) Reload(group *serverconfigs.ServerAddressGroup) {
// ServerHTTP 处理HTTP请求 // ServerHTTP 处理HTTP请求
func (this *HTTPListener) ServeHTTP(rawWriter http.ResponseWriter, rawReq *http.Request) { func (this *HTTPListener) ServeHTTP(rawWriter http.ResponseWriter, rawReq *http.Request) {
// 不支持Connect
if rawReq.Method == http.MethodConnect {
http.Error(rawWriter, "Method Not Allowed", http.StatusMethodNotAllowed)
return
}
// 域名 // 域名
var reqHost = rawReq.Host var reqHost = strings.ToLower(strings.TrimRight(rawReq.Host, "."))
// TLS域名 // TLS域名
if this.isIP(reqHost) { if this.isIP(reqHost) {
@@ -175,6 +179,15 @@ func (this *HTTPListener) ServeHTTP(rawWriter http.ResponseWriter, rawReq *http.
} }
} }
// 检查用户
if server != nil && server.UserId > 0 {
if !SharedUserManager.CheckUserServersIsEnabled(server.UserId) {
rawWriter.WriteHeader(http.StatusNotFound)
_, _ = rawWriter.Write([]byte("The site owner is unavailable."))
return
}
}
// 包装新请求对象 // 包装新请求对象
var req = &HTTPRequest{ var req = &HTTPRequest{
RawReq: rawReq, RawReq: rawReq,

View File

@@ -1,7 +1,6 @@
package nodes package nodes
import ( import (
"bytes"
"errors" "errors"
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs" "github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs" "github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
@@ -9,6 +8,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/utils" "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/lists" "github.com/iwind/TeaGo/lists"
"github.com/iwind/TeaGo/maps" "github.com/iwind/TeaGo/maps"
@@ -213,15 +213,14 @@ func (this *ListenerManager) findProcessNameWithPort(isUdp bool, port string) st
option = "u" option = "u"
} }
var cmd = exec.Command(path, "-"+option+"lpn", "sport = :"+port) var cmd = executils.NewTimeoutCmd(10*time.Second, path, "-"+option+"lpn", "sport = :"+port)
var output = &bytes.Buffer{} cmd.WithStdout()
cmd.Stdout = output
err = cmd.Run() err = cmd.Run()
if err != nil { if err != nil {
return "" return ""
} }
var matches = regexp.MustCompile(`(?U)\(\("(.+)",pid=\d+,fd=\d+\)\)`).FindStringSubmatch(output.String()) var matches = regexp.MustCompile(`(?U)\(\("(.+)",pid=\d+,fd=\d+\)\)`).FindStringSubmatch(cmd.Stdout())
if len(matches) > 1 { if len(matches) > 1 {
return matches[1] return matches[1]
} }

View File

@@ -92,7 +92,7 @@ func (this *TCPListener) handleConn(conn net.Conn) error {
} }
// 是否已达到流量限制 // 是否已达到流量限制
if this.reachedTrafficLimit() { if this.reachedTrafficLimit() || (server.UserId > 0 && !SharedUserManager.CheckUserServersIsEnabled(server.UserId)) {
// 关闭连接 // 关闭连接
tcpConn, ok := conn.(LingerConn) tcpConn, ok := conn.(LingerConn)
if ok { if ok {

View File

@@ -9,6 +9,8 @@ import (
"github.com/TeaOSLab/EdgeNode/internal/utils" "github.com/TeaOSLab/EdgeNode/internal/utils"
"github.com/iwind/TeaGo/types" "github.com/iwind/TeaGo/types"
"github.com/pires/go-proxyproto" "github.com/pires/go-proxyproto"
"golang.org/x/net/ipv4"
"golang.org/x/net/ipv6"
"net" "net"
"strings" "strings"
"sync" "sync"
@@ -19,10 +21,57 @@ const (
UDPConnLifeSeconds = 30 UDPConnLifeSeconds = 30
) )
type UDPPacketListener interface {
ReadFrom(b []byte) (n int, cm any, src net.Addr, err error)
WriteTo(b []byte, cm any, dst net.Addr) (n int, err error)
LocalAddr() net.Addr
}
type UDPIPv4Listener struct {
rawListener *ipv4.PacketConn
}
func NewUDPIPv4Listener(rawListener *ipv4.PacketConn) *UDPIPv4Listener {
return &UDPIPv4Listener{rawListener: rawListener}
}
func (this *UDPIPv4Listener) ReadFrom(b []byte) (n int, cm any, src net.Addr, err error) {
return this.rawListener.ReadFrom(b)
}
func (this *UDPIPv4Listener) WriteTo(b []byte, cm any, dst net.Addr) (n int, err error) {
return this.rawListener.WriteTo(b, cm.(*ipv4.ControlMessage), dst)
}
func (this *UDPIPv4Listener) LocalAddr() net.Addr {
return this.rawListener.LocalAddr()
}
type UDPIPv6Listener struct {
rawListener *ipv6.PacketConn
}
func NewUDPIPv6Listener(rawListener *ipv6.PacketConn) *UDPIPv6Listener {
return &UDPIPv6Listener{rawListener: rawListener}
}
func (this *UDPIPv6Listener) ReadFrom(b []byte) (n int, cm any, src net.Addr, err error) {
return this.rawListener.ReadFrom(b)
}
func (this *UDPIPv6Listener) WriteTo(b []byte, cm any, dst net.Addr) (n int, err error) {
return this.rawListener.WriteTo(b, cm.(*ipv6.ControlMessage), dst)
}
func (this *UDPIPv6Listener) LocalAddr() net.Addr {
return this.rawListener.LocalAddr()
}
type UDPListener struct { type UDPListener struct {
BaseListener BaseListener
Listener *net.UDPConn IPv4Listener *ipv4.PacketConn
IPv6Listener *ipv6.PacketConn
connMap map[string]*UDPConn connMap map[string]*UDPConn
connLocker sync.Mutex connLocker sync.Mutex
@@ -36,6 +85,60 @@ type UDPListener struct {
} }
func (this *UDPListener) Serve() error { func (this *UDPListener) Serve() error {
if this.Group == nil {
return nil
}
var server = this.Group.FirstServer()
if server == nil {
return nil
}
var serverId = server.Id
var wg = &sync.WaitGroup{}
wg.Add(2) // 2 = ipv4 + ipv6
go func() {
defer wg.Done()
if this.IPv4Listener != nil {
err := this.IPv4Listener.SetControlMessage(ipv4.FlagDst, true)
if err != nil {
remotelogs.ServerError(serverId, "UDP_LISTENER", "can not serve ipv4 listener: "+err.Error(), "", nil)
return
}
err = this.servePacketListener(NewUDPIPv4Listener(this.IPv4Listener))
if err != nil {
remotelogs.ServerError(serverId, "UDP_LISTENER", "can not serve ipv4 listener: "+err.Error(), "", nil)
return
}
}
}()
go func() {
defer wg.Done()
if this.IPv6Listener != nil {
err := this.IPv6Listener.SetControlMessage(ipv6.FlagDst, true)
if err != nil {
remotelogs.ServerError(serverId, "UDP_LISTENER", "can not serve ipv6 listener: "+err.Error(), "", nil)
return
}
err = this.servePacketListener(NewUDPIPv6Listener(this.IPv6Listener))
if err != nil {
remotelogs.ServerError(serverId, "UDP_LISTENER", "can not serve ipv6 listener: "+err.Error(), "", nil)
return
}
}
}()
wg.Wait()
return nil
}
func (this *UDPListener) servePacketListener(listener UDPPacketListener) error {
// 获取分组端口 // 获取分组端口
var groupAddr = this.Group.Addr() var groupAddr = this.Group.Addr()
var portIndex = strings.LastIndex(groupAddr, ":") var portIndex = strings.LastIndex(groupAddr, ":")
@@ -67,7 +170,12 @@ func (this *UDPListener) Serve() error {
return nil return nil
} }
n, addr, err := this.Listener.ReadFrom(buffer) // 检查用户状态
if firstServer.UserId > 0 && !SharedUserManager.CheckUserServersIsEnabled(firstServer.UserId) {
return nil
}
n, cm, clientAddr, err := listener.ReadFrom(buffer)
if err != nil { if err != nil {
if this.isClosed { if this.isClosed {
return nil return nil
@@ -77,14 +185,14 @@ func (this *UDPListener) Serve() error {
if n > 0 { if n > 0 {
this.connLocker.Lock() this.connLocker.Lock()
conn, ok := this.connMap[addr.String()] conn, ok := this.connMap[clientAddr.String()]
this.connLocker.Unlock() this.connLocker.Unlock()
if ok && !conn.IsOk() { if ok && !conn.IsOk() {
_ = conn.Close() _ = conn.Close()
ok = false ok = false
} }
if !ok { if !ok {
originConn, err := this.connectOrigin(firstServer.Id, this.reverseProxy, addr) originConn, err := this.connectOrigin(firstServer.Id, this.reverseProxy, listener.LocalAddr(), clientAddr)
if err != nil { if err != nil {
remotelogs.Error("UDP_LISTENER", "unable to connect to origin server: "+err.Error()) remotelogs.Error("UDP_LISTENER", "unable to connect to origin server: "+err.Error())
continue continue
@@ -93,9 +201,9 @@ func (this *UDPListener) Serve() error {
remotelogs.Error("UDP_LISTENER", "unable to find a origin server") remotelogs.Error("UDP_LISTENER", "unable to find a origin server")
continue continue
} }
conn = NewUDPConn(firstServer, addr, this.Listener, originConn.(*net.UDPConn)) conn = NewUDPConn(firstServer, clientAddr, listener, cm, originConn.(*net.UDPConn))
this.connLocker.Lock() this.connLocker.Lock()
this.connMap[addr.String()] = conn this.connMap[clientAddr.String()] = conn
this.connLocker.Unlock() this.connLocker.Unlock()
} }
_, _ = conn.Write(buffer[:n]) _, _ = conn.Write(buffer[:n])
@@ -117,7 +225,26 @@ func (this *UDPListener) Close() error {
} }
this.connLocker.Unlock() this.connLocker.Unlock()
return this.Listener.Close() var errorStrings = []string{}
if this.IPv4Listener != nil {
err := this.IPv4Listener.Close()
if err != nil {
errorStrings = append(errorStrings, err.Error())
}
}
if this.IPv6Listener != nil {
err := this.IPv6Listener.Close()
if err != nil {
errorStrings = append(errorStrings, err.Error())
}
}
if len(errorStrings) > 0 {
return errors.New(errorStrings[0])
}
return nil
} }
func (this *UDPListener) Reload(group *serverconfigs.ServerAddressGroup) { func (this *UDPListener) Reload(group *serverconfigs.ServerAddressGroup) {
@@ -132,7 +259,7 @@ func (this *UDPListener) Reload(group *serverconfigs.ServerAddressGroup) {
this.reverseProxy = firstServer.ReverseProxy this.reverseProxy = firstServer.ReverseProxy
} }
func (this *UDPListener) connectOrigin(serverId int64, reverseProxy *serverconfigs.ReverseProxyConfig, remoteAddr net.Addr) (conn net.Conn, err error) { func (this *UDPListener) connectOrigin(serverId int64, reverseProxy *serverconfigs.ReverseProxyConfig, localAddr net.Addr, remoteAddr net.Addr) (conn net.Conn, err error) {
if reverseProxy == nil { if reverseProxy == nil {
return nil, errors.New("no reverse proxy config") return nil, errors.New("no reverse proxy config")
} }
@@ -181,12 +308,12 @@ func (this *UDPListener) connectOrigin(serverId int64, reverseProxy *serverconfi
if strings.Contains(remoteAddr.String(), "[") { if strings.Contains(remoteAddr.String(), "[") {
transportProtocol = proxyproto.UDPv6 transportProtocol = proxyproto.UDPv6
} }
header := proxyproto.Header{ var header = proxyproto.Header{
Version: byte(reverseProxy.ProxyProtocol.Version), Version: byte(reverseProxy.ProxyProtocol.Version),
Command: proxyproto.PROXY, Command: proxyproto.PROXY,
TransportProtocol: transportProtocol, TransportProtocol: transportProtocol,
SourceAddr: remoteAddr, SourceAddr: remoteAddr,
DestinationAddr: this.Listener.LocalAddr(), DestinationAddr: localAddr,
} }
_, err = header.WriteTo(conn) _, err = header.WriteTo(conn)
if err != nil { if err != nil {
@@ -224,21 +351,21 @@ func (this *UDPListener) gcConns() {
// UDPConn 自定义的UDP连接管理 // UDPConn 自定义的UDP连接管理
type UDPConn struct { type UDPConn struct {
addr net.Addr addr net.Addr
proxyConn net.Conn proxyListener UDPPacketListener
serverConn net.Conn serverConn net.Conn
activatedAt int64 activatedAt int64
isOk bool isOk bool
isClosed bool isClosed bool
} }
func NewUDPConn(server *serverconfigs.ServerConfig, addr net.Addr, proxyConn *net.UDPConn, serverConn *net.UDPConn) *UDPConn { func NewUDPConn(server *serverconfigs.ServerConfig, addr net.Addr, proxyListener UDPPacketListener, cm any, serverConn *net.UDPConn) *UDPConn {
var conn = &UDPConn{ var conn = &UDPConn{
addr: addr, addr: addr,
proxyConn: proxyConn, proxyListener: proxyListener,
serverConn: serverConn, serverConn: serverConn,
activatedAt: time.Now().Unix(), activatedAt: time.Now().Unix(),
isOk: true, isOk: true,
} }
// 统计 // 统计
@@ -246,6 +373,14 @@ func NewUDPConn(server *serverconfigs.ServerConfig, addr net.Addr, proxyConn *ne
stats.SharedTrafficStatManager.Add(server.Id, "", 0, 0, 1, 0, 0, 0, server.ShouldCheckTrafficLimit(), server.PlanId()) stats.SharedTrafficStatManager.Add(server.Id, "", 0, 0, 1, 0, 0, 0, server.ShouldCheckTrafficLimit(), server.PlanId())
} }
// 处理ControlMessage
switch controlMessage := cm.(type) {
case *ipv4.ControlMessage:
controlMessage.Src = controlMessage.Dst
case *ipv6.ControlMessage:
controlMessage.Src = controlMessage.Dst
}
goman.New(func() { goman.New(func() {
var buffer = utils.BytePool4k.Get() var buffer = utils.BytePool4k.Get()
defer func() { defer func() {
@@ -256,7 +391,8 @@ func NewUDPConn(server *serverconfigs.ServerConfig, addr net.Addr, proxyConn *ne
n, err := serverConn.Read(buffer) n, err := serverConn.Read(buffer)
if n > 0 { if n > 0 {
conn.activatedAt = time.Now().Unix() conn.activatedAt = time.Now().Unix()
_, writingErr := proxyConn.WriteTo(buffer[:n], addr)
_, writingErr := proxyListener.WriteTo(buffer[:n], cm, addr)
if writingErr != nil { if writingErr != nil {
conn.isOk = false conn.isOk = false
break break

View File

@@ -4,11 +4,13 @@ import (
"bytes" "bytes"
"encoding/json" "encoding/json"
"errors" "errors"
"github.com/TeaOSLab/EdgeCommon/pkg/configutils"
iplib "github.com/TeaOSLab/EdgeCommon/pkg/iplibrary" iplib "github.com/TeaOSLab/EdgeCommon/pkg/iplibrary"
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs" "github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
"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"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/ddosconfigs" "github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/ddosconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs"
"github.com/TeaOSLab/EdgeNode/internal/caches" "github.com/TeaOSLab/EdgeNode/internal/caches"
"github.com/TeaOSLab/EdgeNode/internal/configs" "github.com/TeaOSLab/EdgeNode/internal/configs"
"github.com/TeaOSLab/EdgeNode/internal/conns" "github.com/TeaOSLab/EdgeNode/internal/conns"
@@ -23,12 +25,13 @@ import (
"github.com/TeaOSLab/EdgeNode/internal/stats" "github.com/TeaOSLab/EdgeNode/internal/stats"
"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/clock" _ "github.com/TeaOSLab/EdgeNode/internal/utils/agents" // 引入Agent管理器
_ "github.com/TeaOSLab/EdgeNode/internal/utils/clock" // 触发时钟更新
"github.com/TeaOSLab/EdgeNode/internal/utils/jsonutils"
"github.com/TeaOSLab/EdgeNode/internal/waf" "github.com/TeaOSLab/EdgeNode/internal/waf"
"github.com/andybalholm/brotli" "github.com/andybalholm/brotli"
"github.com/iwind/TeaGo/Tea" "github.com/iwind/TeaGo/Tea"
"github.com/iwind/TeaGo/lists" "github.com/iwind/TeaGo/lists"
"github.com/iwind/TeaGo/logs"
"github.com/iwind/TeaGo/maps" "github.com/iwind/TeaGo/maps"
"github.com/iwind/TeaGo/types" "github.com/iwind/TeaGo/types"
"github.com/iwind/gosock/pkg/gosock" "github.com/iwind/gosock/pkg/gosock"
@@ -37,6 +40,7 @@ import (
"os" "os"
"os/exec" "os/exec"
"os/signal" "os/signal"
"path/filepath"
"runtime" "runtime"
"runtime/debug" "runtime/debug"
"sort" "sort"
@@ -58,18 +62,27 @@ type Node struct {
sock *gosock.Sock sock *gosock.Sock
locker sync.Mutex locker sync.Mutex
maxCPU int32 oldMaxCPU int32
maxThreads int oldMaxThreads int
timezone string oldTimezone string
oldHTTPCachePolicies []*serverconfigs.HTTPCachePolicy
oldHTTPFirewallPolicies []*firewallconfigs.HTTPFirewallPolicy
oldFirewallActions []*firewallconfigs.FirewallActionConfig
oldMetricItems []*serverconfigs.MetricItemConfig
updatingServerMap map[int64]*serverconfigs.ServerConfig updatingServerMap map[int64]*serverconfigs.ServerConfig
lastAPINodeVersion int64
lastAPINodeAddrs []string // 以前的API节点地址
lastTaskVersion int64
} }
func NewNode() *Node { func NewNode() *Node {
return &Node{ return &Node{
sock: gosock.NewTmpSock(teaconst.ProcessName), sock: gosock.NewTmpSock(teaconst.ProcessName),
maxThreads: -1, oldMaxThreads: -1,
maxCPU: -1, oldMaxCPU: -1,
updatingServerMap: map[int64]*serverconfigs.ServerConfig{}, updatingServerMap: map[int64]*serverconfigs.ServerConfig{},
} }
} }
@@ -91,6 +104,10 @@ func (this *Node) Test() error {
// Start 启动 // Start 启动
func (this *Node) Start() { func (this *Node) Start() {
// 设置netdns
// 这个需要放在所有网络访问的最前面
_ = os.Setenv("GODEBUG", "netdns=go")
_, ok := os.LookupEnv("EdgeDaemon") _, ok := os.LookupEnv("EdgeDaemon")
if ok { if ok {
remotelogs.Println("NODE", "start from daemon") remotelogs.Println("NODE", "start from daemon")
@@ -166,11 +183,6 @@ func (this *Node) Start() {
NewNodeStatusExecutor().Listen() NewNodeStatusExecutor().Listen()
}) })
// 同步时间
goman.New(func() {
clock.Start()
})
// 读取配置 // 读取配置
nodeConfig, err := nodeconfigs.SharedNodeConfig() nodeConfig, err := nodeconfigs.SharedNodeConfig()
if err != nil { if err != nil {
@@ -190,7 +202,7 @@ func (this *Node) Start() {
} }
} }
sharedNodeConfig = nodeConfig sharedNodeConfig = nodeConfig
this.onReload(nodeConfig) this.onReload(nodeConfig, true)
// 发送事件 // 发送事件
events.Notify(events.EventLoaded) events.Notify(events.EventLoaded)
@@ -205,9 +217,7 @@ func (this *Node) Start() {
// 统计 // 统计
goman.New(func() { goman.New(func() {
stats.SharedTrafficStatManager.Start(func() *nodeconfigs.NodeConfig { stats.SharedTrafficStatManager.Start()
return sharedNodeConfig
})
}) })
goman.New(func() { goman.New(func() {
stats.SharedHTTPRequestStatManager.Start() stats.SharedHTTPRequestStatManager.Start()
@@ -313,8 +323,9 @@ func (this *Node) loop() error {
return errors.New("create rpc client failed: " + err.Error()) return errors.New("create rpc client failed: " + err.Error())
} }
var nodeCtx = rpcClient.Context() tasksResp, err := rpcClient.NodeTaskRPC.FindNodeTasks(rpcClient.Context(), &pb.FindNodeTasksRequest{
tasksResp, err := rpcClient.NodeTaskRPC.FindNodeTasks(nodeCtx, &pb.FindNodeTasksRequest{}) Version: this.lastTaskVersion,
})
if err != nil { if err != nil {
if rpc.IsConnError(err) && !Tea.IsTesting() { if rpc.IsConnError(err) && !Tea.IsTesting() {
return nil return nil
@@ -322,139 +333,181 @@ func (this *Node) loop() error {
return errors.New("read node tasks failed: " + err.Error()) return errors.New("read node tasks failed: " + err.Error())
} }
for _, task := range tasksResp.NodeTasks { for _, task := range tasksResp.NodeTasks {
switch task.Type { err := this.execTask(rpcClient, task)
case "ipItemChanged": if !this.finishTask(task.Id, task.Version, err) {
// 防止阻塞 // 防止失败的任务无法重试
select { break
case iplibrary.IPListUpdateNotify <- true:
default:
}
// 修改为已同步
_, err = rpcClient.NodeTaskRPC.ReportNodeTaskDone(nodeCtx, &pb.ReportNodeTaskDoneRequest{
NodeTaskId: task.Id,
IsOk: true,
Error: "",
})
if err != nil {
return err
}
case "configChanged":
if task.ServerId > 0 {
err = this.syncServerConfig(task.ServerId)
} else {
if !task.IsPrimary {
// 我们等等主节点配置准备完毕
time.Sleep(2 * time.Second)
}
err = this.syncConfig(task.Version)
}
if err != nil {
_, err = rpcClient.NodeTaskRPC.ReportNodeTaskDone(nodeCtx, &pb.ReportNodeTaskDoneRequest{
NodeTaskId: task.Id,
IsOk: false,
Error: err.Error(),
})
} else {
_, err = rpcClient.NodeTaskRPC.ReportNodeTaskDone(nodeCtx, &pb.ReportNodeTaskDoneRequest{
NodeTaskId: task.Id,
IsOk: true,
Error: "",
})
}
if err != nil {
return err
}
case "nodeVersionChanged":
if !sharedUpgradeManager.IsInstalling() {
goman.New(func() {
sharedUpgradeManager.Start()
})
}
case "scriptsChanged":
err = this.reloadCommonScripts()
if err != nil {
return errors.New("reload common scripts failed: " + err.Error())
}
// 修改为已同步
_, err = rpcClient.NodeTaskRPC.ReportNodeTaskDone(nodeCtx, &pb.ReportNodeTaskDoneRequest{
NodeTaskId: task.Id,
IsOk: true,
Error: "",
})
if err != nil {
return err
}
case "nodeLevelChanged":
levelInfoResp, err := rpcClient.NodeRPC.FindNodeLevelInfo(nodeCtx, &pb.FindNodeLevelInfoRequest{})
if err != nil {
return err
}
sharedNodeConfig.Level = levelInfoResp.Level
var parentNodes = map[int64][]*nodeconfigs.ParentNodeConfig{}
if len(levelInfoResp.ParentNodesMapJSON) > 0 {
err = json.Unmarshal(levelInfoResp.ParentNodesMapJSON, &parentNodes)
if err != nil {
return errors.New("decode level info failed: " + err.Error())
}
}
sharedNodeConfig.ParentNodes = parentNodes
// 修改为已同步
_, err = rpcClient.NodeTaskRPC.ReportNodeTaskDone(nodeCtx, &pb.ReportNodeTaskDoneRequest{
NodeTaskId: task.Id,
IsOk: true,
Error: "",
})
if err != nil {
return err
}
case "ddosProtectionChanged":
resp, err := rpcClient.NodeRPC.FindNodeDDoSProtection(nodeCtx, &pb.FindNodeDDoSProtectionRequest{})
if err != nil {
return err
}
if len(resp.DdosProtectionJSON) == 0 {
if sharedNodeConfig != nil {
sharedNodeConfig.DDoSProtection = nil
}
} else {
var ddosProtectionConfig = &ddosconfigs.ProtectionConfig{}
err = json.Unmarshal(resp.DdosProtectionJSON, ddosProtectionConfig)
if err != nil {
return errors.New("decode DDoS protection config failed: " + err.Error())
}
if sharedNodeConfig != nil {
sharedNodeConfig.DDoSProtection = ddosProtectionConfig
}
err = firewalls.SharedDDoSProtectionManager.Apply(ddosProtectionConfig)
if err != nil {
// 不阻塞
remotelogs.Error("NODE", "apply DDoS protection failed: "+err.Error())
}
}
// 修改为已同步
_, err = rpcClient.NodeTaskRPC.ReportNodeTaskDone(nodeCtx, &pb.ReportNodeTaskDoneRequest{
NodeTaskId: task.Id,
IsOk: true,
Error: "",
})
if err != nil {
return err
}
} }
} }
return nil return nil
} }
// 执行任务
func (this *Node) execTask(rpcClient *rpc.RPCClient, task *pb.NodeTask) error {
switch task.Type {
case "ipItemChanged":
// 防止阻塞
select {
case iplibrary.IPListUpdateNotify <- true:
default:
}
case "configChanged":
if task.ServerId > 0 {
return this.syncServerConfig(task.ServerId)
}
if !task.IsPrimary {
// 我们等等主节点配置准备完毕
time.Sleep(2 * time.Second)
}
return this.syncConfig(task.Version)
case "nodeVersionChanged":
if !sharedUpgradeManager.IsInstalling() {
goman.New(func() {
sharedUpgradeManager.Start()
})
}
case "scriptsChanged":
err := this.reloadCommonScripts()
if err != nil {
return errors.New("reload common scripts failed: " + err.Error())
}
case "nodeLevelChanged":
levelInfoResp, err := rpcClient.NodeRPC.FindNodeLevelInfo(rpcClient.Context(), &pb.FindNodeLevelInfoRequest{})
if err != nil {
return err
}
if sharedNodeConfig != nil {
sharedNodeConfig.Level = levelInfoResp.Level
}
var parentNodes = map[int64][]*nodeconfigs.ParentNodeConfig{}
if len(levelInfoResp.ParentNodesMapJSON) > 0 {
err = json.Unmarshal(levelInfoResp.ParentNodesMapJSON, &parentNodes)
if err != nil {
return errors.New("decode level info failed: " + err.Error())
}
}
if sharedNodeConfig != nil {
sharedNodeConfig.ParentNodes = parentNodes
}
case "ddosProtectionChanged":
resp, err := rpcClient.NodeRPC.FindNodeDDoSProtection(rpcClient.Context(), &pb.FindNodeDDoSProtectionRequest{})
if err != nil {
return err
}
if len(resp.DdosProtectionJSON) == 0 {
if sharedNodeConfig != nil {
sharedNodeConfig.DDoSProtection = nil
}
return nil
}
var ddosProtectionConfig = &ddosconfigs.ProtectionConfig{}
err = json.Unmarshal(resp.DdosProtectionJSON, ddosProtectionConfig)
if err != nil {
return errors.New("decode DDoS protection config failed: " + err.Error())
}
if ddosProtectionConfig != nil && sharedNodeConfig != nil {
sharedNodeConfig.DDoSProtection = ddosProtectionConfig
}
err = firewalls.SharedDDoSProtectionManager.Apply(ddosProtectionConfig)
if err != nil {
// 不阻塞
remotelogs.Warn("NODE", "apply DDoS protection failed: "+err.Error())
return nil
}
case "globalServerConfigChanged":
resp, err := rpcClient.NodeRPC.FindNodeGlobalServerConfig(rpcClient.Context(), &pb.FindNodeGlobalServerConfigRequest{})
if err != nil {
return err
}
if len(resp.GlobalServerConfigJSON) > 0 {
var globalServerConfig = serverconfigs.DefaultGlobalServerConfig()
err = json.Unmarshal(resp.GlobalServerConfigJSON, globalServerConfig)
if err != nil {
return errors.New("decode global server config failed: " + err.Error())
}
if globalServerConfig != nil {
err = globalServerConfig.Init()
if err != nil {
return errors.New("validate global server config failed: " + err.Error())
}
if sharedNodeConfig != nil {
sharedNodeConfig.GlobalServerConfig = globalServerConfig
}
}
}
case "userServersStateChanged":
if task.UserId > 0 {
resp, err := rpcClient.UserRPC.CheckUserServersState(rpcClient.Context(), &pb.CheckUserServersStateRequest{UserId: task.UserId})
if err != nil {
return err
}
SharedUserManager.UpdateUserServersIsEnabled(task.UserId, resp.IsEnabled)
if resp.IsEnabled {
err = this.syncUserServersConfig(task.UserId)
if err != nil {
return err
}
}
}
default:
remotelogs.Error("NODE", "task '"+types.String(task.Id)+"', type '"+task.Type+"' has not been handled")
}
return nil
}
// 标记任务完成
func (this *Node) finishTask(taskId int64, taskVersion int64, taskErr error) (success bool) {
if taskId <= 0 {
return true
}
rpcClient, err := rpc.SharedRPC()
if err != nil {
remotelogs.Debug("NODE", "create rpc client failed: "+err.Error())
return false
}
var isOk = taskErr == nil
if isOk && taskVersion > this.lastTaskVersion {
this.lastTaskVersion = taskVersion
}
var errMsg = ""
if taskErr != nil {
errMsg = taskErr.Error()
}
_, err = rpcClient.NodeTaskRPC.ReportNodeTaskDone(rpcClient.Context(), &pb.ReportNodeTaskDoneRequest{
NodeTaskId: taskId,
IsOk: isOk,
Error: errMsg,
})
success = err == nil
if err != nil {
// 连接错误不需要上报到服务中心
if rpc.IsConnError(err) {
remotelogs.Debug("NODE", "report task done failed: "+err.Error())
} else {
remotelogs.Error("NODE", "report task done failed: "+err.Error())
}
}
return success
}
// 读取API配置 // 读取API配置
func (this *Node) syncConfig(taskVersion int64) error { func (this *Node) syncConfig(taskVersion int64) error {
this.locker.Lock() this.locker.Lock()
@@ -483,10 +536,8 @@ func (this *Node) syncConfig(taskVersion int64) error {
} }
// 获取同步任务 // 获取同步任务
nodeCtx := rpcClient.Context()
// TODO 这里考虑只同步版本号有变更的 // TODO 这里考虑只同步版本号有变更的
configResp, err := rpcClient.NodeRPC.FindCurrentNodeConfig(nodeCtx, &pb.FindCurrentNodeConfigRequest{ configResp, err := rpcClient.NodeRPC.FindCurrentNodeConfig(rpcClient.Context(), &pb.FindCurrentNodeConfigRequest{
Version: -1, // 更新所有版本 Version: -1, // 更新所有版本
Compress: true, Compress: true,
NodeTaskVersion: taskVersion, NodeTaskVersion: taskVersion,
@@ -557,7 +608,7 @@ func (this *Node) syncConfig(taskVersion int64) error {
remotelogs.Println("NODE", "loading config ...") remotelogs.Println("NODE", "loading config ...")
} }
this.onReload(nodeConfig) this.onReload(nodeConfig, true)
// 发送事件 // 发送事件
events.Notify(events.EventReload) events.Notify(events.EventReload)
@@ -597,6 +648,36 @@ func (this *Node) syncServerConfig(serverId int64) error {
return nil return nil
} }
// 同步某个用户下的所有服务配置
func (this *Node) syncUserServersConfig(userId int64) error {
rpcClient, err := rpc.SharedRPC()
if err != nil {
return err
}
serverConfigsResp, err := rpcClient.ServerRPC.ComposeAllUserServersConfig(rpcClient.Context(), &pb.ComposeAllUserServersConfigRequest{
UserId: userId,
})
if err != nil {
return err
}
if len(serverConfigsResp.ServersConfigJSON) == 0 {
return nil
}
var serverConfigs = []*serverconfigs.ServerConfig{}
err = json.Unmarshal(serverConfigsResp.ServersConfigJSON, &serverConfigs)
if err != nil {
return err
}
this.locker.Lock()
defer this.locker.Unlock()
for _, config := range serverConfigs {
this.updatingServerMap[config.Id] = config
}
return nil
}
// 启动同步计时器 // 启动同步计时器
func (this *Node) startSyncTimer() { func (this *Node) startSyncTimer() {
// TODO 这个时间间隔可以自行设置 // TODO 这个时间间隔可以自行设置
@@ -658,12 +739,12 @@ func (this *Node) checkClusterConfig() error {
return err return err
} }
logs.Println("[NODE]registering node to cluster ...") remotelogs.Debug("NODE", "registering node to cluster ...")
resp, err := rpcClient.NodeRPC.RegisterClusterNode(rpcClient.ClusterContext(config.ClusterId, config.Secret), &pb.RegisterClusterNodeRequest{Name: HOSTNAME}) resp, err := rpcClient.NodeRPC.RegisterClusterNode(rpcClient.ClusterContext(config.ClusterId, config.Secret), &pb.RegisterClusterNodeRequest{Name: HOSTNAME})
if err != nil { if err != nil {
return err return err
} }
logs.Println("[NODE]registered successfully") remotelogs.Debug("NODE", "registered successfully")
// 写入到配置文件中 // 写入到配置文件中
if len(resp.Endpoints) == 0 { if len(resp.Endpoints) == 0 {
@@ -671,8 +752,8 @@ func (this *Node) checkClusterConfig() error {
} }
var apiConfig = &configs.APIConfig{ var apiConfig = &configs.APIConfig{
RPC: struct { RPC: struct {
Endpoints []string `yaml:"endpoints"` Endpoints []string `yaml:"endpoints" json:"endpoints"`
DisableUpdate bool `yaml:"disableUpdate"` DisableUpdate bool `yaml:"disableUpdate" json:"disableUpdate"`
}{ }{
Endpoints: resp.Endpoints, Endpoints: resp.Endpoints,
DisableUpdate: false, DisableUpdate: false,
@@ -680,12 +761,12 @@ func (this *Node) checkClusterConfig() error {
NodeId: resp.UniqueId, NodeId: resp.UniqueId,
Secret: resp.Secret, Secret: resp.Secret,
} }
logs.Println("[NODE]writing 'configs/api.yaml' ...") remotelogs.Debug("NODE", "writing 'configs/api.yaml' ...")
err = apiConfig.WriteFile(Tea.ConfigFile("api.yaml")) err = apiConfig.WriteFile(Tea.ConfigFile("api.yaml"))
if err != nil { if err != nil {
return err return err
} }
logs.Println("[NODE]wrote 'configs/api.yaml' successfully") remotelogs.Debug("NODE", "wrote 'configs/api.yaml' successfully")
return nil return nil
} }
@@ -741,6 +822,7 @@ func (this *Node) listenSock() error {
// 退出主进程 // 退出主进程
events.Notify(events.EventQuit) events.Notify(events.EventQuit)
time.Sleep(100 * time.Millisecond)
utils.Exit() utils.Exit()
case "quit": case "quit":
_ = cmd.ReplyOk() _ = cmd.ReplyOk()
@@ -798,16 +880,56 @@ func (this *Node) listenSock() error {
}, },
}) })
case "conns": case "conns":
var addrs = []string{} var connMaps = []maps.Map{}
var connMap = conns.SharedMap.AllConns() var connMap = conns.SharedMap.AllConns()
for _, conn := range connMap { for _, conn := range connMap {
addrs = append(addrs, conn.RemoteAddr().String()) var createdAt int64
var lastReadAt int64
var lastWriteAt int64
var lastErrString = ""
clientConn, ok := conn.(*ClientConn)
if ok {
createdAt = clientConn.CreatedAt()
lastReadAt = clientConn.LastReadAt()
lastWriteAt = clientConn.LastWriteAt()
var lastErr = clientConn.LastErr()
if lastErr != nil {
lastErrString = lastErr.Error()
}
}
var age int64 = -1
var lastReadAge int64 = -1
var lastWriteAge int64 = -1
var currentTime = time.Now().Unix()
if createdAt > 0 {
age = currentTime - createdAt
}
if lastReadAt > 0 {
lastReadAge = currentTime - lastReadAt
}
if lastWriteAt > 0 {
lastWriteAge = currentTime - lastWriteAt
}
connMaps = append(connMaps, maps.Map{
"addr": conn.RemoteAddr().String(),
"age": age,
"readAge": lastReadAge,
"writeAge": lastWriteAge,
"lastErr": lastErrString,
})
} }
sort.Slice(connMaps, func(i, j int) bool {
var m1 = connMaps[i]
var m2 = connMaps[j]
return m1.GetInt64("age") < m2.GetInt64("age")
})
_ = cmd.Reply(&gosock.Command{ _ = cmd.Reply(&gosock.Command{
Params: map[string]interface{}{ Params: map[string]interface{}{
"addrs": addrs, "conns": connMaps,
"total": len(addrs), "total": len(connMaps),
}, },
}) })
case "dropIP": case "dropIP":
@@ -839,6 +961,11 @@ func (this *Node) listenSock() error {
} else { } else {
_ = cmd.ReplyOk() _ = cmd.ReplyOk()
} }
case "closeIP":
var m = maps.NewMap(cmd.Params)
var ip = m.GetString("ip")
conns.SharedMap.CloseIPConns(ip)
_ = cmd.ReplyOk()
case "removeIP": case "removeIP":
var m = maps.NewMap(cmd.Params) var m = maps.NewMap(cmd.Params)
var ip = m.GetString("ip") var ip = m.GetString("ip")
@@ -889,12 +1016,12 @@ func (this *Node) listenSock() error {
err := this.sock.Listen() err := this.sock.Listen()
if err != nil { if err != nil {
logs.Println("NODE", err.Error()) remotelogs.Debug("NODE", err.Error())
} }
}) })
events.OnKey(events.EventQuit, this, func() { events.OnKey(events.EventQuit, this, func() {
remotelogs.Println("NODE", "quit unix sock") remotelogs.Debug("NODE", "quit unix sock")
_ = this.sock.Close() _ = this.sock.Close()
}) })
@@ -902,97 +1029,165 @@ func (this *Node) listenSock() error {
} }
// 重载配置调用 // 重载配置调用
func (this *Node) onReload(config *nodeconfigs.NodeConfig) { func (this *Node) onReload(config *nodeconfigs.NodeConfig, reloadAll bool) {
nodeconfigs.ResetNodeConfig(config) nodeconfigs.ResetNodeConfig(config)
sharedNodeConfig = config sharedNodeConfig = config
// 缓存策略 if reloadAll {
caches.SharedManager.MaxDiskCapacity = config.MaxCacheDiskCapacity // 缓存策略
caches.SharedManager.MaxMemoryCapacity = config.MaxCacheMemoryCapacity var subDirs = config.CacheDiskSubDirs
caches.SharedManager.DiskDir = config.CacheDiskDir for _, subDir := range subDirs {
if len(config.HTTPCachePolicies) > 0 { subDir.Path = filepath.Clean(subDir.Path)
caches.SharedManager.UpdatePolicies(config.HTTPCachePolicies) }
} else { if len(subDirs) > 0 {
caches.SharedManager.UpdatePolicies([]*serverconfigs.HTTPCachePolicy{}) sort.Slice(subDirs, func(i, j int) bool {
return subDirs[i].Path < subDirs[j].Path
})
}
var cachePoliciesChanged = !jsonutils.Equal(caches.SharedManager.MaxDiskCapacity, config.MaxCacheDiskCapacity) ||
!jsonutils.Equal(caches.SharedManager.MaxMemoryCapacity, config.MaxCacheMemoryCapacity) ||
!jsonutils.Equal(caches.SharedManager.MainDiskDir, config.CacheDiskDir) ||
!jsonutils.Equal(caches.SharedManager.SubDiskDirs, subDirs) ||
!jsonutils.Equal(this.oldHTTPCachePolicies, config.HTTPCachePolicies)
caches.SharedManager.MaxDiskCapacity = config.MaxCacheDiskCapacity
caches.SharedManager.MaxMemoryCapacity = config.MaxCacheMemoryCapacity
caches.SharedManager.MainDiskDir = config.CacheDiskDir
caches.SharedManager.SubDiskDirs = subDirs
if cachePoliciesChanged {
// copy
this.oldHTTPCachePolicies = []*serverconfigs.HTTPCachePolicy{}
err := jsonutils.Copy(&this.oldHTTPCachePolicies, config.HTTPCachePolicies)
if err != nil {
remotelogs.Error("NODE", "onReload: copy HTTPCachePolicies failed: "+err.Error())
}
// update
if len(config.HTTPCachePolicies) > 0 {
caches.SharedManager.UpdatePolicies(config.HTTPCachePolicies)
} else {
caches.SharedManager.UpdatePolicies([]*serverconfigs.HTTPCachePolicy{})
}
}
} }
// WAF策略 // WAF策略
waf.SharedWAFManager.UpdatePolicies(config.FindAllFirewallPolicies()) // 包含了服务里的WAF策略所以需要整体更新
iplibrary.SharedActionManager.UpdateActions(config.FirewallActions) var allFirewallPolicies = config.FindAllFirewallPolicies()
if !jsonutils.Equal(allFirewallPolicies, this.oldHTTPFirewallPolicies) {
// 统计指标 // copy
metrics.SharedManager.Update(config.MetricItems) this.oldHTTPFirewallPolicies = []*firewallconfigs.HTTPFirewallPolicy{}
err := jsonutils.Copy(&this.oldHTTPFirewallPolicies, allFirewallPolicies)
// max cpu
if config.MaxCPU != this.maxCPU {
if config.MaxCPU > 0 && config.MaxCPU < int32(runtime.NumCPU()) {
runtime.GOMAXPROCS(int(config.MaxCPU))
remotelogs.Println("NODE", "[CPU]set max cpu to '"+types.String(config.MaxCPU)+"'")
} else {
var threads = runtime.NumCPU() * 4
runtime.GOMAXPROCS(threads)
remotelogs.Println("NODE", "[CPU]set max cpu to '"+types.String(threads)+"'")
}
this.maxCPU = config.MaxCPU
}
// max threads
if config.MaxThreads != this.maxThreads {
if config.MaxThreads > 0 {
debug.SetMaxThreads(config.MaxThreads)
remotelogs.Println("NODE", "[THREADS]set max threads to '"+types.String(config.MaxThreads)+"'")
} else {
debug.SetMaxThreads(nodeconfigs.DefaultMaxThreads)
remotelogs.Println("NODE", "[THREADS]set max threads to '"+types.String(nodeconfigs.DefaultMaxThreads)+"'")
}
this.maxThreads = config.MaxThreads
}
// timezone
var timeZone = config.TimeZone
if len(timeZone) == 0 {
timeZone = "Asia/Shanghai"
}
if this.timezone != timeZone {
location, err := time.LoadLocation(timeZone)
if err != nil { if err != nil {
remotelogs.Error("NODE", "[TIMEZONE]change time zone failed: "+err.Error()) remotelogs.Error("NODE", "onReload: copy HTTPFirewallPolicies failed: "+err.Error())
return
} }
remotelogs.Println("NODE", "[TIMEZONE]change time zone to '"+timeZone+"'") // update
time.Local = location waf.SharedWAFManager.UpdatePolicies(allFirewallPolicies)
this.timezone = timeZone
} }
// product information if reloadAll {
if config.ProductConfig != nil { if !jsonutils.Equal(config.FirewallActions, this.oldFirewallActions) {
teaconst.GlobalProductName = config.ProductConfig.Name // copy
} this.oldFirewallActions = []*firewallconfigs.FirewallActionConfig{}
err := jsonutils.Copy(&this.oldFirewallActions, config.FirewallActions)
if err != nil {
remotelogs.Error("NODE", "onReload: copy FirewallActionConfigs failed: "+err.Error())
}
// DNS resolver // update
if config.DNSResolver != nil { iplibrary.SharedActionManager.UpdateActions(config.FirewallActions)
var err error }
switch config.DNSResolver.Type {
case nodeconfigs.DNSResolverTypeGoNative: // 统计指标
err = os.Setenv("GODEBUG", "netdns=go") if !jsonutils.Equal(this.oldMetricItems, config.MetricItems) {
case nodeconfigs.DNSResolverTypeCGO: // copy
err = os.Setenv("GODEBUG", "netdns=cgo") this.oldMetricItems = []*serverconfigs.MetricItemConfig{}
default: err := jsonutils.Copy(&this.oldMetricItems, config.MetricItems)
if err != nil {
remotelogs.Error("NODE", "onReload: copy MetricItemConfigs failed: "+err.Error())
}
// update
metrics.SharedManager.Update(config.MetricItems)
}
// max cpu
if config.MaxCPU != this.oldMaxCPU {
if config.MaxCPU > 0 && config.MaxCPU < int32(runtime.NumCPU()) {
runtime.GOMAXPROCS(int(config.MaxCPU))
remotelogs.Println("NODE", "[CPU]set max cpu to '"+types.String(config.MaxCPU)+"'")
} else {
var threads = runtime.NumCPU() * 4
runtime.GOMAXPROCS(threads)
remotelogs.Println("NODE", "[CPU]set max cpu to '"+types.String(threads)+"'")
}
this.oldMaxCPU = config.MaxCPU
}
// max threads
if config.MaxThreads != this.oldMaxThreads {
if config.MaxThreads > 0 {
debug.SetMaxThreads(config.MaxThreads)
remotelogs.Println("NODE", "[THREADS]set max threads to '"+types.String(config.MaxThreads)+"'")
} else {
debug.SetMaxThreads(nodeconfigs.DefaultMaxThreads)
remotelogs.Println("NODE", "[THREADS]set max threads to '"+types.String(nodeconfigs.DefaultMaxThreads)+"'")
}
this.oldMaxThreads = config.MaxThreads
}
// timezone
var timeZone = config.TimeZone
if len(timeZone) == 0 {
timeZone = "Asia/Shanghai"
}
if this.oldTimezone != timeZone {
location, err := time.LoadLocation(timeZone)
if err != nil {
remotelogs.Error("NODE", "[TIMEZONE]change time zone failed: "+err.Error())
return
}
remotelogs.Println("NODE", "[TIMEZONE]change time zone to '"+timeZone+"'")
time.Local = location
this.oldTimezone = timeZone
}
// product information
if config.ProductConfig != nil {
teaconst.GlobalProductName = config.ProductConfig.Name
}
// DNS resolver
if config.DNSResolver != nil {
var err error
switch config.DNSResolver.Type {
case nodeconfigs.DNSResolverTypeGoNative:
err = os.Setenv("GODEBUG", "netdns=go")
case nodeconfigs.DNSResolverTypeCGO:
err = os.Setenv("GODEBUG", "netdns=cgo")
default:
// 默认使用go原生
err = os.Setenv("GODEBUG", "netdns=go")
}
if err != nil {
remotelogs.Error("NODE", "[DNS_RESOLVER]set env failed: "+err.Error())
}
} else {
// 默认使用go原生 // 默认使用go原生
err = os.Setenv("GODEBUG", "netdns=go") err := os.Setenv("GODEBUG", "netdns=go")
} if err != nil {
if err != nil { remotelogs.Error("NODE", "[DNS_RESOLVER]set env failed: "+err.Error())
remotelogs.Error("NODE", "[DNS_RESOLVER]set env failed: "+err.Error()) }
}
} else {
// 默认使用go原生
err := os.Setenv("GODEBUG", "netdns=go")
if err != nil {
remotelogs.Error("NODE", "[DNS_RESOLVER]set env failed: "+err.Error())
} }
// API Node地址这里不限制是否为空因为在为空时仍然要有对应的处理
this.changeAPINodeAddrs(config.APINodeAddrs)
} }
} }
@@ -1028,7 +1223,7 @@ func (this *Node) reloadServer() {
} }
} }
this.onReload(newNodeConfig) this.onReload(newNodeConfig, false)
err = sharedListenerManager.Start(newNodeConfig) err = sharedListenerManager.Start(newNodeConfig)
if err != nil { if err != nil {
@@ -1037,6 +1232,7 @@ func (this *Node) reloadServer() {
} }
} }
// 检查硬盘
func (this *Node) checkDisk() { func (this *Node) checkDisk() {
if runtime.GOOS != "linux" { if runtime.GOOS != "linux" {
return return
@@ -1057,3 +1253,69 @@ func (this *Node) checkDisk() {
} }
} }
} }
// 检查API节点地址
func (this *Node) changeAPINodeAddrs(apiNodeAddrs []*serverconfigs.NetworkAddressConfig) {
var addrs = []string{}
for _, addr := range apiNodeAddrs {
err := addr.Init()
if err != nil {
remotelogs.Error("NODE", "changeAPINodeAddrs: validate api node address '"+configutils.QuoteIP(addr.Host)+":"+addr.PortRange+"' failed: "+err.Error())
} else {
addrs = append(addrs, addr.FullAddresses()...)
}
}
sort.Strings(addrs)
if utils.EqualStrings(this.lastAPINodeAddrs, addrs) {
return
}
this.lastAPINodeAddrs = addrs
config, err := configs.LoadAPIConfig()
if err != nil {
remotelogs.Error("NODE", "changeAPINodeAddrs: "+err.Error())
return
}
if config == nil {
return
}
var oldEndpoints = config.RPC.Endpoints
rpcClient, err := rpc.SharedRPC()
if err != nil {
return
}
if len(addrs) > 0 {
this.lastAPINodeVersion++
var v = this.lastAPINodeVersion
// 异步检测,防止阻塞
go func(v int64) {
// 测试新的API节点地址
if rpcClient.TestEndpoints(addrs) {
config.RPC.Endpoints = addrs
} else {
config.RPC.Endpoints = oldEndpoints
this.lastAPINodeAddrs = nil // 恢复为空,以便于下次更新重试
}
// 检查测试中间有无新的变更
if v != this.lastAPINodeVersion {
return
}
err = rpcClient.UpdateConfig(config)
if err != nil {
remotelogs.Error("NODE", "changeAPINodeAddrs: update rpc config failed: "+err.Error())
}
}(v)
return
}
err = rpcClient.UpdateConfig(config)
if err != nil {
remotelogs.Error("NODE", "changeAPINodeAddrs: update rpc config failed: "+err.Error())
}
}

View File

@@ -31,12 +31,15 @@ type NodeStatusExecutor struct {
cpuLogicalCount int cpuLogicalCount int
cpuPhysicalCount int cpuPhysicalCount int
apiCallStat *rpc.CallStat
ticker *time.Ticker ticker *time.Ticker
} }
func NewNodeStatusExecutor() *NodeStatusExecutor { func NewNodeStatusExecutor() *NodeStatusExecutor {
return &NodeStatusExecutor{ return &NodeStatusExecutor{
ticker: time.NewTicker(30 * time.Second), ticker: time.NewTicker(30 * time.Second),
apiCallStat: rpc.NewCallStat(10),
} }
} }
@@ -78,6 +81,11 @@ func (this *NodeStatusExecutor) update() {
status.CacheTotalMemorySize = caches.SharedManager.TotalMemorySize() status.CacheTotalMemorySize = caches.SharedManager.TotalMemorySize()
status.TrafficInBytes = teaconst.InTrafficBytes status.TrafficInBytes = teaconst.InTrafficBytes
status.TrafficOutBytes = teaconst.OutTrafficBytes status.TrafficOutBytes = teaconst.OutTrafficBytes
apiSuccessPercent, apiAvgCostSeconds := this.apiCallStat.Sum()
status.APISuccessPercent = apiSuccessPercent
status.APIAvgCostSeconds = apiAvgCostSeconds
var localFirewall = firewalls.Firewall() var localFirewall = firewalls.Firewall()
if localFirewall != nil && !localFirewall.IsMock() { if localFirewall != nil && !localFirewall.IsMock() {
status.LocalFirewallName = localFirewall.Name() status.LocalFirewallName = localFirewall.Name()
@@ -125,9 +133,13 @@ func (this *NodeStatusExecutor) update() {
remotelogs.Error("NODE_STATUS", "failed to open rpc: "+err.Error()) remotelogs.Error("NODE_STATUS", "failed to open rpc: "+err.Error())
return return
} }
var before = time.Now()
_, err = rpcClient.NodeRPC.UpdateNodeStatus(rpcClient.Context(), &pb.UpdateNodeStatusRequest{ _, err = rpcClient.NodeRPC.UpdateNodeStatus(rpcClient.Context(), &pb.UpdateNodeStatusRequest{
StatusJSON: jsonData, StatusJSON: jsonData,
}) })
var costSeconds = time.Since(before).Seconds()
this.apiCallStat.Add(err == nil, costSeconds)
if err != nil { if err != nil {
if rpc.IsConnError(err) { if rpc.IsConnError(err) {
remotelogs.Warn("NODE_STATUS", "rpc UpdateNodeStatus() failed: "+err.Error()) remotelogs.Warn("NODE_STATUS", "rpc UpdateNodeStatus() failed: "+err.Error())
@@ -140,7 +152,7 @@ func (this *NodeStatusExecutor) update() {
// 更新CPU // 更新CPU
func (this *NodeStatusExecutor) updateCPU(status *nodeconfigs.NodeStatus) { func (this *NodeStatusExecutor) updateCPU(status *nodeconfigs.NodeStatus) {
duration := time.Duration(0) var duration = time.Duration(0)
if this.isFirstTime { if this.isFirstTime {
duration = 100 * time.Millisecond duration = 100 * time.Millisecond
} }
@@ -195,8 +207,8 @@ func (this *NodeStatusExecutor) updateDisk(status *nodeconfigs.NodeStatus) {
}) })
// 当前TeaWeb所在的fs // 当前TeaWeb所在的fs
rootFS := "" var rootFS = ""
rootTotal := uint64(0) var rootTotal = uint64(0)
if lists.ContainsString([]string{"darwin", "linux", "freebsd"}, runtime.GOOS) { if lists.ContainsString([]string{"darwin", "linux", "freebsd"}, runtime.GOOS) {
for _, p := range partitions { for _, p := range partitions {
if p.Mountpoint == "/" { if p.Mountpoint == "/" {
@@ -210,9 +222,9 @@ func (this *NodeStatusExecutor) updateDisk(status *nodeconfigs.NodeStatus) {
} }
} }
total := rootTotal var total = rootTotal
totalUsage := uint64(0) var totalUsage = uint64(0)
maxUsage := float64(0) var maxUsage = float64(0)
for _, partition := range partitions { for _, partition := range partitions {
if runtime.GOOS != "windows" && !strings.Contains(partition.Device, "/") && !strings.Contains(partition.Device, "\\") { if runtime.GOOS != "windows" && !strings.Contains(partition.Device, "/") && !strings.Contains(partition.Device, "\\") {
continue continue
@@ -252,7 +264,7 @@ func (this *NodeStatusExecutor) updateDisk(status *nodeconfigs.NodeStatus) {
// 缓存空间 // 缓存空间
func (this *NodeStatusExecutor) updateCacheSpace(status *nodeconfigs.NodeStatus) { func (this *NodeStatusExecutor) updateCacheSpace(status *nodeconfigs.NodeStatus) {
var result = []maps.Map{} var result = []maps.Map{}
cachePaths := caches.SharedManager.FindAllCachePaths() var cachePaths = caches.SharedManager.FindAllCachePaths()
for _, path := range cachePaths { for _, path := range cachePaths {
var stat unix.Statfs_t var stat unix.Statfs_t
err := unix.Statfs(path, &stat) err := unix.Statfs(path, &stat)

View File

@@ -1,5 +1,4 @@
//go:build !windows //go:build !windows
// +build !windows
package nodes package nodes

View File

@@ -1,48 +0,0 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package nodes
import (
"github.com/TeaOSLab/EdgeNode/internal/ttlcache"
"github.com/TeaOSLab/EdgeNode/internal/utils"
"github.com/iwind/TeaGo/types"
"strings"
"sync"
"time"
)
var sharedCNAMEManager = NewServerCNAMEManager()
// ServerCNAMEManager 服务CNAME管理
// TODO 需要自动更新缓存里的记录
type ServerCNAMEManager struct {
ttlCache *ttlcache.Cache
locker sync.Mutex
}
func NewServerCNAMEManager() *ServerCNAMEManager {
return &ServerCNAMEManager{
ttlCache: ttlcache.NewCache(),
}
}
func (this *ServerCNAMEManager) Lookup(domain string) string {
if len(domain) == 0 {
return ""
}
var item = this.ttlCache.Read(domain)
if item != nil {
return types.String(item.Value)
}
cname, _ := utils.LookupCNAME(domain)
if len(cname) > 0 {
cname = strings.TrimSuffix(cname, ".")
}
this.ttlCache.Write(domain, cname, time.Now().Unix()+600)
return cname
}

View File

@@ -1,19 +0,0 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package nodes
import (
"testing"
"time"
)
func TestServerCNameManager_Lookup(t *testing.T) {
var cnameManager = NewServerCNAMEManager()
t.Log(cnameManager.Lookup("www.yun4s.cn"))
var before = time.Now()
defer func() {
t.Log(time.Since(before).Seconds()*1000, "ms")
}()
t.Log(cnameManager.Lookup("www.yun4s.cn"))
}

View File

@@ -10,6 +10,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/utils" "github.com/TeaOSLab/EdgeNode/internal/utils"
executils "github.com/TeaOSLab/EdgeNode/internal/utils/exec"
"github.com/iwind/TeaGo/maps" "github.com/iwind/TeaGo/maps"
"os" "os"
"os/exec" "os/exec"
@@ -20,15 +21,18 @@ import (
func init() { func init() {
var manager = NewSystemServiceManager() var manager = NewSystemServiceManager()
events.On(events.EventReload, func() { events.On(events.EventReload, func() {
err := manager.Setup() goman.New(func() {
if err != nil { err := manager.Setup()
remotelogs.Error("SYSTEM_SERVICE", "setup system services failed: "+err.Error()) if err != nil {
} remotelogs.Error("SYSTEM_SERVICE", "setup system services failed: "+err.Error())
}
})
}) })
} }
// SystemServiceManager 系统服务管理 // SystemServiceManager 系统服务管理
type SystemServiceManager struct { type SystemServiceManager struct {
lastIsOn int // -1, 0, 1
} }
func NewSystemServiceManager() *SystemServiceManager { func NewSystemServiceManager() *SystemServiceManager {
@@ -68,7 +72,8 @@ func (this *SystemServiceManager) setupSystemd(params maps.Map) error {
if err != nil { if err != nil {
return err return err
} }
config := &nodeconfigs.SystemdServiceConfig{}
var config = &nodeconfigs.SystemdServiceConfig{}
err = json.Unmarshal(data, config) err = json.Unmarshal(data, config)
if err != nil { if err != nil {
return err return err
@@ -82,42 +87,60 @@ func (this *SystemServiceManager) setupSystemd(params maps.Map) error {
if len(systemctl) == 0 { if len(systemctl) == 0 {
return errors.New("can not find 'systemctl' on the system") return errors.New("can not find 'systemctl' on the system")
} }
cmd := utils.NewCommandExecutor()
shortName := teaconst.SystemdServiceName // 记录上次状态
cmd.Add(systemctl, "is-enabled", shortName) var isOnInt int
output, err := cmd.Run() if config.IsOn {
if err != nil { isOnInt = 1
return err } else {
isOnInt = 0
} }
if this.lastIsOn == isOnInt {
return nil
}
defer func() {
this.lastIsOn = isOnInt
}()
var shortName = teaconst.SystemdServiceName
var cmd = executils.NewTimeoutCmd(10*time.Second, systemctl, "is-enabled", shortName)
cmd.WithStdout()
err = cmd.Run()
var hasInstalled = err == nil
if config.IsOn { if config.IsOn {
exe, err := os.Executable() exe, err := os.Executable()
if err != nil { if err != nil {
return err return err
} }
// 启动Service // 检查文件路径是否变化
goman.New(func() { if hasInstalled && cmd.Stdout() == "enabled" {
time.Sleep(5 * time.Second)
_ = exec.Command(systemctl, "start", teaconst.SystemdServiceName).Start()
})
if output == "enabled" {
// 检查文件路径是否变化
data, err := os.ReadFile("/etc/systemd/system/" + teaconst.SystemdServiceName + ".service") data, err := os.ReadFile("/etc/systemd/system/" + teaconst.SystemdServiceName + ".service")
if err == nil && bytes.Index(data, []byte(exe)) > 0 { if err == nil && bytes.Index(data, []byte(exe)) > 0 {
return nil return nil
} }
} }
manager := utils.NewServiceManager(shortName, teaconst.ProductName)
// 安装服务
var manager = utils.NewServiceManager(shortName, teaconst.ProductName)
err = manager.Install(exe, []string{}) err = manager.Install(exe, []string{})
if err != nil { if err != nil {
return err return err
} }
// 启动服务
goman.New(func() {
time.Sleep(5 * time.Second)
_ = executils.NewTimeoutCmd(30*time.Second, systemctl, "start", teaconst.SystemdServiceName).Start()
})
} else { } else {
manager := utils.NewServiceManager(shortName, teaconst.ProductName) if hasInstalled {
err = manager.Uninstall() var manager = utils.NewServiceManager(shortName, teaconst.ProductName)
if err != nil { err = manager.Uninstall()
return err if err != nil {
return err
}
} }
} }

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