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