272 lines
7.4 KiB
Go
272 lines
7.4 KiB
Go
// 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)
|
||
}
|