Compare commits
90 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f53d4c8951 | ||
|
|
70d8aa5b33 | ||
|
|
1aa4be9000 | ||
|
|
a7c7c73f70 | ||
|
|
0b441021d8 | ||
|
|
7db0c8cf62 | ||
|
|
6da9cb6dcf | ||
|
|
0af580eb26 | ||
|
|
52085bdc1c | ||
|
|
72f1eea721 | ||
|
|
6d52b022b2 | ||
|
|
ea41c9b0b3 | ||
|
|
ed6127c2bb | ||
|
|
b6d95a84fc | ||
|
|
c71e68bdea | ||
|
|
c44583f249 | ||
|
|
c53773c2db | ||
|
|
793994a3fe | ||
|
|
4c3deb1156 | ||
|
|
24ca5a5ace | ||
|
|
8bbbf57827 | ||
|
|
888df02d0c | ||
|
|
8988765cef | ||
|
|
f675b88761 | ||
|
|
9bd4975478 | ||
|
|
95abb7bfae | ||
|
|
d9fa3dcc3b | ||
|
|
964524816f | ||
|
|
d124c9be18 | ||
|
|
1a05402076 | ||
|
|
c4b1790102 | ||
|
|
613acbff95 | ||
|
|
e6ab98ad11 | ||
|
|
1121869f14 | ||
|
|
91efe57e1b | ||
|
|
95f2573263 | ||
|
|
09aa85f51c | ||
|
|
c6279a1076 | ||
|
|
47ccb64cfb | ||
|
|
5c218567e1 | ||
|
|
c161d84fdf | ||
|
|
495b553285 | ||
|
|
21b770ba8b | ||
|
|
e9f94e0767 | ||
|
|
644ada1da9 | ||
|
|
0c40250849 | ||
|
|
1d1134a86d | ||
|
|
28e7664eb7 | ||
|
|
50f3ad641c | ||
|
|
cc7cf5f8c5 | ||
|
|
339f0f6e94 | ||
|
|
f558e43342 | ||
|
|
e374e5c90c | ||
|
|
563b775e49 | ||
|
|
de9e1a4515 | ||
|
|
f64b36f17a | ||
|
|
f0e8c82d31 | ||
|
|
5770d43230 | ||
|
|
d4944c236f | ||
|
|
33c761a187 | ||
|
|
d7e6da8d2c | ||
|
|
44d1a2415c | ||
|
|
c98ff50f06 | ||
|
|
8835fcb09e | ||
|
|
77c56e58c0 | ||
|
|
72c65ca4ee | ||
|
|
ab019b0bdc | ||
|
|
9709e45ad2 | ||
|
|
be1f80003c | ||
|
|
252fcca383 | ||
|
|
04ae8fa4a0 | ||
|
|
c95bd7776a | ||
|
|
8219167d05 | ||
|
|
e0a6881343 | ||
|
|
6e985d7f06 | ||
|
|
66719b05dd | ||
|
|
7197583fea | ||
|
|
ce29024eef | ||
|
|
e1ac67f7fa | ||
|
|
01812144dd | ||
|
|
1c34e49629 | ||
|
|
f233fbfb25 | ||
|
|
5387115e4a | ||
|
|
d82c03db23 | ||
|
|
230c5c3766 | ||
|
|
927425149e | ||
|
|
5ce1aab92c | ||
|
|
195742bb26 | ||
|
|
006cc2912d | ||
|
|
2d4ba90c3b |
@@ -1 +1,2 @@
|
||||
* `global.yaml` - 全局配置
|
||||
* `api.template.yaml` - API相关配置模板
|
||||
* `cluster.template.yaml` - 通过集群自动接入节点模板
|
||||
11
internal/apps/main.go
Normal file
11
internal/apps/main.go
Normal 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()
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
package caches
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils/fasttime"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
@@ -36,7 +36,7 @@ type Item struct {
|
||||
}
|
||||
|
||||
func (this *Item) IsExpired() bool {
|
||||
return this.ExpiredAt < utils.UnixTime()
|
||||
return this.ExpiredAt < fasttime.Now().Unix()
|
||||
}
|
||||
|
||||
func (this *Item) TotalSize() int64 {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -3,13 +3,13 @@
|
||||
package caches
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"errors"
|
||||
teaconst "github.com/TeaOSLab/EdgeNode/internal/const"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/goman"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils/dbs"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils/fasttime"
|
||||
"github.com/iwind/TeaGo/logs"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
timeutil "github.com/iwind/TeaGo/utils/time"
|
||||
@@ -82,14 +82,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 +110,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 +125,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)
|
||||
@@ -246,7 +247,7 @@ func (this *FileListDB) AddAsync(hash string, item *Item) error {
|
||||
item.StaleAt = item.ExpiredAt
|
||||
}
|
||||
|
||||
this.writeBatch.Add(this.insertSQL, hash, item.Key, item.HeaderSize, item.BodySize, item.MetaSize, item.ExpiredAt, item.StaleAt, item.Host, item.ServerId, utils.UnixTime(), timeutil.Format("YW"))
|
||||
this.writeBatch.Add(this.insertSQL, hash, item.Key, item.HeaderSize, item.BodySize, item.MetaSize, item.ExpiredAt, item.StaleAt, item.Host, item.ServerId, fasttime.Now().Unix(), timeutil.Format("YW"))
|
||||
return nil
|
||||
|
||||
}
|
||||
@@ -258,7 +259,7 @@ func (this *FileListDB) AddSync(hash string, item *Item) error {
|
||||
item.StaleAt = item.ExpiredAt
|
||||
}
|
||||
|
||||
_, err := this.insertStmt.Exec(hash, item.Key, item.HeaderSize, item.BodySize, item.MetaSize, item.ExpiredAt, item.StaleAt, item.Host, item.ServerId, utils.UnixTime(), timeutil.Format("YW"))
|
||||
_, err := this.insertStmt.Exec(hash, item.Key, item.HeaderSize, item.BodySize, item.MetaSize, item.ExpiredAt, item.StaleAt, item.Host, item.ServerId, fasttime.Now().Unix(), timeutil.Format("YW"))
|
||||
if err != nil {
|
||||
return this.WrapError(err)
|
||||
}
|
||||
@@ -377,8 +378,8 @@ func (this *FileListDB) CleanPrefix(prefix string) error {
|
||||
return nil
|
||||
}
|
||||
var count = int64(10000)
|
||||
var staleLife = 600 // TODO 需要可以设置
|
||||
var unixTime = utils.UnixTime() // 只删除当前的,不删除新的
|
||||
var staleLife = 600 // TODO 需要可以设置
|
||||
var unixTime = fasttime.Now().Unix() // 只删除当前的,不删除新的
|
||||
for {
|
||||
result, err := this.writeDB.Exec(`UPDATE "`+this.itemsTableName+`" SET expiredAt=0,staleAt=? WHERE id IN (SELECT id FROM "`+this.itemsTableName+`" WHERE expiredAt>0 AND createdAt<=? AND INSTR("key", ?)=1 LIMIT `+types.String(count)+`)`, unixTime+int64(staleLife), unixTime, prefix)
|
||||
if err != nil {
|
||||
@@ -424,8 +425,8 @@ func (this *FileListDB) CleanMatchKey(key string) error {
|
||||
queryKey = strings.Replace(queryKey, "*", "%", 1)
|
||||
|
||||
// TODO 检查大批量数据下的操作性能
|
||||
var staleLife = 600 // TODO 需要可以设置
|
||||
var unixTime = utils.UnixTime() // 只删除当前的,不删除新的
|
||||
var staleLife = 600 // TODO 需要可以设置
|
||||
var unixTime = fasttime.Now().Unix() // 只删除当前的,不删除新的
|
||||
|
||||
_, err = this.writeDB.Exec(`UPDATE "`+this.itemsTableName+`" SET "expiredAt"=0, "staleAt"=? WHERE "host" GLOB ? AND "host" NOT GLOB ? AND "key" LIKE ? ESCAPE '\'`, unixTime+int64(staleLife), host, "*."+host, queryKey)
|
||||
if err != nil {
|
||||
@@ -466,8 +467,8 @@ func (this *FileListDB) CleanMatchPrefix(prefix string) error {
|
||||
queryPrefix += "%"
|
||||
|
||||
// TODO 检查大批量数据下的操作性能
|
||||
var staleLife = 600 // TODO 需要可以设置
|
||||
var unixTime = utils.UnixTime() // 只删除当前的,不删除新的
|
||||
var staleLife = 600 // TODO 需要可以设置
|
||||
var unixTime = fasttime.Now().Unix() // 只删除当前的,不删除新的
|
||||
|
||||
_, err = this.writeDB.Exec(`UPDATE "`+this.itemsTableName+`" SET "expiredAt"=0, "staleAt"=? WHERE "host" GLOB ? AND "host" NOT GLOB ? AND "key" LIKE ? ESCAPE '\'`, unixTime+int64(staleLife), host, "*."+host, queryPrefix)
|
||||
return err
|
||||
|
||||
@@ -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{})
|
||||
@@ -172,10 +177,15 @@ func (this *Manager) TotalDiskSize() int64 {
|
||||
this.locker.RLock()
|
||||
defer this.locker.RUnlock()
|
||||
|
||||
total := int64(0)
|
||||
var total = int64(0)
|
||||
for _, storage := range this.storageMap {
|
||||
total += storage.TotalDiskSize()
|
||||
}
|
||||
|
||||
if total < 0 {
|
||||
total = 0
|
||||
}
|
||||
|
||||
return total
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
package caches
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils/fasttime"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils/linkedlist"
|
||||
)
|
||||
|
||||
@@ -19,7 +19,7 @@ func NewOpenFilePool(filename string) *OpenFilePool {
|
||||
var pool = &OpenFilePool{
|
||||
filename: filename,
|
||||
c: make(chan *OpenFile, 1024),
|
||||
version: utils.UnixTimeMilli(),
|
||||
version: fasttime.Now().UnixMilli(),
|
||||
}
|
||||
pool.linkItem = linkedlist.NewItem(pool)
|
||||
return pool
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/trackers"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils/fasttime"
|
||||
setutils "github.com/TeaOSLab/EdgeNode/internal/utils/sets"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils/sizes"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/zero"
|
||||
@@ -32,7 +33,7 @@ type MemoryItem struct {
|
||||
}
|
||||
|
||||
func (this *MemoryItem) IsExpired() bool {
|
||||
return this.ExpiresAt < utils.UnixTime()
|
||||
return this.ExpiresAt < fasttime.Now().Unix()
|
||||
}
|
||||
|
||||
type MemoryStorage struct {
|
||||
@@ -119,7 +120,7 @@ func (this *MemoryStorage) OpenReader(key string, useStale bool, isPartial bool)
|
||||
return nil, ErrNotFound
|
||||
}
|
||||
|
||||
if useStale || (item.ExpiresAt > utils.UnixTime()) {
|
||||
if useStale || (item.ExpiresAt > fasttime.Now().Unix()) {
|
||||
reader := NewMemoryReader(item)
|
||||
err := reader.Init()
|
||||
if err != nil {
|
||||
@@ -508,7 +509,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
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
var sharedBrotliReaderPool *ReaderPool
|
||||
|
||||
func init() {
|
||||
if teaconst.IsDaemon {
|
||||
if !teaconst.IsMain {
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
var sharedDeflateReaderPool *ReaderPool
|
||||
|
||||
func init() {
|
||||
if teaconst.IsDaemon {
|
||||
if !teaconst.IsMain {
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
var sharedGzipReaderPool *ReaderPool
|
||||
|
||||
func init() {
|
||||
if teaconst.IsDaemon {
|
||||
if !teaconst.IsMain {
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
var sharedZSTDReaderPool *ReaderPool
|
||||
|
||||
func init() {
|
||||
if teaconst.IsDaemon {
|
||||
if !teaconst.IsMain {
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
var sharedBrotliWriterPool *WriterPool
|
||||
|
||||
func init() {
|
||||
if teaconst.IsDaemon {
|
||||
if !teaconst.IsMain {
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
var sharedDeflateWriterPool *WriterPool
|
||||
|
||||
func init() {
|
||||
if teaconst.IsDaemon {
|
||||
if !teaconst.IsMain {
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
var sharedGzipWriterPool *WriterPool
|
||||
|
||||
func init() {
|
||||
if teaconst.IsDaemon {
|
||||
if !teaconst.IsMain {
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
var sharedZSTDWriterPool *WriterPool
|
||||
|
||||
func init() {
|
||||
if teaconst.IsDaemon {
|
||||
if !teaconst.IsMain {
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package teaconst
|
||||
|
||||
const (
|
||||
Version = "0.6.0"
|
||||
Version = "1.0.0"
|
||||
|
||||
ProductName = "Edge Node"
|
||||
ProcessName = "edge-node"
|
||||
|
||||
@@ -5,6 +5,7 @@ package teaconst
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -15,7 +16,7 @@ var (
|
||||
|
||||
NodeId int64 = 0
|
||||
NodeIdString = ""
|
||||
IsDaemon = len(os.Args) > 1 && os.Args[1] == "daemon"
|
||||
IsMain = checkMain()
|
||||
|
||||
GlobalProductName = nodeconfigs.DefaultProductName
|
||||
|
||||
@@ -24,3 +25,15 @@ var (
|
||||
|
||||
DiskIsFast = false // 是否为高速硬盘
|
||||
)
|
||||
|
||||
// 检查是否为主程序
|
||||
func checkMain() bool {
|
||||
if len(os.Args) == 1 ||
|
||||
(len(os.Args) >= 2 && os.Args[1] == "pprof") {
|
||||
return true
|
||||
}
|
||||
exe, _ := os.Executable()
|
||||
return strings.HasSuffix(exe, ".test") ||
|
||||
strings.HasSuffix(exe, ".test.exe") ||
|
||||
strings.Contains(exe, "___")
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
@@ -19,14 +20,18 @@ import (
|
||||
"github.com/iwind/TeaGo/types"
|
||||
stringutil "github.com/iwind/TeaGo/utils/string"
|
||||
"net"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
var SharedDDoSProtectionManager = NewDDoSProtectionManager()
|
||||
|
||||
func init() {
|
||||
if !teaconst.IsMain {
|
||||
return
|
||||
}
|
||||
|
||||
events.On(events.EventReload, func() {
|
||||
if nftablesInstance == nil {
|
||||
return
|
||||
@@ -56,6 +61,8 @@ func init() {
|
||||
type DDoSProtectionManager struct {
|
||||
lastAllowIPList []string
|
||||
lastConfig []byte
|
||||
|
||||
locker sync.Mutex
|
||||
}
|
||||
|
||||
// NewDDoSProtectionManager 获取新对象
|
||||
@@ -65,6 +72,12 @@ func NewDDoSProtectionManager() *DDoSProtectionManager {
|
||||
|
||||
// Apply 应用配置
|
||||
func (this *DDoSProtectionManager) Apply(config *ddosconfigs.ProtectionConfig) error {
|
||||
// 加锁防止并发更改
|
||||
if !this.locker.TryLock() {
|
||||
return nil
|
||||
}
|
||||
defer this.locker.Unlock()
|
||||
|
||||
// 同集群节点IP白名单
|
||||
var allowIPListChanged = false
|
||||
nodeConfig, _ := nodeconfigs.SharedNodeConfig()
|
||||
@@ -86,7 +99,7 @@ func (this *DDoSProtectionManager) Apply(config *ddosconfigs.ProtectionConfig) e
|
||||
}
|
||||
remotelogs.Println("FIREWALL", "change DDoS protection config")
|
||||
|
||||
if len(this.nftExe()) == 0 {
|
||||
if len(nftables.NftExePath()) == 0 {
|
||||
return errors.New("can not find nft command")
|
||||
}
|
||||
|
||||
@@ -152,7 +165,7 @@ func (this *DDoSProtectionManager) Apply(config *ddosconfigs.ProtectionConfig) e
|
||||
|
||||
// 添加TCP规则
|
||||
func (this *DDoSProtectionManager) addTCPRules(tcpConfig *ddosconfigs.TCPConfig) error {
|
||||
var nftExe = this.nftExe()
|
||||
var nftExe = nftables.NftExePath()
|
||||
if len(nftExe) == 0 {
|
||||
return nil
|
||||
}
|
||||
@@ -541,7 +554,7 @@ func (this *DDoSProtectionManager) updateAllowIPList(allIPList []string) error {
|
||||
_, ok := oldMap[ip]
|
||||
if !ok {
|
||||
// 不存在则添加
|
||||
err = set.AddIPElement(ip, nil)
|
||||
err = set.AddIPElement(ip, nil, false)
|
||||
if err != nil {
|
||||
return errors.New("add ip '" + ip + "' failed: " + err.Error())
|
||||
}
|
||||
@@ -552,8 +565,3 @@ func (this *DDoSProtectionManager) updateAllowIPList(allIPList []string) error {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (this *DDoSProtectionManager) nftExe() string {
|
||||
path, _ := exec.LookPath("nft")
|
||||
return path
|
||||
}
|
||||
|
||||
@@ -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" {
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
//go:build linux
|
||||
// +build linux
|
||||
|
||||
package firewalls
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/configutils"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/conns"
|
||||
teaconst "github.com/TeaOSLab/EdgeNode/internal/const"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/events"
|
||||
@@ -13,9 +13,9 @@ import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/goman"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
|
||||
executils "github.com/TeaOSLab/EdgeNode/internal/utils/exec"
|
||||
"github.com/google/nftables/expr"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
"net"
|
||||
"os/exec"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"strings"
|
||||
@@ -24,7 +24,7 @@ import (
|
||||
|
||||
// check nft status, if being enabled we load it automatically
|
||||
func init() {
|
||||
if teaconst.IsDaemon {
|
||||
if !teaconst.IsMain {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -37,8 +37,8 @@ func init() {
|
||||
ticker.Stop()
|
||||
break
|
||||
}
|
||||
_, err := exec.LookPath("nft")
|
||||
if err == nil {
|
||||
var nftExe = nftables.NftExePath()
|
||||
if len(nftExe) > 0 {
|
||||
nftablesFirewall, err := NewNFTablesFirewall()
|
||||
if err != nil {
|
||||
continue
|
||||
@@ -110,8 +110,8 @@ type NFTablesFirewall struct {
|
||||
allowIPv4Set *nftables.Set
|
||||
allowIPv6Set *nftables.Set
|
||||
|
||||
denyIPv4Set *nftables.Set
|
||||
denyIPv6Set *nftables.Set
|
||||
denyIPv4Sets []*nftables.Set
|
||||
denyIPv6Sets []*nftables.Set
|
||||
|
||||
firewalld *Firewalld
|
||||
|
||||
@@ -120,9 +120,9 @@ type NFTablesFirewall struct {
|
||||
|
||||
func (this *NFTablesFirewall) init() error {
|
||||
// check nft
|
||||
nftPath, err := exec.LookPath("nft")
|
||||
if err != nil {
|
||||
return errors.New("nft not found")
|
||||
var nftPath = nftables.NftExePath()
|
||||
if len(nftPath) == 0 {
|
||||
return errors.New("'nft' not found")
|
||||
}
|
||||
this.version = this.readVersion(nftPath)
|
||||
|
||||
@@ -186,7 +186,7 @@ func (this *NFTablesFirewall) init() error {
|
||||
|
||||
// allow set
|
||||
// "allow" should be always first
|
||||
for _, setAction := range []string{"allow", "deny"} {
|
||||
for _, setAction := range []string{"allow", "deny", "deny1", "deny2", "deny3", "deny4"} {
|
||||
var setName = setAction + "_set"
|
||||
|
||||
set, err := table.GetSet(setName)
|
||||
@@ -216,32 +216,42 @@ func (this *NFTablesFirewall) init() error {
|
||||
if setAction == "allow" {
|
||||
this.allowIPv4Set = set
|
||||
} else {
|
||||
this.denyIPv4Set = set
|
||||
this.denyIPv4Sets = append(this.denyIPv4Sets, set)
|
||||
}
|
||||
} else if tableDef.IsIPv6 {
|
||||
if setAction == "allow" {
|
||||
this.allowIPv6Set = set
|
||||
} else {
|
||||
this.denyIPv6Set = set
|
||||
this.denyIPv6Sets = append(this.denyIPv6Sets, set)
|
||||
}
|
||||
}
|
||||
|
||||
// rule
|
||||
var ruleName = []byte(setAction)
|
||||
rule, err := chain.GetRuleWithUserData(ruleName)
|
||||
|
||||
// 将以前的drop规则删掉,替换成后面的reject
|
||||
if err == nil && setAction != "allow" && rule != nil && rule.VerDict() == expr.VerdictDrop {
|
||||
deleteErr := chain.DeleteRule(rule)
|
||||
if deleteErr == nil {
|
||||
err = nftables.ErrRuleNotFound
|
||||
rule = nil
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
if nftables.IsNotFound(err) {
|
||||
if tableDef.IsIPv4 {
|
||||
if setAction == "allow" {
|
||||
rule, err = chain.AddAcceptIPv4SetRule(setName, ruleName)
|
||||
} else {
|
||||
rule, err = chain.AddDropIPv4SetRule(setName, ruleName)
|
||||
rule, err = chain.AddRejectIPv4SetRule(setName, ruleName)
|
||||
}
|
||||
} else if tableDef.IsIPv6 {
|
||||
if setAction == "allow" {
|
||||
rule, err = chain.AddAcceptIPv6SetRule(setName, ruleName)
|
||||
} else {
|
||||
rule, err = chain.AddDropIPv6SetRule(setName, ruleName)
|
||||
rule, err = chain.AddRejectIPv6SetRule(setName, ruleName)
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
@@ -265,7 +275,7 @@ func (this *NFTablesFirewall) init() error {
|
||||
for ipItem := range this.dropIPQueue {
|
||||
switch ipItem.action {
|
||||
case "drop":
|
||||
err = this.DropSourceIP(ipItem.ip, ipItem.timeoutSeconds, false)
|
||||
err := this.DropSourceIP(ipItem.ip, ipItem.timeoutSeconds, false)
|
||||
if err != nil {
|
||||
remotelogs.Warn("NFTABLES", "drop ip '"+ipItem.ip+"' failed: "+err.Error())
|
||||
}
|
||||
@@ -324,14 +334,14 @@ func (this *NFTablesFirewall) AllowSourceIP(ip string) error {
|
||||
if this.allowIPv6Set == nil {
|
||||
return errors.New("ipv6 ip set is nil")
|
||||
}
|
||||
return this.allowIPv6Set.AddElement(data.To16(), nil)
|
||||
return this.allowIPv6Set.AddElement(data.To16(), nil, false)
|
||||
}
|
||||
|
||||
// ipv4
|
||||
if this.allowIPv4Set == nil {
|
||||
return errors.New("ipv4 ip set is nil")
|
||||
}
|
||||
return this.allowIPv4Set.AddElement(data.To4(), nil)
|
||||
return this.allowIPv4Set.AddElement(data.To4(), nil, false)
|
||||
}
|
||||
|
||||
// RejectSourceIP 拒绝某个源IP连接
|
||||
@@ -371,22 +381,23 @@ func (this *NFTablesFirewall) DropSourceIP(ip string, timeoutSeconds int, async
|
||||
// 再次尝试关闭连接
|
||||
defer conns.SharedMap.CloseIPConns(ip)
|
||||
|
||||
var ipLong = configutils.IPString2Long(ip)
|
||||
if strings.Contains(ip, ":") { // ipv6
|
||||
if this.denyIPv6Set == nil {
|
||||
return errors.New("ipv6 ip set is nil")
|
||||
if len(this.denyIPv6Sets) == 0 {
|
||||
return errors.New("ipv6 ip set not found")
|
||||
}
|
||||
return this.denyIPv6Set.AddElement(data.To16(), &nftables.ElementOptions{
|
||||
return this.denyIPv6Sets[ipLong%uint64(len(this.denyIPv6Sets))].AddElement(data.To16(), &nftables.ElementOptions{
|
||||
Timeout: time.Duration(timeoutSeconds) * time.Second,
|
||||
})
|
||||
}, false)
|
||||
}
|
||||
|
||||
// ipv4
|
||||
if this.denyIPv4Set == nil {
|
||||
return errors.New("ipv4 ip set is nil")
|
||||
if len(this.denyIPv4Sets) == 0 {
|
||||
return errors.New("ipv4 ip set not found")
|
||||
}
|
||||
return this.denyIPv4Set.AddElement(data.To4(), &nftables.ElementOptions{
|
||||
return this.denyIPv4Sets[ipLong%uint64(len(this.denyIPv4Sets))].AddElement(data.To4(), &nftables.ElementOptions{
|
||||
Timeout: time.Duration(timeoutSeconds) * time.Second,
|
||||
})
|
||||
}, false)
|
||||
}
|
||||
|
||||
// RemoveSourceIP 删除某个源IP
|
||||
@@ -396,9 +407,10 @@ func (this *NFTablesFirewall) RemoveSourceIP(ip string) error {
|
||||
return errors.New("invalid ip '" + ip + "'")
|
||||
}
|
||||
|
||||
var ipLong = configutils.IPString2Long(ip)
|
||||
if strings.Contains(ip, ":") { // ipv6
|
||||
if this.denyIPv6Set != nil {
|
||||
err := this.denyIPv6Set.DeleteElement(data.To16())
|
||||
if len(this.denyIPv6Sets) > 0 {
|
||||
err := this.denyIPv6Sets[ipLong%uint64(len(this.denyIPv6Sets))].DeleteElement(data.To16())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -415,13 +427,14 @@ func (this *NFTablesFirewall) RemoveSourceIP(ip string) error {
|
||||
}
|
||||
|
||||
// ipv4
|
||||
if this.allowIPv4Set != nil {
|
||||
err := this.denyIPv4Set.DeleteElement(data.To4())
|
||||
if len(this.denyIPv4Sets) > 0 {
|
||||
err := this.denyIPv4Sets[ipLong%uint64(len(this.denyIPv4Sets))].DeleteElement(data.To4())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = this.allowIPv4Set.DeleteElement(data.To4())
|
||||
}
|
||||
if this.allowIPv4Set != nil {
|
||||
err := this.allowIPv4Set.DeleteElement(data.To4())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -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" {
|
||||
@@ -33,8 +38,7 @@ func init() {
|
||||
}
|
||||
|
||||
if os.Getgid() == 0 { // root user only
|
||||
_, err := exec.LookPath("nft")
|
||||
if err == nil {
|
||||
if len(NftExePath()) > 0 {
|
||||
return
|
||||
}
|
||||
goman.New(func() {
|
||||
@@ -48,6 +52,25 @@ func init() {
|
||||
})
|
||||
}
|
||||
|
||||
// NftExePath 查找nftables可执行文件路径
|
||||
func NftExePath() string {
|
||||
path, _ := exec.LookPath("nft")
|
||||
if len(path) > 0 {
|
||||
return path
|
||||
}
|
||||
|
||||
for _, possiblePath := range []string{
|
||||
"/usr/sbin/nft",
|
||||
} {
|
||||
_, err := os.Stat(possiblePath)
|
||||
if err == nil {
|
||||
return possiblePath
|
||||
}
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
type Installer struct {
|
||||
}
|
||||
|
||||
@@ -62,8 +85,7 @@ func (this *Installer) Install() error {
|
||||
}
|
||||
|
||||
// 检查是否已经存在
|
||||
_, err := exec.LookPath("nft")
|
||||
if err == nil {
|
||||
if len(NftExePath()) > 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -56,7 +56,7 @@ func (this *Set) Name() string {
|
||||
return this.rawSet.Name
|
||||
}
|
||||
|
||||
func (this *Set) AddElement(key []byte, options *ElementOptions) error {
|
||||
func (this *Set) AddElement(key []byte, options *ElementOptions, overwrite bool) error {
|
||||
var rawElement = nft.SetElement{
|
||||
Key: key,
|
||||
}
|
||||
@@ -73,7 +73,7 @@ func (this *Set) AddElement(key []byte, options *ElementOptions) error {
|
||||
err = this.conn.Commit()
|
||||
if err != nil {
|
||||
// retry if exists
|
||||
if strings.Contains(err.Error(), "file exists") {
|
||||
if overwrite && strings.Contains(err.Error(), "file exists") {
|
||||
deleteErr := this.conn.Raw().SetDeleteElements(this.rawSet, []nft.SetElement{
|
||||
{
|
||||
Key: key,
|
||||
@@ -93,16 +93,16 @@ func (this *Set) AddElement(key []byte, options *ElementOptions) error {
|
||||
return err
|
||||
}
|
||||
|
||||
func (this *Set) AddIPElement(ip string, options *ElementOptions) error {
|
||||
func (this *Set) AddIPElement(ip string, options *ElementOptions, overwrite bool) error {
|
||||
var ipObj = net.ParseIP(ip)
|
||||
if ipObj == nil {
|
||||
return errors.New("invalid ip '" + ip + "'")
|
||||
}
|
||||
|
||||
if utils.IsIPv4(ip) {
|
||||
return this.AddElement(ipObj.To4(), options)
|
||||
return this.AddElement(ipObj.To4(), options, overwrite)
|
||||
} else {
|
||||
return this.AddElement(ipObj.To16(), options)
|
||||
return this.AddElement(ipObj.To16(), options, overwrite)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
30
internal/firewalls/utils.go
Normal file
30
internal/firewalls/utils.go
Normal file
@@ -0,0 +1,30 @@
|
||||
// Copyright 2023 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
|
||||
package firewalls
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// DropTemporaryTo 使用本地防火墙临时拦截IP数据包
|
||||
func DropTemporaryTo(ip string, expiresAt int64) {
|
||||
// 如果为0,则表示是长期有效
|
||||
if expiresAt <= 0 {
|
||||
expiresAt = time.Now().Unix() + 3600
|
||||
}
|
||||
|
||||
var timeout = expiresAt - time.Now().Unix()
|
||||
if timeout < 1 {
|
||||
return
|
||||
}
|
||||
if timeout > 3600 {
|
||||
timeout = 3600
|
||||
}
|
||||
|
||||
// 使用本地防火墙延长封禁
|
||||
var fw = Firewall()
|
||||
if fw != nil && !fw.IsMock() {
|
||||
// 这里 int(int64) 转换的前提是限制了 timeout <= 3600,否则将有整型溢出的风险
|
||||
_ = fw.DropSourceIP(ip, int(timeout), true)
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package iplibrary
|
||||
|
||||
import "github.com/TeaOSLab/EdgeNode/internal/utils"
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils/fasttime"
|
||||
)
|
||||
|
||||
type IPItemType = string
|
||||
|
||||
@@ -45,7 +47,7 @@ func (this *IPItem) containsIPv4(ip uint64) bool {
|
||||
return false
|
||||
}
|
||||
}
|
||||
if this.ExpiredAt > 0 && this.ExpiredAt < utils.UnixTime() {
|
||||
if this.ExpiredAt > 0 && this.ExpiredAt < fasttime.Now().Unix() {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
@@ -56,7 +58,7 @@ func (this *IPItem) containsIPv6(ip uint64) bool {
|
||||
if this.IPFrom != ip {
|
||||
return false
|
||||
}
|
||||
if this.ExpiredAt > 0 && this.ExpiredAt < utils.UnixTime() {
|
||||
if this.ExpiredAt > 0 && this.ExpiredAt < fasttime.Now().Unix() {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
@@ -64,7 +66,7 @@ func (this *IPItem) containsIPv6(ip uint64) bool {
|
||||
|
||||
// 检查是否包所有IP
|
||||
func (this *IPItem) containsAll() bool {
|
||||
if this.ExpiredAt > 0 && this.ExpiredAt < utils.UnixTime() {
|
||||
if this.ExpiredAt > 0 && this.ExpiredAt < fasttime.Now().Unix() {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
|
||||
@@ -3,6 +3,7 @@ package iplibrary
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils/expires"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils/fasttime"
|
||||
"sort"
|
||||
"sync"
|
||||
)
|
||||
@@ -72,6 +73,25 @@ func (this *IPList) Contains(ip uint64) bool {
|
||||
return item != nil
|
||||
}
|
||||
|
||||
// ContainsExpires 判断是否包含某个IP
|
||||
func (this *IPList) ContainsExpires(ip uint64) (expiresAt int64, ok bool) {
|
||||
this.locker.RLock()
|
||||
if len(this.allItemsMap) > 0 {
|
||||
this.locker.RUnlock()
|
||||
return 0, true
|
||||
}
|
||||
|
||||
var item = this.lookupIP(ip)
|
||||
|
||||
this.locker.RUnlock()
|
||||
|
||||
if item == nil {
|
||||
return
|
||||
}
|
||||
|
||||
return item.ExpiredAt, true
|
||||
}
|
||||
|
||||
// ContainsIPStrings 是否包含一组IP中的任意一个,并返回匹配的第一个Item
|
||||
func (this *IPList) ContainsIPStrings(ipStrings []string) (item *IPItem, found bool) {
|
||||
if len(ipStrings) == 0 {
|
||||
@@ -110,7 +130,7 @@ func (this *IPList) addItem(item *IPItem, sortable bool) {
|
||||
return
|
||||
}
|
||||
|
||||
if item.ExpiredAt > 0 && item.ExpiredAt < utils.UnixTime() {
|
||||
if item.ExpiredAt > 0 && item.ExpiredAt < fasttime.Now().Unix() {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -155,7 +175,7 @@ func (this *IPList) addItem(item *IPItem, sortable bool) {
|
||||
this.locker.Unlock()
|
||||
}
|
||||
|
||||
// 对列表进行排序
|
||||
// 对列表进行排序
|
||||
func (this *IPList) sortItems() {
|
||||
sort.Slice(this.sortedItems, func(i, j int) bool {
|
||||
var item1 = this.sortedItems[i]
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -10,50 +10,54 @@ import (
|
||||
|
||||
// AllowIP 检查IP是否被允许访问
|
||||
// 如果一个IP不在任何名单中,则允许访问
|
||||
func AllowIP(ip string, serverId int64) (canGoNext bool, inAllowList bool) {
|
||||
func AllowIP(ip string, serverId int64) (canGoNext bool, inAllowList bool, expiresAt int64) {
|
||||
if !Tea.IsTesting() { // 如果在测试环境,我们不加入一些白名单,以便于可以在本地和局域网正常测试
|
||||
// 放行lo
|
||||
if ip == "127.0.0.1" || ip == "::1" {
|
||||
return true, true
|
||||
return true, true, 0
|
||||
}
|
||||
|
||||
// check node
|
||||
nodeConfig, err := nodeconfigs.SharedNodeConfig()
|
||||
if err == nil && nodeConfig.IPIsAutoAllowed(ip) {
|
||||
return true, true
|
||||
return true, true, 0
|
||||
}
|
||||
}
|
||||
|
||||
var ipLong = utils.IP2Long(ip)
|
||||
if ipLong == 0 {
|
||||
return false, false
|
||||
return false, false, 0
|
||||
}
|
||||
|
||||
// check white lists
|
||||
if GlobalWhiteIPList.Contains(ipLong) {
|
||||
return true, true
|
||||
return true, true, 0
|
||||
}
|
||||
|
||||
if serverId > 0 {
|
||||
var list = SharedServerListManager.FindWhiteList(serverId, false)
|
||||
if list != nil && list.Contains(ipLong) {
|
||||
return true, true
|
||||
return true, true, 0
|
||||
}
|
||||
}
|
||||
|
||||
// check black lists
|
||||
if GlobalBlackIPList.Contains(ipLong) {
|
||||
return false, false
|
||||
expiresAt, ok := GlobalBlackIPList.ContainsExpires(ipLong)
|
||||
if ok {
|
||||
return false, false, expiresAt
|
||||
}
|
||||
|
||||
if serverId > 0 {
|
||||
var list = SharedServerListManager.FindBlackList(serverId, false)
|
||||
if list != nil && list.Contains(ipLong) {
|
||||
return false, false
|
||||
if list != nil {
|
||||
expiresAt, ok = list.ContainsExpires(ipLong)
|
||||
if ok {
|
||||
return false, false, expiresAt
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true, false
|
||||
return true, false, 0
|
||||
}
|
||||
|
||||
// IsInWhiteList 检查IP是否在白名单中
|
||||
@@ -73,7 +77,7 @@ func AllowIPStrings(ipStrings []string, serverId int64) bool {
|
||||
return true
|
||||
}
|
||||
for _, ip := range ipStrings {
|
||||
isAllowed, _ := AllowIP(ip, serverId)
|
||||
isAllowed, _, _ := AllowIP(ip, serverId)
|
||||
if !isAllowed {
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ var SharedIPListManager = NewIPListManager()
|
||||
var IPListUpdateNotify = make(chan bool, 1)
|
||||
|
||||
func init() {
|
||||
if teaconst.IsDaemon {
|
||||
if !teaconst.IsMain {
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -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()
|
||||
})
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/stats"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/ttlcache"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils/fasttime"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/waf"
|
||||
"github.com/iwind/TeaGo/Tea"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
@@ -42,7 +43,11 @@ type ClientConn struct {
|
||||
lastErr error
|
||||
|
||||
readDeadlineTime int64
|
||||
isShortReading bool // header or handshake
|
||||
isShortReading bool // reading header or tls handshake
|
||||
|
||||
isDebugging bool
|
||||
autoReadTimeout bool
|
||||
autoWriteTimeout bool
|
||||
}
|
||||
|
||||
func NewClientConn(rawConn net.Conn, isHTTP bool, isTLS bool, isInAllowList bool) net.Conn {
|
||||
@@ -59,6 +64,14 @@ func NewClientConn(rawConn net.Conn, isHTTP bool, isTLS bool, isInAllowList bool
|
||||
createdAt: time.Now().Unix(),
|
||||
}
|
||||
|
||||
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)
|
||||
@@ -71,14 +84,14 @@ func NewClientConn(rawConn net.Conn, isHTTP bool, isTLS bool, isInAllowList bool
|
||||
}
|
||||
|
||||
func (this *ClientConn) Read(b []byte) (n int, err error) {
|
||||
var globalServerConfig = sharedNodeConfig.GlobalServerConfig
|
||||
|
||||
if globalServerConfig != nil && globalServerConfig.Performance.Debug {
|
||||
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
|
||||
}
|
||||
}()
|
||||
}
|
||||
@@ -93,8 +106,7 @@ func (this *ClientConn) Read(b []byte) (n int, err error) {
|
||||
}
|
||||
|
||||
// 设置读超时时间
|
||||
var autoReadTimeout = globalServerConfig != nil && globalServerConfig.Performance.AutoReadTimeout
|
||||
if this.isHTTP && !this.isShortReading && autoReadTimeout {
|
||||
if this.isHTTP && !this.isPersistent && !this.isShortReading && this.autoReadTimeout {
|
||||
this.setHTTPReadTimeout()
|
||||
}
|
||||
|
||||
@@ -134,20 +146,24 @@ func (this *ClientConn) Read(b []byte) (n int, err error) {
|
||||
}
|
||||
|
||||
func (this *ClientConn) Write(b []byte) (n int, err error) {
|
||||
var globalServerConfig = sharedNodeConfig.GlobalServerConfig
|
||||
if len(b) == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
if globalServerConfig != nil && globalServerConfig.Performance.Debug {
|
||||
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 globalServerConfig != nil && globalServerConfig.Performance.AutoWriteTimeout {
|
||||
if this.autoWriteTimeout {
|
||||
// TODO L2 -> L1 写入时不限制时间
|
||||
var timeoutSeconds = len(b) / 1024
|
||||
if timeoutSeconds < 3 {
|
||||
@@ -157,18 +173,26 @@ func (this *ClientConn) Write(b []byte) (n int, err error) {
|
||||
}
|
||||
|
||||
// 延长读超时时间
|
||||
if this.isHTTP {
|
||||
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 {
|
||||
// TODO 需要加入在serverId绑定之前的带宽
|
||||
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.AddBandwidth(this.userId, this.serverId, int64(float64(n)/cost), int64(n))
|
||||
} else {
|
||||
stats.SharedBandwidthStatManager.AddBandwidth(this.userId, this.serverId, int64(n), int64(n))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -216,8 +240,7 @@ func (this *ClientConn) SetDeadline(t time.Time) error {
|
||||
|
||||
func (this *ClientConn) SetReadDeadline(t time.Time) error {
|
||||
// 如果开启了HTTP自动读超时选项,则自动控制超时时间
|
||||
var globalServerConfig = sharedNodeConfig.GlobalServerConfig
|
||||
if this.isHTTP && globalServerConfig != nil && globalServerConfig.Performance.AutoReadTimeout {
|
||||
if this.isHTTP && !this.isPersistent && this.autoReadTimeout {
|
||||
this.isShortReading = false
|
||||
|
||||
var unixTime = t.Unix()
|
||||
@@ -266,7 +289,7 @@ func (this *ClientConn) resetSYNFlood() {
|
||||
func (this *ClientConn) increaseSYNFlood(synFloodConfig *firewallconfigs.SYNFloodConfig) {
|
||||
var ip = this.RawIP()
|
||||
if len(ip) > 0 && !iplibrary.IsInWhiteList(ip) && (!synFloodConfig.IgnoreLocal || !utils.IsLocalIP(ip)) {
|
||||
var timestamp = utils.NextMinuteUnixTime()
|
||||
var timestamp = fasttime.Now().UnixNextMinute()
|
||||
var result = ttlcache.SharedCache.IncreaseInt64("SYN_FLOOD:"+ip, 1, timestamp, true)
|
||||
var minAttempts = synFloodConfig.MinAttempts
|
||||
if minAttempts < 5 {
|
||||
|
||||
@@ -4,6 +4,8 @@ package nodes
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/firewalls"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/iplibrary"
|
||||
"net"
|
||||
)
|
||||
|
||||
@@ -16,6 +18,9 @@ type BaseClientConn struct {
|
||||
remoteAddr string
|
||||
hasLimit bool
|
||||
|
||||
isPersistent bool // 是否为持久化连接
|
||||
fingerprint []byte
|
||||
|
||||
isClosed bool
|
||||
|
||||
rawIP string
|
||||
@@ -45,7 +50,20 @@ func (this *BaseClientConn) Bind(serverId int64, remoteAddr string, maxConnsPerS
|
||||
}
|
||||
|
||||
// SetServerId 设置服务ID
|
||||
func (this *BaseClientConn) SetServerId(serverId int64) {
|
||||
func (this *BaseClientConn) SetServerId(serverId int64) (goNext bool) {
|
||||
goNext = true
|
||||
|
||||
// 检查服务相关IP黑名单
|
||||
if serverId > 0 && len(this.rawIP) > 0 {
|
||||
// 是否在白名单中
|
||||
ok, _, expiresAt := iplibrary.AllowIP(this.rawIP, serverId)
|
||||
if !ok {
|
||||
_ = this.rawConn.Close()
|
||||
firewalls.DropTemporaryTo(this.rawIP, expiresAt)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
this.serverId = serverId
|
||||
|
||||
// 设置包装前连接
|
||||
@@ -58,6 +76,8 @@ func (this *BaseClientConn) SetServerId(serverId int64) {
|
||||
case *ClientConn:
|
||||
conn.SetServerId(serverId)
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// ServerId 读取当前连接绑定的服务ID
|
||||
@@ -122,3 +142,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
|
||||
}
|
||||
|
||||
@@ -16,11 +16,20 @@ type ClientConnInterface interface {
|
||||
ServerId() int64
|
||||
|
||||
// SetServerId 设置服务ID
|
||||
SetServerId(serverId int64)
|
||||
SetServerId(serverId int64) (goNext bool)
|
||||
|
||||
// SetUserId 设置所属服务的用户ID
|
||||
SetUserId(userId int64)
|
||||
|
||||
// UserId 获取当前连接所属服务的用户ID
|
||||
UserId() int64
|
||||
|
||||
// SetIsPersistent 设置是否为持久化
|
||||
SetIsPersistent(isPersistent bool)
|
||||
|
||||
// SetFingerprint 设置指纹信息
|
||||
SetFingerprint(fingerprint []byte)
|
||||
|
||||
// Fingerprint 读取指纹信息
|
||||
Fingerprint() []byte
|
||||
}
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -8,7 +8,6 @@ import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/iplibrary"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/waf"
|
||||
"net"
|
||||
"time"
|
||||
)
|
||||
|
||||
// ClientListener 客户端网络监听
|
||||
@@ -43,25 +42,17 @@ func (this *ClientListener) Accept() (net.Conn, error) {
|
||||
ip, _, err := net.SplitHostPort(conn.RemoteAddr().String())
|
||||
var isInAllowList = false
|
||||
if err == nil {
|
||||
canGoNext, inAllowList := iplibrary.AllowIP(ip, 0)
|
||||
canGoNext, inAllowList, expiresAt := iplibrary.AllowIP(ip, 0)
|
||||
isInAllowList = inAllowList
|
||||
if !waf.SharedIPWhiteList.Contains(waf.IPTypeAll, firewallconfigs.FirewallScopeGlobal, 0, ip) {
|
||||
expiresAt, ok := waf.SharedIPBlackList.ContainsExpires(waf.IPTypeAll, firewallconfigs.FirewallScopeGlobal, 0, ip)
|
||||
if ok {
|
||||
var timeout = expiresAt - time.Now().Unix()
|
||||
if timeout > 0 {
|
||||
if !canGoNext {
|
||||
firewalls.DropTemporaryTo(ip, expiresAt)
|
||||
} else {
|
||||
if !waf.SharedIPWhiteList.Contains(waf.IPTypeAll, firewallconfigs.FirewallScopeGlobal, 0, ip) {
|
||||
var ok = false
|
||||
expiresAt, ok = waf.SharedIPBlackList.ContainsExpires(waf.IPTypeAll, firewallconfigs.FirewallScopeGlobal, 0, ip)
|
||||
if ok {
|
||||
canGoNext = false
|
||||
|
||||
if timeout > 3600 {
|
||||
timeout = 3600
|
||||
}
|
||||
|
||||
// 使用本地防火墙延长封禁
|
||||
var fw = firewalls.Firewall()
|
||||
if fw != nil && !fw.IsMock() {
|
||||
// 这里 int(int64) 转换的前提是限制了 timeout <= 3600,否则将有整型溢出的风险
|
||||
_ = fw.DropSourceIP(ip, int(timeout), true)
|
||||
}
|
||||
firewalls.DropTemporaryTo(ip, expiresAt)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"google.golang.org/grpc/status"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
var sharedHTTPAccessLogQueue = NewHTTPAccessLogQueue()
|
||||
@@ -25,9 +26,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() {
|
||||
@@ -134,14 +138,23 @@ Loop:
|
||||
return nil
|
||||
}
|
||||
|
||||
// ToValidUTF8 处理访问日志中的非UTF-8字节
|
||||
func (this *HTTPAccessLogQueue) ToValidUTF8(accessLog *pb.HTTPAccessLog) {
|
||||
accessLog.RemoteAddr = utils.ToValidUTF8string(accessLog.RemoteAddr)
|
||||
accessLog.RemoteUser = utils.ToValidUTF8string(accessLog.RemoteUser)
|
||||
accessLog.RequestURI = utils.ToValidUTF8string(accessLog.RequestURI)
|
||||
accessLog.RequestPath = utils.ToValidUTF8string(accessLog.RequestPath)
|
||||
accessLog.RequestFilename = utils.ToValidUTF8string(accessLog.RequestFilename)
|
||||
accessLog.RequestBody = bytes.ToValidUTF8(accessLog.RequestBody, []byte{})
|
||||
accessLog.Host = utils.ToValidUTF8string(accessLog.Host)
|
||||
accessLog.Hostname = utils.ToValidUTF8string(accessLog.Hostname)
|
||||
|
||||
for k, v := range accessLog.SentHeader {
|
||||
if !utf8.ValidString(k) {
|
||||
delete(accessLog.SentHeader, k)
|
||||
continue
|
||||
}
|
||||
|
||||
for _, v := range accessLog.SentHeader {
|
||||
for index, s := range v.Values {
|
||||
v.Values[index] = utils.ToValidUTF8string(s)
|
||||
}
|
||||
@@ -153,15 +166,27 @@ func (this *HTTPAccessLogQueue) ToValidUTF8(accessLog *pb.HTTPAccessLog) {
|
||||
accessLog.ContentType = utils.ToValidUTF8string(accessLog.ContentType)
|
||||
|
||||
for k, c := range accessLog.Cookie {
|
||||
if !utf8.ValidString(k) {
|
||||
delete(accessLog.Cookie, k)
|
||||
continue
|
||||
}
|
||||
accessLog.Cookie[k] = utils.ToValidUTF8string(c)
|
||||
}
|
||||
|
||||
accessLog.Args = utils.ToValidUTF8string(accessLog.Args)
|
||||
accessLog.QueryString = utils.ToValidUTF8string(accessLog.QueryString)
|
||||
|
||||
for _, v := range accessLog.Header {
|
||||
for k, v := range accessLog.Header {
|
||||
if !utf8.ValidString(k) {
|
||||
delete(accessLog.Header, k)
|
||||
continue
|
||||
}
|
||||
for index, s := range v.Values {
|
||||
v.Values[index] = utils.ToValidUTF8string(s)
|
||||
}
|
||||
}
|
||||
|
||||
for k, v := range accessLog.Errors {
|
||||
accessLog.Errors[k] = utils.ToValidUTF8string(v)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
func TestHTTPAccessLogQueue_Push(t *testing.T) {
|
||||
@@ -135,6 +136,16 @@ func TestHTTPAccessLogQueue_Memory(t *testing.T) {
|
||||
time.Sleep(5 * time.Second)
|
||||
}
|
||||
|
||||
func TestUTF8_IsValid(t *testing.T) {
|
||||
t.Log(utf8.ValidString("abc"))
|
||||
|
||||
var noneUTF8Bytes = []byte{}
|
||||
for i := 0; i < 254; i++ {
|
||||
noneUTF8Bytes = append(noneUTF8Bytes, uint8(i))
|
||||
}
|
||||
t.Log(utf8.ValidString(string(noneUTF8Bytes)))
|
||||
}
|
||||
|
||||
func BenchmarkHTTPAccessLogQueue_ToValidUTF8(b *testing.B) {
|
||||
runtime.GOMAXPROCS(1)
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package nodes
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils/fasttime"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
@@ -15,7 +15,7 @@ type HTTPClient struct {
|
||||
func NewHTTPClient(rawClient *http.Client) *HTTPClient {
|
||||
return &HTTPClient{
|
||||
rawClient: rawClient,
|
||||
accessAt: utils.UnixTime(),
|
||||
accessAt: fasttime.Now().Unix(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ func (this *HTTPClient) RawClient() *http.Client {
|
||||
|
||||
// UpdateAccessTime 更新访问时间
|
||||
func (this *HTTPClient) UpdateAccessTime() {
|
||||
this.accessAt = utils.UnixTime()
|
||||
this.accessAt = fasttime.Now().Unix()
|
||||
}
|
||||
|
||||
// AccessTime 获取访问时间
|
||||
|
||||
@@ -11,12 +11,12 @@ func TestHTTPClientPool_Client(t *testing.T) {
|
||||
pool := NewHTTPClientPool()
|
||||
|
||||
{
|
||||
origin := &serverconfigs.OriginConfig{
|
||||
var origin = &serverconfigs.OriginConfig{
|
||||
Id: 1,
|
||||
Version: 2,
|
||||
Addr: &serverconfigs.NetworkAddressConfig{Host: "127.0.0.1", PortRange: "1234"},
|
||||
}
|
||||
err := origin.Init()
|
||||
err := origin.Init(nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -43,7 +43,7 @@ func TestHTTPClientPool_cleanClients(t *testing.T) {
|
||||
Version: 2,
|
||||
Addr: &serverconfigs.NetworkAddressConfig{Host: "127.0.0.1", PortRange: "1234"},
|
||||
}
|
||||
err := origin.Init()
|
||||
err := origin.Init(nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -60,17 +60,19 @@ func TestHTTPClientPool_cleanClients(t *testing.T) {
|
||||
func BenchmarkHTTPClientPool_Client(b *testing.B) {
|
||||
runtime.GOMAXPROCS(1)
|
||||
|
||||
origin := &serverconfigs.OriginConfig{
|
||||
var origin = &serverconfigs.OriginConfig{
|
||||
Id: 1,
|
||||
Version: 2,
|
||||
Addr: &serverconfigs.NetworkAddressConfig{Host: "127.0.0.1", PortRange: "1234"},
|
||||
}
|
||||
err := origin.Init()
|
||||
err := origin.Init(nil)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
|
||||
pool := NewHTTPClientPool()
|
||||
b.ResetTimer()
|
||||
|
||||
var pool = NewHTTPClientPool()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, _ = pool.Client(nil, origin, origin.Addr.PickAddress(), nil, false)
|
||||
}
|
||||
|
||||
@@ -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() {
|
||||
@@ -391,7 +403,7 @@ func (this *HTTPRequest) doEnd() {
|
||||
attackBytes = this.CalculateSize()
|
||||
}
|
||||
|
||||
stats.SharedTrafficStatManager.Add(this.ReqServer.Id, this.ReqHost, this.writer.SentBodyBytes()+this.writer.SentHeaderBytes(), cachedBytes, 1, countCached, countAttacks, attackBytes, this.ReqServer.ShouldCheckTrafficLimit(), this.ReqServer.PlanId())
|
||||
stats.SharedTrafficStatManager.Add(this.ReqServer.UserId, this.ReqServer.Id, this.ReqHost, this.writer.SentBodyBytes()+this.writer.SentHeaderBytes(), cachedBytes, 1, countCached, countAttacks, attackBytes, this.ReqServer.ShouldCheckTrafficLimit(), this.ReqServer.PlanId())
|
||||
|
||||
// 指标
|
||||
if metrics.SharedManager.HasHTTPMetrics() {
|
||||
@@ -572,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 {
|
||||
@@ -728,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":
|
||||
@@ -841,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 + "}"
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/compressions"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils/fasttime"
|
||||
rangeutils "github.com/TeaOSLab/EdgeNode/internal/utils/ranges"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
"io"
|
||||
@@ -328,7 +329,7 @@ func (this *HTTPRequest) doCacheRead(useStale bool) (shouldStop bool) {
|
||||
}
|
||||
|
||||
// 设置cache.age变量
|
||||
var age = strconv.FormatInt(utils.UnixTime()-reader.LastModified(), 10)
|
||||
var age = strconv.FormatInt(fasttime.Now().Unix()-reader.LastModified(), 10)
|
||||
this.varMapping["cache.age"] = age
|
||||
|
||||
if addStatusHeader {
|
||||
|
||||
8
internal/nodes/http_request_cc.go
Normal file
8
internal/nodes/http_request_cc.go
Normal 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
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -21,6 +21,8 @@ func (this *HTTPRequest) doHealthCheck(key string, isHealthCheck *bool) (stop bo
|
||||
}
|
||||
*isHealthCheck = true
|
||||
|
||||
this.web.StatRef = nil
|
||||
|
||||
if !data.GetBool("accessLogIsOn") {
|
||||
this.disableLog = true
|
||||
}
|
||||
|
||||
@@ -25,6 +25,16 @@ func (this *HTTPRequest) doHostRedirect() (blocked bool) {
|
||||
if !u.MatchRequest(this.Format) {
|
||||
continue
|
||||
}
|
||||
|
||||
var status = u.Status
|
||||
if status <= 0 {
|
||||
if searchEngineRegex.MatchString(this.RawReq.UserAgent()) {
|
||||
status = http.StatusMovedPermanently
|
||||
} else {
|
||||
status = http.StatusTemporaryRedirect
|
||||
}
|
||||
}
|
||||
|
||||
if len(u.Type) == 0 || u.Type == serverconfigs.HTTPHostRedirectTypeURL {
|
||||
if u.MatchPrefix { // 匹配前缀
|
||||
if strings.HasPrefix(fullURL, u.BeforeURL) {
|
||||
@@ -38,11 +48,8 @@ func (this *HTTPRequest) doHostRedirect() (blocked bool) {
|
||||
return false
|
||||
}
|
||||
|
||||
if u.Status <= 0 {
|
||||
u.Status = http.StatusTemporaryRedirect
|
||||
}
|
||||
this.processResponseHeaders(this.writer.Header(), u.Status)
|
||||
http.Redirect(this.RawWriter, this.RawReq, afterURL, u.Status)
|
||||
this.processResponseHeaders(this.writer.Header(), status)
|
||||
http.Redirect(this.RawWriter, this.RawReq, afterURL, status)
|
||||
return true
|
||||
}
|
||||
} else if u.MatchRegexp { // 正则匹配
|
||||
@@ -83,11 +90,8 @@ func (this *HTTPRequest) doHostRedirect() (blocked bool) {
|
||||
}
|
||||
}
|
||||
|
||||
if u.Status <= 0 {
|
||||
u.Status = http.StatusTemporaryRedirect
|
||||
}
|
||||
this.processResponseHeaders(this.writer.Header(), u.Status)
|
||||
http.Redirect(this.RawWriter, this.RawReq, afterURL, u.Status)
|
||||
this.processResponseHeaders(this.writer.Header(), status)
|
||||
http.Redirect(this.RawWriter, this.RawReq, afterURL, status)
|
||||
return true
|
||||
} else { // 精准匹配
|
||||
if fullURL == u.RealBeforeURL() {
|
||||
@@ -104,11 +108,8 @@ func (this *HTTPRequest) doHostRedirect() (blocked bool) {
|
||||
}
|
||||
}
|
||||
|
||||
if u.Status <= 0 {
|
||||
u.Status = http.StatusTemporaryRedirect
|
||||
}
|
||||
this.processResponseHeaders(this.writer.Header(), u.Status)
|
||||
http.Redirect(this.RawWriter, this.RawReq, afterURL, u.Status)
|
||||
this.processResponseHeaders(this.writer.Header(), status)
|
||||
http.Redirect(this.RawWriter, this.RawReq, afterURL, status)
|
||||
return true
|
||||
}
|
||||
}
|
||||
@@ -142,11 +143,16 @@ func (this *HTTPRequest) doHostRedirect() (blocked bool) {
|
||||
// 终止匹配
|
||||
return false
|
||||
}
|
||||
if u.Status <= 0 {
|
||||
u.Status = http.StatusTemporaryRedirect
|
||||
|
||||
this.processResponseHeaders(this.writer.Header(), status)
|
||||
|
||||
// 参数
|
||||
var qIndex = strings.Index(this.uri, "?")
|
||||
if qIndex >= 0 {
|
||||
afterURL += this.uri[qIndex:]
|
||||
}
|
||||
this.processResponseHeaders(this.writer.Header(), u.Status)
|
||||
http.Redirect(this.RawWriter, this.RawReq, afterURL, u.Status)
|
||||
|
||||
http.Redirect(this.RawWriter, this.RawReq, afterURL, status)
|
||||
return true
|
||||
}
|
||||
} else if u.Type == serverconfigs.HTTPHostRedirectTypePort {
|
||||
@@ -193,11 +199,9 @@ func (this *HTTPRequest) doHostRedirect() (blocked bool) {
|
||||
// 终止匹配
|
||||
return false
|
||||
}
|
||||
if u.Status <= 0 {
|
||||
u.Status = http.StatusTemporaryRedirect
|
||||
}
|
||||
this.processResponseHeaders(this.writer.Header(), u.Status)
|
||||
http.Redirect(this.RawWriter, this.RawReq, afterURL, u.Status)
|
||||
|
||||
this.processResponseHeaders(this.writer.Header(), status)
|
||||
http.Redirect(this.RawWriter, this.RawReq, afterURL, status)
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
|
||||
func (this *HTTPRequest) doRequestLimit() (shouldStop bool) {
|
||||
// 是否在全局名单中
|
||||
_, isInAllowedList := iplibrary.AllowIP(this.RemoteAddr(), this.ReqServer.Id)
|
||||
_, isInAllowedList, _ := iplibrary.AllowIP(this.RemoteAddr(), this.ReqServer.Id)
|
||||
if isInAllowedList {
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -35,12 +35,13 @@ func (this *HTTPRequest) doMismatch() {
|
||||
if sharedNodeConfig.GlobalServerConfig != nil && sharedNodeConfig.GlobalServerConfig.HTTPAll.MatchDomainStrictly {
|
||||
// 检查cc
|
||||
// TODO 可以在管理端配置是否开启以及最多尝试次数
|
||||
// 要考虑到服务在切换集群时,域名未生效状态时,用户访问的仍然是老集群中的节点,就会产生找不到域名的情况
|
||||
if len(remoteIP) > 0 {
|
||||
const maxAttempts = 100
|
||||
if ttlcache.SharedCache.IncreaseInt64("MISMATCH_DOMAIN:"+remoteIP, int64(1), time.Now().Unix()+60, false) > maxAttempts {
|
||||
// 在加入之前再次检查黑名单
|
||||
if !waf.SharedIPBlackList.Contains(waf.IPTypeAll, firewallconfigs.FirewallScopeGlobal, 0, remoteIP) {
|
||||
waf.SharedIPBlackList.RecordIP(waf.IPTypeAll, firewallconfigs.FirewallScopeGlobal, 0, remoteIP, time.Now().Unix()+int64(3600), 0, true, 0, 0, "access mismatch domain '"+this.RawReq.Host+"' too frequently")
|
||||
waf.SharedIPBlackList.Add(waf.IPTypeAll, firewallconfigs.FirewallScopeGlobal, 0, remoteIP, time.Now().Unix()+3600)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
)
|
||||
|
||||
func (this *HTTPRequest) doRedirectToHTTPS(redirectToHTTPSConfig *serverconfigs.HTTPRedirectToHTTPSConfig) (shouldBreak bool) {
|
||||
host := this.RawReq.Host
|
||||
var host = this.RawReq.Host
|
||||
|
||||
// 检查域名是否匹配
|
||||
if !redirectToHTTPSConfig.MatchDomain(host) {
|
||||
@@ -22,7 +22,7 @@ func (this *HTTPRequest) doRedirectToHTTPS(redirectToHTTPSConfig *serverconfigs.
|
||||
host = redirectToHTTPSConfig.Host
|
||||
}
|
||||
} else if redirectToHTTPSConfig.Port > 0 {
|
||||
lastIndex := strings.LastIndex(host, ":")
|
||||
var lastIndex = strings.LastIndex(host, ":")
|
||||
if lastIndex > 0 {
|
||||
host = host[:lastIndex]
|
||||
}
|
||||
@@ -30,18 +30,18 @@ func (this *HTTPRequest) doRedirectToHTTPS(redirectToHTTPSConfig *serverconfigs.
|
||||
host = host + ":" + strconv.Itoa(redirectToHTTPSConfig.Port)
|
||||
}
|
||||
} else {
|
||||
lastIndex := strings.LastIndex(host, ":")
|
||||
var lastIndex = strings.LastIndex(host, ":")
|
||||
if lastIndex > 0 {
|
||||
host = host[:lastIndex]
|
||||
}
|
||||
}
|
||||
|
||||
statusCode := http.StatusMovedPermanently
|
||||
var statusCode = http.StatusMovedPermanently
|
||||
if redirectToHTTPSConfig.Status > 0 {
|
||||
statusCode = redirectToHTTPSConfig.Status
|
||||
}
|
||||
|
||||
newURL := "https://" + host + this.RawReq.RequestURI
|
||||
var newURL = "https://" + host + this.RawReq.RequestURI
|
||||
this.processResponseHeaders(this.writer.Header(), statusCode)
|
||||
http.Redirect(this.writer, this.RawReq, newURL, statusCode)
|
||||
|
||||
|
||||
@@ -21,13 +21,15 @@ func (this *HTTPRequest) doReverseProxy() {
|
||||
return
|
||||
}
|
||||
|
||||
var isLowVersionHTTP = this.RawReq.ProtoMajor < 1 /** 0.x **/ || (this.RawReq.ProtoMajor == 1 && this.RawReq.ProtoMinor == 0 /** 1.0 **/)
|
||||
|
||||
var retries = 3
|
||||
|
||||
var failedOriginIds []int64
|
||||
var failedLnNodeIds []int64
|
||||
|
||||
for i := 0; i < retries; i++ {
|
||||
originId, lnNodeId, shouldRetry := this.doOriginRequest(failedOriginIds, failedLnNodeIds, i == 0, i == retries-1)
|
||||
originId, lnNodeId, shouldRetry := this.doOriginRequest(failedOriginIds, failedLnNodeIds, i == 0, i == retries-1, isLowVersionHTTP)
|
||||
if !shouldRetry {
|
||||
break
|
||||
}
|
||||
@@ -41,7 +43,7 @@ func (this *HTTPRequest) doReverseProxy() {
|
||||
}
|
||||
|
||||
// 请求源站
|
||||
func (this *HTTPRequest) doOriginRequest(failedOriginIds []int64, failedLnNodeIds []int64, isFirstTry bool, isLastRetry bool) (originId int64, lnNodeId int64, shouldRetry bool) {
|
||||
func (this *HTTPRequest) doOriginRequest(failedOriginIds []int64, failedLnNodeIds []int64, isFirstTry bool, isLastRetry bool, isLowVersionHTTP bool) (originId int64, lnNodeId int64, shouldRetry bool) {
|
||||
// 对URL的处理
|
||||
var stripPrefix = this.reverseProxy.StripPrefix
|
||||
var requestURI = this.reverseProxy.RequestURI
|
||||
@@ -321,6 +323,16 @@ func (this *HTTPRequest) doOriginRequest(failedOriginIds []int64, failedLnNodeId
|
||||
return
|
||||
}
|
||||
|
||||
// 是否为1.1以下
|
||||
if isLowVersionHTTP && resp.ContentLength < 0 {
|
||||
this.writer.WriteHeader(http.StatusBadRequest)
|
||||
_, _ = this.writer.WriteString("The content does not support " + this.RawReq.Proto + " request.")
|
||||
if resp.Body != nil {
|
||||
_ = resp.Body.Close()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// 记录相关数据
|
||||
this.originStatus = int32(resp.StatusCode)
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
|
||||
// 统计
|
||||
func (this *HTTPRequest) doStat() {
|
||||
if this.ReqServer == nil {
|
||||
if this.ReqServer == nil || this.web == nil || this.web.StatRef == nil {
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"crypto/rand"
|
||||
"fmt"
|
||||
teaconst "github.com/TeaOSLab/EdgeNode/internal/const"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils/fasttime"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils/ranges"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
"io"
|
||||
@@ -15,7 +15,11 @@ import (
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
// 其中的每个括号里的内容都在被引用,不能轻易修改
|
||||
// 搜索引擎和爬虫正则
|
||||
var searchEngineRegex = regexp.MustCompile(`(?i)(60spider|adldxbot|adsbot-google|applebot|admantx|alexa|baidu|bingbot|bingpreview|facebookexternalhit|googlebot|proximic|slurp|sogou|twitterbot|yandex)`)
|
||||
var spiderRegexp = regexp.MustCompile(`(?i)(python|pycurl|http-client|httpclient|apachebench|nethttp|http_request|java|perl|ruby|scrapy|php|rust)`)
|
||||
|
||||
// 内容范围正则,其中的每个括号里的内容都在被引用,不能轻易修改
|
||||
var contentRangeRegexp = regexp.MustCompile(`^bytes (\d+)-(\d+)/(\d+|\*)`)
|
||||
|
||||
// 分解Range
|
||||
@@ -180,7 +184,7 @@ var httpRequestTimestamp int64
|
||||
var httpRequestId int32 = 1_000_000
|
||||
|
||||
func httpRequestNextId() string {
|
||||
unixTime, unixTimeString := utils.UnixTimeMilliString()
|
||||
unixTime, unixTimeString := fasttime.Now().UnixMilliString()
|
||||
if unixTime > httpRequestTimestamp {
|
||||
atomic.StoreInt32(&httpRequestId, 1_000_000)
|
||||
httpRequestTimestamp = unixTime
|
||||
@@ -208,3 +212,13 @@ func httpAcceptEncoding(acceptEncodings string, encoding string) bool {
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// 跳转到某个URL
|
||||
func httpRedirect(writer http.ResponseWriter, req *http.Request, url string, code int) {
|
||||
if len(writer.Header().Get("Content-Type")) == 0 {
|
||||
// 设置Content-Type,是为了让页面不输出链接
|
||||
writer.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||
}
|
||||
|
||||
http.Redirect(writer, req, url, code)
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ func (this *HTTPRequest) doWAFRequest() (blocked bool) {
|
||||
}
|
||||
|
||||
// 是否在全局名单中
|
||||
canGoNext, isInAllowedList := iplibrary.AllowIP(remoteAddr, this.ReqServer.Id)
|
||||
canGoNext, isInAllowedList, _ := iplibrary.AllowIP(remoteAddr, this.ReqServer.Id)
|
||||
if !canGoNext {
|
||||
this.disableLog = true
|
||||
this.Close()
|
||||
@@ -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,27 @@ 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
|
||||
}
|
||||
|
||||
// DisableAccessLog 在当前请求中不使用访问日志
|
||||
func (this *HTTPRequest) DisableAccessLog() {
|
||||
this.disableLog = true
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -10,8 +10,10 @@ 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/fasttime"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils/readers"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils/writers"
|
||||
_ "github.com/biessek/golang-ico"
|
||||
@@ -39,6 +41,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 +105,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
|
||||
|
||||
@@ -282,7 +300,7 @@ func (this *HTTPWriter) PrepareCache(resp *http.Response, size int64) {
|
||||
}
|
||||
}
|
||||
|
||||
var expiresAt = utils.UnixTime() + life
|
||||
var expiresAt = fasttime.Now().Unix() + life
|
||||
|
||||
if this.req.isLnRequest {
|
||||
// 返回上级节点过期时间
|
||||
@@ -315,6 +333,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 {
|
||||
@@ -691,6 +715,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) {
|
||||
|
||||
@@ -36,6 +36,15 @@ func (this *BaseListener) buildTLSConfig() *tls.Config {
|
||||
return &tls.Config{
|
||||
Certificates: nil,
|
||||
GetConfigForClient: func(clientInfo *tls.ClientHelloInfo) (config *tls.Config, e error) {
|
||||
// 指纹信息
|
||||
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,6 +59,15 @@ func (this *BaseListener) buildTLSConfig() *tls.Config {
|
||||
return tlsPolicy.TLSConfig(), nil
|
||||
},
|
||||
GetCertificate: func(clientInfo *tls.ClientHelloInfo) (certificate *tls.Certificate, e error) {
|
||||
// 指纹信息
|
||||
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
|
||||
|
||||
10
internal/nodes/listener_base_ext.go
Normal file
10
internal/nodes/listener_base_ext.go
Normal 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
|
||||
}
|
||||
@@ -23,7 +23,7 @@ func TestBaseListener_FindServer(t *testing.T) {
|
||||
{Name: types.String(i) + ".hello.com"},
|
||||
},
|
||||
}
|
||||
_ = server.Init()
|
||||
_ = server.Init(nil)
|
||||
listener.Group.Add(server)
|
||||
}
|
||||
|
||||
|
||||
@@ -4,9 +4,7 @@ import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
|
||||
"github.com/iwind/TeaGo/Tea"
|
||||
"golang.org/x/net/http2"
|
||||
"io"
|
||||
"log"
|
||||
"net"
|
||||
@@ -84,13 +82,7 @@ func (this *HTTPListener) Serve() error {
|
||||
if this.isHTTPS {
|
||||
this.httpServer.TLSConfig = this.buildTLSConfig()
|
||||
|
||||
// support http/2
|
||||
err := http2.ConfigureServer(this.httpServer, nil)
|
||||
if err != nil {
|
||||
remotelogs.Error("HTTP_LISTENER", "configure http2 error: "+err.Error())
|
||||
}
|
||||
|
||||
err = this.httpServer.ServeTLS(this.Listener, "", "")
|
||||
err := this.httpServer.ServeTLS(this.Listener, "", "")
|
||||
if err != nil && err != http.ErrServerClosed {
|
||||
return err
|
||||
}
|
||||
@@ -114,6 +106,12 @@ func (this *HTTPListener) Reload(group *serverconfigs.ServerAddressGroup) {
|
||||
|
||||
// ServerHTTP 处理HTTP请求
|
||||
func (this *HTTPListener) ServeHTTP(rawWriter http.ResponseWriter, rawReq *http.Request) {
|
||||
var globalServerConfig = sharedNodeConfig.GlobalServerConfig
|
||||
if globalServerConfig != nil && !globalServerConfig.HTTPAll.SupportsLowVersionHTTP && (rawReq.ProtoMajor < 1 /** 0.x **/ || (rawReq.ProtoMajor == 1 && rawReq.ProtoMinor == 0 /** 1.0 **/)) {
|
||||
http.Error(rawWriter, rawReq.Proto+" request is not supported.", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// 不支持Connect
|
||||
if rawReq.Method == http.MethodConnect {
|
||||
http.Error(rawWriter, "Method Not Allowed", http.StatusMethodNotAllowed)
|
||||
@@ -173,7 +171,10 @@ func (this *HTTPListener) ServeHTTP(rawWriter http.ResponseWriter, rawReq *http.
|
||||
if requestConn != nil {
|
||||
clientConn, ok := requestConn.(ClientConnInterface)
|
||||
if ok {
|
||||
clientConn.SetServerId(server.Id)
|
||||
var goNext = clientConn.SetServerId(server.Id)
|
||||
if !goNext {
|
||||
return
|
||||
}
|
||||
clientConn.SetUserId(server.UserId)
|
||||
}
|
||||
}
|
||||
@@ -226,14 +227,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
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
@@ -77,12 +86,6 @@ func (this *ListenerManager) Start(node *nodeconfigs.NodeConfig) error {
|
||||
}**/
|
||||
this.lastConfig = node
|
||||
|
||||
// 初始化
|
||||
err, _ := node.Init()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 所有的新地址
|
||||
groupAddrs := []string{}
|
||||
availableServerGroups := node.AvailableGroups()
|
||||
@@ -115,7 +118,7 @@ func (this *ListenerManager) Start(node *nodeconfigs.NodeConfig) error {
|
||||
addr := group.FullAddr()
|
||||
listener, ok := this.listenersMap[addr]
|
||||
if ok {
|
||||
remotelogs.Println("LISTENER_MANAGER", "reload '"+this.prettyAddress(addr)+"'")
|
||||
// 不需要打印reload信息,防止日志数量过多
|
||||
listener.Reload(group)
|
||||
} else {
|
||||
remotelogs.Println("LISTENER_MANAGER", "listen '"+this.prettyAddress(addr)+"'")
|
||||
|
||||
@@ -75,7 +75,10 @@ func (this *TCPListener) handleConn(conn net.Conn) error {
|
||||
// 绑定连接和服务
|
||||
clientConn, ok := conn.(ClientConnInterface)
|
||||
if ok {
|
||||
clientConn.SetServerId(server.Id)
|
||||
var goNext = clientConn.SetServerId(server.Id)
|
||||
if !goNext {
|
||||
return nil
|
||||
}
|
||||
clientConn.SetUserId(server.UserId)
|
||||
} else {
|
||||
tlsConn, ok := conn.(*tls.Conn)
|
||||
@@ -84,7 +87,10 @@ func (this *TCPListener) handleConn(conn net.Conn) error {
|
||||
if internalConn != nil {
|
||||
clientConn, ok = internalConn.(ClientConnInterface)
|
||||
if ok {
|
||||
clientConn.SetServerId(server.Id)
|
||||
var goNext = clientConn.SetServerId(server.Id)
|
||||
if !goNext {
|
||||
return nil
|
||||
}
|
||||
clientConn.SetUserId(server.UserId)
|
||||
}
|
||||
}
|
||||
@@ -114,14 +120,14 @@ func (this *TCPListener) handleConn(conn net.Conn) error {
|
||||
serverName = tlsConn.ConnectionState().ServerName
|
||||
if len(serverName) > 0 {
|
||||
// 统计
|
||||
stats.SharedTrafficStatManager.Add(server.Id, serverName, 0, 0, 1, 0, 0, 0, server.ShouldCheckTrafficLimit(), server.PlanId())
|
||||
stats.SharedTrafficStatManager.Add(server.UserId, server.Id, serverName, 0, 0, 1, 0, 0, 0, server.ShouldCheckTrafficLimit(), server.PlanId())
|
||||
recordStat = true
|
||||
}
|
||||
}
|
||||
|
||||
// 统计
|
||||
if !recordStat {
|
||||
stats.SharedTrafficStatManager.Add(server.Id, "", 0, 0, 1, 0, 0, 0, server.ShouldCheckTrafficLimit(), server.PlanId())
|
||||
stats.SharedTrafficStatManager.Add(server.UserId, server.Id, "", 0, 0, 1, 0, 0, 0, server.ShouldCheckTrafficLimit(), server.PlanId())
|
||||
}
|
||||
|
||||
originConn, err := this.connectOrigin(server.Id, serverName, server.ReverseProxy, conn.RemoteAddr().String())
|
||||
@@ -176,7 +182,7 @@ func (this *TCPListener) handleConn(conn net.Conn) error {
|
||||
|
||||
// 记录流量
|
||||
if server != nil {
|
||||
stats.SharedTrafficStatManager.Add(server.Id, "", int64(n), 0, 0, 0, 0, 0, server.ShouldCheckTrafficLimit(), server.PlanId())
|
||||
stats.SharedTrafficStatManager.Add(server.UserId, server.Id, "", int64(n), 0, 0, 0, 0, 0, server.ShouldCheckTrafficLimit(), server.PlanId())
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
|
||||
@@ -370,7 +370,7 @@ func NewUDPConn(server *serverconfigs.ServerConfig, addr net.Addr, proxyListener
|
||||
|
||||
// 统计
|
||||
if server != nil {
|
||||
stats.SharedTrafficStatManager.Add(server.Id, "", 0, 0, 1, 0, 0, 0, server.ShouldCheckTrafficLimit(), server.PlanId())
|
||||
stats.SharedTrafficStatManager.Add(server.UserId, server.Id, "", 0, 0, 1, 0, 0, 0, server.ShouldCheckTrafficLimit(), server.PlanId())
|
||||
}
|
||||
|
||||
// 处理ControlMessage
|
||||
@@ -401,10 +401,10 @@ func NewUDPConn(server *serverconfigs.ServerConfig, addr net.Addr, proxyListener
|
||||
// 记录流量和带宽
|
||||
if server != nil {
|
||||
// 流量
|
||||
stats.SharedTrafficStatManager.Add(server.Id, "", int64(n), 0, 0, 0, 0, 0, server.ShouldCheckTrafficLimit(), server.PlanId())
|
||||
stats.SharedTrafficStatManager.Add(server.UserId, server.Id, "", int64(n), 0, 0, 0, 0, 0, server.ShouldCheckTrafficLimit(), server.PlanId())
|
||||
|
||||
// 带宽
|
||||
stats.SharedBandwidthStatManager.Add(server.UserId, server.Id, int64(n))
|
||||
stats.SharedBandwidthStatManager.AddBandwidth(server.UserId, server.Id, int64(n), int64(n))
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
|
||||
@@ -9,7 +9,6 @@ import (
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/ddosconfigs"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/caches"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/configs"
|
||||
@@ -44,6 +43,7 @@ import (
|
||||
"runtime"
|
||||
"runtime/debug"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
@@ -55,6 +55,7 @@ var nodeConfigChangedNotify = make(chan bool, 8)
|
||||
var nodeConfigUpdatedAt int64
|
||||
var DaemonIsOn = false
|
||||
var DaemonPid = 0
|
||||
var nodeInstance *Node
|
||||
|
||||
// Node 节点
|
||||
type Node struct {
|
||||
@@ -75,16 +76,18 @@ type Node struct {
|
||||
lastAPINodeVersion int64
|
||||
lastAPINodeAddrs []string // 以前的API节点地址
|
||||
|
||||
lastTaskVersion int64
|
||||
lastTaskVersion int64
|
||||
lastUpdatingServerListId int64
|
||||
}
|
||||
|
||||
func NewNode() *Node {
|
||||
return &Node{
|
||||
nodeInstance = &Node{
|
||||
sock: gosock.NewTmpSock(teaconst.ProcessName),
|
||||
oldMaxThreads: -1,
|
||||
oldMaxCPU: -1,
|
||||
updatingServerMap: map[int64]*serverconfigs.ServerConfig{},
|
||||
}
|
||||
return nodeInstance
|
||||
}
|
||||
|
||||
// Test 检查配置
|
||||
@@ -135,6 +138,9 @@ func (this *Node) Start() {
|
||||
remotelogs.Error("NODE", "initialize ip library failed: "+err.Error())
|
||||
}
|
||||
|
||||
// 调整系统参数
|
||||
this.checkSystem()
|
||||
|
||||
// 检查硬盘类型
|
||||
this.checkDisk()
|
||||
|
||||
@@ -191,7 +197,7 @@ func (this *Node) Start() {
|
||||
}
|
||||
teaconst.NodeId = nodeConfig.Id
|
||||
teaconst.NodeIdString = types.String(teaconst.NodeId)
|
||||
err, serverErrors := nodeConfig.Init()
|
||||
err, serverErrors := nodeConfig.Init(nil)
|
||||
if err != nil {
|
||||
remotelogs.Error("NODE", "init node config failed: "+err.Error())
|
||||
return
|
||||
@@ -236,8 +242,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()
|
||||
@@ -306,208 +310,6 @@ func (this *Node) InstallSystemService() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// 循环
|
||||
func (this *Node) loop() error {
|
||||
var tr = trackers.Begin("CHECK_NODE_CONFIG_CHANGES")
|
||||
defer tr.End()
|
||||
|
||||
// 检查api.yaml是否存在
|
||||
var apiConfigFile = Tea.ConfigFile("api.yaml")
|
||||
_, err := os.Stat(apiConfigFile)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
rpcClient, err := rpc.SharedRPC()
|
||||
if err != nil {
|
||||
return errors.New("create rpc client failed: " + err.Error())
|
||||
}
|
||||
|
||||
tasksResp, err := rpcClient.NodeTaskRPC.FindNodeTasks(rpcClient.Context(), &pb.FindNodeTasksRequest{
|
||||
Version: this.lastTaskVersion,
|
||||
})
|
||||
if err != nil {
|
||||
if rpc.IsConnError(err) && !Tea.IsTesting() {
|
||||
return nil
|
||||
}
|
||||
return errors.New("read node tasks failed: " + err.Error())
|
||||
}
|
||||
for _, task := range tasksResp.NodeTasks {
|
||||
err := this.execTask(rpcClient, task)
|
||||
if !this.finishTask(task.Id, task.Version, err) {
|
||||
// 防止失败的任务无法重试
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// 执行任务
|
||||
func (this *Node) execTask(rpcClient *rpc.RPCClient, task *pb.NodeTask) error {
|
||||
switch task.Type {
|
||||
case "ipItemChanged":
|
||||
// 防止阻塞
|
||||
select {
|
||||
case iplibrary.IPListUpdateNotify <- true:
|
||||
default:
|
||||
|
||||
}
|
||||
case "configChanged":
|
||||
if task.ServerId > 0 {
|
||||
return this.syncServerConfig(task.ServerId)
|
||||
}
|
||||
if !task.IsPrimary {
|
||||
// 我们等等主节点配置准备完毕
|
||||
time.Sleep(2 * time.Second)
|
||||
}
|
||||
return this.syncConfig(task.Version)
|
||||
case "nodeVersionChanged":
|
||||
if !sharedUpgradeManager.IsInstalling() {
|
||||
goman.New(func() {
|
||||
sharedUpgradeManager.Start()
|
||||
})
|
||||
}
|
||||
case "scriptsChanged":
|
||||
err := this.reloadCommonScripts()
|
||||
if err != nil {
|
||||
return errors.New("reload common scripts failed: " + err.Error())
|
||||
}
|
||||
case "nodeLevelChanged":
|
||||
levelInfoResp, err := rpcClient.NodeRPC.FindNodeLevelInfo(rpcClient.Context(), &pb.FindNodeLevelInfoRequest{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if sharedNodeConfig != nil {
|
||||
sharedNodeConfig.Level = levelInfoResp.Level
|
||||
}
|
||||
|
||||
var parentNodes = map[int64][]*nodeconfigs.ParentNodeConfig{}
|
||||
if len(levelInfoResp.ParentNodesMapJSON) > 0 {
|
||||
err = json.Unmarshal(levelInfoResp.ParentNodesMapJSON, &parentNodes)
|
||||
if err != nil {
|
||||
return errors.New("decode level info failed: " + err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
if sharedNodeConfig != nil {
|
||||
sharedNodeConfig.ParentNodes = parentNodes
|
||||
}
|
||||
case "ddosProtectionChanged":
|
||||
resp, err := rpcClient.NodeRPC.FindNodeDDoSProtection(rpcClient.Context(), &pb.FindNodeDDoSProtectionRequest{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(resp.DdosProtectionJSON) == 0 {
|
||||
if sharedNodeConfig != nil {
|
||||
sharedNodeConfig.DDoSProtection = nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var ddosProtectionConfig = &ddosconfigs.ProtectionConfig{}
|
||||
err = json.Unmarshal(resp.DdosProtectionJSON, ddosProtectionConfig)
|
||||
if err != nil {
|
||||
return errors.New("decode DDoS protection config failed: " + err.Error())
|
||||
}
|
||||
|
||||
if ddosProtectionConfig != nil && sharedNodeConfig != nil {
|
||||
sharedNodeConfig.DDoSProtection = ddosProtectionConfig
|
||||
}
|
||||
|
||||
err = firewalls.SharedDDoSProtectionManager.Apply(ddosProtectionConfig)
|
||||
if err != nil {
|
||||
// 不阻塞
|
||||
remotelogs.Warn("NODE", "apply DDoS protection failed: "+err.Error())
|
||||
return nil
|
||||
}
|
||||
case "globalServerConfigChanged":
|
||||
resp, err := rpcClient.NodeRPC.FindNodeGlobalServerConfig(rpcClient.Context(), &pb.FindNodeGlobalServerConfigRequest{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(resp.GlobalServerConfigJSON) > 0 {
|
||||
var globalServerConfig = serverconfigs.DefaultGlobalServerConfig()
|
||||
err = json.Unmarshal(resp.GlobalServerConfigJSON, globalServerConfig)
|
||||
if err != nil {
|
||||
return errors.New("decode global server config failed: " + err.Error())
|
||||
}
|
||||
|
||||
if globalServerConfig != nil {
|
||||
err = globalServerConfig.Init()
|
||||
if err != nil {
|
||||
return errors.New("validate global server config failed: " + err.Error())
|
||||
}
|
||||
if sharedNodeConfig != nil {
|
||||
sharedNodeConfig.GlobalServerConfig = globalServerConfig
|
||||
}
|
||||
}
|
||||
}
|
||||
case "userServersStateChanged":
|
||||
if task.UserId > 0 {
|
||||
resp, err := rpcClient.UserRPC.CheckUserServersState(rpcClient.Context(), &pb.CheckUserServersStateRequest{UserId: task.UserId})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
SharedUserManager.UpdateUserServersIsEnabled(task.UserId, resp.IsEnabled)
|
||||
|
||||
if resp.IsEnabled {
|
||||
err = this.syncUserServersConfig(task.UserId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
default:
|
||||
remotelogs.Error("NODE", "task '"+types.String(task.Id)+"', type '"+task.Type+"' has not been handled")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// 标记任务完成
|
||||
func (this *Node) finishTask(taskId int64, taskVersion int64, taskErr error) (success bool) {
|
||||
if taskId <= 0 {
|
||||
return true
|
||||
}
|
||||
|
||||
rpcClient, err := rpc.SharedRPC()
|
||||
if err != nil {
|
||||
remotelogs.Debug("NODE", "create rpc client failed: "+err.Error())
|
||||
return false
|
||||
}
|
||||
|
||||
var isOk = taskErr == nil
|
||||
if isOk && taskVersion > this.lastTaskVersion {
|
||||
this.lastTaskVersion = taskVersion
|
||||
}
|
||||
|
||||
var errMsg = ""
|
||||
if taskErr != nil {
|
||||
errMsg = taskErr.Error()
|
||||
}
|
||||
|
||||
_, err = rpcClient.NodeTaskRPC.ReportNodeTaskDone(rpcClient.Context(), &pb.ReportNodeTaskDoneRequest{
|
||||
NodeTaskId: taskId,
|
||||
IsOk: isOk,
|
||||
Error: errMsg,
|
||||
})
|
||||
success = err == nil
|
||||
|
||||
if err != nil {
|
||||
// 连接错误不需要上报到服务中心
|
||||
if rpc.IsConnError(err) {
|
||||
remotelogs.Debug("NODE", "report task done failed: "+err.Error())
|
||||
} else {
|
||||
remotelogs.Error("NODE", "report task done failed: "+err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
return success
|
||||
}
|
||||
|
||||
// 读取API配置
|
||||
func (this *Node) syncConfig(taskVersion int64) error {
|
||||
this.locker.Lock()
|
||||
@@ -541,6 +343,7 @@ func (this *Node) syncConfig(taskVersion int64) error {
|
||||
Version: -1, // 更新所有版本
|
||||
Compress: true,
|
||||
NodeTaskVersion: taskVersion,
|
||||
UseDataMap: true,
|
||||
})
|
||||
if err != nil {
|
||||
return errors.New("read config from rpc failed: " + err.Error())
|
||||
@@ -591,7 +394,7 @@ func (this *Node) syncConfig(taskVersion int64) error {
|
||||
return err
|
||||
}
|
||||
|
||||
err, serverErrors := nodeConfig.Init()
|
||||
err, serverErrors := nodeConfig.Init(nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -603,9 +406,9 @@ func (this *Node) syncConfig(taskVersion int64) error {
|
||||
|
||||
// 刷新配置
|
||||
if this.isLoaded {
|
||||
remotelogs.Println("NODE", "reloading config ...")
|
||||
remotelogs.Println("NODE", "reloading node config ...")
|
||||
} else {
|
||||
remotelogs.Println("NODE", "loading config ...")
|
||||
remotelogs.Println("NODE", "loading node config ...")
|
||||
}
|
||||
|
||||
this.onReload(nodeConfig, true)
|
||||
@@ -619,6 +422,9 @@ func (this *Node) syncConfig(taskVersion int64) error {
|
||||
|
||||
this.isLoaded = true
|
||||
|
||||
// 整体更新不需要再更新单个服务
|
||||
this.updatingServerMap = map[int64]*serverconfigs.ServerConfig{}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -693,7 +499,7 @@ func (this *Node) startSyncTimer() {
|
||||
for {
|
||||
select {
|
||||
case <-taskTicker.C: // 定期执行
|
||||
err := this.loop()
|
||||
err := this.loopTasks()
|
||||
if err != nil {
|
||||
remotelogs.Error("NODE", "sync config error: "+err.Error())
|
||||
continue
|
||||
@@ -701,7 +507,7 @@ func (this *Node) startSyncTimer() {
|
||||
case <-serverChangeTicker.C: // 服务变化
|
||||
this.reloadServer()
|
||||
case <-nodeTaskNotify: // 有新的更新任务
|
||||
err := this.loop()
|
||||
err := this.loopTasks()
|
||||
if err != nil {
|
||||
remotelogs.Error("NODE", "sync config error: "+err.Error())
|
||||
continue
|
||||
@@ -1189,6 +995,9 @@ func (this *Node) onReload(config *nodeconfigs.NodeConfig, reloadAll bool) {
|
||||
// API Node地址,这里不限制是否为空,因为在为空时仍然要有对应的处理
|
||||
this.changeAPINodeAddrs(config.APINodeAddrs)
|
||||
}
|
||||
|
||||
// 刷新IP库
|
||||
this.reloadIPLibrary()
|
||||
}
|
||||
|
||||
// reload server config
|
||||
@@ -1196,7 +1005,9 @@ func (this *Node) reloadServer() {
|
||||
this.locker.Lock()
|
||||
defer this.locker.Unlock()
|
||||
|
||||
if len(this.updatingServerMap) > 0 {
|
||||
var countUpdatingServers = len(this.updatingServerMap)
|
||||
const maxPrintServers = 10
|
||||
if countUpdatingServers > 0 {
|
||||
var updatingServerMap = this.updatingServerMap
|
||||
this.updatingServerMap = map[int64]*serverconfigs.ServerConfig{}
|
||||
newNodeConfig, err := nodeconfigs.CloneNodeConfig(sharedNodeConfig)
|
||||
@@ -1206,13 +1017,23 @@ func (this *Node) reloadServer() {
|
||||
}
|
||||
for serverId, serverConfig := range updatingServerMap {
|
||||
if serverConfig != nil {
|
||||
if countUpdatingServers < maxPrintServers {
|
||||
remotelogs.Debug("NODE", "load server '"+types.String(serverId)+"'")
|
||||
}
|
||||
newNodeConfig.AddServer(serverConfig)
|
||||
} else {
|
||||
if countUpdatingServers < maxPrintServers {
|
||||
remotelogs.Debug("NODE", "remove server '"+types.String(serverId)+"'")
|
||||
}
|
||||
newNodeConfig.RemoveServer(serverId)
|
||||
}
|
||||
}
|
||||
|
||||
err, serverErrors := newNodeConfig.Init()
|
||||
if countUpdatingServers >= maxPrintServers {
|
||||
remotelogs.Debug("NODE", "reload "+types.String(countUpdatingServers)+" servers")
|
||||
}
|
||||
|
||||
err, serverErrors := newNodeConfig.Init(nil)
|
||||
if err != nil {
|
||||
remotelogs.Error("NODE", "apply server config error: "+err.Error())
|
||||
return
|
||||
@@ -1232,6 +1053,56 @@ func (this *Node) reloadServer() {
|
||||
}
|
||||
}
|
||||
|
||||
// 检查系统
|
||||
func (this *Node) checkSystem() {
|
||||
if runtime.GOOS != "linux" || os.Getgid() != 0 {
|
||||
return
|
||||
}
|
||||
|
||||
type variable struct {
|
||||
name string
|
||||
minValue int
|
||||
maxValue int
|
||||
}
|
||||
|
||||
const dir = "/proc/sys"
|
||||
|
||||
for _, v := range []variable{
|
||||
{name: "net.core.somaxconn", minValue: 2048},
|
||||
{name: "net.ipv4.tcp_max_syn_backlog", minValue: 2048},
|
||||
{name: "net.core.netdev_max_backlog", minValue: 4096},
|
||||
{name: "net.ipv4.tcp_fin_timeout", maxValue: 10},
|
||||
{name: "net.ipv4.tcp_max_tw_buckets", minValue: 65535},
|
||||
{name: "net.core.rmem_default", minValue: 4 << 20},
|
||||
{name: "net.core.wmem_default", minValue: 4 << 20},
|
||||
{name: "net.core.rmem_max", minValue: 32 << 20},
|
||||
{name: "net.core.wmem_max", minValue: 32 << 20},
|
||||
} {
|
||||
var path = dir + "/" + strings.Replace(v.name, ".", "/", -1)
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
data = bytes.TrimSpace(data)
|
||||
if len(data) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
var oldValue = types.Int(string(data))
|
||||
if v.minValue > 0 && oldValue < v.minValue {
|
||||
err = os.WriteFile(path, []byte(types.String(v.minValue)), 0666)
|
||||
if err == nil {
|
||||
remotelogs.Println("NODE", "change kernel parameter '"+v.name+"' from '"+types.String(oldValue)+"' to '"+types.String(v.minValue)+"'")
|
||||
}
|
||||
} else if v.maxValue > 0 && oldValue > v.maxValue {
|
||||
err = os.WriteFile(path, []byte(types.String(v.maxValue)), 0666)
|
||||
if err == nil {
|
||||
remotelogs.Println("NODE", "change kernel parameter '"+v.name+"' from '"+types.String(oldValue)+"' to '"+types.String(v.maxValue)+"'")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 检查硬盘
|
||||
func (this *Node) checkDisk() {
|
||||
if runtime.GOOS != "linux" {
|
||||
|
||||
@@ -7,3 +7,11 @@ package nodes
|
||||
func (this *Node) reloadCommonScripts() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (this *Node) reloadIPLibrary() {
|
||||
|
||||
}
|
||||
|
||||
func (this *Node) notifyPlusChange() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 更新负载
|
||||
|
||||
355
internal/nodes/node_tasks.go
Normal file
355
internal/nodes/node_tasks.go
Normal file
@@ -0,0 +1,355 @@
|
||||
// Copyright 2023 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
|
||||
package nodes
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/ddosconfigs"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/firewalls"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/goman"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/iplibrary"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/rpc"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/trackers"
|
||||
"github.com/iwind/TeaGo/Tea"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
// 循环
|
||||
func (this *Node) loopTasks() error {
|
||||
var tr = trackers.Begin("CHECK_NODE_CONFIG_CHANGES")
|
||||
defer tr.End()
|
||||
|
||||
// 检查api.yaml是否存在
|
||||
var apiConfigFile = Tea.ConfigFile("api.yaml")
|
||||
_, err := os.Stat(apiConfigFile)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
rpcClient, err := rpc.SharedRPC()
|
||||
if err != nil {
|
||||
return errors.New("create rpc client failed: " + err.Error())
|
||||
}
|
||||
|
||||
tasksResp, err := rpcClient.NodeTaskRPC.FindNodeTasks(rpcClient.Context(), &pb.FindNodeTasksRequest{
|
||||
Version: this.lastTaskVersion,
|
||||
})
|
||||
if err != nil {
|
||||
if rpc.IsConnError(err) && !Tea.IsTesting() {
|
||||
return nil
|
||||
}
|
||||
return errors.New("read node tasks failed: " + err.Error())
|
||||
}
|
||||
for _, task := range tasksResp.NodeTasks {
|
||||
err := this.execTask(rpcClient, task)
|
||||
if !this.finishTask(task.Id, task.Version, err) {
|
||||
// 防止失败的任务无法重试
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// 执行任务
|
||||
func (this *Node) execTask(rpcClient *rpc.RPCClient, task *pb.NodeTask) error {
|
||||
var err error
|
||||
switch task.Type {
|
||||
case "ipItemChanged":
|
||||
err = this.execIPItemChangedTask()
|
||||
case "configChanged":
|
||||
err = this.execConfigChangedTask(task)
|
||||
case "nodeVersionChanged":
|
||||
err = this.execNodeVersionChangedTask()
|
||||
case "scriptsChanged":
|
||||
err = this.execScriptsChangedTask()
|
||||
case "nodeLevelChanged":
|
||||
err = this.execNodeLevelChangedTask(rpcClient)
|
||||
case "ddosProtectionChanged":
|
||||
err = this.execDDoSProtectionChangedTask(rpcClient)
|
||||
case "globalServerConfigChanged":
|
||||
err = this.execGlobalServerConfigChangedTask(rpcClient)
|
||||
case "userServersStateChanged":
|
||||
err = this.execUserServersStateChangedTask(rpcClient, task)
|
||||
case "uamPolicyChanged":
|
||||
err = this.execUAMPolicyChangedTask(rpcClient)
|
||||
case "updatingServers":
|
||||
err = this.execUpdatingServersTask(rpcClient)
|
||||
case "plusChanged":
|
||||
err = this.notifyPlusChange()
|
||||
default:
|
||||
remotelogs.Error("NODE", "task '"+types.String(task.Id)+"', type '"+task.Type+"' has not been handled")
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// 更新IP条目变更
|
||||
func (this *Node) execIPItemChangedTask() error {
|
||||
// 防止阻塞
|
||||
select {
|
||||
case iplibrary.IPListUpdateNotify <- true:
|
||||
default:
|
||||
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// 更新节点配置变更
|
||||
func (this *Node) execConfigChangedTask(task *pb.NodeTask) error {
|
||||
if task.ServerId > 0 {
|
||||
return this.syncServerConfig(task.ServerId)
|
||||
}
|
||||
if !task.IsPrimary {
|
||||
// 我们等等主节点配置准备完毕
|
||||
time.Sleep(2 * time.Second)
|
||||
}
|
||||
return this.syncConfig(task.Version)
|
||||
}
|
||||
|
||||
// 节点程序版本号变更
|
||||
func (this *Node) execNodeVersionChangedTask() error {
|
||||
if !sharedUpgradeManager.IsInstalling() {
|
||||
goman.New(func() {
|
||||
sharedUpgradeManager.Start()
|
||||
})
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// 脚本库变更
|
||||
func (this *Node) execScriptsChangedTask() error {
|
||||
err := this.reloadCommonScripts()
|
||||
if err != nil {
|
||||
return errors.New("reload common scripts failed: " + err.Error())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// 节点级别变更
|
||||
func (this *Node) execNodeLevelChangedTask(rpcClient *rpc.RPCClient) error {
|
||||
levelInfoResp, err := rpcClient.NodeRPC.FindNodeLevelInfo(rpcClient.Context(), &pb.FindNodeLevelInfoRequest{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if sharedNodeConfig != nil {
|
||||
sharedNodeConfig.Level = levelInfoResp.Level
|
||||
}
|
||||
|
||||
var parentNodes = map[int64][]*nodeconfigs.ParentNodeConfig{}
|
||||
if len(levelInfoResp.ParentNodesMapJSON) > 0 {
|
||||
err = json.Unmarshal(levelInfoResp.ParentNodesMapJSON, &parentNodes)
|
||||
if err != nil {
|
||||
return errors.New("decode level info failed: " + err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
if sharedNodeConfig != nil {
|
||||
sharedNodeConfig.ParentNodes = parentNodes
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// UAM策略变更
|
||||
func (this *Node) execUAMPolicyChangedTask(rpcClient *rpc.RPCClient) error {
|
||||
remotelogs.Println("NODE", "updating uam policies ...")
|
||||
resp, err := rpcClient.NodeRPC.FindNodeUAMPolicies(rpcClient.Context(), &pb.FindNodeUAMPoliciesRequest{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var uamPolicyMap = map[int64]*nodeconfigs.UAMPolicy{}
|
||||
for _, policy := range resp.UamPolicies {
|
||||
if len(policy.UamPolicyJSON) > 0 {
|
||||
var uamPolicy = &nodeconfigs.UAMPolicy{}
|
||||
err = json.Unmarshal(policy.UamPolicyJSON, uamPolicy)
|
||||
if err != nil {
|
||||
remotelogs.Error("NODE", "decode uam policy failed: "+err.Error())
|
||||
continue
|
||||
}
|
||||
err = uamPolicy.Init()
|
||||
if err != nil {
|
||||
remotelogs.Error("NODE", "initialize uam policy failed: "+err.Error())
|
||||
continue
|
||||
}
|
||||
uamPolicyMap[policy.NodeClusterId] = uamPolicy
|
||||
}
|
||||
}
|
||||
sharedNodeConfig.UpdateUAMPolicies(uamPolicyMap)
|
||||
return nil
|
||||
}
|
||||
|
||||
// DDoS配置变更
|
||||
func (this *Node) execDDoSProtectionChangedTask(rpcClient *rpc.RPCClient) error {
|
||||
resp, err := rpcClient.NodeRPC.FindNodeDDoSProtection(rpcClient.Context(), &pb.FindNodeDDoSProtectionRequest{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(resp.DdosProtectionJSON) == 0 {
|
||||
if sharedNodeConfig != nil {
|
||||
sharedNodeConfig.DDoSProtection = nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var ddosProtectionConfig = &ddosconfigs.ProtectionConfig{}
|
||||
err = json.Unmarshal(resp.DdosProtectionJSON, ddosProtectionConfig)
|
||||
if err != nil {
|
||||
return errors.New("decode DDoS protection config failed: " + err.Error())
|
||||
}
|
||||
|
||||
if ddosProtectionConfig != nil && sharedNodeConfig != nil {
|
||||
sharedNodeConfig.DDoSProtection = ddosProtectionConfig
|
||||
}
|
||||
|
||||
go func() {
|
||||
err = firewalls.SharedDDoSProtectionManager.Apply(ddosProtectionConfig)
|
||||
if err != nil {
|
||||
// 不阻塞
|
||||
remotelogs.Warn("NODE", "apply DDoS protection failed: "+err.Error())
|
||||
}
|
||||
}()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// 服务全局配置变更
|
||||
func (this *Node) execGlobalServerConfigChangedTask(rpcClient *rpc.RPCClient) error {
|
||||
resp, err := rpcClient.NodeRPC.FindNodeGlobalServerConfig(rpcClient.Context(), &pb.FindNodeGlobalServerConfigRequest{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(resp.GlobalServerConfigJSON) > 0 {
|
||||
var globalServerConfig = serverconfigs.DefaultGlobalServerConfig()
|
||||
err = json.Unmarshal(resp.GlobalServerConfigJSON, globalServerConfig)
|
||||
if err != nil {
|
||||
return errors.New("decode global server config failed: " + err.Error())
|
||||
}
|
||||
|
||||
if globalServerConfig != nil {
|
||||
err = globalServerConfig.Init()
|
||||
if err != nil {
|
||||
return errors.New("validate global server config failed: " + err.Error())
|
||||
}
|
||||
if sharedNodeConfig != nil {
|
||||
sharedNodeConfig.GlobalServerConfig = globalServerConfig
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// 单个用户服务状态变更
|
||||
func (this *Node) execUserServersStateChangedTask(rpcClient *rpc.RPCClient, task *pb.NodeTask) error {
|
||||
if task.UserId > 0 {
|
||||
resp, err := rpcClient.UserRPC.CheckUserServersState(rpcClient.Context(), &pb.CheckUserServersStateRequest{UserId: task.UserId})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
SharedUserManager.UpdateUserServersIsEnabled(task.UserId, resp.IsEnabled)
|
||||
|
||||
if resp.IsEnabled {
|
||||
err = this.syncUserServersConfig(task.UserId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// 更新一组服务列表
|
||||
func (this *Node) execUpdatingServersTask(rpcClient *rpc.RPCClient) error {
|
||||
if this.lastUpdatingServerListId <= 0 {
|
||||
this.lastUpdatingServerListId = sharedNodeConfig.UpdatingServerListId
|
||||
}
|
||||
|
||||
resp, err := rpcClient.UpdatingServerListRPC.FindUpdatingServerLists(rpcClient.Context(), &pb.FindUpdatingServerListsRequest{LastId: this.lastUpdatingServerListId})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if resp.MaxId <= 0 || len(resp.ServersJSON) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
var serverConfigs = []*serverconfigs.ServerConfig{}
|
||||
err = json.Unmarshal(resp.ServersJSON, &serverConfigs)
|
||||
if err != nil {
|
||||
return errors.New("decode server configs failed: " + err.Error())
|
||||
}
|
||||
|
||||
if resp.MaxId > this.lastUpdatingServerListId {
|
||||
this.lastUpdatingServerListId = resp.MaxId
|
||||
}
|
||||
|
||||
if len(serverConfigs) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
this.locker.Lock()
|
||||
defer this.locker.Unlock()
|
||||
for _, serverConfig := range serverConfigs {
|
||||
if serverConfig == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if serverConfig.IsOn {
|
||||
this.updatingServerMap[serverConfig.Id] = serverConfig
|
||||
} else {
|
||||
this.updatingServerMap[serverConfig.Id] = nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// 标记任务完成
|
||||
func (this *Node) finishTask(taskId int64, taskVersion int64, taskErr error) (success bool) {
|
||||
if taskId <= 0 {
|
||||
return true
|
||||
}
|
||||
|
||||
rpcClient, err := rpc.SharedRPC()
|
||||
if err != nil {
|
||||
remotelogs.Debug("NODE", "create rpc client failed: "+err.Error())
|
||||
return false
|
||||
}
|
||||
|
||||
var isOk = taskErr == nil
|
||||
if isOk && taskVersion > this.lastTaskVersion {
|
||||
this.lastTaskVersion = taskVersion
|
||||
}
|
||||
|
||||
var errMsg = ""
|
||||
if taskErr != nil {
|
||||
errMsg = taskErr.Error()
|
||||
}
|
||||
|
||||
_, err = rpcClient.NodeTaskRPC.ReportNodeTaskDone(rpcClient.Context(), &pb.ReportNodeTaskDoneRequest{
|
||||
NodeTaskId: taskId,
|
||||
IsOk: isOk,
|
||||
Error: errMsg,
|
||||
})
|
||||
success = err == nil
|
||||
|
||||
if err != nil {
|
||||
// 连接错误不需要上报到服务中心
|
||||
if rpc.IsConnError(err) {
|
||||
remotelogs.Debug("NODE", "report task done failed: "+err.Error())
|
||||
} else {
|
||||
remotelogs.Error("NODE", "report task done failed: "+err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
return success
|
||||
}
|
||||
@@ -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()
|
||||
|
||||
@@ -19,6 +19,10 @@ import (
|
||||
)
|
||||
|
||||
func init() {
|
||||
if !teaconst.IsMain {
|
||||
return
|
||||
}
|
||||
|
||||
var manager = NewSystemServiceManager()
|
||||
events.On(events.EventReload, func() {
|
||||
goman.New(func() {
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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() {
|
||||
@@ -31,7 +35,7 @@ func init() {
|
||||
err := uploadLogs()
|
||||
tr.End()
|
||||
if err != nil {
|
||||
logs.Println("[LOG]" + err.Error())
|
||||
logs.Println("[LOG]upload logs failed: " + err.Error())
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -187,7 +191,7 @@ func ServerError(serverId int64, tag string, description string, logType nodecon
|
||||
if len(params) > 0 {
|
||||
p, err := json.Marshal(params)
|
||||
if err != nil {
|
||||
logs.Println("[LOG]" + err.Error())
|
||||
logs.Println("[LOG]ServerError(): json encode failed: " + err.Error())
|
||||
} else {
|
||||
paramsJSON = p
|
||||
}
|
||||
@@ -219,7 +223,7 @@ func ServerSuccess(serverId int64, tag string, description string, logType nodec
|
||||
if len(params) > 0 {
|
||||
p, err := json.Marshal(params)
|
||||
if err != nil {
|
||||
logs.Println("[LOG]" + err.Error())
|
||||
logs.Println("[LOG]ServerSuccess(): json encode failed: " + err.Error())
|
||||
} else {
|
||||
paramsJSON = p
|
||||
}
|
||||
@@ -251,7 +255,7 @@ func ServerLog(serverId int64, tag string, description string, logType nodeconfi
|
||||
if len(params) > 0 {
|
||||
p, err := json.Marshal(params)
|
||||
if err != nil {
|
||||
logs.Println("[LOG]" + err.Error())
|
||||
logs.Println("[LOG]ServerLog(): json encode failed: " + err.Error())
|
||||
} else {
|
||||
paramsJSON = p
|
||||
}
|
||||
|
||||
@@ -51,6 +51,8 @@ type RPCClient struct {
|
||||
ScriptRPC pb.ScriptServiceClient
|
||||
UserRPC pb.UserServiceClient
|
||||
ClientAgentIPRPC pb.ClientAgentIPServiceClient
|
||||
AuthorityKeyRPC pb.AuthorityKeyServiceClient
|
||||
UpdatingServerListRPC pb.UpdatingServerListServiceClient
|
||||
}
|
||||
|
||||
func NewRPCClient(apiConfig *configs.APIConfig) (*RPCClient, error) {
|
||||
@@ -85,6 +87,8 @@ func NewRPCClient(apiConfig *configs.APIConfig) (*RPCClient, error) {
|
||||
client.ScriptRPC = pb.NewScriptServiceClient(client)
|
||||
client.UserRPC = pb.NewUserServiceClient(client)
|
||||
client.ClientAgentIPRPC = pb.NewClientAgentIPServiceClient(client)
|
||||
client.AuthorityKeyRPC = pb.NewAuthorityKeyServiceClient(client)
|
||||
client.UpdatingServerListRPC = pb.NewUpdatingServerListServiceClient(client)
|
||||
|
||||
err := client.init()
|
||||
if err != nil {
|
||||
@@ -231,8 +235,8 @@ func (this *RPCClient) init() error {
|
||||
}
|
||||
var conn *grpc.ClientConn
|
||||
var callOptions = grpc.WithDefaultCallOptions(
|
||||
grpc.MaxCallRecvMsgSize(128*1024*1024),
|
||||
grpc.MaxCallSendMsgSize(128*1024*1024),
|
||||
grpc.MaxCallRecvMsgSize(512<<20),
|
||||
grpc.MaxCallSendMsgSize(512<<20),
|
||||
grpc.UseCompressor(gzip.Name),
|
||||
)
|
||||
if u.Scheme == "http" {
|
||||
|
||||
@@ -3,15 +3,20 @@
|
||||
package stats
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"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"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/rpc"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils/fasttime"
|
||||
"github.com/iwind/TeaGo/Tea"
|
||||
"github.com/iwind/TeaGo/logs"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
timeutil "github.com/iwind/TeaGo/utils/time"
|
||||
"os"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
@@ -21,27 +26,47 @@ var SharedBandwidthStatManager = NewBandwidthStatManager()
|
||||
const bandwidthTimestampDelim = 2 // N秒平均,更为精确
|
||||
|
||||
func init() {
|
||||
if !teaconst.IsMain {
|
||||
return
|
||||
}
|
||||
|
||||
events.On(events.EventLoaded, func() {
|
||||
goman.New(func() {
|
||||
SharedBandwidthStatManager.Start()
|
||||
})
|
||||
})
|
||||
|
||||
events.On(events.EventQuit, func() {
|
||||
SharedBandwidthStatManager.Cancel()
|
||||
|
||||
err := SharedBandwidthStatManager.Save()
|
||||
if err != nil {
|
||||
remotelogs.Error("STAT", "save bandwidth stats failed: "+err.Error())
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
type BandwidthStat struct {
|
||||
Day string
|
||||
TimeAt string
|
||||
UserId int64
|
||||
ServerId int64
|
||||
Day string `json:"day"`
|
||||
TimeAt string `json:"timeAt"`
|
||||
UserId int64 `json:"userId"`
|
||||
ServerId int64 `json:"serverId"`
|
||||
|
||||
CurrentBytes int64
|
||||
CurrentTimestamp int64
|
||||
MaxBytes int64
|
||||
CurrentBytes int64 `json:"currentBytes"`
|
||||
CurrentTimestamp int64 `json:"currentTimestamp"`
|
||||
MaxBytes int64 `json:"maxBytes"`
|
||||
TotalBytes int64 `json:"totalBytes"`
|
||||
|
||||
CachedBytes int64 `json:"cachedBytes"`
|
||||
AttackBytes int64 `json:"attackBytes"`
|
||||
CountRequests int64 `json:"countRequests"`
|
||||
CountCachedRequests int64 `json:"countCachedRequests"`
|
||||
CountAttackRequests int64 `json:"countAttackRequests"`
|
||||
}
|
||||
|
||||
// BandwidthStatManager 服务带宽统计
|
||||
type BandwidthStatManager struct {
|
||||
m map[string]*BandwidthStat // key => *BandwidthStat
|
||||
m map[string]*BandwidthStat // serverId@day@time => *BandwidthStat
|
||||
|
||||
pbStats []*pb.ServerBandwidthStat
|
||||
|
||||
@@ -49,16 +74,25 @@ type BandwidthStatManager struct {
|
||||
|
||||
ticker *time.Ticker
|
||||
locker sync.Mutex
|
||||
|
||||
cacheFile string // 上一次的缓存文件
|
||||
}
|
||||
|
||||
func NewBandwidthStatManager() *BandwidthStatManager {
|
||||
return &BandwidthStatManager{
|
||||
m: map[string]*BandwidthStat{},
|
||||
ticker: time.NewTicker(1 * time.Minute), // 时间小于1分钟是为了更快速地上传结果
|
||||
m: map[string]*BandwidthStat{},
|
||||
ticker: time.NewTicker(1 * time.Minute), // 时间小于1分钟是为了更快速地上传结果
|
||||
cacheFile: Tea.Root + "/data/bandwidth.dat",
|
||||
}
|
||||
}
|
||||
|
||||
func (this *BandwidthStatManager) Start() {
|
||||
// 从上次数据中恢复
|
||||
this.locker.Lock()
|
||||
this.recover()
|
||||
this.locker.Unlock()
|
||||
|
||||
// 循环上报数据
|
||||
for range this.ticker.C {
|
||||
err := this.Loop()
|
||||
if err != nil && !rpc.IsConnError(err) {
|
||||
@@ -76,7 +110,7 @@ func (this *BandwidthStatManager) Loop() error {
|
||||
|
||||
var now = time.Now()
|
||||
var day = timeutil.Format("Ymd", now)
|
||||
var currentTime = timeutil.FormatTime("Hi", now.Unix()/300*300)
|
||||
var currentTime = timeutil.FormatTime("Hi", now.Unix()/300*300) // 300s = 5 minutes
|
||||
|
||||
if this.lastTime == currentTime {
|
||||
return nil
|
||||
@@ -100,14 +134,28 @@ func (this *BandwidthStatManager) Loop() error {
|
||||
this.locker.Lock()
|
||||
for key, stat := range this.m {
|
||||
if stat.Day < day || stat.TimeAt < currentTime {
|
||||
// 防止数据出现错误
|
||||
if stat.CachedBytes > stat.TotalBytes {
|
||||
stat.CachedBytes = stat.TotalBytes
|
||||
}
|
||||
if stat.AttackBytes > stat.TotalBytes {
|
||||
stat.AttackBytes = stat.TotalBytes
|
||||
}
|
||||
|
||||
pbStats = append(pbStats, &pb.ServerBandwidthStat{
|
||||
Id: 0,
|
||||
UserId: stat.UserId,
|
||||
ServerId: stat.ServerId,
|
||||
Day: stat.Day,
|
||||
TimeAt: stat.TimeAt,
|
||||
Bytes: stat.MaxBytes / bandwidthTimestampDelim,
|
||||
NodeRegionId: regionId,
|
||||
Id: 0,
|
||||
UserId: stat.UserId,
|
||||
ServerId: stat.ServerId,
|
||||
Day: stat.Day,
|
||||
TimeAt: stat.TimeAt,
|
||||
Bytes: stat.MaxBytes / bandwidthTimestampDelim,
|
||||
TotalBytes: stat.TotalBytes,
|
||||
CachedBytes: stat.CachedBytes,
|
||||
AttackBytes: stat.AttackBytes,
|
||||
CountRequests: stat.CountRequests,
|
||||
CountCachedRequests: stat.CountCachedRequests,
|
||||
CountAttackRequests: stat.CountAttackRequests,
|
||||
NodeRegionId: regionId,
|
||||
})
|
||||
delete(this.m, key)
|
||||
}
|
||||
@@ -131,23 +179,23 @@ func (this *BandwidthStatManager) Loop() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Add 添加带宽数据
|
||||
func (this *BandwidthStatManager) Add(userId int64, serverId int64, bytes int64) {
|
||||
if serverId <= 0 || bytes == 0 {
|
||||
// AddBandwidth 添加带宽数据
|
||||
func (this *BandwidthStatManager) AddBandwidth(userId int64, serverId int64, peekBytes int64, totalBytes int64) {
|
||||
if serverId <= 0 || (peekBytes == 0 && totalBytes == 0) {
|
||||
return
|
||||
}
|
||||
|
||||
var now = time.Now()
|
||||
var now = fasttime.Now()
|
||||
var timestamp = now.Unix() / bandwidthTimestampDelim * bandwidthTimestampDelim // 将时间戳均分成N等份
|
||||
var day = timeutil.Format("Ymd", now)
|
||||
var timeAt = timeutil.FormatTime("Hi", now.Unix()/300*300)
|
||||
var day = now.Ymd()
|
||||
var timeAt = now.Round5Hi()
|
||||
var key = types.String(serverId) + "@" + day + "@" + timeAt
|
||||
|
||||
// 增加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,28 +204,50 @@ func (this *BandwidthStatManager) Add(userId int64, serverId int64, bytes int64)
|
||||
// 此刻如果发生用户ID(userId)的变化也忽略,等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,
|
||||
}
|
||||
}
|
||||
this.locker.Unlock()
|
||||
}
|
||||
|
||||
// AddTraffic 添加请求数据
|
||||
func (this *BandwidthStatManager) AddTraffic(serverId int64, cachedBytes int64, countRequests int64, countCachedRequests int64, countAttacks int64, attackBytes int64) {
|
||||
var now = fasttime.Now()
|
||||
var day = now.Ymd()
|
||||
var timeAt = now.Round5Hi()
|
||||
var key = types.String(serverId) + "@" + day + "@" + timeAt
|
||||
this.locker.Lock()
|
||||
// 只有有记录了才会添加
|
||||
stat, ok := this.m[key]
|
||||
if ok {
|
||||
stat.CachedBytes += cachedBytes
|
||||
stat.CountRequests += countRequests
|
||||
stat.CountCachedRequests += countCachedRequests
|
||||
stat.CountAttackRequests += countAttacks
|
||||
stat.AttackBytes += attackBytes
|
||||
}
|
||||
this.locker.Unlock()
|
||||
}
|
||||
|
||||
func (this *BandwidthStatManager) Inspect() {
|
||||
this.locker.Lock()
|
||||
logs.PrintAsJSON(this.m)
|
||||
@@ -195,3 +265,50 @@ func (this *BandwidthStatManager) Map() map[int64]int64 /** serverId => max byte
|
||||
|
||||
return m
|
||||
}
|
||||
|
||||
// Save 保存到本地磁盘
|
||||
func (this *BandwidthStatManager) Save() error {
|
||||
this.locker.Lock()
|
||||
defer this.locker.Unlock()
|
||||
|
||||
data, err := json.Marshal(this.m)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_ = os.Remove(this.cacheFile)
|
||||
return os.WriteFile(this.cacheFile, data, 0666)
|
||||
}
|
||||
|
||||
// Cancel 取消上传
|
||||
func (this *BandwidthStatManager) Cancel() {
|
||||
this.ticker.Stop()
|
||||
}
|
||||
|
||||
// 从本地缓存文件中恢复数据
|
||||
func (this *BandwidthStatManager) recover() {
|
||||
cacheData, err := os.ReadFile(this.cacheFile)
|
||||
if err == nil {
|
||||
var m = map[string]*BandwidthStat{}
|
||||
err = json.Unmarshal(cacheData, &m)
|
||||
if err == nil && len(m) > 0 {
|
||||
var lastTime = ""
|
||||
for _, stat := range m {
|
||||
if stat.Day != fasttime.Now().Ymd() {
|
||||
continue
|
||||
}
|
||||
if len(lastTime) == 0 || stat.TimeAt > lastTime {
|
||||
lastTime = stat.TimeAt
|
||||
}
|
||||
}
|
||||
if len(lastTime) > 0 {
|
||||
var availableTime = timeutil.FormatTime("Hi", (time.Now().Unix()-300) /** 只保留5分钟的 **/ /300*300) // 300s = 5 minutes
|
||||
if lastTime >= availableTime {
|
||||
this.m = m
|
||||
this.lastTime = lastTime
|
||||
}
|
||||
}
|
||||
}
|
||||
_ = os.Remove(this.cacheFile)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,31 +3,94 @@
|
||||
package stats_test
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/stats"
|
||||
"runtime"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
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.AddBandwidth(1, 1, 10, 10)
|
||||
manager.AddBandwidth(1, 1, 10, 10)
|
||||
manager.AddBandwidth(1, 1, 10, 10)
|
||||
time.Sleep(1 * time.Second)
|
||||
manager.Add(1, 1, 85)
|
||||
manager.AddBandwidth(1, 1, 85, 85)
|
||||
time.Sleep(1 * time.Second)
|
||||
manager.Add(1, 1, 25)
|
||||
manager.Add(1, 1, 75)
|
||||
manager.AddBandwidth(1, 1, 25, 25)
|
||||
manager.AddBandwidth(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.AddBandwidth(1, 1, 10, 10)
|
||||
manager.AddBandwidth(1, 1, 10, 10)
|
||||
manager.AddBandwidth(1, 1, 10, 10)
|
||||
err := manager.Loop()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkBandwidthStatManager_Add(b *testing.B) {
|
||||
var manager = stats.NewBandwidthStatManager()
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
var i int
|
||||
for pb.Next() {
|
||||
i++
|
||||
manager.AddBandwidth(1, int64(i%100), 10, 10)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkBandwidthStatManager_Slice(b *testing.B) {
|
||||
runtime.GOMAXPROCS(1)
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
var pbStats = []*pb.ServerBandwidthStat{}
|
||||
for j := 0; j < 100; j++ {
|
||||
var stat = &stats.BandwidthStat{}
|
||||
pbStats = append(pbStats, &pb.ServerBandwidthStat{
|
||||
Id: 0,
|
||||
UserId: stat.UserId,
|
||||
ServerId: stat.ServerId,
|
||||
Day: stat.Day,
|
||||
TimeAt: stat.TimeAt,
|
||||
Bytes: stat.MaxBytes / 2,
|
||||
TotalBytes: stat.TotalBytes,
|
||||
CachedBytes: stat.CachedBytes,
|
||||
AttackBytes: stat.AttackBytes,
|
||||
CountRequests: stat.CountRequests,
|
||||
CountCachedRequests: stat.CountCachedRequests,
|
||||
CountAttackRequests: stat.CountAttackRequests,
|
||||
NodeRegionId: 1,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkBandwidthStatManager_Slice2(b *testing.B) {
|
||||
runtime.GOMAXPROCS(1)
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
var statsSlice = []*stats.BandwidthStat{}
|
||||
for j := 0; j < 100; j++ {
|
||||
var stat = &stats.BandwidthStat{}
|
||||
statsSlice = append(statsSlice, stat)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkBandwidthStatManager_Slice3(b *testing.B) {
|
||||
runtime.GOMAXPROCS(1)
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
var statsSlice = make([]*stats.BandwidthStat, 2000)
|
||||
for j := 0; j < 100; j++ {
|
||||
var stat = &stats.BandwidthStat{}
|
||||
statsSlice[j] = stat
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,8 +17,10 @@ import (
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
timeutil "github.com/iwind/TeaGo/utils/time"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
@@ -45,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 获取新对象
|
||||
@@ -59,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{},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,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时间,以方便我们调试
|
||||
@@ -86,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()
|
||||
@@ -110,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())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -148,7 +162,7 @@ func (this *HTTPRequestStatManager) AddRemoteAddr(serverId int64, remoteAddr str
|
||||
|
||||
// AddUserAgent 添加UserAgent
|
||||
func (this *HTTPRequestStatManager) AddUserAgent(serverId int64, userAgent string, ip string) {
|
||||
if len(userAgent) == 0 {
|
||||
if len(userAgent) == 0 || strings.ContainsRune(userAgent, '@') /** 非常重要,防止后面组合字符串时出现异常 **/ {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -183,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
|
||||
}
|
||||
@@ -264,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]),
|
||||
@@ -321,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{
|
||||
|
||||
@@ -8,10 +8,11 @@ import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/monitor"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/rpc"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils/fasttime"
|
||||
"github.com/iwind/TeaGo/Tea"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
@@ -21,6 +22,7 @@ import (
|
||||
var SharedTrafficStatManager = NewTrafficStatManager()
|
||||
|
||||
type TrafficItem struct {
|
||||
UserId int64
|
||||
Bytes int64
|
||||
CachedBytes int64
|
||||
CountRequests int64
|
||||
@@ -44,8 +46,8 @@ const trafficStatsMaxLife = 1200 // 最大只保存20分钟内的数据
|
||||
|
||||
// TrafficStatManager 区域流量统计
|
||||
type TrafficStatManager struct {
|
||||
itemMap map[string]*TrafficItem // [timestamp serverId] => *TrafficItem
|
||||
domainsMap map[string]*TrafficItem // timestamp @ serverId @ domain => *TrafficItem
|
||||
itemMap map[string]*TrafficItem // [timestamp serverId] => *TrafficItem
|
||||
domainsMap map[int64]map[string]*TrafficItem // serverIde => { timestamp @ domain => *TrafficItem }
|
||||
|
||||
pbItems []*pb.ServerDailyStat
|
||||
pbDomainItems []*pb.UploadServerDailyStatsRequest_DomainStat
|
||||
@@ -59,7 +61,7 @@ type TrafficStatManager struct {
|
||||
func NewTrafficStatManager() *TrafficStatManager {
|
||||
var manager = &TrafficStatManager{
|
||||
itemMap: map[string]*TrafficItem{},
|
||||
domainsMap: map[string]*TrafficItem{},
|
||||
domainsMap: map[int64]map[string]*TrafficItem{},
|
||||
}
|
||||
|
||||
return manager
|
||||
@@ -106,25 +108,30 @@ func (this *TrafficStatManager) Start() {
|
||||
}
|
||||
|
||||
// Add 添加流量
|
||||
func (this *TrafficStatManager) Add(serverId int64, domain string, bytes int64, cachedBytes int64, countRequests int64, countCachedRequests int64, countAttacks int64, attackBytes int64, checkingTrafficLimit bool, planId int64) {
|
||||
func (this *TrafficStatManager) Add(userId int64, serverId int64, domain string, bytes int64, cachedBytes int64, countRequests int64, countCachedRequests int64, countAttacks int64, attackBytes int64, checkingTrafficLimit bool, planId int64) {
|
||||
if serverId == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
// 添加到带宽
|
||||
SharedBandwidthStatManager.AddTraffic(serverId, cachedBytes, countRequests, countCachedRequests, countAttacks, attackBytes)
|
||||
|
||||
if bytes == 0 && countRequests == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
this.totalRequests++
|
||||
|
||||
var timestamp = utils.FloorUnixTime(300)
|
||||
var timestamp = fasttime.Now().UnixFloor(300)
|
||||
var key = strconv.FormatInt(timestamp, 10) + strconv.FormatInt(serverId, 10)
|
||||
this.locker.Lock()
|
||||
|
||||
// 总的流量
|
||||
item, ok := this.itemMap[key]
|
||||
if !ok {
|
||||
item = &TrafficItem{}
|
||||
item = &TrafficItem{
|
||||
UserId: userId,
|
||||
}
|
||||
this.itemMap[key] = item
|
||||
}
|
||||
item.Bytes += bytes
|
||||
@@ -137,11 +144,17 @@ func (this *TrafficStatManager) Add(serverId int64, domain string, bytes int64,
|
||||
item.PlanId = planId
|
||||
|
||||
// 单个域名流量
|
||||
var domainKey = strconv.FormatInt(timestamp, 10) + "@" + strconv.FormatInt(serverId, 10) + "@" + domain
|
||||
domainItem, ok := this.domainsMap[domainKey]
|
||||
var domainKey = types.String(timestamp) + "@" + domain
|
||||
serverDomainMap, ok := this.domainsMap[serverId]
|
||||
if !ok {
|
||||
serverDomainMap = map[string]*TrafficItem{}
|
||||
this.domainsMap[serverId] = serverDomainMap
|
||||
}
|
||||
|
||||
domainItem, ok := serverDomainMap[domainKey]
|
||||
if !ok {
|
||||
domainItem = &TrafficItem{}
|
||||
this.domainsMap[domainKey] = domainItem
|
||||
serverDomainMap[domainKey] = domainItem
|
||||
}
|
||||
domainItem.Bytes += bytes
|
||||
domainItem.CachedBytes += cachedBytes
|
||||
@@ -173,7 +186,7 @@ func (this *TrafficStatManager) Upload() error {
|
||||
|
||||
// reset
|
||||
this.itemMap = map[string]*TrafficItem{}
|
||||
this.domainsMap = map[string]*TrafficItem{}
|
||||
this.domainsMap = map[int64]map[string]*TrafficItem{}
|
||||
|
||||
this.locker.Unlock()
|
||||
|
||||
@@ -190,6 +203,7 @@ func (this *TrafficStatManager) Upload() error {
|
||||
}
|
||||
|
||||
pbServerStats = append(pbServerStats, &pb.ServerDailyStat{
|
||||
UserId: item.UserId,
|
||||
ServerId: serverId,
|
||||
NodeRegionId: regionId,
|
||||
Bytes: item.Bytes,
|
||||
@@ -205,23 +219,43 @@ func (this *TrafficStatManager) Upload() error {
|
||||
}
|
||||
|
||||
// 域名统计
|
||||
const maxDomainsPerServer = 20
|
||||
var pbDomainStats = []*pb.UploadServerDailyStatsRequest_DomainStat{}
|
||||
for key, item := range domainMap {
|
||||
var pieces = strings.SplitN(key, "@", 3)
|
||||
if len(pieces) != 3 {
|
||||
continue
|
||||
for serverId, serverDomainMap := range domainMap {
|
||||
// 如果超过单个服务最大值,则只取前N个
|
||||
var shouldTrim = len(serverDomainMap) > maxDomainsPerServer
|
||||
var tempItems []*pb.UploadServerDailyStatsRequest_DomainStat
|
||||
|
||||
for key, item := range serverDomainMap {
|
||||
var pieces = strings.SplitN(key, "@", 2)
|
||||
if len(pieces) != 2 {
|
||||
continue
|
||||
}
|
||||
var pbItem = &pb.UploadServerDailyStatsRequest_DomainStat{
|
||||
ServerId: serverId,
|
||||
Domain: pieces[1],
|
||||
Bytes: item.Bytes,
|
||||
CachedBytes: item.CachedBytes,
|
||||
CountRequests: item.CountRequests,
|
||||
CountCachedRequests: item.CountCachedRequests,
|
||||
CountAttackRequests: item.CountAttackRequests,
|
||||
AttackBytes: item.AttackBytes,
|
||||
CreatedAt: types.Int64(pieces[0]),
|
||||
}
|
||||
if !shouldTrim {
|
||||
pbDomainStats = append(pbDomainStats, pbItem)
|
||||
} else {
|
||||
tempItems = append(tempItems, pbItem)
|
||||
}
|
||||
}
|
||||
|
||||
if shouldTrim {
|
||||
sort.Slice(tempItems, func(i, j int) bool {
|
||||
return tempItems[i].CountRequests > tempItems[j].CountRequests
|
||||
})
|
||||
|
||||
pbDomainStats = append(pbDomainStats, tempItems[:maxDomainsPerServer]...)
|
||||
}
|
||||
pbDomainStats = append(pbDomainStats, &pb.UploadServerDailyStatsRequest_DomainStat{
|
||||
ServerId: types.Int64(pieces[1]),
|
||||
Domain: pieces[2],
|
||||
Bytes: item.Bytes,
|
||||
CachedBytes: item.CachedBytes,
|
||||
CountRequests: item.CountRequests,
|
||||
CountCachedRequests: item.CountCachedRequests,
|
||||
CountAttackRequests: item.CountAttackRequests,
|
||||
AttackBytes: item.AttackBytes,
|
||||
CreatedAt: types.Int64(pieces[0]),
|
||||
})
|
||||
}
|
||||
|
||||
// 历史未提交记录
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
package stats
|
||||
|
||||
import (
|
||||
"github.com/iwind/TeaGo/rands"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
"math/rand"
|
||||
"runtime"
|
||||
"testing"
|
||||
)
|
||||
@@ -8,7 +11,7 @@ import (
|
||||
func TestTrafficStatManager_Add(t *testing.T) {
|
||||
manager := NewTrafficStatManager()
|
||||
for i := 0; i < 100; i++ {
|
||||
manager.Add(1, "goedge.cn", 1, 0, 0, 0, 0, 0, false, 0)
|
||||
manager.Add(1, 1, "goedge.cn", 1, 0, 0, 0, 0, 0, false, 0)
|
||||
}
|
||||
t.Log(manager.itemMap)
|
||||
}
|
||||
@@ -16,7 +19,7 @@ func TestTrafficStatManager_Add(t *testing.T) {
|
||||
func TestTrafficStatManager_Upload(t *testing.T) {
|
||||
manager := NewTrafficStatManager()
|
||||
for i := 0; i < 100; i++ {
|
||||
manager.Add(1, "goedge.cn", 1, 0, 0, 0, 0, 0, false, 0)
|
||||
manager.Add(1, 1, "goedge.cn"+types.String(rands.Int(0, 10)), 1, 0, 1, 0, 0, 0, false, 0)
|
||||
}
|
||||
err := manager.Upload()
|
||||
if err != nil {
|
||||
@@ -28,8 +31,12 @@ func TestTrafficStatManager_Upload(t *testing.T) {
|
||||
func BenchmarkTrafficStatManager_Add(b *testing.B) {
|
||||
runtime.GOMAXPROCS(1)
|
||||
|
||||
manager := NewTrafficStatManager()
|
||||
for i := 0; i < b.N; i++ {
|
||||
manager.Add(1, "goedge.cn", 1024, 1, 0, 0, 0, 0, false, 0)
|
||||
}
|
||||
var manager = NewTrafficStatManager()
|
||||
b.ResetTimer()
|
||||
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
manager.Add(1, 1, "goedge.cn"+types.String(rand.Int63()%10), 1024, 1, 0, 0, 0, 0, false, 0)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ package ttlcache
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils"
|
||||
"time"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils/fasttime"
|
||||
)
|
||||
|
||||
var SharedCache = NewCache()
|
||||
@@ -10,8 +10,10 @@ var SharedCache = NewCache()
|
||||
// Cache TTL缓存
|
||||
// 最大的缓存时间为30 * 86400
|
||||
// Piece数据结构:
|
||||
// Piece1 | Piece2 | Piece3 | ...
|
||||
// [ Item1, Item2, ... ] | ...
|
||||
//
|
||||
// Piece1 | Piece2 | Piece3 | ...
|
||||
// [ Item1, Item2, ... ] | ...
|
||||
//
|
||||
// KeyMap列表数据结构
|
||||
// { timestamp1 => [key1, key2, ...] }, ...
|
||||
type Cache struct {
|
||||
@@ -69,12 +71,12 @@ func NewCache(opt ...OptionInterface) *Cache {
|
||||
return cache
|
||||
}
|
||||
|
||||
func (this *Cache) Write(key string, value interface{}, expiredAt int64) (ok bool) {
|
||||
func (this *Cache) Write(key string, value any, expiredAt int64) (ok bool) {
|
||||
if this.isDestroyed {
|
||||
return
|
||||
}
|
||||
|
||||
var currentTimestamp = utils.UnixTime()
|
||||
var currentTimestamp = fasttime.Now().Unix()
|
||||
if expiredAt <= currentTimestamp {
|
||||
return
|
||||
}
|
||||
@@ -83,8 +85,8 @@ func (this *Cache) Write(key string, value interface{}, expiredAt int64) (ok boo
|
||||
if expiredAt > maxExpiredAt {
|
||||
expiredAt = maxExpiredAt
|
||||
}
|
||||
uint64Key := HashKey([]byte(key))
|
||||
pieceIndex := uint64Key % this.countPieces
|
||||
var uint64Key = HashKey([]byte(key))
|
||||
var pieceIndex = uint64Key % this.countPieces
|
||||
return this.pieces[pieceIndex].Add(uint64Key, &Item{
|
||||
Value: value,
|
||||
expiredAt: expiredAt,
|
||||
@@ -96,22 +98,22 @@ func (this *Cache) IncreaseInt64(key string, delta int64, expiredAt int64, exten
|
||||
return 0
|
||||
}
|
||||
|
||||
currentTimestamp := time.Now().Unix()
|
||||
var currentTimestamp = fasttime.Now().Unix()
|
||||
if expiredAt <= currentTimestamp {
|
||||
return 0
|
||||
}
|
||||
|
||||
maxExpiredAt := currentTimestamp + 30*86400
|
||||
var maxExpiredAt = currentTimestamp + 30*86400
|
||||
if expiredAt > maxExpiredAt {
|
||||
expiredAt = maxExpiredAt
|
||||
}
|
||||
uint64Key := HashKey([]byte(key))
|
||||
pieceIndex := uint64Key % this.countPieces
|
||||
var uint64Key = HashKey([]byte(key))
|
||||
var pieceIndex = uint64Key % this.countPieces
|
||||
return this.pieces[pieceIndex].IncreaseInt64(uint64Key, delta, expiredAt, extend)
|
||||
}
|
||||
|
||||
func (this *Cache) Read(key string) (item *Item) {
|
||||
uint64Key := HashKey([]byte(key))
|
||||
var uint64Key = HashKey([]byte(key))
|
||||
return this.pieces[uint64Key%this.countPieces].Read(uint64Key)
|
||||
}
|
||||
|
||||
@@ -120,7 +122,7 @@ func (this *Cache) readIntKey(key uint64) (value *Item) {
|
||||
}
|
||||
|
||||
func (this *Cache) Delete(key string) {
|
||||
uint64Key := HashKey([]byte(key))
|
||||
var uint64Key = HashKey([]byte(key))
|
||||
this.pieces[uint64Key%this.countPieces].Delete(uint64Key)
|
||||
}
|
||||
|
||||
@@ -137,7 +139,7 @@ func (this *Cache) Count() (count int) {
|
||||
|
||||
func (this *Cache) GC() {
|
||||
this.pieces[this.gcPieceIndex].GC()
|
||||
newIndex := this.gcPieceIndex + 1
|
||||
var newIndex = this.gcPieceIndex + 1
|
||||
if newIndex >= int(this.countPieces) {
|
||||
newIndex = 0
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package ttlcache
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils/fasttime"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils/testutils"
|
||||
"github.com/iwind/TeaGo/assert"
|
||||
"github.com/iwind/TeaGo/rands"
|
||||
@@ -195,7 +195,7 @@ func BenchmarkCache_Add(b *testing.B) {
|
||||
|
||||
var cache = NewCache()
|
||||
for i := 0; i < b.N; i++ {
|
||||
cache.Write(strconv.Itoa(i), i, utils.UnixTime()+int64(i%1024))
|
||||
cache.Write(strconv.Itoa(i), i, fasttime.Now().Unix()+int64(i%1024))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -207,7 +207,7 @@ func BenchmarkCache_Add_Parallel(b *testing.B) {
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
var j = atomic.AddInt64(&i, 1)
|
||||
cache.Write(types.String(j), j, utils.UnixTime()+i%1024)
|
||||
cache.Write(types.String(j), j, fasttime.Now().Unix()+i%1024)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package ttlcache
|
||||
|
||||
type Item struct {
|
||||
Value interface{}
|
||||
Value any
|
||||
expiredAt int64
|
||||
}
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
package ttlcache
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils/expires"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils/fasttime"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Piece struct {
|
||||
@@ -27,7 +26,7 @@ func NewPiece(maxItems int) *Piece {
|
||||
|
||||
func (this *Piece) Add(key uint64, item *Item) (ok bool) {
|
||||
this.locker.Lock()
|
||||
if len(this.m) >= this.maxItems {
|
||||
if this.maxItems > 0 && len(this.m) >= this.maxItems {
|
||||
this.locker.Unlock()
|
||||
return
|
||||
}
|
||||
@@ -42,7 +41,7 @@ func (this *Piece) Add(key uint64, item *Item) (ok bool) {
|
||||
func (this *Piece) IncreaseInt64(key uint64, delta int64, expiredAt int64, extend bool) (result int64) {
|
||||
this.locker.Lock()
|
||||
item, ok := this.m[key]
|
||||
if ok && item.expiredAt > time.Now().Unix() {
|
||||
if ok && item.expiredAt > fasttime.Now().Unix() {
|
||||
result = types.Int64(item.Value) + delta
|
||||
item.Value = result
|
||||
if extend {
|
||||
@@ -75,7 +74,7 @@ func (this *Piece) Delete(key uint64) {
|
||||
func (this *Piece) Read(key uint64) (item *Item) {
|
||||
this.locker.RLock()
|
||||
item = this.m[key]
|
||||
if item != nil && item.expiredAt < utils.UnixTime() {
|
||||
if item != nil && item.expiredAt < fasttime.Now().Unix() {
|
||||
item = nil
|
||||
}
|
||||
this.locker.RUnlock()
|
||||
@@ -91,7 +90,7 @@ func (this *Piece) Count() (count int) {
|
||||
}
|
||||
|
||||
func (this *Piece) GC() {
|
||||
var currentTime = time.Now().Unix()
|
||||
var currentTime = fasttime.Now().Unix()
|
||||
if this.lastGCTime == 0 {
|
||||
this.lastGCTime = currentTime - 3600
|
||||
}
|
||||
|
||||
@@ -3,16 +3,16 @@
|
||||
package agents
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"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"
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -20,11 +20,11 @@ const (
|
||||
)
|
||||
|
||||
type DB struct {
|
||||
db *sql.DB
|
||||
db *dbs.DB
|
||||
path string
|
||||
|
||||
insertAgentIPStmt *sql.Stmt
|
||||
listAgentIPsStmt *sql.Stmt
|
||||
insertAgentIPStmt *dbs.Stmt
|
||||
listAgentIPsStmt *dbs.Stmt
|
||||
}
|
||||
|
||||
func NewDB(path string) *DB {
|
||||
@@ -51,7 +51,7 @@ func (this *DB) Init() error {
|
||||
}
|
||||
|
||||
// TODO 思考 data.db 的数据安全性
|
||||
db, err := sql.Open("sqlite3", "file:"+this.path+"?cache=shared&mode=rwc&_journal_mode=WAL")
|
||||
db, err := dbs.OpenWriter("file:" + this.path + "?cache=shared&mode=rwc&_journal_mode=WAL&_locking_mode=EXCLUSIVE")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -94,9 +94,13 @@ func (this *DB) InsertAgentIP(ipId int64, ip string, agentCode string) error {
|
||||
return errors.New("db should not be nil")
|
||||
}
|
||||
|
||||
this.log("InsertAgentIP", "id:", ipId, "ip:", ip, "agent:", agentCode)
|
||||
_, err := this.insertAgentIPStmt.Exec(ipId, ip, agentCode)
|
||||
if err != nil {
|
||||
// 不提示ID重复错误
|
||||
if strings.Contains(err.Error(), "UNIQUE constraint") {
|
||||
return nil
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -130,7 +134,7 @@ func (this *DB) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, stmt := range []*sql.Stmt{
|
||||
for _, stmt := range []*dbs.Stmt{
|
||||
this.insertAgentIPStmt,
|
||||
this.listAgentIPsStmt,
|
||||
} {
|
||||
|
||||
@@ -4,6 +4,7 @@ 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"
|
||||
@@ -16,6 +17,10 @@ import (
|
||||
var SharedManager = NewManager()
|
||||
|
||||
func init() {
|
||||
if !teaconst.IsMain {
|
||||
return
|
||||
}
|
||||
|
||||
events.On(events.EventLoaded, func() {
|
||||
goman.New(func() {
|
||||
SharedManager.Start()
|
||||
|
||||
@@ -4,6 +4,7 @@ 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"
|
||||
@@ -13,6 +14,10 @@ import (
|
||||
)
|
||||
|
||||
func init() {
|
||||
if !teaconst.IsMain {
|
||||
return
|
||||
}
|
||||
|
||||
events.On(events.EventLoaded, func() {
|
||||
goman.New(func() {
|
||||
SharedQueue.Start()
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
|
||||
@@ -15,6 +15,10 @@ import (
|
||||
)
|
||||
|
||||
func init() {
|
||||
if !teaconst.IsMain {
|
||||
return
|
||||
}
|
||||
|
||||
var ticker = time.NewTicker(5 * time.Second)
|
||||
|
||||
events.On(events.EventLoaded, func() {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package expires
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils/fasttime"
|
||||
"github.com/iwind/TeaGo/assert"
|
||||
"github.com/iwind/TeaGo/logs"
|
||||
timeutil "github.com/iwind/TeaGo/utils/time"
|
||||
@@ -197,7 +197,7 @@ func BenchmarkList_GC(b *testing.B) {
|
||||
for m := 0; m < 1_000; m++ {
|
||||
var list = NewList()
|
||||
for j := 0; j < 10_000; j++ {
|
||||
list.Add(uint64(j), utils.UnixTime()+100)
|
||||
list.Add(uint64(j), fasttime.Now().Unix()+100)
|
||||
}
|
||||
lists = append(lists, list)
|
||||
}
|
||||
|
||||
93
internal/utils/fasttime/time_fast.go
Normal file
93
internal/utils/fasttime/time_fast.go
Normal file
@@ -0,0 +1,93 @@
|
||||
// Copyright 2023 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
|
||||
package fasttime
|
||||
|
||||
import (
|
||||
teaconst "github.com/TeaOSLab/EdgeNode/internal/const"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/goman"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
timeutil "github.com/iwind/TeaGo/utils/time"
|
||||
"time"
|
||||
)
|
||||
|
||||
var sharedFastTime = NewFastTime()
|
||||
|
||||
func init() {
|
||||
if !teaconst.IsMain {
|
||||
return
|
||||
}
|
||||
|
||||
var ticker = time.NewTicker(200 * time.Millisecond)
|
||||
goman.New(func() {
|
||||
for range ticker.C {
|
||||
sharedFastTime = NewFastTime()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func Now() *FastTime {
|
||||
return sharedFastTime
|
||||
}
|
||||
|
||||
type FastTime struct {
|
||||
rawTime time.Time
|
||||
unixTime int64
|
||||
unixTimeMilli int64
|
||||
unixTimeMilliString string
|
||||
ymd string
|
||||
round5Hi string
|
||||
}
|
||||
|
||||
func NewFastTime() *FastTime {
|
||||
var rawTime = time.Now()
|
||||
|
||||
return &FastTime{
|
||||
rawTime: rawTime,
|
||||
unixTime: rawTime.Unix(),
|
||||
unixTimeMilli: rawTime.UnixMilli(),
|
||||
unixTimeMilliString: types.String(rawTime.UnixMilli()),
|
||||
ymd: timeutil.Format("Ymd", rawTime),
|
||||
round5Hi: timeutil.FormatTime("Hi", rawTime.Unix()/300*300),
|
||||
}
|
||||
}
|
||||
|
||||
// Unix 最快获取时间戳的方式,通常用在不需要特别精确时间戳的场景
|
||||
func (this *FastTime) Unix() int64 {
|
||||
return this.unixTime
|
||||
}
|
||||
|
||||
// UnixFloor 取整
|
||||
func (this *FastTime) UnixFloor(seconds int) int64 {
|
||||
return this.unixTime / int64(seconds) * int64(seconds)
|
||||
}
|
||||
|
||||
// UnixCell 取整并加1
|
||||
func (this *FastTime) UnixCell(seconds int) int64 {
|
||||
return this.unixTime/int64(seconds)*int64(seconds) + int64(seconds)
|
||||
}
|
||||
|
||||
// UnixNextMinute 获取下一分钟开始的时间戳
|
||||
func (this *FastTime) UnixNextMinute() int64 {
|
||||
return this.UnixCell(60)
|
||||
}
|
||||
|
||||
// UnixMilli 获取时间戳,精确到毫秒
|
||||
func (this *FastTime) UnixMilli() int64 {
|
||||
return this.unixTimeMilli
|
||||
}
|
||||
|
||||
func (this *FastTime) UnixMilliString() (int64, string) {
|
||||
return this.unixTimeMilli, this.unixTimeMilliString
|
||||
}
|
||||
|
||||
func (this *FastTime) Ymd() string {
|
||||
return this.ymd
|
||||
}
|
||||
|
||||
func (this *FastTime) Round5Hi() string {
|
||||
return this.round5Hi
|
||||
}
|
||||
|
||||
func (this *FastTime) Format(layout string) string {
|
||||
return timeutil.Format(layout, this.rawTime)
|
||||
}
|
||||
57
internal/utils/fasttime/time_fast_test.go
Normal file
57
internal/utils/fasttime/time_fast_test.go
Normal file
@@ -0,0 +1,57 @@
|
||||
// Copyright 2023 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
|
||||
package fasttime_test
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils/fasttime"
|
||||
timeutil "github.com/iwind/TeaGo/utils/time"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestFastTime_Unix(t *testing.T) {
|
||||
for i := 0; i < 5; i++ {
|
||||
var now = fasttime.Now()
|
||||
t.Log(now.Unix(), now.UnixMilli(), "real:", time.Now().Unix())
|
||||
time.Sleep(1 * time.Second)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFastTime_UnixMilli(t *testing.T) {
|
||||
t.Log(fasttime.Now().UnixMilliString())
|
||||
}
|
||||
|
||||
func TestFastTime_UnixFloor(t *testing.T) {
|
||||
var now = fasttime.Now()
|
||||
|
||||
var timestamp = time.Now().Unix()
|
||||
t.Log("floor 60:", timestamp, now.UnixFloor(60), timeutil.FormatTime("Y-m-d H:i:s", now.UnixFloor(60)))
|
||||
t.Log("ceil 60:", timestamp, now.UnixCell(60), timeutil.FormatTime("Y-m-d H:i:s", now.UnixCell(60)))
|
||||
t.Log("floor 300:", timestamp, now.UnixFloor(300), timeutil.FormatTime("Y-m-d H:i:s", now.UnixFloor(300)))
|
||||
t.Log("next minute:", now.UnixNextMinute(), timeutil.FormatTime("Y-m-d H:i:s", now.UnixNextMinute()))
|
||||
t.Log("day:", now.Ymd())
|
||||
t.Log("round 5 minute:", now.Round5Hi())
|
||||
}
|
||||
|
||||
func TestFastTime_Format(t *testing.T) {
|
||||
var now = fasttime.Now()
|
||||
t.Log(now.Format("Y-m-d H:i:s"))
|
||||
}
|
||||
|
||||
func BenchmarkNewFastTime(b *testing.B) {
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
var now = fasttime.Now()
|
||||
_ = now.Ymd()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkNewFastTime_Raw(b *testing.B) {
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
var now = time.Now()
|
||||
_ = timeutil.Format("Ymd", now)
|
||||
}
|
||||
})
|
||||
}
|
||||
82
internal/utils/fileutils/locker.go
Normal file
82
internal/utils/fileutils/locker.go
Normal 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
|
||||
}
|
||||
24
internal/utils/fileutils/locker_test.go
Normal file
24
internal/utils/fileutils/locker_test.go
Normal 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)
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,10 @@ import (
|
||||
var SharedFreeHoursManager = NewFreeHoursManager()
|
||||
|
||||
func init() {
|
||||
if !teaconst.IsMain {
|
||||
return
|
||||
}
|
||||
|
||||
events.On(events.EventLoaded, func() {
|
||||
goman.New(func() {
|
||||
SharedFreeHoursManager.Start()
|
||||
|
||||
78
internal/utils/maps/map_fixed.go
Normal file
78
internal/utils/maps/map_fixed.go
Normal file
@@ -0,0 +1,78 @@
|
||||
// Copyright 2023 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
|
||||
package maputils
|
||||
|
||||
import "sync"
|
||||
|
||||
type KeyType interface {
|
||||
string | int | int64 | int32 | uint64 | uint32
|
||||
}
|
||||
|
||||
type ValueType interface {
|
||||
any
|
||||
}
|
||||
|
||||
// FixedMap
|
||||
// TODO 解决已存在元素不能按顺序弹出的问题
|
||||
type FixedMap[KeyT KeyType, ValueT ValueType] struct {
|
||||
m map[KeyT]ValueT
|
||||
keys []KeyT
|
||||
|
||||
maxSize int
|
||||
locker sync.RWMutex
|
||||
}
|
||||
|
||||
func NewFixedMap[KeyT KeyType, ValueT ValueType](maxSize int) *FixedMap[KeyT, ValueT] {
|
||||
return &FixedMap[KeyT, ValueT]{
|
||||
maxSize: maxSize,
|
||||
m: map[KeyT]ValueT{},
|
||||
}
|
||||
}
|
||||
|
||||
func (this *FixedMap[KeyT, ValueT]) Put(key KeyT, value ValueT) {
|
||||
this.locker.Lock()
|
||||
defer this.locker.Unlock()
|
||||
|
||||
if this.maxSize <= 0 {
|
||||
return
|
||||
}
|
||||
|
||||
_, exists := this.m[key]
|
||||
this.m[key] = value
|
||||
|
||||
if !exists {
|
||||
this.keys = append(this.keys, key)
|
||||
|
||||
if len(this.keys) > this.maxSize {
|
||||
var firstKey = this.keys[0]
|
||||
this.keys = this.keys[1:]
|
||||
delete(this.m, firstKey)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (this *FixedMap[KeyT, ValueT]) Get(key KeyT) (value ValueT, ok bool) {
|
||||
this.locker.RLock()
|
||||
defer this.locker.RUnlock()
|
||||
value, ok = this.m[key]
|
||||
return
|
||||
}
|
||||
|
||||
func (this *FixedMap[KeyT, ValueT]) Has(key KeyT) bool {
|
||||
this.locker.RLock()
|
||||
defer this.locker.RUnlock()
|
||||
_, ok := this.m[key]
|
||||
return ok
|
||||
}
|
||||
|
||||
func (this *FixedMap[KeyT, ValueT]) Keys() []KeyT {
|
||||
this.locker.RLock()
|
||||
defer this.locker.RUnlock()
|
||||
return this.keys
|
||||
}
|
||||
|
||||
func (this *FixedMap[KeyT, ValueT]) RawMap() map[KeyT]ValueT {
|
||||
this.locker.RLock()
|
||||
defer this.locker.RUnlock()
|
||||
return this.m
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user