1.4.5.2
This commit is contained in:
271
EdgeNode/internal/nodes/http_request_ln_plus.go
Normal file
271
EdgeNode/internal/nodes/http_request_ln_plus.go
Normal file
@@ -0,0 +1,271 @@
|
||||
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
//go:build plus
|
||||
|
||||
package nodes
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/iputils"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils/encrypt"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils/fasttime"
|
||||
setutils "github.com/TeaOSLab/EdgeNode/internal/utils/sets"
|
||||
"github.com/iwind/TeaGo/lists"
|
||||
"github.com/iwind/TeaGo/rands"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
stringutil "github.com/iwind/TeaGo/utils/string"
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
LNKeyHeader = "X-Edge-Ln-Key"
|
||||
LNExpiresHeader = "X-Edge-Ln-Expires"
|
||||
LnExpiresSeconds int64 = 30
|
||||
)
|
||||
|
||||
var lnOriginConfigsMap = map[string]*serverconfigs.OriginConfig{}
|
||||
var lnEncryptMethodMap = map[string]encrypt.MethodInterface{}
|
||||
var lnRequestIdSet = setutils.NewFixedSet(32 * 1024)
|
||||
|
||||
var lnNodeIPMap = map[string]bool{} // node ip => bool
|
||||
var lnNodeIPLocker = &sync.RWMutex{}
|
||||
|
||||
var lnLocker = &sync.RWMutex{}
|
||||
|
||||
func existsLnNodeIP(nodeIP string) bool {
|
||||
lnNodeIPLocker.RLock()
|
||||
defer lnNodeIPLocker.RUnlock()
|
||||
return lnNodeIPMap[nodeIP]
|
||||
}
|
||||
|
||||
func (this *HTTPRequest) checkLnRequest() bool {
|
||||
var keyString = this.RawReq.Header.Get(LNKeyHeader)
|
||||
if len(keyString) <= 20 {
|
||||
return false
|
||||
}
|
||||
|
||||
// 删除
|
||||
this.RawReq.Header.Del(LNKeyHeader)
|
||||
|
||||
// 当前连接IP
|
||||
connIP, _, _ := net.SplitHostPort(this.RawReq.RemoteAddr)
|
||||
var realIP = this.RawReq.Header.Get("X-Real-Ip")
|
||||
if len(realIP) > 0 && !iputils.IsValid(realIP) {
|
||||
realIP = ""
|
||||
}
|
||||
|
||||
// 如果是在已经识别的L[n-1]节点IP列表中,无需再次验证
|
||||
if existsLnNodeIP(connIP) && len(realIP) > 0 {
|
||||
this.tags = append(this.tags, "L"+types.String(this.nodeConfig.Level))
|
||||
this.lnRemoteAddr = realIP
|
||||
return true
|
||||
}
|
||||
|
||||
// 如果已经在允许的IP中,则直接允许通过,无需再次验证
|
||||
// 这个需要放在 existsLnNodeIP() 检查之后
|
||||
if this.nodeConfig != nil && this.nodeConfig.IPIsAutoAllowed(connIP) && len(realIP) > 0 {
|
||||
this.tags = append(this.tags, "L"+types.String(this.nodeConfig.Level))
|
||||
this.lnRemoteAddr = realIP
|
||||
lnNodeIPLocker.Lock()
|
||||
lnNodeIPMap[connIP] = true
|
||||
lnNodeIPLocker.Unlock()
|
||||
return true
|
||||
}
|
||||
|
||||
// 检查Key
|
||||
keyEncodedData, err := base64.StdEncoding.DecodeString(keyString)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
var secret = this.nodeConfig.SecretHash()
|
||||
lnLocker.Lock()
|
||||
method, ok := lnEncryptMethodMap[secret]
|
||||
if !ok {
|
||||
method, err = encrypt.NewMethodInstance("aes-192-cfb", secret, secret)
|
||||
if err != nil {
|
||||
lnLocker.Unlock()
|
||||
return false
|
||||
}
|
||||
}
|
||||
lnLocker.Unlock()
|
||||
|
||||
keyData, err := method.Decrypt(keyEncodedData)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
var key = &LnRequestKey{}
|
||||
err = UnmarshalLnRequestKey(keyData, key)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// Method和URL需要一致
|
||||
if key.URLMd5 != stringutil.Md5(this.URL()) || key.Method != this.Method() {
|
||||
return false
|
||||
}
|
||||
|
||||
// N秒钟过期,这里要求两个节点时间相差不能超过此时间
|
||||
var currentUnixTime = fasttime.Now().Unix()
|
||||
if key.Timestamp < currentUnixTime-LnExpiresSeconds {
|
||||
return false
|
||||
}
|
||||
|
||||
// 检查请求ID唯一性
|
||||
// TODO 因为FixedSet是有限的,这里仍然无法避免重放攻击
|
||||
// TODO 而RequestId是并发的,并不能简单的对比大小
|
||||
// TODO 所以为了绝对的安全,需要使用HTTPS并检查子节点的IP
|
||||
if lnRequestIdSet.Has(key.RequestId) {
|
||||
return false
|
||||
}
|
||||
lnRequestIdSet.Push(key.RequestId)
|
||||
|
||||
this.lnRemoteAddr = key.RemoteAddr
|
||||
|
||||
this.tags = append(this.tags, "L"+types.String(this.nodeConfig.Level))
|
||||
|
||||
// 当前连接IP
|
||||
if len(connIP) > 0 {
|
||||
lnNodeIPLocker.Lock()
|
||||
lnNodeIPMap[connIP] = true
|
||||
lnNodeIPLocker.Unlock()
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (this *HTTPRequest) getLnOrigin(excludingNodeIds []int64, urlHash uint64) (originConfig *serverconfigs.OriginConfig, lnNodeId int64, hasMultipleNodes bool) {
|
||||
var parentNodesMap = this.nodeConfig.ParentNodes // 需要复制,防止运行过程中修改
|
||||
if len(parentNodesMap) == 0 {
|
||||
return nil, 0, false
|
||||
}
|
||||
|
||||
parentNodes, ok := parentNodesMap[this.ReqServer.ClusterId]
|
||||
var countParentNodes = len(parentNodes)
|
||||
if ok && countParentNodes > 0 {
|
||||
var parentNode *nodeconfigs.ParentNodeConfig
|
||||
|
||||
// 尝试顺序读取
|
||||
if len(excludingNodeIds) > 0 {
|
||||
for _, node := range parentNodes {
|
||||
if !lists.ContainsInt64(excludingNodeIds, node.Id) {
|
||||
parentNode = node
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 尝试随机读取
|
||||
if parentNode == nil {
|
||||
if countParentNodes == 1 {
|
||||
parentNode = parentNodes[0]
|
||||
} else {
|
||||
var method = serverconfigs.LnRequestSchedulingMethodURLMapping
|
||||
if this.nodeConfig != nil {
|
||||
var globalServerConfig = this.nodeConfig.GlobalServerConfig // copy
|
||||
if globalServerConfig != nil {
|
||||
method = globalServerConfig.HTTPAll.LnRequestSchedulingMethod
|
||||
}
|
||||
}
|
||||
|
||||
switch method {
|
||||
case serverconfigs.LnRequestSchedulingMethodRandom:
|
||||
// 随机选取一个L2节点,有利于流量均衡,但同一份缓存可能会存在多个L2节点上,占用更多的空间
|
||||
parentNode = parentNodes[rands.Int(0, countParentNodes-1)]
|
||||
default:
|
||||
// 从固定的L2节点读取内容,优点是能够提升缓存命中率,缺点是可能会导致多个L2节点流量不均衡
|
||||
parentNode = parentNodes[urlHash%uint64(countParentNodes)]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
lnNodeId = parentNode.Id
|
||||
|
||||
var countAddrs = len(parentNode.Addrs)
|
||||
if countAddrs == 0 {
|
||||
return nil, 0, false
|
||||
}
|
||||
|
||||
var addr = parentNode.Addrs[rands.Int(0, countAddrs-1)]
|
||||
|
||||
var protocol = this.requestScheme() // http|https TODO 需要可以设置是否强制HTTPS回二级节点
|
||||
var portString = types.String(this.requestServerPort())
|
||||
var originKey = protocol + "@" + addr + "@" + portString
|
||||
lnLocker.RLock()
|
||||
config, ok := lnOriginConfigsMap[originKey]
|
||||
lnLocker.RUnlock()
|
||||
|
||||
if !ok {
|
||||
config = &serverconfigs.OriginConfig{
|
||||
Id: 0,
|
||||
IsOn: true,
|
||||
Addr: &serverconfigs.NetworkAddressConfig{
|
||||
Protocol: serverconfigs.Protocol(protocol),
|
||||
Host: addr,
|
||||
PortRange: portString,
|
||||
},
|
||||
IsOk: true,
|
||||
}
|
||||
|
||||
err := config.Init(context.Background())
|
||||
if err != nil {
|
||||
remotelogs.Error("HTTP_REQUEST", "create ln origin config failed: "+err.Error())
|
||||
return nil, 0, false
|
||||
}
|
||||
|
||||
lnLocker.Lock()
|
||||
lnOriginConfigsMap[originKey] = config
|
||||
lnLocker.Unlock()
|
||||
}
|
||||
|
||||
// 添加Header
|
||||
this.RawReq.Header.Set(LNKeyHeader, this.encodeLnKey(parentNode.SecretHash))
|
||||
|
||||
return config, lnNodeId, len(parentNodes) > 0
|
||||
}
|
||||
|
||||
return nil, 0, false
|
||||
}
|
||||
|
||||
func (this *HTTPRequest) encodeLnKey(secretHash string) string {
|
||||
var key = &LnRequestKey{
|
||||
NodeId: this.nodeConfig.Id,
|
||||
RequestId: this.requestId,
|
||||
Timestamp: time.Now().Unix(),
|
||||
RemoteAddr: this.RemoteAddr(),
|
||||
URLMd5: stringutil.Md5(this.URL()),
|
||||
Method: this.Method(),
|
||||
}
|
||||
|
||||
data, err := key.AsPB()
|
||||
if err != nil {
|
||||
remotelogs.Error("HTTP_REQUEST", "ln request: encode key failed: "+err.Error())
|
||||
return ""
|
||||
}
|
||||
|
||||
lnLocker.Lock()
|
||||
method, ok := lnEncryptMethodMap[secretHash]
|
||||
if !ok {
|
||||
method, err = encrypt.NewMethodInstance("aes-192-cfb", secretHash, secretHash)
|
||||
if err != nil {
|
||||
remotelogs.Error("HTTP_REQUEST", "ln request: create encrypt method failed: "+err.Error())
|
||||
lnLocker.Unlock()
|
||||
return ""
|
||||
}
|
||||
lnEncryptMethodMap[secretHash] = method
|
||||
}
|
||||
lnLocker.Unlock()
|
||||
|
||||
dst, err := method.Encrypt(data)
|
||||
if err != nil {
|
||||
remotelogs.Error("HTTP_REQUEST", "ln request: encode key failed: "+err.Error())
|
||||
return ""
|
||||
}
|
||||
return base64.StdEncoding.EncodeToString(dst)
|
||||
}
|
||||
Reference in New Issue
Block a user