Compare commits

..

87 Commits

Author SHA1 Message Date
刘祥超
644ada1da9 优化代码 2023-03-12 20:32:15 +08:00
刘祥超
0c40250849 优化代码 2023-03-12 16:36:59 +08:00
刘祥超
1d1134a86d 优化代码 2023-03-12 16:18:16 +08:00
刘祥超
28e7664eb7 修复源站重试时可能产生的file is writing错误 2023-03-12 16:09:06 +08:00
刘祥超
50f3ad641c 优化统计相关程序 2023-03-12 12:19:25 +08:00
刘祥超
cc7cf5f8c5 限制统计系统和浏览器的最大长度,减少随机UserAgent攻击影响 2023-03-11 11:58:05 +08:00
刘祥超
339f0f6e94 上传统计数据时增加单次上传数量限制 2023-03-11 11:21:03 +08:00
刘祥超
f558e43342 根据系统内存调整访问日志队列长度 2023-03-10 22:31:40 +08:00
刘祥超
e374e5c90c 优化命令识别 2023-03-10 22:05:40 +08:00
刘祥超
563b775e49 优化命令执行速度 2023-03-10 22:01:39 +08:00
刘祥超
de9e1a4515 执行一般命令时不运行init()中内容 2023-03-10 15:14:14 +08:00
刘祥超
f64b36f17a WAF cc防护增加“检查请求来源指纹”选项 2023-03-10 10:41:16 +08:00
刘祥超
f0e8c82d31 增加CC防护(开源用户需要自己实现) 2023-03-09 15:15:22 +08:00
刘祥超
5770d43230 WAF cc2尝试使用指纹统计方法 2023-03-08 16:59:44 +08:00
刘祥超
d4944c236f 修复几处测试用例 2023-03-08 10:13:41 +08:00
刘祥超
33c761a187 修复本地数据库无法异步提交事务的Bug 2023-03-07 16:48:03 +08:00
刘祥超
d7e6da8d2c 对本地数据库文件进行加锁 2023-03-07 16:22:32 +08:00
刘祥超
44d1a2415c 集群服务设置增加“记录找不到网站日志”选项 2023-03-07 10:30:55 +08:00
刘祥超
c98ff50f06 暂时取消GET302和POST307的迟滞 2023-03-07 08:51:39 +08:00
刘祥超
8835fcb09e GET302/POST307增加访问迟滞 2023-03-06 16:28:54 +08:00
刘祥超
77c56e58c0 GET302/POST307兼容safari浏览器 2023-03-06 16:27:06 +08:00
刘祥超
72c65ca4ee 修复GET302和POST307关闭连接后无法响应的问题 2023-03-06 16:10:58 +08:00
刘祥超
ab019b0bdc 修复在连接读写优化模式下fastcgi无法正常工作的Bug 2023-03-06 10:33:54 +08:00
刘祥超
9709e45ad2 修改软内存限制设置错误 2023-03-05 21:19:10 +08:00
刘祥超
be1f80003c 增加软内存最大值限制 2023-03-05 12:34:46 +08:00
刘祥超
252fcca383 增加测试用例 2023-03-03 14:28:58 +08:00
刘祥超
04ae8fa4a0 系统内存不足时,尝试自动回收内存 2023-03-02 10:54:25 +08:00
刘祥超
c95bd7776a WAF拦截动作可以设置最大封禁时间,从而实现封禁时间随机 2023-03-01 19:00:08 +08:00
刘祥超
8219167d05 WAF支持忽略全局WAF规则 2023-03-01 16:46:43 +08:00
刘祥超
e0a6881343 上传带宽数据时同时上传流量数据 2023-02-27 10:48:16 +08:00
刘祥超
6e985d7f06 修复GET302和POST307无限循环的问题 2023-02-24 17:02:59 +08:00
刘祥超
66719b05dd 修复WAF验证码不能输入超出6位数字的Bug 2023-02-16 14:44:56 +08:00
刘祥超
7197583fea 增加变量${requestPathLowerExtension} 2023-02-10 10:43:30 +08:00
刘祥超
ce29024eef 写入Agent IP时,不提示id重复错误 2023-02-01 10:18:08 +08:00
刘祥超
e1ac67f7fa 版本更改为0.6.4 2023-02-01 10:07:30 +08:00
刘祥超
01812144dd 优化带宽统计 2023-01-12 19:09:57 +08:00
刘祥超
1c34e49629 优化代码 2023-01-12 19:02:38 +08:00
刘祥超
f233fbfb25 版本号修改为0.6.3 2023-01-11 15:44:53 +08:00
刘祥超
5387115e4a 优化代码 2023-01-11 15:24:48 +08:00
刘祥超
d82c03db23 修复在HTTPS下无法连接Websocket的问题 2023-01-10 21:20:27 +08:00
刘祥超
230c5c3766 版本号修改为0.6.2 2023-01-10 21:18:53 +08:00
刘祥超
927425149e 优化代码 2023-01-10 09:47:56 +08:00
刘祥超
5ce1aab92c 修复域名跳转时没有携带参数的Bug 2023-01-09 20:06:09 +08:00
刘祥超
195742bb26 修复读超时时间(ReadDeadline)导致WAFGET302、POST307延时关闭连接的问题 2023-01-09 15:56:59 +08:00
刘祥超
006cc2912d 版本修改为0.6.1 2023-01-09 15:49:16 +08:00
刘祥超
2d4ba90c3b 改进在自动读超时模式下的Websocket连接 2023-01-09 12:36:33 +08:00
刘祥超
a2e6aaaa18 WAF增加“在IP列表内”操作符/优化部分操作符代号 2023-01-08 10:15:46 +08:00
刘祥超
8e68da7725 集群服务设置增加自动读超时选项 2023-01-07 20:04:05 +08:00
刘祥超
7abb84c880 优化网络连接关闭速度 2023-01-07 10:03:32 +08:00
刘祥超
a17878f5b2 WAF增加包含任一字符串、包含所有字符串操作符 2023-01-06 20:07:15 +08:00
刘祥超
8a8881ac47 IP范围支持多行 2023-01-06 19:14:09 +08:00
刘祥超
c567404b7a 优化连接相关代码 2023-01-05 11:13:35 +08:00
刘祥超
b220b0f48e 优化读取HTTP请求Header和握手超时时间 2023-01-05 00:40:49 +08:00
刘祥超
9609c90d75 边缘节点增加数据读超时,以改进客户端上传数据过慢的问题 2023-01-04 20:43:10 +08:00
刘祥超
2c3c32af5b 优化代码 2023-01-02 10:44:10 +08:00
刘祥超
b4a4b2e9b1 集群服务设置中增加性能设置 2023-01-01 19:27:38 +08:00
刘祥超
c42ff1e1e9 实现UA名单功能 2022-12-30 20:49:43 +08:00
刘祥超
9fed1141c2 默认情况下内容压缩不支持Partial Content 2022-12-30 11:44:07 +08:00
刘祥超
e87f031293 增加CORS自适应跨域 2022-12-29 17:16:42 +08:00
刘祥超
c4bac7f43c 优化代码 2022-12-27 18:58:29 +08:00
刘祥超
47818f972e 自动转换访问域名中的大写字母 2022-12-25 15:23:56 +08:00
刘祥超
218a0300c5 修复测试用例 2022-12-23 18:53:49 +08:00
刘祥超
63f6c4177f 修复测试用例 2022-12-23 18:17:32 +08:00
刘祥超
1830c22a31 增加自动Agent识别 2022-12-22 11:38:59 +08:00
刘祥超
18611e8a7c 写数据超时时断开同客户端连接 2022-12-21 16:11:55 +08:00
刘祥超
c45f7adf04 优化连接相关代码 2022-12-21 15:59:07 +08:00
刘祥超
1a200918a8 不支持CONNECT方法 2022-12-19 16:27:58 +08:00
刘祥超
b942bb776e 国家/地区封禁、省份封禁时支持IP变量 2022-12-18 16:04:12 +08:00
刘祥超
5cf84efccd 优化内容为空的缓存 2022-12-14 15:26:18 +08:00
刘祥超
ebb6ebd10c 修复WAF中反斜杠符号(\)有可能解析错误的Bug 2022-12-14 12:27:07 +08:00
刘祥超
42d0d63cf4 优化代码 2022-12-13 18:08:50 +08:00
刘祥超
96f8f7e925 增加edge-node ip.close IP命令 2022-12-12 19:23:58 +08:00
刘祥超
e7e7214d58 调整慢连接超时算法 2022-12-12 10:04:36 +08:00
刘祥超
ade979a725 向客户端写入数据超时时立即关闭连接 2022-12-10 19:51:05 +08:00
刘祥超
60a8de13e7 TCP单次向客户端写入数据时超过30秒即认为超时 2022-12-10 18:22:00 +08:00
刘祥超
9fa24bed0a 修复WAF记录IP动作时无法不超时的Bug 2022-12-06 11:01:34 +08:00
刘祥超
87bc1a7e03 优化OpenFileCache 2022-12-05 11:16:04 +08:00
刘祥超
1a05f56149 优化缓存相关代码 2022-12-05 10:46:44 +08:00
刘祥超
f88db576e1 优化代码 2022-12-05 09:57:01 +08:00
刘祥超
dc3f26ea1a 减少WAF预读尺寸 2022-12-02 21:08:03 +08:00
刘祥超
6fc30144f7 在edge-node conns命令中显示连接时长 2022-12-02 17:03:16 +08:00
刘祥超
25b0b98bd4 增加默认的源站连接数 2022-12-02 10:39:07 +08:00
刘祥超
27b5817d5e 优化请求限制逻辑,连接关闭时自动终止内容发送 2022-11-29 19:14:46 +08:00
刘祥超
dcb61dfd33 版本号更改为0.6.0 2022-11-29 15:42:21 +08:00
刘祥超
bbcfdbbf5e 优化代码 2022-11-29 15:33:12 +08:00
刘祥超
b2a1bef08f 修复服务WAF配置无法更新的Bug 2022-11-28 18:13:08 +08:00
刘祥超
2b18b5c2ca 修改版本号为0.5.9 2022-11-28 18:08:19 +08:00
119 changed files with 3009 additions and 574 deletions

View File

@@ -25,7 +25,7 @@ func main() {
Product(teaconst.ProductName).
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 + " [ip.drop|ip.reject|ip.remove] IP")
Usage(teaconst.ProcessName + " [ip.drop|ip.reject|ip.remove|ip.close] IP")
app.On("test", func() {
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() {
var args = os.Args[2:]
if len(args) == 0 {

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

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

View File

@@ -7,9 +7,9 @@ import (
"github.com/TeaOSLab/EdgeNode/internal/goman"
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
"github.com/TeaOSLab/EdgeNode/internal/ttlcache"
"github.com/TeaOSLab/EdgeNode/internal/utils/dbs"
"github.com/TeaOSLab/EdgeNode/internal/utils/fnv"
"github.com/iwind/TeaGo/types"
_ "github.com/mattn/go-sqlite3"
"os"
"sync/atomic"
"time"
@@ -450,7 +450,7 @@ func (this *FileList) UpgradeV3(oldDir string, brokenOnError bool) error {
remotelogs.Println("CACHE", "upgrading local database finished")
}()
db, err := sql.Open("sqlite3", "file:"+indexDBPath+"?cache=shared&mode=rwc&_journal_mode=WAL&_sync=OFF")
db, err := dbs.OpenWriter("file:" + indexDBPath + "?cache=shared&mode=rwc&_journal_mode=WAL&_sync=OFF&_locking_mode=EXCLUSIVE")
if err != nil {
return err
}

View File

@@ -3,7 +3,6 @@
package caches
import (
"database/sql"
"errors"
teaconst "github.com/TeaOSLab/EdgeNode/internal/const"
"github.com/TeaOSLab/EdgeNode/internal/goman"
@@ -82,14 +81,15 @@ func (this *FileListDB) Open(dbPath string) error {
}
// write db
writeDB, err := sql.Open("sqlite3", "file:"+dbPath+"?cache=private&mode=rwc&_journal_mode=WAL&_sync=OFF&_cache_size="+types.String(cacheSize)+"&_secure_delete=FAST")
// 这里不能加 EXCLUSIVE 锁,不然异步事务可能会失败
writeDB, err := dbs.OpenWriter("file:" + dbPath + "?cache=private&mode=rwc&_journal_mode=WAL&_sync=OFF&_cache_size=" + types.String(cacheSize) + "&_secure_delete=FAST")
if err != nil {
return errors.New("open write database failed: " + err.Error())
}
writeDB.SetMaxOpenConns(1)
this.writeDB = dbs.NewDB(writeDB)
this.writeDB = writeDB
// TODO 耗时过长,暂时不整理数据库
// TODO 需要根据行数来判断是否VACUUM
@@ -109,7 +109,7 @@ func (this *FileListDB) Open(dbPath string) error {
}
}
this.writeBatch = dbs.NewBatch(writeDB, 4)
this.writeBatch = dbs.NewBatch(writeDB.RawDB(), 4)
this.writeBatch.OnFail(func(err error) {
remotelogs.Warn("LIST_FILE_DB", "run batch failed: "+err.Error()+" ("+filepath.Base(this.dbPath)+")")
})
@@ -124,14 +124,14 @@ func (this *FileListDB) Open(dbPath string) error {
}
// read db
readDB, err := sql.Open("sqlite3", "file:"+dbPath+"?cache=private&mode=ro&_journal_mode=WAL&_sync=OFF&_cache_size="+types.String(cacheSize))
readDB, err := dbs.OpenReader("file:" + dbPath + "?cache=private&mode=ro&_journal_mode=WAL&_sync=OFF&_cache_size=" + types.String(cacheSize))
if err != nil {
return errors.New("open read database failed: " + err.Error())
}
readDB.SetMaxOpenConns(runtime.NumCPU())
this.readDB = dbs.NewDB(readDB)
this.readDB = readDB
if teaconst.EnableDBStat {
this.readDB.EnableStat(true)
@@ -180,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`)
if err != nil {
return err
}
this.deleteByHashSQL = `DELETE FROM "` + this.itemsTableName + `" WHERE "hash"=?`
this.deleteByHashStmt, err = this.writeDB.Prepare(this.deleteByHashSQL)

View File

@@ -3,6 +3,7 @@ package caches
import (
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/shared"
teaconst "github.com/TeaOSLab/EdgeNode/internal/const"
"github.com/TeaOSLab/EdgeNode/internal/events"
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
"github.com/iwind/TeaGo/lists"
@@ -14,6 +15,10 @@ import (
var SharedManager = NewManager()
func init() {
if !teaconst.IsMain {
return
}
events.On(events.EventQuit, func() {
remotelogs.Println("CACHE", "quiting cache manager")
SharedManager.UpdatePolicies([]*serverconfigs.HTTPCachePolicy{})

View File

@@ -129,6 +129,9 @@ func (this *OpenFileCache) Close(filename string) {
pool, ok := this.poolMap[filename]
if ok {
// 设置关闭状态
pool.SetClosing()
delete(this.poolMap, filename)
this.poolList.Remove(pool.linkItem)
_ = this.watcher.Remove(filename)

View File

@@ -12,6 +12,7 @@ type OpenFilePool struct {
linkItem *linkedlist.Item
filename string
version int64
isClosed bool
}
func NewOpenFilePool(filename string) *OpenFilePool {
@@ -29,26 +30,43 @@ func (this *OpenFilePool) Filename() string {
}
func (this *OpenFilePool) Get() (*OpenFile, bool) {
// 如果已经关闭,直接返回
if this.isClosed {
return nil, false
}
select {
case file := <-this.c:
err := file.SeekStart()
if err != nil {
_ = file.Close()
return nil, true
}
file.version = this.version
if file != nil {
err := file.SeekStart()
if err != nil {
_ = file.Close()
return nil, true
}
file.version = this.version
return file, true
return file, true
}
return nil, false
default:
return nil, false
}
}
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 {
_ = file.Close()
return false
}
// 加入Pool
select {
case this.c <- file:
return true
@@ -63,14 +81,18 @@ func (this *OpenFilePool) Len() int {
return len(this.c)
}
func (this *OpenFilePool) SetClosing() {
this.isClosed = true
}
func (this *OpenFilePool) Close() {
Loop:
this.isClosed = true
for {
select {
case file := <-this.c:
_ = file.Close()
default:
break Loop
return
}
}
}

View File

@@ -215,6 +215,10 @@ func (this *FileReader) ReadHeader(buf []byte, callback ReaderFunc) error {
}
func (this *FileReader) ReadBody(buf []byte, callback ReaderFunc) error {
if this.bodySize == 0 {
return nil
}
var isOk = false
defer func() {
@@ -257,6 +261,12 @@ func (this *FileReader) ReadBody(buf []byte, callback ReaderFunc) 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)
if err != nil && err != io.EOF {
_ = this.discard()

View File

@@ -710,9 +710,6 @@ func (this *FileStorage) Delete(key string) error {
return nil
}
this.locker.Lock()
defer this.locker.Unlock()
// 先尝试内存缓存
this.runMemoryStorageSafety(func(memoryStorage *MemoryStorage) {
_ = memoryStorage.Delete(key)
@@ -733,9 +730,6 @@ func (this *FileStorage) Delete(key string) error {
// Stat 统计
func (this *FileStorage) Stat() (*Stat, error) {
this.locker.RLock()
defer this.locker.RUnlock()
return this.list.Stat(func(hash string) bool {
return true
})
@@ -767,57 +761,61 @@ func (this *FileStorage) CleanAll() error {
}
}
var dirNameReg = regexp.MustCompile(`^[0-9a-f]{2}$`)
for _, rootDir := range rootDirs {
var dir = rootDir + "/p" + types.String(this.policy.Id)
fp, err := os.Open(dir)
if err != nil {
return err
}
defer func() {
_ = fp.Close()
}()
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
}
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())
}
})
if !stat.IsDir() {
return nil
}
// 改成待删除
subDirs, err := fp.Readdir(-1)
}(dir)
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
}
// 修改目录名
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
@@ -830,9 +828,6 @@ func (this *FileStorage) Purge(keys []string, urlType string) error {
return nil
}
this.locker.Lock()
defer this.locker.Unlock()
// 先尝试内存缓存
this.runMemoryStorageSafety(func(memoryStorage *MemoryStorage) {
_ = memoryStorage.Purge(keys, urlType)
@@ -1218,9 +1213,12 @@ func (this *FileStorage) hotLoop() {
}
err = reader.ReadBody(buf, func(n int) (goNext bool, err error) {
_, err = writer.Write(buf[:n])
if err == nil {
goNext = true
goNext = true
if n > 0 {
_, err = writer.Write(buf[:n])
if err != nil {
goNext = false
}
}
return
})

View File

@@ -508,7 +508,11 @@ func (this *MemoryStorage) flushItem(key string) {
if !ok {
return
}
if !item.IsDone || item.IsExpired() {
if !item.IsDone {
remotelogs.Error("CACHE", "flush items failed: open writer failed: item has not been done")
return
}
if item.IsExpired() {
return
}

View File

@@ -14,15 +14,22 @@ import (
)
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, -1, false)
if err != nil {
t.Fatal(err)
}
if err != nil {
t.Fatal(err)
}
_, _ = writer.WriteHeader([]byte("Header"))
_, _ = writer.Write([]byte("Hello"))
_, _ = writer.Write([]byte(", World"))
err = writer.Close()
if err != nil {
t.Fatal(err)
}
t.Log(storage.valuesMap)
{
@@ -30,6 +37,7 @@ func TestMemoryStorage_OpenWriter(t *testing.T) {
if err != nil {
if err == ErrNotFound {
t.Log("not found: abc")
return
} else {
t.Fatal(err)
}
@@ -102,13 +110,17 @@ func TestMemoryStorage_OpenReaderLock(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, -1, false)
if err != nil {
t.Fatal(err)
}
_, _ = writer.Write([]byte("Hello"))
err = writer.Close()
if err != nil {
t.Fatal(err)
}
t.Log(len(storage.valuesMap))
}
{
@@ -117,6 +129,10 @@ func TestMemoryStorage_Delete(t *testing.T) {
t.Fatal(err)
}
_, _ = writer.Write([]byte("Hello"))
err = writer.Close()
if err != nil {
t.Fatal(err)
}
t.Log(len(storage.valuesMap))
}
_ = storage.Delete("abc1")
@@ -124,7 +140,7 @@ func TestMemoryStorage_Delete(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
{
writer, err := storage.OpenWriter("abc", expiredAt, 200, -1, -1, -1, false)
@@ -132,6 +148,10 @@ func TestMemoryStorage_Stat(t *testing.T) {
t.Fatal(err)
}
_, _ = writer.Write([]byte("Hello"))
err = writer.Close()
if err != nil {
t.Fatal(err)
}
t.Log(len(storage.valuesMap))
storage.AddToList(&Item{
Key: "abc",
@@ -145,6 +165,10 @@ func TestMemoryStorage_Stat(t *testing.T) {
t.Fatal(err)
}
_, _ = writer.Write([]byte("Hello"))
err = writer.Close()
if err != nil {
t.Fatal(err)
}
t.Log(len(storage.valuesMap))
storage.AddToList(&Item{
Key: "abc1",
@@ -161,14 +185,18 @@ func TestMemoryStorage_Stat(t *testing.T) {
}
func TestMemoryStorage_CleanAll(t *testing.T) {
storage := NewMemoryStorage(&serverconfigs.HTTPCachePolicy{}, nil)
expiredAt := time.Now().Unix() + 60
var storage = NewMemoryStorage(&serverconfigs.HTTPCachePolicy{}, nil)
var expiredAt = time.Now().Unix() + 60
{
writer, err := storage.OpenWriter("abc", expiredAt, 200, -1, -1, -1, false)
if err != nil {
t.Fatal(err)
}
_, _ = writer.Write([]byte("Hello"))
err = writer.Close()
if err != nil {
t.Fatal(err)
}
storage.AddToList(&Item{
Key: "abc",
BodySize: 5,
@@ -181,6 +209,10 @@ func TestMemoryStorage_CleanAll(t *testing.T) {
t.Fatal(err)
}
_, _ = writer.Write([]byte("Hello"))
err = writer.Close()
if err != nil {
t.Fatal(err)
}
storage.AddToList(&Item{
Key: "abc1",
BodySize: 5,
@@ -204,6 +236,10 @@ func TestMemoryStorage_Purge(t *testing.T) {
t.Fatal(err)
}
_, _ = writer.Write([]byte("Hello"))
err = writer.Close()
if err != nil {
t.Fatal(err)
}
storage.AddToList(&Item{
Key: "abc",
BodySize: 5,
@@ -216,6 +252,10 @@ func TestMemoryStorage_Purge(t *testing.T) {
t.Fatal(err)
}
_, _ = writer.Write([]byte("Hello"))
err = writer.Close()
if err != nil {
t.Fatal(err)
}
storage.AddToList(&Item{
Key: "abc1",
BodySize: 5,
@@ -231,7 +271,7 @@ func TestMemoryStorage_Purge(t *testing.T) {
}
func TestMemoryStorage_Expire(t *testing.T) {
storage := NewMemoryStorage(&serverconfigs.HTTPCachePolicy{
var storage = NewMemoryStorage(&serverconfigs.HTTPCachePolicy{
MemoryAutoPurgeInterval: 5,
}, nil)
err := storage.Init()
@@ -247,6 +287,10 @@ func TestMemoryStorage_Expire(t *testing.T) {
t.Fatal(err)
}
_, _ = writer.Write([]byte("Hello"))
err = writer.Close()
if err != nil {
t.Fatal(err)
}
storage.AddToList(&Item{
Key: key,
BodySize: 5,
@@ -257,7 +301,7 @@ func TestMemoryStorage_Expire(t *testing.T) {
}
func TestMemoryStorage_Locker(t *testing.T) {
storage := NewMemoryStorage(&serverconfigs.HTTPCachePolicy{}, nil)
var storage = NewMemoryStorage(&serverconfigs.HTTPCachePolicy{}, nil)
err := storage.Init()
if err != nil {
t.Fatal(err)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

@@ -15,7 +15,7 @@ var (
NodeId int64 = 0
NodeIdString = ""
IsDaemon = len(os.Args) > 1 && os.Args[1] == "daemon"
IsMain = len(os.Args) == 1 || (len(os.Args) >= 2 && os.Args[1] == "pprof")
GlobalProductName = nodeconfigs.DefaultProductName

View File

@@ -9,6 +9,7 @@ import (
"errors"
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/ddosconfigs"
teaconst "github.com/TeaOSLab/EdgeNode/internal/const"
"github.com/TeaOSLab/EdgeNode/internal/events"
"github.com/TeaOSLab/EdgeNode/internal/firewalls/nftables"
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
@@ -27,6 +28,10 @@ import (
var SharedDDoSProtectionManager = NewDDoSProtectionManager()
func init() {
if !teaconst.IsMain {
return
}
events.On(events.EventReload, func() {
if nftablesInstance == nil {
return

View File

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

View File

@@ -24,7 +24,7 @@ import (
// check nft status, if being enabled we load it automatically
func init() {
if teaconst.IsDaemon {
if !teaconst.IsMain {
return
}

View File

@@ -5,6 +5,7 @@ package nftables
import (
"errors"
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
teaconst "github.com/TeaOSLab/EdgeNode/internal/const"
"github.com/TeaOSLab/EdgeNode/internal/events"
"github.com/TeaOSLab/EdgeNode/internal/goman"
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
@@ -17,6 +18,10 @@ import (
)
func init() {
if !teaconst.IsMain {
return
}
events.On(events.EventReload, func() {
// linux only
if runtime.GOOS != "linux" {

View File

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

View File

@@ -3,28 +3,27 @@
package iplibrary
import (
"database/sql"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeNode/internal/events"
"github.com/TeaOSLab/EdgeNode/internal/goman"
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
"github.com/TeaOSLab/EdgeNode/internal/utils/dbs"
"github.com/iwind/TeaGo/Tea"
_ "github.com/mattn/go-sqlite3"
"os"
"path/filepath"
"time"
)
type IPListDB struct {
db *sql.DB
db *dbs.DB
itemTableName string
deleteExpiredItemsStmt *sql.Stmt
deleteItemStmt *sql.Stmt
insertItemStmt *sql.Stmt
selectItemsStmt *sql.Stmt
selectMaxVersionStmt *sql.Stmt
deleteExpiredItemsStmt *dbs.Stmt
deleteItemStmt *dbs.Stmt
insertItemStmt *dbs.Stmt
selectItemsStmt *dbs.Stmt
selectMaxVersionStmt *dbs.Stmt
cleanTicker *time.Ticker
@@ -56,7 +55,7 @@ func (this *IPListDB) init() error {
var path = this.dir + "/ip_list.db"
db, err := sql.Open("sqlite3", "file:"+path+"?cache=shared&mode=rwc&_journal_mode=WAL&_sync=OFF")
db, err := dbs.OpenWriter("file:" + path + "?cache=shared&mode=rwc&_journal_mode=WAL&_sync=OFF&_locking_mode=EXCLUSIVE")
if err != nil {
return err
}

View File

@@ -20,7 +20,7 @@ var SharedIPListManager = NewIPListManager()
var IPListUpdateNotify = make(chan bool, 1)
func init() {
if teaconst.IsDaemon {
if !teaconst.IsMain {
return
}

View File

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

View File

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

View File

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

View File

@@ -3,6 +3,7 @@
package nodes
import (
"errors"
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs"
"github.com/TeaOSLab/EdgeNode/internal/conns"
@@ -25,17 +26,30 @@ import (
type ClientConn struct {
BaseClientConn
isTLS bool
hasDeadline bool
hasRead bool
createdAt int64
isTLS bool
isHTTP bool
hasRead bool
isLO bool // 是否为环路
isInAllowList 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, isInAllowList bool) net.Conn {
func NewClientConn(rawConn net.Conn, isHTTP bool, isTLS bool, isInAllowList bool) net.Conn {
// 是否为环路
var remoteAddr = rawConn.RemoteAddr().String()
var isLO = strings.HasPrefix(remoteAddr, "127.0.0.1:") || strings.HasPrefix(remoteAddr, "[::1]:")
@@ -43,11 +57,21 @@ func NewClientConn(rawConn net.Conn, isTLS bool, quickClose bool, isInAllowList
var conn = &ClientConn{
BaseClientConn: BaseClientConn{rawConn: rawConn},
isTLS: isTLS,
isHTTP: isHTTP,
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 可以在配置中设置此值
_ = conn.SetLinger(nodeconfigs.DefaultTCPLinger)
}
@@ -59,6 +83,18 @@ func NewClientConn(rawConn net.Conn, isTLS bool, quickClose bool, isInAllowList
}
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())
} else {
this.lastErr = nil
}
}()
}
// 环路直接读取
if this.isLO {
n, err = this.rawConn.Read(b)
@@ -68,34 +104,29 @@ func (this *ClientConn) Read(b []byte) (n int, err error) {
return
}
// TLS
if this.isTLS {
if !this.hasDeadline {
_ = this.rawConn.SetReadDeadline(time.Now().Add(time.Duration(nodeconfigs.DefaultTLSHandshakeTimeout) * time.Second)) // TODO 握手超时时间可以设置
this.hasDeadline = true
defer func() {
_ = this.rawConn.SetReadDeadline(time.Time{})
}()
}
// 设置读超时时间
if this.isHTTP && !this.isPersistent && !this.isShortReading && this.autoReadTimeout {
this.setHTTPReadTimeout()
}
// 开始读取
n, err = this.rawConn.Read(b)
if n > 0 {
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)
} else {
_ = this.SetLinger(nodeconfigs.DefaultTCPLinger)
}
// 忽略白名单和局域网
if !this.isInAllowList && !utils.IsLocalIP(this.RawIP()) {
if this.isHTTP && !this.isInAllowList && !utils.IsLocalIP(this.RawIP()) {
// SYN Flood检测
if this.serverId == 0 || !this.hasResetSYNFlood {
var synFloodConfig = sharedNodeConfig.SYNFloodConfig()
@@ -114,17 +145,67 @@ func (this *ClientConn) Read(b []byte) (n int, err error) {
}
func (this *ClientConn) Write(b []byte) (n int, err error) {
if len(b) == 0 {
return 0, nil
}
if this.isDebugging {
this.lastWriteAt = time.Now().Unix()
defer func() {
if err != nil {
this.lastErr = errors.New("write error: " + err.Error())
} else {
this.lastErr = nil
}
}()
}
// 设置写超时时间
if this.autoWriteTimeout {
// TODO L2 -> L1 写入时不限制时间
var timeoutSeconds = len(b) / 1024
if timeoutSeconds < 3 {
timeoutSeconds = 3
}
_ = this.rawConn.SetWriteDeadline(time.Now().Add(time.Duration(timeoutSeconds) * time.Second)) // TODO 时间可以设置
}
// 延长读超时时间
if this.isHTTP && !this.isPersistent && this.autoReadTimeout {
this.setHTTPReadTimeout()
}
// 开始写入
var before = time.Now()
n, err = this.rawConn.Write(b)
if n > 0 {
// 统计当前服务带宽
if this.serverId > 0 {
if !this.isLO || Tea.IsTesting() { // 环路不统计带宽,避免缓存预热等行为产生带宽
atomic.AddUint64(&teaconst.OutTrafficBytes, uint64(n))
stats.SharedBandwidthStatManager.Add(this.userId, this.serverId, int64(n))
var cost = time.Since(before).Seconds()
if cost > 1 {
stats.SharedBandwidthStatManager.Add(this.userId, this.serverId, int64(float64(n)/cost), int64(n))
} else {
stats.SharedBandwidthStatManager.Add(this.userId, this.serverId, int64(n), int64(n))
}
}
}
}
// 如果是写入超时,则立即关闭连接
if err != nil && os.IsTimeout(err) {
// TODO 考虑对多次慢连接的IP做出惩罚
conn, ok := this.rawConn.(LingerConn)
if ok {
_ = conn.SetLinger(0)
}
_ = this.Close()
}
return
}
@@ -156,6 +237,26 @@ func (this *ClientConn) SetDeadline(t time.Time) error {
}
func (this *ClientConn) SetReadDeadline(t time.Time) error {
// 如果开启了HTTP自动读超时选项则自动控制超时时间
if this.isHTTP && !this.isPersistent && this.autoReadTimeout {
this.isShortReading = false
var unixTime = t.Unix()
if unixTime < 10 {
return nil
}
if unixTime == this.readDeadlineTime {
return nil
}
this.readDeadlineTime = unixTime
var seconds = -time.Since(t)
if seconds <= 0 || seconds > HTTPIdleTimeout {
return nil
}
if seconds < HTTPIdleTimeout-1*time.Second {
this.isShortReading = true
}
}
return this.rawConn.SetReadDeadline(t)
}
@@ -163,6 +264,22 @@ func (this *ClientConn) SetWriteDeadline(t time.Time) error {
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() {
ttlcache.SharedCache.Delete("SYN_FLOOD:" + this.RawIP())
}
@@ -194,3 +311,8 @@ func (this *ClientConn) increaseSYNFlood(synFloodConfig *firewallconfigs.SYNFloo
}
}
}
// 设置读超时时间
func (this *ClientConn) setHTTPReadTimeout() {
_ = this.SetReadDeadline(time.Now().Add(HTTPIdleTimeout))
}

View File

@@ -16,6 +16,9 @@ type BaseClientConn struct {
remoteAddr string
hasLimit bool
isPersistent bool // 是否为持久化连接
fingerprint []byte
isClosed bool
rawIP string
@@ -122,3 +125,17 @@ func (this *BaseClientConn) SetLinger(seconds int) error {
}
return nil
}
func (this *BaseClientConn) SetIsPersistent(isPersistent bool) {
this.isPersistent = isPersistent
}
// SetFingerprint 设置指纹信息
func (this *BaseClientConn) SetFingerprint(fingerprint []byte) {
this.fingerprint = fingerprint
}
// Fingerprint 读取指纹信息
func (this *BaseClientConn) Fingerprint() []byte {
return this.fingerprint
}

View File

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

View File

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

View File

@@ -14,14 +14,14 @@ import (
// ClientListener 客户端网络监听
type ClientListener struct {
rawListener net.Listener
isHTTP bool
isTLS bool
quickClose bool
}
func NewClientListener(listener net.Listener, quickClose bool) *ClientListener {
func NewClientListener(listener net.Listener, isHTTP bool) *ClientListener {
return &ClientListener{
rawListener: listener,
quickClose: quickClose,
isHTTP: isHTTP,
}
}
@@ -78,7 +78,7 @@ func (this *ClientListener) Accept() (net.Conn, error) {
}
}
return NewClientConn(conn, this.isTLS, this.quickClose, isInAllowList), nil
return NewClientConn(conn, this.isHTTP, this.isTLS, isInAllowList), nil
}
func (this *ClientListener) Close() error {

View File

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

View File

@@ -25,9 +25,12 @@ type HTTPAccessLogQueue struct {
// NewHTTPAccessLogQueue 获取新对象
func NewHTTPAccessLogQueue() *HTTPAccessLogQueue {
// 队列中最大的值,超出此数量的访问日志会被丢弃
// TODO 需要可以在界面中设置
maxSize := 20000
queue := &HTTPAccessLogQueue{
var maxSize = 2_000 * (1 + utils.SystemMemoryGB()/2)
if maxSize > 20_000 {
maxSize = 20_000
}
var queue = &HTTPAccessLogQueue{
queue: make(chan *pb.HTTPAccessLog, maxSize),
}
goman.New(func() {

View File

@@ -9,6 +9,7 @@ import (
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeNode/internal/caches"
"github.com/TeaOSLab/EdgeNode/internal/compressions"
teaconst "github.com/TeaOSLab/EdgeNode/internal/const"
"github.com/TeaOSLab/EdgeNode/internal/events"
"github.com/TeaOSLab/EdgeNode/internal/goman"
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
@@ -23,6 +24,10 @@ import (
)
func init() {
if !teaconst.IsMain {
return
}
events.On(events.EventStart, func() {
goman.New(func() {
SharedHTTPCacheTaskManager.Start()

View File

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

View File

@@ -221,6 +221,18 @@ func (this *HTTPRequest) Do() {
}
}
// CC
if !isHealthCheck {
if this.web.CC != nil {
if this.web.CC.IsOn {
if this.doCC() {
this.doEnd()
return
}
}
}
}
// WAF
if this.web.FirewallRef != nil && this.web.FirewallRef.IsOn {
if this.doWAFRequest() {
@@ -237,6 +249,14 @@ func (this *HTTPRequest) Do() {
}
}
// 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.doAuth() {
@@ -526,6 +546,11 @@ func (this *HTTPRequest) configureWeb(web *serverconfigs.HTTPWebConfig, isTop bo
this.web.Referers = web.Referers
}
// user agent
if web.UserAgent != nil && (web.UserAgent.IsPrior || isTop) {
this.web.UserAgent = web.UserAgent
}
// request limit
if web.RequestLimit != nil && (web.RequestLimit.IsPrior || isTop) {
this.web.RequestLimit = web.RequestLimit
@@ -559,6 +584,11 @@ func (this *HTTPRequest) configureWeb(web *serverconfigs.HTTPWebConfig, isTop bo
this.web.UAM = web.UAM
}
// CC
if web.CC != nil && (web.CC.IsPrior || isTop) {
this.web.CC = web.CC
}
// 重写规则
if len(web.RewriteRefs) > 0 {
for index, ref := range web.RewriteRefs {
@@ -715,6 +745,8 @@ func (this *HTTPRequest) Format(source string) string {
return this.Path()
case "requestPathExtension":
return filepath.Ext(this.Path())
case "requestPathLowerExtension":
return strings.ToLower(filepath.Ext(this.Path()))
case "requestLength":
return strconv.FormatInt(this.requestLength(), 10)
case "requestTime":
@@ -828,7 +860,7 @@ func (this *HTTPRequest) Format(source string) string {
}
// response.xxx.xxx
dotIndex := strings.Index(suffix, ".")
dotIndex = strings.Index(suffix, ".")
if dotIndex < 0 {
return "${" + varName + "}"
}
@@ -1133,6 +1165,8 @@ func (this *HTTPRequest) requestRemoteAddr(supportVar bool) string {
// 获取请求的客户端地址列表
func (this *HTTPRequest) requestRemoteAddrs() (result []string) {
result = append(result, this.requestRemoteAddr(true))
// X-Forwarded-For
var forwardedFor = this.RawReq.Header.Get("X-Forwarded-For")
if len(forwardedFor) > 0 {
@@ -1554,7 +1588,7 @@ func (this *HTTPRequest) processRequestHeaders(reqHeader http.Header) {
}
// 是否已删除
if this.web.ResponseHeaderPolicy.ContainsDeletedHeader(header.Name) {
if this.web.RequestHeaderPolicy.ContainsDeletedHeader(header.Name) {
continue
}
@@ -1692,6 +1726,36 @@ func (this *HTTPRequest) processResponseHeaders(responseHeader http.Header, stat
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

View File

@@ -0,0 +1,8 @@
// Copyright 2023 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
//go:build !plus
package nodes
func (this *HTTPRequest) doCC() (block bool) {
return
}

View File

@@ -73,6 +73,16 @@ func (this *HTTPRequest) doFastcgi() (shouldStop bool) {
}
}
// 设置为持久化连接
var requestConn = this.RawReq.Context().Value(HTTPConnContextKey)
if requestConn == nil {
return
}
requestClientConn, ok := requestConn.(ClientConnInterface)
if ok {
requestClientConn.SetIsPersistent(true)
}
// 连接池配置
poolSize := fastcgi.PoolSize
if poolSize <= 0 {

View File

@@ -146,6 +146,13 @@ func (this *HTTPRequest) doHostRedirect() (blocked bool) {
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
}

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.AddUserAgent(this.ReqServer.Id, this.requestHeader("User-Agent"))
stats.SharedHTTPRequestStatManager.AddUserAgent(this.ReqServer.Id, this.requestHeader("User-Agent"), this.remoteAddr)
}

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

@@ -67,7 +67,7 @@ func (this *HTTPRequest) doWAFRequest() (blocked bool) {
// 当前服务的独立设置
if this.web.FirewallPolicy != nil && this.web.FirewallPolicy.IsOn {
blocked, breakChecking := this.checkWAFRequest(this.web.FirewallPolicy, forceLog, forceLogRequestBody, forceLogRegionDenying)
blocked, breakChecking := this.checkWAFRequest(this.web.FirewallPolicy, forceLog, forceLogRequestBody, forceLogRegionDenying, false)
if blocked {
return true
}
@@ -78,7 +78,7 @@ func (this *HTTPRequest) doWAFRequest() (blocked bool) {
// 公用的防火墙设置
if this.ReqServer.HTTPFirewallPolicy != nil && this.ReqServer.HTTPFirewallPolicy.IsOn {
blocked, breakChecking := this.checkWAFRequest(this.ReqServer.HTTPFirewallPolicy, forceLog, forceLogRequestBody, forceLogRegionDenying)
blocked, breakChecking := this.checkWAFRequest(this.ReqServer.HTTPFirewallPolicy, forceLog, forceLogRequestBody, forceLogRegionDenying, this.web.FirewallRef.IgnoreGlobalRules)
if blocked {
return true
}
@@ -90,7 +90,7 @@ func (this *HTTPRequest) doWAFRequest() (blocked bool) {
return
}
func (this *HTTPRequest) checkWAFRequest(firewallPolicy *firewallconfigs.HTTPFirewallPolicy, forceLog bool, logRequestBody bool, logDenying bool) (blocked bool, breakChecking bool) {
func (this *HTTPRequest) checkWAFRequest(firewallPolicy *firewallconfigs.HTTPFirewallPolicy, forceLog bool, logRequestBody bool, logDenying bool, ignoreRules bool) (blocked bool, breakChecking bool) {
// 检查配置是否为空
if firewallPolicy == nil || !firewallPolicy.IsOn || firewallPolicy.Inbound == nil || !firewallPolicy.Inbound.IsOn || firewallPolicy.Mode == firewallconfigs.FirewallModeBypass {
return
@@ -211,8 +211,13 @@ func (this *HTTPRequest) checkWAFRequest(firewallPolicy *firewallconfigs.HTTPFir
}
}
// 是否执行规则
if ignoreRules {
return
}
// 规则测试
w := waf.SharedWAFManager.FindWAF(firewallPolicy.Id)
var w = waf.SharedWAFManager.FindWAF(firewallPolicy.Id)
if w == nil {
return
}
@@ -267,7 +272,7 @@ func (this *HTTPRequest) doWAFResponse(resp *http.Response) (blocked bool) {
}
if this.web.FirewallPolicy != nil && this.web.FirewallPolicy.IsOn {
blocked := this.checkWAFResponse(this.web.FirewallPolicy, resp, forceLog, forceLogRequestBody)
blocked := this.checkWAFResponse(this.web.FirewallPolicy, resp, forceLog, forceLogRequestBody, false)
if blocked {
return true
}
@@ -275,7 +280,7 @@ func (this *HTTPRequest) doWAFResponse(resp *http.Response) (blocked bool) {
// 公用的防火墙设置
if this.ReqServer.HTTPFirewallPolicy != nil && this.ReqServer.HTTPFirewallPolicy.IsOn {
blocked := this.checkWAFResponse(this.ReqServer.HTTPFirewallPolicy, resp, forceLog, forceLogRequestBody)
blocked := this.checkWAFResponse(this.ReqServer.HTTPFirewallPolicy, resp, forceLog, forceLogRequestBody, this.web.FirewallRef.IgnoreGlobalRules)
if blocked {
return true
}
@@ -283,12 +288,17 @@ func (this *HTTPRequest) doWAFResponse(resp *http.Response) (blocked bool) {
return
}
func (this *HTTPRequest) checkWAFResponse(firewallPolicy *firewallconfigs.HTTPFirewallPolicy, resp *http.Response, forceLog bool, logRequestBody bool) (blocked bool) {
func (this *HTTPRequest) checkWAFResponse(firewallPolicy *firewallconfigs.HTTPFirewallPolicy, resp *http.Response, forceLog bool, logRequestBody bool, ignoreRules bool) (blocked bool) {
if firewallPolicy == nil || !firewallPolicy.IsOn || !firewallPolicy.Outbound.IsOn || firewallPolicy.Mode == firewallconfigs.FirewallModeBypass {
return
}
w := waf.SharedWAFManager.FindWAF(firewallPolicy.Id)
// 是否执行规则
if ignoreRules {
return
}
var w = waf.SharedWAFManager.FindWAF(firewallPolicy.Id)
if w == nil {
return
}
@@ -392,3 +402,22 @@ func (this *HTTPRequest) WAFOnAction(action interface{}) (goNext bool) {
}
return true
}
func (this *HTTPRequest) WAFFingerprint() []byte {
// 目前只有HTTPS请求才有指纹
if !this.IsHTTPS {
return nil
}
var requestConn = this.RawReq.Context().Value(HTTPConnContextKey)
if requestConn == nil {
return nil
}
clientConn, ok := requestConn.(ClientConnInterface)
if ok {
return clientConn.Fingerprint()
}
return nil
}

View File

@@ -70,6 +70,13 @@ func (this *HTTPRequest) doWebsocket(requestHost string, isLastRetry bool) (shou
this.RawReq.Header.Set("Origin", newRequestOrigin)
}
// 获取当前连接
var requestConn = this.RawReq.Context().Value(HTTPConnContextKey)
if requestConn == nil {
return
}
// 连接源站
// TODO 增加N次错误重试重试的时候需要尝试不同的源站
originConn, _, err := OriginConnect(this.origin, this.requestServerPort(), this.RawReq.RemoteAddr, requestHost)
if err != nil {
@@ -102,6 +109,11 @@ func (this *HTTPRequest) doWebsocket(requestHost string, isLastRetry bool) (shou
return
}
requestClientConn, ok := requestConn.(ClientConnInterface)
if ok {
requestClientConn.SetIsPersistent(true)
}
clientConn, _, err := this.writer.Hijack()
if err != nil || clientConn == nil {
this.write50x(err, http.StatusInternalServerError, "Failed to get origin site connection", "获取源站连接失败", false)

View File

@@ -10,6 +10,7 @@ import (
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
"github.com/TeaOSLab/EdgeNode/internal/caches"
"github.com/TeaOSLab/EdgeNode/internal/compressions"
teaconst "github.com/TeaOSLab/EdgeNode/internal/const"
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
"github.com/TeaOSLab/EdgeNode/internal/utils"
"github.com/TeaOSLab/EdgeNode/internal/utils/readers"
@@ -39,6 +40,10 @@ var webpMaxBufferSize int64 = 1_000_000_000
var webpTotalBufferSize int64 = 0
func init() {
if !teaconst.IsMain {
return
}
var systemMemory = utils.SystemMemoryGB() / 8
if systemMemory > 0 {
webpMaxBufferSize = int64(systemMemory) * 1024 * 1024 * 1024
@@ -99,6 +104,18 @@ func NewHTTPWriter(req *HTTPRequest, httpResponseWriter http.ResponseWriter) *HT
// Prepare 准备输出
func (this *HTTPWriter) Prepare(resp *http.Response, size int64, status int, enableCache bool) (delayHeaders bool) {
// 清理以前数据,防止重试时发生异常错误
if this.compressionCacheWriter != nil {
_ = this.compressionCacheWriter.Discard()
this.compressionCacheWriter = nil
}
if this.cacheWriter != nil {
_ = this.cacheWriter.Discard()
this.cacheWriter = nil
}
// 新的请求相关数据
this.size = size
this.statusCode = status
@@ -132,7 +149,7 @@ func (this *HTTPWriter) Prepare(resp *http.Response, size int64, status int, ena
this.req.web.RequestLimit != nil &&
this.req.web.RequestLimit.IsOn &&
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
@@ -315,6 +332,12 @@ func (this *HTTPWriter) PrepareCache(resp *http.Response, size int64) {
}
}
}
// 先清理以前的
if this.cacheWriter != nil {
_ = this.cacheWriter.Discard()
}
cacheWriter, err := storage.OpenWriter(cacheKey, expiresAt, this.StatusCode(), this.calculateHeaderLength(), totalSize, cacheRef.MaxSizeBytes(), this.isPartial)
if err != nil {
if err == caches.ErrEntityTooLarge && addStatusHeader {
@@ -584,6 +607,11 @@ func (this *HTTPWriter) PrepareCompression(resp *http.Response, size int64) {
return
}
// 分区内容不压缩,防止读取失败
if !this.compressionConfig.EnablePartialContent && this.StatusCode() == http.StatusPartialContent {
return
}
if this.compressionConfig.Level <= 0 {
return
}
@@ -686,6 +714,9 @@ func (this *HTTPWriter) PrepareCompression(resp *http.Response, size int64) {
}
if compressionCacheWriter != nil {
if this.compressionCacheWriter != nil {
_ = this.compressionCacheWriter.Close()
}
this.compressionCacheWriter = compressionCacheWriter
var teeWriter = writers.NewTeeWriterCloser(this.writer, compressionCacheWriter)
teeWriter.OnFail(func(err error) {

View File

@@ -36,7 +36,16 @@ func (this *BaseListener) buildTLSConfig() *tls.Config {
return &tls.Config{
Certificates: nil,
GetConfigForClient: func(clientInfo *tls.ClientHelloInfo) (config *tls.Config, e error) {
tlsPolicy, _, err := this.matchSSL(clientInfo.ServerName)
// 指纹信息
var fingerprint = this.calculateFingerprint(clientInfo)
if len(fingerprint) > 0 {
clientConn, ok := clientInfo.Conn.(ClientConnInterface)
if ok {
clientConn.SetFingerprint(fingerprint)
}
}
tlsPolicy, _, err := this.matchSSL(this.helloServerName(clientInfo))
if err != nil {
return nil, err
}
@@ -50,7 +59,16 @@ func (this *BaseListener) buildTLSConfig() *tls.Config {
return tlsPolicy.TLSConfig(), nil
},
GetCertificate: func(clientInfo *tls.ClientHelloInfo) (certificate *tls.Certificate, e error) {
tlsPolicy, cert, err := this.matchSSL(clientInfo.ServerName)
// 指纹信息
var fingerprint = this.calculateFingerprint(clientInfo)
if len(fingerprint) > 0 {
clientConn, ok := clientInfo.Conn.(ClientConnInterface)
if ok {
clientConn.SetFingerprint(fingerprint)
}
}
tlsPolicy, cert, err := this.matchSSL(this.helloServerName(clientInfo))
if err != nil {
return nil, err
}
@@ -182,3 +200,18 @@ func (this *BaseListener) findNamedServerMatched(name string) (serverConfig *ser
return nil, name
}
// 从Hello信息中获取服务名称
func (this *BaseListener) helloServerName(clientInfo *tls.ClientHelloInfo) string {
var serverName = clientInfo.ServerName
if len(serverName) == 0 {
var localAddr = clientInfo.Conn.LocalAddr()
if localAddr != nil {
tcpAddr, ok := localAddr.(*net.TCPAddr)
if ok {
serverName = tcpAddr.IP.String()
}
}
}
return serverName
}

View File

@@ -0,0 +1,10 @@
// Copyright 2023 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
//go:build !plus
package nodes
import "crypto/tls"
func (this *BaseListener) calculateFingerprint(clientInfo *tls.ClientHelloInfo) []byte {
return nil
}

View File

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

View File

@@ -18,6 +18,8 @@ import (
var httpErrorLogger = log.New(io.Discard, "", 0)
const HTTPIdleTimeout = 75 * time.Second
type contextKey struct {
key string
}
@@ -43,16 +45,12 @@ func (this *HTTPListener) Serve() error {
this.httpServer = &http.Server{
Addr: this.addr,
Handler: this,
ReadTimeout: 1 * time.Hour, // TODO 改成可以配置
ReadHeaderTimeout: 3 * time.Second, // TODO 改成可以配置
WriteTimeout: 2 * time.Hour, // TODO 改成可以配置
IdleTimeout: 75 * time.Second, // TODO 改成可以配置
ReadHeaderTimeout: 3 * time.Second, // TODO 改成可以配置
IdleTimeout: HTTPIdleTimeout, // TODO 改成可以配置
ConnState: func(conn net.Conn, state http.ConnState) {
switch state {
case http.StateNew:
atomic.AddInt64(&this.countActiveConnections, 1)
case http.StateActive, http.StateIdle, http.StateHijacked:
// Nothing to do
case http.StateClosed:
atomic.AddInt64(&this.countActiveConnections, -1)
}
@@ -116,8 +114,14 @@ func (this *HTTPListener) Reload(group *serverconfigs.ServerAddressGroup) {
// ServerHTTP 处理HTTP请求
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 = strings.TrimRight(rawReq.Host, ".")
var reqHost = strings.ToLower(strings.TrimRight(rawReq.Host, "."))
// TLS域名
if this.isIP(reqHost) {
@@ -222,14 +226,21 @@ func (this *HTTPListener) emptyServer() *serverconfigs.ServerConfig {
Type: serverconfigs.ServerTypeHTTPProxy,
}
var accessLogRef = serverconfigs.NewHTTPAccessLogRef()
// TODO 需要配置是否记录日志
accessLogRef.IsOn = true
accessLogRef.Fields = append([]int{}, serverconfigs.HTTPAccessLogDefaultFieldsCodes...)
server.Web = &serverconfigs.HTTPWebConfig{
IsOn: true,
AccessLogRef: accessLogRef,
// 检查是否开启访问日志
if sharedNodeConfig != nil {
var globalServerConfig = sharedNodeConfig.GlobalServerConfig
if globalServerConfig != nil && globalServerConfig.HTTPAccessLog.EnableServerNotFound {
var accessLogRef = serverconfigs.NewHTTPAccessLogRef()
accessLogRef.IsOn = true
accessLogRef.Fields = append([]int{}, serverconfigs.HTTPAccessLogDefaultFieldsCodes...)
server.Web = &serverconfigs.HTTPWebConfig{
IsOn: true,
AccessLogRef: accessLogRef,
}
}
}
// TODO 需要对访问频率过多的IP进行惩罚
return server
}

View File

@@ -4,6 +4,7 @@ import (
"errors"
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
teaconst "github.com/TeaOSLab/EdgeNode/internal/const"
"github.com/TeaOSLab/EdgeNode/internal/firewalls"
"github.com/TeaOSLab/EdgeNode/internal/goman"
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
@@ -23,7 +24,15 @@ import (
"time"
)
var sharedListenerManager = NewListenerManager()
var sharedListenerManager *ListenerManager
func init() {
if !teaconst.IsMain {
return
}
sharedListenerManager = NewListenerManager()
}
// ListenerManager 端口监听管理器
type ListenerManager struct {

View File

@@ -404,7 +404,7 @@ func NewUDPConn(server *serverconfigs.ServerConfig, addr net.Addr, proxyListener
stats.SharedTrafficStatManager.Add(server.Id, "", int64(n), 0, 0, 0, 0, 0, server.ShouldCheckTrafficLimit(), server.PlanId())
// 带宽
stats.SharedBandwidthStatManager.Add(server.UserId, server.Id, int64(n))
stats.SharedBandwidthStatManager.Add(server.UserId, server.Id, int64(n), int64(n))
}
}
if err != nil {

View File

@@ -25,7 +25,8 @@ import (
"github.com/TeaOSLab/EdgeNode/internal/stats"
"github.com/TeaOSLab/EdgeNode/internal/trackers"
"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/andybalholm/brotli"
@@ -235,8 +236,6 @@ func (this *Node) Start() {
// Daemon 实现守护进程
func (this *Node) Daemon() {
teaconst.IsDaemon = true
var isDebug = lists.ContainsString(os.Args, "debug")
for {
conn, err := this.sock.Dial()
@@ -879,16 +878,56 @@ func (this *Node) listenSock() error {
},
})
case "conns":
var addrs = []string{}
var connMaps = []maps.Map{}
var connMap = conns.SharedMap.AllConns()
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{
Params: map[string]interface{}{
"addrs": addrs,
"total": len(addrs),
"conns": connMaps,
"total": len(connMaps),
},
})
case "dropIP":
@@ -920,6 +959,11 @@ func (this *Node) listenSock() error {
} else {
_ = cmd.ReplyOk()
}
case "closeIP":
var m = maps.NewMap(cmd.Params)
var ip = m.GetString("ip")
conns.SharedMap.CloseIPConns(ip)
_ = cmd.ReplyOk()
case "removeIP":
var m = maps.NewMap(cmd.Params)
var ip = m.GetString("ip")
@@ -987,50 +1031,48 @@ func (this *Node) onReload(config *nodeconfigs.NodeConfig, reloadAll bool) {
nodeconfigs.ResetNodeConfig(config)
sharedNodeConfig = config
// 不需要每次都全部重新加载
if !reloadAll {
return
}
// 缓存策略
var subDirs = config.CacheDiskSubDirs
for _, subDir := range subDirs {
subDir.Path = filepath.Clean(subDir.Path)
}
if len(subDirs) > 0 {
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())
if reloadAll {
// 缓存策略
var subDirs = config.CacheDiskSubDirs
for _, subDir := range subDirs {
subDir.Path = filepath.Clean(subDir.Path)
}
if len(subDirs) > 0 {
sort.Slice(subDirs, func(i, j int) bool {
return subDirs[i].Path < subDirs[j].Path
})
}
// update
if len(config.HTTPCachePolicies) > 0 {
caches.SharedManager.UpdatePolicies(config.HTTPCachePolicies)
} else {
caches.SharedManager.UpdatePolicies([]*serverconfigs.HTTPCachePolicy{})
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策略所以需要整体更新
var allFirewallPolicies = config.FindAllFirewallPolicies()
if !jsonutils.Equal(allFirewallPolicies, this.oldHTTPFirewallPolicies) {
// copy
@@ -1044,105 +1086,107 @@ func (this *Node) onReload(config *nodeconfigs.NodeConfig, reloadAll bool) {
waf.SharedWAFManager.UpdatePolicies(allFirewallPolicies)
}
if !jsonutils.Equal(config.FirewallActions, this.oldFirewallActions) {
// copy
this.oldFirewallActions = []*firewallconfigs.FirewallActionConfig{}
err := jsonutils.Copy(&this.oldFirewallActions, config.FirewallActions)
if err != nil {
remotelogs.Error("NODE", "onReload: copy FirewallActionConfigs failed: "+err.Error())
if reloadAll {
if !jsonutils.Equal(config.FirewallActions, this.oldFirewallActions) {
// copy
this.oldFirewallActions = []*firewallconfigs.FirewallActionConfig{}
err := jsonutils.Copy(&this.oldFirewallActions, config.FirewallActions)
if err != nil {
remotelogs.Error("NODE", "onReload: copy FirewallActionConfigs failed: "+err.Error())
}
// update
iplibrary.SharedActionManager.UpdateActions(config.FirewallActions)
}
// update
iplibrary.SharedActionManager.UpdateActions(config.FirewallActions)
}
// 统计指标
if !jsonutils.Equal(this.oldMetricItems, config.MetricItems) {
// copy
this.oldMetricItems = []*serverconfigs.MetricItemConfig{}
err := jsonutils.Copy(&this.oldMetricItems, config.MetricItems)
if err != nil {
remotelogs.Error("NODE", "onReload: copy MetricItemConfigs failed: "+err.Error())
}
// 统计指标
if !jsonutils.Equal(this.oldMetricItems, config.MetricItems) {
// copy
this.oldMetricItems = []*serverconfigs.MetricItemConfig{}
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)
}
// 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)+"'")
}
// 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)+"'")
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 {
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")
err := os.Setenv("GODEBUG", "netdns=go")
if err != nil {
remotelogs.Error("NODE", "[DNS_RESOLVER]set env failed: "+err.Error())
}
}
if err != nil {
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)
// API Node地址这里不限制是否为空因为在为空时仍然要有对应的处理
this.changeAPINodeAddrs(config.APINodeAddrs)
}
}
// reload server config

View File

@@ -8,6 +8,8 @@ import (
"github.com/iwind/TeaGo/maps"
"github.com/shirou/gopsutil/v3/load"
"github.com/shirou/gopsutil/v3/mem"
"runtime"
"runtime/debug"
)
// 更新内存
@@ -31,6 +33,18 @@ func (this *NodeStatusExecutor) updateMem(status *nodeconfigs.NodeStatus) {
"total": status.MemoryTotal,
"used": stat.Used,
})
// 内存严重不足时自动释放内存
if stat.Total > 0 {
var minFreeMemory = stat.Total / 8
if minFreeMemory > 1<<30 {
minFreeMemory = 1 << 30
}
if stat.Free < minFreeMemory {
runtime.GC()
debug.FreeOSMemory()
}
}
}
// 更新负载

View File

@@ -4,6 +4,7 @@ package nodes
import (
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
teaconst "github.com/TeaOSLab/EdgeNode/internal/const"
"github.com/TeaOSLab/EdgeNode/internal/events"
"github.com/TeaOSLab/EdgeNode/internal/goman"
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
@@ -16,6 +17,10 @@ import (
var SharedOriginStateManager = NewOriginStateManager()
func init() {
if !teaconst.IsMain {
return
}
events.On(events.EventLoaded, func() {
goman.New(func() {
SharedOriginStateManager.Start()

View File

@@ -19,6 +19,10 @@ import (
)
func init() {
if !teaconst.IsMain {
return
}
var manager = NewSystemServiceManager()
events.On(events.EventReload, func() {
goman.New(func() {

View File

@@ -4,6 +4,7 @@ package nodes
import (
"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/goman"
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
@@ -15,6 +16,10 @@ import (
var sharedOCSPTask = NewOCSPUpdateTask()
func init() {
if !teaconst.IsMain {
return
}
events.On(events.EventLoaded, func() {
sharedOCSPTask.version = sharedNodeConfig.OCSPVersion

View File

@@ -3,6 +3,7 @@ package nodes
import (
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeNode/internal/configs"
teaconst "github.com/TeaOSLab/EdgeNode/internal/const"
"github.com/TeaOSLab/EdgeNode/internal/events"
"github.com/TeaOSLab/EdgeNode/internal/goman"
"github.com/TeaOSLab/EdgeNode/internal/rpc"
@@ -16,6 +17,10 @@ import (
var sharedSyncAPINodesTask = NewSyncAPINodesTask()
func init() {
if !teaconst.IsMain {
return
}
events.On(events.EventStart, func() {
goman.New(func() {
sharedSyncAPINodesTask.Start()

View File

@@ -3,6 +3,7 @@ package nodes
import (
"errors"
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
teaconst "github.com/TeaOSLab/EdgeNode/internal/const"
"github.com/TeaOSLab/EdgeNode/internal/events"
"github.com/TeaOSLab/EdgeNode/internal/goman"
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
@@ -17,6 +18,10 @@ import (
var sharedTOAManager = NewTOAManager()
func init() {
if !teaconst.IsMain {
return
}
events.On(events.EventReload, func() {
err := sharedTOAManager.Run(sharedNodeConfig.TOA)
if err != nil {

View File

@@ -57,6 +57,79 @@ func TestRegexp_ParseKeywords(t *testing.T) {
}
}
func TestRegexp_Special(t *testing.T) {
var unescape = func(v string) string {
//replace urlencoded characters
var chars = [][2]string{
{`\s`, `(\s|%09|%0A|\+)`},
{`\(`, `(\(|%28)`},
{`=`, `(=|%3D)`},
{`<`, `(<|%3C)`},
{`\*`, `(\*|%2A)`},
{`\\`, `(\\|%2F)`},
{`!`, `(!|%21)`},
{`/`, `(/|%2F)`},
{`;`, `(;|%3B)`},
{`\+`, `(\+|%20)`},
}
for _, c := range chars {
if !strings.Contains(v, c[0]) {
continue
}
var pieces = strings.Split(v, c[0])
// 修复piece中错误的\
for pieceIndex, piece := range pieces {
var l = len(piece)
if l == 0 {
continue
}
if piece[l-1] != '\\' {
continue
}
// 计算\的数量
var countBackSlashes = 0
for i := l - 1; i >= 0; i-- {
if piece[i] == '\\' {
countBackSlashes++
} else {
break
}
}
if countBackSlashes%2 == 1 {
// 去掉最后一个
pieces[pieceIndex] = piece[:len(piece)-1]
}
}
v = strings.Join(pieces, c[1])
}
return v
}
for _, s := range []string{
`\\s`,
`\s\W`,
`aaaa/\W`,
`aaaa\/\W`,
`aaaa\=\W`,
`aaaa\\=\W`,
`aaaa\\\=\W`,
`aaaa\\\\=\W`,
} {
var es = unescape(s)
t.Log(s, "=>", es)
_, err := re.Compile(es)
if err != nil {
t.Fatal(err)
}
}
}
func TestRegexp_ParseKeywords2(t *testing.T) {
var a = assert.NewAssertion(t)

View File

@@ -20,6 +20,10 @@ import (
var logChan = make(chan *pb.NodeLog, 64) // 队列数量不需要太长,因为日志通常仅仅为调试用
func init() {
if !teaconst.IsMain {
return
}
// 定期上传日志
var ticker = time.NewTicker(60 * time.Second)
if Tea.IsTesting() {

View File

@@ -50,6 +50,7 @@ type RPCClient struct {
SSLCertRPC pb.SSLCertServiceClient
ScriptRPC pb.ScriptServiceClient
UserRPC pb.UserServiceClient
ClientAgentIPRPC pb.ClientAgentIPServiceClient
}
func NewRPCClient(apiConfig *configs.APIConfig) (*RPCClient, error) {
@@ -83,6 +84,7 @@ func NewRPCClient(apiConfig *configs.APIConfig) (*RPCClient, error) {
client.SSLCertRPC = pb.NewSSLCertServiceClient(client)
client.ScriptRPC = pb.NewScriptServiceClient(client)
client.UserRPC = pb.NewUserServiceClient(client)
client.ClientAgentIPRPC = pb.NewClientAgentIPServiceClient(client)
err := client.init()
if err != nil {

View File

@@ -5,6 +5,7 @@ package stats
import (
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
"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/goman"
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
@@ -21,6 +22,10 @@ var SharedBandwidthStatManager = NewBandwidthStatManager()
const bandwidthTimestampDelim = 2 // N秒平均更为精确
func init() {
if !teaconst.IsMain {
return
}
events.On(events.EventLoaded, func() {
goman.New(func() {
SharedBandwidthStatManager.Start()
@@ -37,6 +42,7 @@ type BandwidthStat struct {
CurrentBytes int64
CurrentTimestamp int64
MaxBytes int64
TotalBytes int64
}
// BandwidthStatManager 服务带宽统计
@@ -107,6 +113,7 @@ func (this *BandwidthStatManager) Loop() error {
Day: stat.Day,
TimeAt: stat.TimeAt,
Bytes: stat.MaxBytes / bandwidthTimestampDelim,
TotalBytes: stat.TotalBytes,
NodeRegionId: regionId,
})
delete(this.m, key)
@@ -132,8 +139,8 @@ func (this *BandwidthStatManager) Loop() error {
}
// Add 添加带宽数据
func (this *BandwidthStatManager) Add(userId int64, serverId int64, bytes int64) {
if serverId <= 0 || bytes == 0 {
func (this *BandwidthStatManager) Add(userId int64, serverId int64, peekBytes int64, totalBytes int64) {
if serverId <= 0 || (peekBytes == 0 && totalBytes == 0) {
return
}
@@ -146,8 +153,8 @@ func (this *BandwidthStatManager) Add(userId int64, serverId int64, bytes int64)
// 增加TCP Header尺寸这里默认MTU为1500且默认为IPv4
const mtu = 1500
const tcpHeaderSize = 20
if bytes > mtu {
bytes += bytes * tcpHeaderSize / mtu
if peekBytes > mtu {
peekBytes += peekBytes * tcpHeaderSize / mtu
}
this.locker.Lock()
@@ -156,22 +163,25 @@ func (this *BandwidthStatManager) Add(userId int64, serverId int64, bytes int64)
// 此刻如果发生用户IDuserId的变化也忽略等N分钟后有新记录后再换
if stat.CurrentTimestamp == timestamp {
stat.CurrentBytes += bytes
stat.CurrentBytes += peekBytes
} else {
stat.CurrentBytes = bytes
stat.CurrentBytes = peekBytes
stat.CurrentTimestamp = timestamp
}
if stat.CurrentBytes > stat.MaxBytes {
stat.MaxBytes = stat.CurrentBytes
}
stat.TotalBytes += totalBytes
} else {
this.m[key] = &BandwidthStat{
Day: day,
TimeAt: timeAt,
UserId: userId,
ServerId: serverId,
CurrentBytes: bytes,
MaxBytes: bytes,
CurrentBytes: peekBytes,
MaxBytes: peekBytes,
TotalBytes: totalBytes,
CurrentTimestamp: timestamp,
}
}

View File

@@ -10,22 +10,22 @@ import (
func TestBandwidthStatManager_Add(t *testing.T) {
var manager = stats.NewBandwidthStatManager()
manager.Add(1, 1, 10)
manager.Add(1, 1, 10)
manager.Add(1, 1, 10)
manager.Add(1, 1, 10, 10)
manager.Add(1, 1, 10, 10)
manager.Add(1, 1, 10, 10)
time.Sleep(1 * time.Second)
manager.Add(1, 1, 85)
manager.Add(1, 1, 85, 85)
time.Sleep(1 * time.Second)
manager.Add(1, 1, 25)
manager.Add(1, 1, 75)
manager.Add(1, 1, 25, 25)
manager.Add(1, 1, 75, 75)
manager.Inspect()
}
func TestBandwidthStatManager_Loop(t *testing.T) {
var manager = stats.NewBandwidthStatManager()
manager.Add(1, 1, 10)
manager.Add(1, 1, 10)
manager.Add(1, 1, 10)
manager.Add(1, 1, 10, 10)
manager.Add(1, 1, 10, 10)
manager.Add(1, 1, 10, 10)
err := manager.Loop()
if err != nil {
t.Fatal(err)

View File

@@ -11,13 +11,16 @@ import (
"github.com/TeaOSLab/EdgeNode/internal/rpc"
"github.com/TeaOSLab/EdgeNode/internal/trackers"
"github.com/TeaOSLab/EdgeNode/internal/utils"
"github.com/TeaOSLab/EdgeNode/internal/utils/agents"
"github.com/TeaOSLab/EdgeNode/internal/waf"
"github.com/iwind/TeaGo/Tea"
"github.com/iwind/TeaGo/maps"
"github.com/iwind/TeaGo/types"
timeutil "github.com/iwind/TeaGo/utils/time"
"sort"
"strconv"
"strings"
"sync"
"time"
)
@@ -44,7 +47,13 @@ type HTTPRequestStatManager struct {
dailyFirewallRuleGroupMap map[string]int64 // serverId@firewallRuleGroupId@action => count
serverCityCountMap map[string]int16 // serverIdString => count cities
serverSystemCountMap map[string]int16 // serverIdString => count systems
serverBrowserCountMap map[string]int16 // serverIdString => count browsers
totalAttackRequests int64
locker sync.Mutex
}
// NewHTTPRequestStatManager 获取新对象
@@ -58,6 +67,10 @@ func NewHTTPRequestStatManager() *HTTPRequestStatManager {
systemMap: map[string]int64{},
browserMap: map[string]int64{},
dailyFirewallRuleGroupMap: map[string]int64{},
serverCityCountMap: map[string]int16{},
serverSystemCountMap: map[string]int16{},
serverBrowserCountMap: map[string]int16{},
}
}
@@ -77,7 +90,6 @@ func (this *HTTPRequestStatManager) Start() {
}
})
var loopTicker = time.NewTicker(1 * time.Second)
var uploadTicker = time.NewTicker(30 * time.Minute)
if Tea.IsTesting() {
uploadTicker = time.NewTicker(10 * time.Second) // 在测试环境下缩短Ticker时间以方便我们调试
@@ -85,20 +97,12 @@ func (this *HTTPRequestStatManager) Start() {
remotelogs.Println("HTTP_REQUEST_STAT_MANAGER", "start ...")
events.OnKey(events.EventQuit, this, func() {
remotelogs.Println("HTTP_REQUEST_STAT_MANAGER", "quit")
loopTicker.Stop()
uploadTicker.Stop()
})
for range loopTicker.C {
err := this.Loop()
if err != nil {
if rpc.IsConnError(err) {
remotelogs.Warn("HTTP_REQUEST_STAT_MANAGER", err.Error())
} else {
remotelogs.Error("HTTP_REQUEST_STAT_MANAGER", err.Error())
}
}
select {
case <-uploadTicker.C:
// 上传Ticker
goman.New(func() {
for range uploadTicker.C {
var tr = trackers.Begin("UPLOAD_REQUEST_STATS")
err := this.Upload()
tr.End()
@@ -109,9 +113,20 @@ func (this *HTTPRequestStatManager) Start() {
remotelogs.Warn("HTTP_REQUEST_STAT_MANAGER", "upload failed: "+err.Error())
}
}
default:
}
})
// 分析Ticker
for {
err := this.Loop()
if err != nil {
if rpc.IsConnError(err) {
remotelogs.Warn("HTTP_REQUEST_STAT_MANAGER", err.Error())
} else {
remotelogs.Error("HTTP_REQUEST_STAT_MANAGER", err.Error())
}
}
}
}
@@ -146,11 +161,16 @@ func (this *HTTPRequestStatManager) AddRemoteAddr(serverId int64, remoteAddr str
}
// AddUserAgent 添加UserAgent
func (this *HTTPRequestStatManager) AddUserAgent(serverId int64, userAgent string) {
if len(userAgent) == 0 {
func (this *HTTPRequestStatManager) AddUserAgent(serverId int64, userAgent string, ip string) {
if len(userAgent) == 0 || strings.ContainsRune(userAgent, '@') /** 非常重要,防止后面组合字符串时出现异常 **/ {
return
}
// 是否包含一些知名Agent
if len(userAgent) > 0 && len(ip) > 0 && agents.IsAgentFromUserAgent(userAgent) {
agents.SharedQueue.Push(ip)
}
select {
case this.userAgentChan <- strconv.FormatInt(serverId, 10) + "@" + userAgent:
default:
@@ -177,75 +197,101 @@ func (this *HTTPRequestStatManager) AddFirewallRuleGroupId(serverId int64, firew
// Loop 单个循环
func (this *HTTPRequestStatManager) Loop() error {
var timeout = time.NewTimer(10 * time.Minute) // 执行的最大时间
Loop:
for {
select {
case ipString := <-this.ipChan:
// serverId@ip@bytes@isAttack
var pieces = strings.Split(ipString, "@")
if len(pieces) < 4 {
continue
}
var serverId = pieces[0]
var ip = pieces[1]
var result = iplib.LookupIP(ip)
if result != nil && result.IsOk() {
var key = serverId + "@" + result.CountryName() + "@" + result.ProvinceName() + "@" + result.CityName()
stat, ok := this.cityMap[key]
if !ok {
stat = &StatItem{}
this.cityMap[key] = stat
}
stat.Bytes += types.Int64(pieces[2])
stat.CountRequests++
if types.Int8(pieces[3]) == 1 {
stat.AttackBytes += types.Int64(pieces[2])
stat.CountAttackRequests++
}
if len(result.ProviderName()) > 0 {
this.providerMap[serverId+"@"+result.ProviderName()]++
}
}
case userAgentString := <-this.userAgentChan:
var atIndex = strings.Index(userAgentString, "@")
if atIndex < 0 {
continue
}
var serverId = userAgentString[:atIndex]
var userAgent = userAgentString[atIndex+1:]
var result = SharedUserAgentParser.Parse(userAgent)
var osInfo = result.OS
if len(osInfo.Name) > 0 {
dotIndex := strings.Index(osInfo.Version, ".")
if dotIndex > -1 {
osInfo.Version = osInfo.Version[:dotIndex]
}
this.systemMap[serverId+"@"+osInfo.Name+"@"+osInfo.Version]++
}
var browser, browserVersion = result.BrowserName, result.BrowserVersion
if len(browser) > 0 {
dotIndex := strings.Index(browserVersion, ".")
if dotIndex > -1 {
browserVersion = browserVersion[:dotIndex]
}
this.browserMap[serverId+"@"+browser+"@"+browserVersion]++
}
case firewallRuleGroupString := <-this.firewallRuleGroupChan:
this.dailyFirewallRuleGroupMap[firewallRuleGroupString]++
case <-timeout.C:
break Loop
default:
break Loop
select {
case ipString := <-this.ipChan:
// serverId@ip@bytes@isAttack
var pieces = strings.Split(ipString, "@")
if len(pieces) < 4 {
return nil
}
}
var serverId = pieces[0]
var ip = pieces[1]
timeout.Stop()
var result = iplib.LookupIP(ip)
if result != nil && result.IsOk() {
var key = serverId + "@" + types.String(result.CountryId()) + "@" + types.String(result.ProvinceId()) + "@" + types.String(result.CityId())
this.locker.Lock()
stat, ok := this.cityMap[key]
if !ok {
// 检查数量
if this.serverCityCountMap[key] > 128 { // 限制单个服务的城市数量,防止数量过多
this.locker.Unlock()
return nil
}
this.serverCityCountMap[key]++ // 需要放在限制之后因为使用的是int16
stat = &StatItem{}
this.cityMap[key] = stat
}
stat.Bytes += types.Int64(pieces[2])
stat.CountRequests++
if types.Int8(pieces[3]) == 1 {
stat.AttackBytes += types.Int64(pieces[2])
stat.CountAttackRequests++
}
if result.ProviderId() > 0 {
this.providerMap[serverId+"@"+types.String(result.ProviderId())]++
}
this.locker.Unlock()
}
case userAgentString := <-this.userAgentChan:
var atIndex = strings.Index(userAgentString, "@")
if atIndex < 0 {
return nil
}
var serverId = userAgentString[:atIndex]
var userAgent = userAgentString[atIndex+1:]
var result = SharedUserAgentParser.Parse(userAgent)
var osInfo = result.OS
if len(osInfo.Name) > 0 {
dotIndex := strings.Index(osInfo.Version, ".")
if dotIndex > -1 {
osInfo.Version = osInfo.Version[:dotIndex]
}
this.locker.Lock()
var systemKey = serverId + "@" + osInfo.Name + "@" + osInfo.Version
_, ok := this.systemMap[systemKey]
if !ok {
if this.serverSystemCountMap[serverId] < 128 { // 限制最大数据,防止攻击
this.serverSystemCountMap[serverId]++
ok = true
}
}
if ok {
this.systemMap[systemKey]++
}
this.locker.Unlock()
}
var browser, browserVersion = result.BrowserName, result.BrowserVersion
if len(browser) > 0 {
dotIndex := strings.Index(browserVersion, ".")
if dotIndex > -1 {
browserVersion = browserVersion[:dotIndex]
}
this.locker.Lock()
var browserKey = serverId + "@" + browser + "@" + browserVersion
_, ok := this.browserMap[browserKey]
if !ok {
if this.serverBrowserCountMap[serverId] < 256 { // 限制最大数据,防止攻击
this.serverBrowserCountMap[serverId]++
ok = true
}
}
if ok {
this.browserMap[browserKey]++
}
this.locker.Unlock()
}
case firewallRuleGroupString := <-this.firewallRuleGroupChan:
this.locker.Lock()
this.dailyFirewallRuleGroupMap[firewallRuleGroupString]++
this.locker.Unlock()
}
return nil
}
@@ -258,54 +304,178 @@ func (this *HTTPRequestStatManager) Upload() error {
return err
}
// 月份相关
// 拷贝数据
this.locker.Lock()
var cityMap = this.cityMap
var providerMap = this.providerMap
var systemMap = this.systemMap
var browserMap = this.browserMap
var dailyFirewallRuleGroupMap = this.dailyFirewallRuleGroupMap
this.cityMap = map[string]*StatItem{}
this.providerMap = map[string]int64{}
this.systemMap = map[string]int64{}
this.browserMap = map[string]int64{}
this.dailyFirewallRuleGroupMap = map[string]int64{}
this.serverCityCountMap = map[string]int16{}
this.serverSystemCountMap = map[string]int16{}
this.serverBrowserCountMap = map[string]int16{}
this.locker.Unlock()
// 上传限制
var maxCities int16 = 32
var maxProviders int16 = 32
var maxSystems int16 = 64
var maxBrowsers int16 = 64
nodeConfig, _ := nodeconfigs.SharedNodeConfig()
if nodeConfig != nil {
var serverConfig = nodeConfig.GlobalServerConfig // 复制是为了防止在中途修改
if serverConfig != nil {
var uploadConfig = serverConfig.Stat.Upload
if uploadConfig.MaxCities > 0 {
maxCities = uploadConfig.MaxCities
}
if uploadConfig.MaxProviders > 0 {
maxProviders = uploadConfig.MaxProviders
}
if uploadConfig.MaxSystems > 0 {
maxSystems = uploadConfig.MaxSystems
}
if uploadConfig.MaxBrowsers > 0 {
maxBrowsers = uploadConfig.MaxBrowsers
}
}
}
var pbCities = []*pb.UploadServerHTTPRequestStatRequest_RegionCity{}
var pbProviders = []*pb.UploadServerHTTPRequestStatRequest_RegionProvider{}
var pbSystems = []*pb.UploadServerHTTPRequestStatRequest_System{}
var pbBrowsers = []*pb.UploadServerHTTPRequestStatRequest_Browser{}
for k, stat := range this.cityMap {
// 城市
for k, stat := range cityMap {
var pieces = strings.SplitN(k, "@", 4)
var serverId = types.Int64(pieces[0])
pbCities = append(pbCities, &pb.UploadServerHTTPRequestStatRequest_RegionCity{
ServerId: types.Int64(pieces[0]),
CountryName: pieces[1],
ProvinceName: pieces[2],
CityName: pieces[3],
ServerId: serverId,
CountryId: types.Int64(pieces[1]),
ProvinceId: types.Int64(pieces[2]),
CityId: types.Int64(pieces[3]),
CountRequests: stat.CountRequests,
CountAttackRequests: stat.CountAttackRequests,
Bytes: stat.Bytes,
AttackBytes: stat.AttackBytes,
})
}
for k, count := range this.providerMap {
if len(cityMap) > int(maxCities) {
var newPBCities = []*pb.UploadServerHTTPRequestStatRequest_RegionCity{}
sort.Slice(pbCities, func(i, j int) bool {
return pbCities[i].CountRequests > pbCities[j].CountRequests
})
var serverCountMap = map[int64]int16{}
for _, city := range pbCities {
serverCountMap[city.ServerId]++
if serverCountMap[city.ServerId] > maxCities {
continue
}
newPBCities = append(newPBCities, city)
}
if len(pbCities) != len(newPBCities) {
pbCities = newPBCities
}
}
// 运营商
for k, count := range providerMap {
var pieces = strings.SplitN(k, "@", 2)
var serverId = types.Int64(pieces[0])
pbProviders = append(pbProviders, &pb.UploadServerHTTPRequestStatRequest_RegionProvider{
ServerId: types.Int64(pieces[0]),
Name: pieces[1],
Count: count,
ServerId: serverId,
ProviderId: types.Int64(pieces[1]),
Count: count,
})
}
for k, count := range this.systemMap {
if len(providerMap) > int(maxProviders) {
var newPBProviders = []*pb.UploadServerHTTPRequestStatRequest_RegionProvider{}
sort.Slice(pbProviders, func(i, j int) bool {
return pbProviders[i].Count > pbProviders[j].Count
})
var serverCountMap = map[int64]int16{}
for _, provider := range pbProviders {
serverCountMap[provider.ServerId]++
if serverCountMap[provider.ServerId] > maxProviders {
continue
}
newPBProviders = append(newPBProviders, provider)
}
if len(pbProviders) != len(newPBProviders) {
pbProviders = newPBProviders
}
}
// 操作系统
for k, count := range systemMap {
var pieces = strings.SplitN(k, "@", 3)
var serverId = types.Int64(pieces[0])
pbSystems = append(pbSystems, &pb.UploadServerHTTPRequestStatRequest_System{
ServerId: types.Int64(pieces[0]),
ServerId: serverId,
Name: pieces[1],
Version: pieces[2],
Count: count,
})
}
for k, count := range this.browserMap {
if len(systemMap) > int(maxSystems) {
var newPBSystems = []*pb.UploadServerHTTPRequestStatRequest_System{}
sort.Slice(pbSystems, func(i, j int) bool {
return pbSystems[i].Count > pbSystems[j].Count
})
var serverCountMap = map[int64]int16{}
for _, system := range pbSystems {
serverCountMap[system.ServerId]++
if serverCountMap[system.ServerId] > maxSystems {
continue
}
newPBSystems = append(newPBSystems, system)
}
if len(pbSystems) != len(newPBSystems) {
pbSystems = newPBSystems
}
}
// 浏览器
for k, count := range browserMap {
var pieces = strings.SplitN(k, "@", 3)
var serverId = types.Int64(pieces[0])
pbBrowsers = append(pbBrowsers, &pb.UploadServerHTTPRequestStatRequest_Browser{
ServerId: types.Int64(pieces[0]),
ServerId: serverId,
Name: pieces[1],
Version: pieces[2],
Count: count,
})
}
if len(browserMap) > int(maxBrowsers) {
var newPBBrowsers = []*pb.UploadServerHTTPRequestStatRequest_Browser{}
sort.Slice(pbBrowsers, func(i, j int) bool {
return pbBrowsers[i].Count > pbBrowsers[j].Count
})
var serverCountMap = map[int64]int16{}
for _, browser := range pbBrowsers {
serverCountMap[browser.ServerId]++
if serverCountMap[browser.ServerId] > maxBrowsers {
continue
}
newPBBrowsers = append(newPBBrowsers, browser)
}
if len(pbBrowsers) != len(newPBBrowsers) {
pbBrowsers = newPBBrowsers
}
}
// 防火墙相关
var pbFirewallRuleGroups = []*pb.UploadServerHTTPRequestStatRequest_HTTPFirewallRuleGroup{}
for k, count := range this.dailyFirewallRuleGroupMap {
for k, count := range dailyFirewallRuleGroupMap {
var pieces = strings.SplitN(k, "@", 3)
pbFirewallRuleGroups = append(pbFirewallRuleGroups, &pb.UploadServerHTTPRequestStatRequest_HTTPFirewallRuleGroup{
ServerId: types.Int64(pieces[0]),
@@ -315,13 +485,14 @@ func (this *HTTPRequestStatManager) Upload() error {
})
}
// 重置数据
// 这里需要放到上传数据之前,防止因上传失败而导致统计数据堆积
this.cityMap = map[string]*StatItem{}
this.providerMap = map[string]int64{}
this.systemMap = map[string]int64{}
this.browserMap = map[string]int64{}
this.dailyFirewallRuleGroupMap = map[string]int64{}
// 检查是否有数据
if len(pbCities) == 0 &&
len(pbProviders) == 0 &&
len(pbSystems) == 0 &&
len(pbBrowsers) == 0 &&
len(pbFirewallRuleGroups) == 0 {
return nil
}
// 上传数据
_, err = rpcClient.ServerRPC.UploadServerHTTPRequestStat(rpcClient.Context(), &pb.UploadServerHTTPRequestStatRequest{

View File

@@ -37,11 +37,11 @@ func TestHTTPRequestStatManager_Loop_Region(t *testing.T) {
func TestHTTPRequestStatManager_Loop_UserAgent(t *testing.T) {
var manager = NewHTTPRequestStatManager()
manager.AddUserAgent(1, "Mozilla/5.0 (Macintosh; Intel Mac OS X 11_1_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.96 Safari/537.36")
manager.AddUserAgent(1, "Mozilla/5.0 (Macintosh; Intel Mac OS X 11_1_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.96 Safari/537.36")
manager.AddUserAgent(1, "Mozilla/5.0 (Macintosh; Intel Mac OS X 11) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76 Safari/537.36")
manager.AddUserAgent(1, "Mozilla/5.0 (Windows NT 10.0; WOW64; rv:49.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.96 Safari/537.36")
manager.AddUserAgent(1, "Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like Gecko")
manager.AddUserAgent(1, "Mozilla/5.0 (Macintosh; Intel Mac OS X 11_1_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.96 Safari/537.36", "")
manager.AddUserAgent(1, "Mozilla/5.0 (Macintosh; Intel Mac OS X 11_1_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.96 Safari/537.36", "")
manager.AddUserAgent(1, "Mozilla/5.0 (Macintosh; Intel Mac OS X 11) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76 Safari/537.36", "")
manager.AddUserAgent(1, "Mozilla/5.0 (Windows NT 10.0; WOW64; rv:49.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.96 Safari/537.36", "")
manager.AddUserAgent(1, "Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like Gecko", "")
err := manager.Loop()
if err != nil {
t.Fatal(err)

View File

@@ -0,0 +1,39 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package agents
import (
"regexp"
"strings"
)
type Agent struct {
Code string
Keywords []string // user agent keywords
suffixes []string // PTR suffixes
reg *regexp.Regexp
}
func NewAgent(code string, suffixes []string, reg *regexp.Regexp, keywords []string) *Agent {
return &Agent{
Code: code,
suffixes: suffixes,
reg: reg,
Keywords: keywords,
}
}
func (this *Agent) Match(ptr string) bool {
if len(this.suffixes) > 0 {
for _, suffix := range this.suffixes {
if strings.HasSuffix(ptr, suffix) {
return true
}
}
}
if this.reg != nil {
return this.reg.MatchString(ptr)
}
return false
}

View File

@@ -0,0 +1,9 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package agents
type AgentIP struct {
Id int64
IP string
AgentCode string
}

View File

@@ -0,0 +1,31 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package agents
import "strings"
var AllAgents = []*Agent{
NewAgent("baidu", []string{".baidu.com."}, nil, []string{"Baidu"}),
NewAgent("google", []string{".googlebot.com."}, nil, []string{"Google"}),
NewAgent("bing", []string{".search.msn.com."}, nil, []string{"bingbot"}),
NewAgent("sogou", []string{".sogou.com."}, nil, []string{"Sogou"}),
NewAgent("youdao", []string{".163.com."}, nil, []string{"Youdao"}),
NewAgent("yahoo", []string{".yahoo.com."}, nil, []string{"Yahoo"}),
NewAgent("bytedance", []string{".bytedance.com."}, nil, []string{"Bytespider"}),
NewAgent("sm", []string{".sm.cn."}, nil, []string{"YisouSpider"}),
NewAgent("yandex", []string{".yandex.com.", ".yndx.net."}, nil, []string{"Yandex"}),
NewAgent("semrush", []string{".semrush.com."}, nil, []string{"SEMrush"}),
}
func IsAgentFromUserAgent(userAgent string) bool {
for _, agent := range AllAgents {
if len(agent.Keywords) > 0 {
for _, keyword := range agent.Keywords {
if strings.Contains(userAgent, keyword) {
return true
}
}
}
}
return false
}

View File

@@ -0,0 +1,19 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package agents_test
import (
"github.com/TeaOSLab/EdgeNode/internal/utils/agents"
"testing"
)
func TestIsAgentFromUserAgent(t *testing.T) {
t.Log(agents.IsAgentFromUserAgent("Mozilla/5.0 (Linux;u;Android 4.2.2;zh-cn;) AppleWebKit/534.46 (KHTML,like Gecko) Version/5.1 Mobile Safari/10600.6.3 (compatible; Baiduspider/2.0; +http://www.baidu.com/search/spider.html)"))
t.Log(agents.IsAgentFromUserAgent("Mozilla/5.0 (Linux;u;Android 4.2.2;zh-cn;)"))
}
func BenchmarkIsAgentFromUserAgent(b *testing.B) {
for i := 0; i < b.N; i++ {
agents.IsAgentFromUserAgent("Mozilla/5.0 (Linux;u;Android 4.2.2;zh-cn;) AppleWebKit/534.46 (KHTML,like Gecko) Version/5.1 Mobile Safari/10600.6.3 (compatible; Yaho)")
}
}

160
internal/utils/agents/db.go Normal file
View File

@@ -0,0 +1,160 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package agents
import (
"errors"
"github.com/TeaOSLab/EdgeNode/internal/events"
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
"github.com/TeaOSLab/EdgeNode/internal/utils/dbs"
"github.com/iwind/TeaGo/Tea"
"github.com/iwind/TeaGo/types"
"log"
"os"
"path/filepath"
"strings"
)
const (
tableAgentIPs = "agentIPs"
)
type DB struct {
db *dbs.DB
path string
insertAgentIPStmt *dbs.Stmt
listAgentIPsStmt *dbs.Stmt
}
func NewDB(path string) *DB {
var db = &DB{path: path}
events.On(events.EventQuit, func() {
_ = db.Close()
})
return db
}
func (this *DB) Init() error {
// 检查目录是否存在
var dir = filepath.Dir(this.path)
_, err := os.Stat(dir)
if err != nil {
err = os.MkdirAll(dir, 0777)
if err != nil {
return err
}
remotelogs.Println("DB", "create database dir '"+dir+"'")
}
// TODO 思考 data.db 的数据安全性
db, err := dbs.OpenWriter("file:" + this.path + "?cache=shared&mode=rwc&_journal_mode=WAL&_locking_mode=EXCLUSIVE")
if err != nil {
return err
}
db.SetMaxOpenConns(1)
/**_, err = db.Exec("VACUUM")
if err != nil {
return err
}**/
_, err = db.Exec(`CREATE TABLE IF NOT EXISTS "` + tableAgentIPs + `" (
"id" integer NOT NULL PRIMARY KEY AUTOINCREMENT,
"ip" varchar(64),
"agentCode" varchar(128)
);`)
if err != nil {
return err
}
// 预编译语句
// agent ip record statements
this.insertAgentIPStmt, err = db.Prepare(`INSERT INTO "` + tableAgentIPs + `" ("id", "ip", "agentCode") VALUES (?, ?, ?)`)
if err != nil {
return err
}
this.listAgentIPsStmt, err = db.Prepare(`SELECT "id", "ip", "agentCode" FROM "` + tableAgentIPs + `" ORDER BY "id" ASC LIMIT ? OFFSET ?`)
if err != nil {
return err
}
this.db = db
return nil
}
func (this *DB) InsertAgentIP(ipId int64, ip string, agentCode string) error {
if this.db == nil {
return errors.New("db should not be nil")
}
_, err := this.insertAgentIPStmt.Exec(ipId, ip, agentCode)
if err != nil {
// 不提示ID重复错误
if strings.Contains(err.Error(), "UNIQUE constraint") {
return nil
}
return err
}
return nil
}
func (this *DB) ListAgentIPs(offset int64, size int64) (agentIPs []*AgentIP, err error) {
if this.db == nil {
return nil, errors.New("db should not be nil")
}
rows, err := this.listAgentIPsStmt.Query(size, offset)
if err != nil {
return nil, err
}
defer func() {
_ = rows.Close()
}()
for rows.Next() {
var agentIP = &AgentIP{}
err = rows.Scan(&agentIP.Id, &agentIP.IP, &agentIP.AgentCode)
if err != nil {
return nil, err
}
agentIPs = append(agentIPs, agentIP)
}
return
}
func (this *DB) Close() error {
if this.db == nil {
return nil
}
for _, stmt := range []*dbs.Stmt{
this.insertAgentIPStmt,
this.listAgentIPsStmt,
} {
if stmt != nil {
_ = stmt.Close()
}
}
return this.db.Close()
}
// 打印日志
func (this *DB) log(args ...any) {
if !Tea.IsTesting() {
return
}
if len(args) == 0 {
return
}
args[0] = "[" + types.String(args[0]) + "]"
log.Println(args...)
}

View File

@@ -0,0 +1,54 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package agents
import (
"github.com/TeaOSLab/EdgeNode/internal/zero"
"sync"
)
type IPCacheMap struct {
m map[string]zero.Zero
list []string
locker sync.RWMutex
maxLen int
}
func NewIPCacheMap(maxLen int) *IPCacheMap {
if maxLen <= 0 {
maxLen = 65535
}
return &IPCacheMap{
m: map[string]zero.Zero{},
maxLen: maxLen,
}
}
func (this *IPCacheMap) Add(ip string) {
this.locker.Lock()
defer this.locker.Unlock()
// 是否已经存在
_, ok := this.m[ip]
if ok {
return
}
// 超出长度删除第一个
if len(this.list) >= this.maxLen {
delete(this.m, this.list[0])
this.list = this.list[1:]
}
// 加入新数据
this.m[ip] = zero.Zero{}
this.list = append(this.list, ip)
}
func (this *IPCacheMap) Contains(ip string) bool {
this.locker.RLock()
defer this.locker.RUnlock()
_, ok := this.m[ip]
return ok
}

View File

@@ -0,0 +1,33 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package agents
import (
"github.com/iwind/TeaGo/logs"
"testing"
)
func TestNewIPCacheMap(t *testing.T) {
var cacheMap = NewIPCacheMap(3)
t.Log("====")
cacheMap.Add("1")
cacheMap.Add("2")
logs.PrintAsJSON(cacheMap.m, t)
logs.PrintAsJSON(cacheMap.list, t)
t.Log("====")
cacheMap.Add("3")
logs.PrintAsJSON(cacheMap.m, t)
logs.PrintAsJSON(cacheMap.list, t)
t.Log("====")
cacheMap.Add("4")
logs.PrintAsJSON(cacheMap.m, t)
logs.PrintAsJSON(cacheMap.list, t)
t.Log("====")
cacheMap.Add("3")
logs.PrintAsJSON(cacheMap.m, t)
logs.PrintAsJSON(cacheMap.list, t)
}

View File

@@ -0,0 +1,205 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package agents
import (
"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/goman"
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
"github.com/TeaOSLab/EdgeNode/internal/rpc"
"github.com/iwind/TeaGo/Tea"
"sync"
"time"
)
var SharedManager = NewManager()
func init() {
if !teaconst.IsMain {
return
}
events.On(events.EventLoaded, func() {
goman.New(func() {
SharedManager.Start()
})
})
}
// Manager Agent管理器
type Manager struct {
ipMap map[string]string // ip => agentCode
locker sync.RWMutex
db *DB
lastId int64
}
func NewManager() *Manager {
return &Manager{
ipMap: map[string]string{},
}
}
func (this *Manager) SetDB(db *DB) {
this.db = db
}
func (this *Manager) Start() {
remotelogs.Println("AGENT_MANAGER", "starting ...")
err := this.loadDB()
if err != nil {
remotelogs.Error("AGENT_MANAGER", "load database failed: "+err.Error())
return
}
// 从本地数据库中加载
err = this.Load()
if err != nil {
remotelogs.Error("AGENT_MANAGER", "load failed: "+err.Error())
}
// 先从API获取
err = this.LoopAll()
if err != nil {
if rpc.IsConnError(err) {
remotelogs.Debug("AGENT_MANAGER", "retrieve latest agent ip failed: "+err.Error())
} else {
remotelogs.Error("AGENT_MANAGER", "retrieve latest agent ip failed: "+err.Error())
}
}
// 定时获取
var duration = 30 * time.Minute
if Tea.IsTesting() {
duration = 30 * time.Second
}
var ticker = time.NewTicker(duration)
for range ticker.C {
err = this.LoopAll()
if err != nil {
remotelogs.Error("AGENT_MANAGER", "retrieve latest agent ip failed: "+err.Error())
}
}
}
func (this *Manager) Load() error {
var offset int64 = 0
var size int64 = 10000
for {
agentIPs, err := this.db.ListAgentIPs(offset, size)
if err != nil {
return err
}
if len(agentIPs) == 0 {
break
}
for _, agentIP := range agentIPs {
this.locker.Lock()
this.ipMap[agentIP.IP] = agentIP.AgentCode
this.locker.Unlock()
if agentIP.Id > this.lastId {
this.lastId = agentIP.Id
}
}
offset += size
}
return nil
}
func (this *Manager) LoopAll() error {
for {
hasNext, err := this.Loop()
if err != nil {
return err
}
if !hasNext {
break
}
}
return nil
}
// Loop 单次循环获取数据
func (this *Manager) Loop() (hasNext bool, err error) {
rpcClient, err := rpc.SharedRPC()
if err != nil {
return false, err
}
ipsResp, err := rpcClient.ClientAgentIPRPC.ListClientAgentIPsAfterId(rpcClient.Context(), &pb.ListClientAgentIPsAfterIdRequest{
Id: this.lastId,
Size: 10000,
})
if err != nil {
return false, err
}
if len(ipsResp.ClientAgentIPs) == 0 {
return false, nil
}
for _, agentIP := range ipsResp.ClientAgentIPs {
if agentIP.ClientAgent == nil {
// 设置ID
if agentIP.Id > this.lastId {
this.lastId = agentIP.Id
}
continue
}
// 写入到数据库
err = this.db.InsertAgentIP(agentIP.Id, agentIP.Ip, agentIP.ClientAgent.Code)
if err != nil {
return false, err
}
// 写入Map
this.locker.Lock()
this.ipMap[agentIP.Ip] = agentIP.ClientAgent.Code
this.locker.Unlock()
// 设置ID
if agentIP.Id > this.lastId {
this.lastId = agentIP.Id
}
}
return true, nil
}
// AddIP 添加记录
func (this *Manager) AddIP(ip string, agentCode string) {
this.locker.Lock()
this.ipMap[ip] = agentCode
this.locker.Unlock()
}
// LookupIP 查询IP所属Agent
func (this *Manager) LookupIP(ip string) (agentCode string) {
this.locker.RLock()
defer this.locker.RUnlock()
return this.ipMap[ip]
}
// ContainsIP 检查是否有IP相关数据
func (this *Manager) ContainsIP(ip string) bool {
this.locker.RLock()
defer this.locker.RUnlock()
_, ok := this.ipMap[ip]
return ok
}
func (this *Manager) loadDB() error {
var db = NewDB(Tea.Root + "/data/agents.db")
err := db.Init()
if err != nil {
return err
}
this.db = db
return nil
}

View File

@@ -0,0 +1,32 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package agents_test
import (
"github.com/TeaOSLab/EdgeNode/internal/utils/agents"
"github.com/iwind/TeaGo/Tea"
_ "github.com/iwind/TeaGo/bootstrap"
"testing"
)
func TestNewManager(t *testing.T) {
var db = agents.NewDB(Tea.Root + "/data/agents.db")
err := db.Init()
if err != nil {
t.Fatal(err)
}
var manager = agents.NewManager()
manager.SetDB(db)
err = manager.Load()
if err != nil {
t.Fatal(err)
}
_, err = manager.Loop()
if err != nil {
t.Fatal(err)
}
t.Log(manager.LookupIP("192.168.3.100"))
}

View File

@@ -0,0 +1,138 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package agents
import (
"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/goman"
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
"github.com/TeaOSLab/EdgeNode/internal/rpc"
"github.com/iwind/TeaGo/Tea"
"net"
)
func init() {
if !teaconst.IsMain {
return
}
events.On(events.EventLoaded, func() {
goman.New(func() {
SharedQueue.Start()
})
})
}
var SharedQueue = NewQueue()
type Queue struct {
c chan string // chan ip
cacheMap *IPCacheMap
}
func NewQueue() *Queue {
return &Queue{
c: make(chan string, 128),
cacheMap: NewIPCacheMap(65535),
}
}
func (this *Queue) Start() {
for ip := range this.c {
err := this.Process(ip)
if err != nil {
// 不需要上报错误
if Tea.IsTesting() {
remotelogs.Debug("SharedParseQueue", err.Error())
}
continue
}
}
}
// Push 将IP加入到处理队列
func (this *Queue) Push(ip string) {
// 是否在处理中
if this.cacheMap.Contains(ip) {
return
}
this.cacheMap.Add(ip)
// 加入到队列
select {
case this.c <- ip:
default:
}
}
// Process 处理IP
func (this *Queue) Process(ip string) error {
// 是否已经在库中
if SharedManager.ContainsIP(ip) {
return nil
}
ptr, err := this.ParseIP(ip)
if err != nil {
return err
}
if len(ptr) == 0 || ptr == "." {
return nil
}
//remotelogs.Debug("AGENT", ip+" => "+ptr)
var agentCode = this.ParsePtr(ptr)
if len(agentCode) == 0 {
return nil
}
// 加入到本地
SharedManager.AddIP(ip, agentCode)
var pbAgentIP = &pb.CreateClientAgentIPsRequest_AgentIPInfo{
AgentCode: agentCode,
Ip: ip,
Ptr: ptr,
}
rpcClient, err := rpc.SharedRPC()
if err != nil {
return err
}
_, err = rpcClient.ClientAgentIPRPC.CreateClientAgentIPs(rpcClient.Context(), &pb.CreateClientAgentIPsRequest{AgentIPs: []*pb.CreateClientAgentIPsRequest_AgentIPInfo{pbAgentIP}})
if err != nil {
return err
}
return nil
}
// ParseIP 分析IP的PTR值
func (this *Queue) ParseIP(ip string) (ptr string, err error) {
if len(ip) == 0 {
return "", nil
}
names, err := net.LookupAddr(ip)
if err != nil {
return "", err
}
if len(names) == 0 {
return "", nil
}
return names[0], nil
}
// ParsePtr 分析PTR对应的Agent
func (this *Queue) ParsePtr(ptr string) (agentCode string) {
for _, agent := range AllAgents {
if agent.Match(ptr) {
return agent.Code
}
}
return ""
}

View File

@@ -0,0 +1,76 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package agents_test
import (
"github.com/TeaOSLab/EdgeNode/internal/utils/agents"
"github.com/iwind/TeaGo/assert"
_ "github.com/iwind/TeaGo/bootstrap"
"testing"
"time"
)
func TestParseQueue_Process(t *testing.T) {
var queue = agents.NewQueue()
go queue.Start()
time.Sleep(1 * time.Second)
queue.Push("220.181.13.100")
time.Sleep(1 * time.Second)
}
func TestParseQueue_ParseIP(t *testing.T) {
var queue = agents.NewQueue()
for _, ip := range []string{
"192.168.1.100",
"42.120.160.1",
"42.236.10.98",
"124.115.0.100",
} {
ptr, err := queue.ParseIP(ip)
if err != nil {
t.Log(ip, "=>", err)
continue
}
t.Log(ip, "=>", ptr)
}
}
func TestParseQueue_ParsePtr(t *testing.T) {
var a = assert.NewAssertion(t)
var queue = agents.NewQueue()
for _, s := range [][]string{
{"baiduspider-220-181-108-101.crawl.baidu.com.", "baidu"},
{"crawl-66-249-71-219.googlebot.com.", "google"},
{"msnbot-40-77-167-31.search.msn.com.", "bing"},
{"sogouspider-49-7-20-129.crawl.sogou.com.", "sogou"},
{"m13102.mail.163.com.", "youdao"},
{"yeurosport.pat1.tc2.yahoo.com.", "yahoo"},
{"shenmaspider-42-120-160-1.crawl.sm.cn.", "sm"},
{"93-158-161-39.spider.yandex.com.", "yandex"},
{"25.bl.bot.semrush.com.", "semrush"},
} {
a.IsTrue(queue.ParsePtr(s[0]) == s[1])
}
}
func BenchmarkQueue_ParsePtr(b *testing.B) {
var queue = agents.NewQueue()
for i := 0; i < b.N; i++ {
for _, s := range [][]string{
{"baiduspider-220-181-108-101.crawl.baidu.com.", "baidu"},
{"crawl-66-249-71-219.googlebot.com.", "google"},
{"msnbot-40-77-167-31.search.msn.com.", "bing"},
{"sogouspider-49-7-20-129.crawl.sogou.com.", "sogou"},
{"m13102.mail.163.com.", "youdao"},
{"yeurosport.pat1.tc2.yahoo.com.", "yahoo"},
{"shenmaspider-42-120-160-1.crawl.sm.cn.", "sm"},
{"93-158-161-39.spider.yandex.com.", "yandex"},
{"93.158.164.218-red.dhcp.yndx.net.", "yandex"},
{"25.bl.bot.semrush.com.", "semrush"},
} {
queue.ParsePtr(s[0])
}
}
}

View File

@@ -6,6 +6,7 @@ import (
"encoding/binary"
"errors"
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
teaconst "github.com/TeaOSLab/EdgeNode/internal/const"
"github.com/TeaOSLab/EdgeNode/internal/events"
"github.com/TeaOSLab/EdgeNode/internal/goman"
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
@@ -21,6 +22,10 @@ var hasSynced = false
var sharedClockManager = NewClockManager()
func init() {
if !teaconst.IsMain {
return
}
events.On(events.EventLoaded, func() {
goman.New(sharedClockManager.Start)
})

View File

@@ -7,14 +7,56 @@ import (
"database/sql"
"fmt"
"github.com/TeaOSLab/EdgeNode/internal/events"
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
"github.com/TeaOSLab/EdgeNode/internal/utils/fileutils"
_ "github.com/mattn/go-sqlite3"
"strings"
)
type DB struct {
rawDB *sql.DB
locker *fileutils.Locker
rawDB *sql.DB
enableStat bool
}
func OpenWriter(dsn string) (*DB, error) {
return open(dsn, true)
}
func OpenReader(dsn string) (*DB, error) {
return open(dsn, false)
}
func open(dsn string, lock bool) (*DB, error) {
// locker
var locker *fileutils.Locker
if lock {
var path = dsn
var queryIndex = strings.Index(dsn, "?")
if queryIndex >= 0 {
path = path[:queryIndex]
}
path = strings.TrimSpace(strings.TrimPrefix(path, "file:"))
locker = fileutils.NewLocker(path)
err := locker.Lock()
if err != nil {
remotelogs.Warn("DB", "lock '"+path+"' failed: "+err.Error())
locker = nil
}
}
// open
rawDB, err := sql.Open("sqlite3", dsn)
if err != nil {
return nil, err
}
var db = NewDB(rawDB)
db.locker = locker
return db, nil
}
func NewDB(rawDB *sql.DB) *DB {
var db = &DB{
rawDB: rawDB,
@@ -30,6 +72,10 @@ func NewDB(rawDB *sql.DB) *DB {
return db
}
func (this *DB) SetMaxOpenConns(n int) {
this.rawDB.SetMaxOpenConns(n)
}
func (this *DB) EnableStat(b bool) {
this.enableStat = b
}
@@ -81,6 +127,13 @@ func (this *DB) QueryRow(query string, args ...interface{}) *sql.Row {
func (this *DB) Close() error {
events.Remove(fmt.Sprintf("db_%p", this))
defer func() {
if this.locker != nil {
_ = this.locker.Release()
}
}()
return this.rawDB.Close()
}

View File

@@ -15,6 +15,10 @@ import (
)
func init() {
if !teaconst.IsMain {
return
}
var ticker = time.NewTicker(5 * time.Second)
events.On(events.EventLoaded, func() {

View File

@@ -10,6 +10,7 @@ import (
"encoding/json"
"errors"
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
teaconst "github.com/TeaOSLab/EdgeNode/internal/const"
"github.com/TeaOSLab/EdgeNode/internal/events"
"github.com/iwind/TeaGo/logs"
"github.com/iwind/TeaGo/maps"
@@ -22,6 +23,10 @@ var (
)
func init() {
if !teaconst.IsMain {
return
}
events.On(events.EventReload, func() {
nodeConfig, _ := nodeconfigs.SharedNodeConfig()
if nodeConfig != nil {

View File

@@ -0,0 +1,82 @@
// Copyright 2023 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package fileutils
import (
"os"
"syscall"
"time"
)
type Locker struct {
path string
fp *os.File
}
func NewLocker(path string) *Locker {
return &Locker{
path: path + ".lock",
}
}
func (this *Locker) TryLock() (ok bool, err error) {
if this.fp == nil {
fp, err := os.OpenFile(this.path, os.O_CREATE|os.O_WRONLY, 0666)
if err != nil {
return false, err
}
this.fp = fp
}
return this.tryLock()
}
func (this *Locker) Lock() error {
if this.fp == nil {
fp, err := os.OpenFile(this.path, os.O_CREATE|os.O_WRONLY, 0666)
if err != nil {
return err
}
this.fp = fp
}
for {
b, err := this.tryLock()
if err != nil {
_ = this.fp.Close()
return err
}
if b {
return nil
}
time.Sleep(100 * time.Millisecond)
}
}
func (this *Locker) Release() error {
err := this.fp.Close()
if err != nil {
return err
}
this.fp = nil
return nil
}
func (this *Locker) tryLock() (ok bool, err error) {
err = syscall.Flock(int(this.fp.Fd()), syscall.LOCK_EX|syscall.LOCK_NB)
if err == nil {
return true, nil
}
errno, isErrNo := err.(syscall.Errno)
if !isErrNo {
return
}
if !errno.Temporary() {
return
}
err = nil // 不提示错误
return
}

View File

@@ -0,0 +1,24 @@
// Copyright 2023 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package fileutils_test
import (
"github.com/TeaOSLab/EdgeNode/internal/utils/fileutils"
"testing"
)
func TestLocker_Lock(t *testing.T) {
var path = "/tmp/file-test"
var locker = fileutils.NewLocker(path)
err := locker.Lock()
if err != nil {
t.Fatal(err)
}
_ = locker.Release()
var locker2 = fileutils.NewLocker(path)
err = locker2.Lock()
if err != nil {
t.Fatal(err)
}
}

View File

@@ -15,6 +15,10 @@ import (
var SharedFreeHoursManager = NewFreeHoursManager()
func init() {
if !teaconst.IsMain {
return
}
events.On(events.EventLoaded, func() {
goman.New(func() {
SharedFreeHoursManager.Start()

View File

@@ -6,7 +6,7 @@ import (
"regexp"
)
var RegexpDigitNumber = regexp.MustCompile("^\\d+$")
var RegexpDigitNumber = regexp.MustCompile(`^\d+$`)
func Get(object interface{}, keys []string) interface{} {
if len(keys) == 0 {

View File

@@ -67,10 +67,3 @@ func TestRange_ComposeContentRangeHeader(t *testing.T) {
var r = rangeutils.NewRange(1, 100)
t.Log(r.ComposeContentRangeHeader("1000"))
}
func TestRange_SetLength(t *testing.T) {
var r = rangeutils.NewRange(1, 100)
t.Log(r)
t.Log(r.SetLength(1024))
}

View File

@@ -3,6 +3,7 @@ package utils_test
import (
"github.com/TeaOSLab/EdgeNode/internal/utils"
"github.com/iwind/TeaGo/assert"
"github.com/iwind/TeaGo/types"
"strings"
"testing"
)
@@ -65,3 +66,15 @@ func TestContainsSameStrings(t *testing.T) {
a.IsTrue(utils.EqualStrings([]string{"a", "b"}, []string{"a", "b"}))
a.IsTrue(utils.EqualStrings([]string{"a", "b"}, []string{"b", "a"}))
}
func TestToValidUTF8string(t *testing.T) {
for _, s := range []string{
"https://goedge.cn/",
"提升mysql数据表写入速度",
"😆",
string([]byte{'a', 'b', 130, 131, 132, 133, 134, 'c'}),
} {
var u = utils.ToValidUTF8string(s)
t.Log(s, "["+types.String(len(s))+"]", "=>", u, "["+types.String(len(u))+"]")
}
}

View File

@@ -3,12 +3,17 @@
package utils
import (
teaconst "github.com/TeaOSLab/EdgeNode/internal/const"
"github.com/shirou/gopsutil/v3/mem"
)
var systemTotalMemory = -1
func init() {
if !teaconst.IsMain {
return
}
_ = SystemMemoryGB()
}
@@ -23,5 +28,11 @@ func SystemMemoryGB() int {
}
systemTotalMemory = int(stat.Total / 1024 / 1024 / 1024)
if systemTotalMemory <= 0 {
systemTotalMemory = 1
}
setMaxMemory(systemTotalMemory)
return systemTotalMemory
}

View File

@@ -0,0 +1,23 @@
// Copyright 2023 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
//go:build go1.19
package utils
import (
"runtime/debug"
)
// 设置软内存最大值
func setMaxMemory(memoryGB int) {
if memoryGB <= 0 {
memoryGB = 1
}
var maxMemoryBytes int64 = 0
if memoryGB > 10 {
maxMemoryBytes = int64(memoryGB-2) << 30 // 超过10G内存的允许剩余2G内存
} else {
maxMemoryBytes = (int64(memoryGB) << 30) * 80 / 100 // 默认 80%
}
debug.SetMemoryLimit(maxMemoryBytes)
}

View File

@@ -0,0 +1,9 @@
// Copyright 2023 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
//go:build !go1.19
package utils
// 设置软内存最大值
func setMaxMemory(memoryGB int) {
}

View File

@@ -1,6 +1,7 @@
package utils
import (
teaconst "github.com/TeaOSLab/EdgeNode/internal/const"
"github.com/TeaOSLab/EdgeNode/internal/goman"
"github.com/iwind/TeaGo/types"
"time"
@@ -11,6 +12,10 @@ var unixTimeMilli = time.Now().UnixMilli()
var unixTimeMilliString = types.String(unixTimeMilli)
func init() {
if !teaconst.IsMain {
return
}
var ticker = time.NewTicker(200 * time.Millisecond)
goman.New(func() {
for range ticker.C {

View File

@@ -3,6 +3,7 @@
package writers
import (
"context"
"github.com/iwind/TeaGo/types"
"io"
"time"
@@ -11,6 +12,7 @@ import (
// RateLimitWriter 限速写入
type RateLimitWriter struct {
rawWriter io.WriteCloser
ctx context.Context
rateBytes int
@@ -18,9 +20,10 @@ type RateLimitWriter struct {
before time.Time
}
func NewRateLimitWriter(rawWriter io.WriteCloser, rateBytes int64) io.WriteCloser {
func NewRateLimitWriter(ctx context.Context, rawWriter io.WriteCloser, rateBytes int64) io.WriteCloser {
return &RateLimitWriter{
rawWriter: rawWriter,
ctx: ctx,
rateBytes: types.Int(rateBytes),
before: time.Now(),
}
@@ -71,6 +74,14 @@ func (this *RateLimitWriter) write(p []byte) (n int, err error) {
n, err = this.rawWriter.Write(p)
if err == nil {
select {
case <-this.ctx.Done():
err = io.EOF
return
default:
}
this.written += n
if this.written >= this.rateBytes {

View File

@@ -2,10 +2,6 @@
package waf
import (
"net/http"
)
type BaseAction struct {
currentActionId int64
}
@@ -19,16 +15,3 @@ func (this *BaseAction) ActionId() int64 {
func (this *BaseAction) SetActionId(actionId int64) {
this.currentActionId = actionId
}
// CloseConn 关闭连接
func (this *BaseAction) CloseConn(writer http.ResponseWriter) error {
// 断开连接
hijack, ok := writer.(http.Hijacker)
if ok {
conn, _, err := hijack.Hijack()
if err == nil && conn != nil {
return conn.Close()
}
}
return nil
}

View File

@@ -6,6 +6,7 @@ import (
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
"github.com/iwind/TeaGo/Tea"
"github.com/iwind/TeaGo/logs"
"github.com/iwind/TeaGo/rands"
"io"
"net/http"
"os"
@@ -25,6 +26,7 @@ type BlockAction struct {
Body string `yaml:"body" json:"body"` // supports HTML
URL string `yaml:"url" json:"url"`
Timeout int32 `yaml:"timeout" json:"timeout"`
TimeoutMax int32 `yaml:"timeoutMax" json:"timeoutMax"`
Scope string `yaml:"scope" json:"scope"`
}
@@ -41,6 +43,7 @@ func (this *BlockAction) Init(waf *WAF) error {
}
if this.Timeout <= 0 {
this.Timeout = waf.DefaultBlockAction.Timeout
this.TimeoutMax = waf.DefaultBlockAction.TimeoutMax // 只有没有填写封锁时长的时候才会使用默认的封锁时长最大值
}
}
return nil
@@ -65,6 +68,12 @@ func (this *BlockAction) Perform(waf *WAF, group *RuleGroup, set *RuleSet, reque
timeout = 300 // 默认封锁300秒
}
// 随机时长
var timeoutMax = this.TimeoutMax
if timeoutMax > timeout {
timeout = timeout + int32(rands.Int64()%int64(timeoutMax-timeout+1))
}
SharedIPBlackList.RecordIP(IPTypeAll, this.Scope, request.WAFServerId(), request.WAFRemoteIP(), time.Now().Unix()+int64(timeout), waf.Id, waf.UseLocalFirewall, group.Id, set.Id, "")
if writer != nil {

View File

@@ -69,9 +69,10 @@ func (this *Get302Action) Perform(waf *WAF, group *RuleGroup, set *RuleSet, requ
http.Redirect(writer, request.WAFRaw(), Get302Path+"?info="+url.QueryEscape(info), http.StatusFound)
if request.WAFRaw().ProtoMajor == 1 {
_ = this.CloseConn(writer)
flusher, ok := writer.(http.Flusher)
if ok {
flusher.Flush()
}
return false, false
}

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