Files
waf-platform/EdgeNode/internal/nodes/http_request_ln_plus.go
2026-02-04 20:27:13 +08:00

272 lines
7.4 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 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)
}