mirror of
https://github.com/TeaOSLab/EdgeNode.git
synced 2025-11-03 15:00:26 +08:00
实现自动SYN Flood防护
This commit is contained in:
@@ -18,7 +18,7 @@ type Firewalld struct {
|
|||||||
|
|
||||||
func NewFirewalld() *Firewalld {
|
func NewFirewalld() *Firewalld {
|
||||||
var firewalld = &Firewalld{
|
var firewalld = &Firewalld{
|
||||||
cmdQueue: make(chan *exec.Cmd, 2048),
|
cmdQueue: make(chan *exec.Cmd, 4096),
|
||||||
}
|
}
|
||||||
|
|
||||||
path, err := exec.LookPath("firewall-cmd")
|
path, err := exec.LookPath("firewall-cmd")
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// AllowIP 检查IP是否被允许访问
|
// AllowIP 检查IP是否被允许访问
|
||||||
|
// 如果一个IP不在任何名单中,则允许访问
|
||||||
func AllowIP(ip string, serverId int64) bool {
|
func AllowIP(ip string, serverId int64) bool {
|
||||||
var ipLong = utils.IP2Long(ip)
|
var ipLong = utils.IP2Long(ip)
|
||||||
if ipLong == 0 {
|
if ipLong == 0 {
|
||||||
@@ -40,6 +41,17 @@ func AllowIP(ip string, serverId int64) bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IsInWhiteList 检查IP是否在白名单中
|
||||||
|
func IsInWhiteList(ip string) bool {
|
||||||
|
var ipLong = utils.IP2Long(ip)
|
||||||
|
if ipLong == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// check white lists
|
||||||
|
return GlobalWhiteIPList.Contains(ipLong)
|
||||||
|
}
|
||||||
|
|
||||||
// AllowIPStrings 检查一组IP是否被允许访问
|
// AllowIPStrings 检查一组IP是否被允许访问
|
||||||
func AllowIPStrings(ipStrings []string, serverId int64) bool {
|
func AllowIPStrings(ipStrings []string, serverId int64) bool {
|
||||||
if len(ipStrings) == 0 {
|
if len(ipStrings) == 0 {
|
||||||
|
|||||||
@@ -4,9 +4,16 @@ package nodes
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
|
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
|
||||||
|
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs"
|
||||||
teaconst "github.com/TeaOSLab/EdgeNode/internal/const"
|
teaconst "github.com/TeaOSLab/EdgeNode/internal/const"
|
||||||
|
"github.com/TeaOSLab/EdgeNode/internal/iplibrary"
|
||||||
"github.com/TeaOSLab/EdgeNode/internal/ratelimit"
|
"github.com/TeaOSLab/EdgeNode/internal/ratelimit"
|
||||||
|
"github.com/TeaOSLab/EdgeNode/internal/ttlcache"
|
||||||
|
"github.com/TeaOSLab/EdgeNode/internal/utils"
|
||||||
|
"github.com/TeaOSLab/EdgeNode/internal/waf"
|
||||||
|
"github.com/iwind/TeaGo/types"
|
||||||
"net"
|
"net"
|
||||||
|
"os"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
@@ -17,8 +24,9 @@ type ClientConn struct {
|
|||||||
once sync.Once
|
once sync.Once
|
||||||
globalLimiter *ratelimit.Counter
|
globalLimiter *ratelimit.Counter
|
||||||
|
|
||||||
isTLS bool
|
isTLS bool
|
||||||
hasRead bool
|
hasDeadline bool
|
||||||
|
hasRead bool
|
||||||
|
|
||||||
BaseClientConn
|
BaseClientConn
|
||||||
}
|
}
|
||||||
@@ -38,9 +46,9 @@ func NewClientConn(conn net.Conn, isTLS bool, quickClose bool, globalLimiter *ra
|
|||||||
|
|
||||||
func (this *ClientConn) Read(b []byte) (n int, err error) {
|
func (this *ClientConn) Read(b []byte) (n int, err error) {
|
||||||
if this.isTLS {
|
if this.isTLS {
|
||||||
if !this.hasRead {
|
if !this.hasDeadline {
|
||||||
_ = this.rawConn.SetReadDeadline(time.Now().Add(time.Duration(nodeconfigs.DefaultTLSHandshakeTimeout) * time.Second)) // TODO 握手超时时间可以设置
|
_ = this.rawConn.SetReadDeadline(time.Now().Add(time.Duration(nodeconfigs.DefaultTLSHandshakeTimeout) * time.Second)) // TODO 握手超时时间可以设置
|
||||||
this.hasRead = true
|
this.hasDeadline = true
|
||||||
defer func() {
|
defer func() {
|
||||||
_ = this.rawConn.SetReadDeadline(time.Time{})
|
_ = this.rawConn.SetReadDeadline(time.Time{})
|
||||||
}()
|
}()
|
||||||
@@ -50,7 +58,21 @@ func (this *ClientConn) Read(b []byte) (n int, err error) {
|
|||||||
n, err = this.rawConn.Read(b)
|
n, err = this.rawConn.Read(b)
|
||||||
if n > 0 {
|
if n > 0 {
|
||||||
atomic.AddUint64(&teaconst.InTrafficBytes, uint64(n))
|
atomic.AddUint64(&teaconst.InTrafficBytes, uint64(n))
|
||||||
|
this.hasRead = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SYN Flood检测
|
||||||
|
var synFloodConfig = sharedNodeConfig.SYNFloodConfig()
|
||||||
|
if synFloodConfig != nil && synFloodConfig.IsOn {
|
||||||
|
if err != nil && os.IsTimeout(err) {
|
||||||
|
if !this.hasRead {
|
||||||
|
this.checkSYNFlood()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.resetSYNFlood()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -99,3 +121,32 @@ func (this *ClientConn) SetReadDeadline(t time.Time) error {
|
|||||||
func (this *ClientConn) SetWriteDeadline(t time.Time) error {
|
func (this *ClientConn) SetWriteDeadline(t time.Time) error {
|
||||||
return this.rawConn.SetWriteDeadline(t)
|
return this.rawConn.SetWriteDeadline(t)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (this *ClientConn) resetSYNFlood() {
|
||||||
|
// 为了不影响性能,暂时不清除状态
|
||||||
|
//ttlcache.SharedCache.Delete("SYN_FLOOD:" + this.RawIP())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *ClientConn) checkSYNFlood() {
|
||||||
|
var synFloodConfig = sharedNodeConfig.SYNFloodConfig()
|
||||||
|
if synFloodConfig == nil || !synFloodConfig.IsOn {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var ip = this.RawIP()
|
||||||
|
if len(ip) > 0 && !iplibrary.IsInWhiteList(ip) && (!synFloodConfig.IgnoreLocal || !utils.IsLocalIP(ip)) {
|
||||||
|
var timestamp = (utils.UnixTime()/60)*60 + 60
|
||||||
|
var result = ttlcache.SharedCache.IncreaseInt64("SYN_FLOOD:"+ip, 1, timestamp)
|
||||||
|
var minAttempts = synFloodConfig.MinAttempts
|
||||||
|
if minAttempts < 3 {
|
||||||
|
minAttempts = 3
|
||||||
|
}
|
||||||
|
if result >= int64(minAttempts) {
|
||||||
|
var timeout = synFloodConfig.TimeoutSeconds
|
||||||
|
if timeout <= 0 {
|
||||||
|
timeout = 600
|
||||||
|
}
|
||||||
|
waf.SharedIPBlackList.RecordIP(waf.IPTypeAll, firewallconfigs.FirewallScopeGlobal, 0, ip, time.Now().Unix()+int64(timeout), 0, true, 0, 0, "疑似SYN Flood攻击,当前1分钟"+types.String(result)+"次空连接")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -36,3 +36,8 @@ func (this *BaseClientConn) Bind(serverId int64, remoteAddr string, maxConnsPerS
|
|||||||
return sharedClientConnLimiter.Add(this.rawConn.RemoteAddr().String(), serverId, remoteAddr, maxConnsPerServer, maxConnsPerIP)
|
return sharedClientConnLimiter.Add(this.rawConn.RemoteAddr().String(), serverId, remoteAddr, maxConnsPerServer, maxConnsPerIP)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RawIP 原本IP
|
||||||
|
func (this *BaseClientConn) RawIP() string {
|
||||||
|
ip, _, _ := net.SplitHostPort(this.rawConn.RemoteAddr().String())
|
||||||
|
return ip
|
||||||
|
}
|
||||||
|
|||||||
@@ -65,6 +65,7 @@ func (this *WAFManager) convertWAF(policy *firewallconfigs.HTTPFirewallPolicy) (
|
|||||||
Name: policy.Name,
|
Name: policy.Name,
|
||||||
Mode: policy.Mode,
|
Mode: policy.Mode,
|
||||||
UseLocalFirewall: policy.UseLocalFirewall,
|
UseLocalFirewall: policy.UseLocalFirewall,
|
||||||
|
SYNFlood: policy.SYNFlood,
|
||||||
}
|
}
|
||||||
|
|
||||||
// inbound
|
// inbound
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var SharedCache = NewCache()
|
||||||
|
|
||||||
// Cache TTL缓存
|
// Cache TTL缓存
|
||||||
// 最大的缓存时间为30 * 86400
|
// 最大的缓存时间为30 * 86400
|
||||||
// Piece数据结构:
|
// Piece数据结构:
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ func (this *BlockAction) Perform(waf *WAF, group *RuleGroup, set *RuleSet, reque
|
|||||||
timeout = 60 // 默认封锁60秒
|
timeout = 60 // 默认封锁60秒
|
||||||
}
|
}
|
||||||
|
|
||||||
SharedIPBlackList.RecordIP(IPTypeAll, this.Scope, request.WAFServerId(), request.WAFRemoteIP(), time.Now().Unix()+int64(timeout), waf.Id, waf.UseLocalFirewall, group.Id, set.Id)
|
SharedIPBlackList.RecordIP(IPTypeAll, this.Scope, request.WAFServerId(), request.WAFRemoteIP(), time.Now().Unix()+int64(timeout), waf.Id, waf.UseLocalFirewall, group.Id, set.Id, "")
|
||||||
|
|
||||||
if writer != nil {
|
if writer != nil {
|
||||||
// close the connection
|
// close the connection
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ func (this *Post307Action) Perform(waf *WAF, group *RuleGroup, set *RuleSet, req
|
|||||||
life = 600 // 默认10分钟
|
life = 600 // 默认10分钟
|
||||||
}
|
}
|
||||||
var setId = m.GetString("setId")
|
var setId = m.GetString("setId")
|
||||||
SharedIPWhiteList.RecordIP("set:"+setId, this.Scope, request.WAFServerId(), request.WAFRemoteIP(), time.Now().Unix()+life, m.GetInt64("policyId"), false, m.GetInt64("groupId"), m.GetInt64("setId"))
|
SharedIPWhiteList.RecordIP("set:"+setId, this.Scope, request.WAFServerId(), request.WAFRemoteIP(), time.Now().Unix()+life, m.GetInt64("policyId"), false, m.GetInt64("groupId"), m.GetInt64("setId"), "")
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,6 +22,8 @@ type recordIPTask struct {
|
|||||||
level string
|
level string
|
||||||
serverId int64
|
serverId int64
|
||||||
|
|
||||||
|
reason string
|
||||||
|
|
||||||
sourceServerId int64
|
sourceServerId int64
|
||||||
sourceHTTPFirewallPolicyId int64
|
sourceHTTPFirewallPolicyId int64
|
||||||
sourceHTTPFirewallRuleGroupId int64
|
sourceHTTPFirewallRuleGroupId int64
|
||||||
@@ -44,12 +46,16 @@ func init() {
|
|||||||
if strings.Contains(task.ip, ":") {
|
if strings.Contains(task.ip, ":") {
|
||||||
ipType = "ipv6"
|
ipType = "ipv6"
|
||||||
}
|
}
|
||||||
|
var reason = task.reason
|
||||||
|
if len(reason) == 0 {
|
||||||
|
reason = "触发WAF规则自动加入"
|
||||||
|
}
|
||||||
_, err = rpcClient.IPItemRPC().CreateIPItem(rpcClient.Context(), &pb.CreateIPItemRequest{
|
_, err = rpcClient.IPItemRPC().CreateIPItem(rpcClient.Context(), &pb.CreateIPItemRequest{
|
||||||
IpListId: task.listId,
|
IpListId: task.listId,
|
||||||
IpFrom: task.ip,
|
IpFrom: task.ip,
|
||||||
IpTo: "",
|
IpTo: "",
|
||||||
ExpiredAt: task.expiredAt,
|
ExpiredAt: task.expiredAt,
|
||||||
Reason: "触发WAF规则自动加入",
|
Reason: reason,
|
||||||
Type: ipType,
|
Type: ipType,
|
||||||
EventLevel: task.level,
|
EventLevel: task.level,
|
||||||
ServerId: task.serverId,
|
ServerId: task.serverId,
|
||||||
|
|||||||
@@ -153,7 +153,7 @@ func (this *CaptchaValidator) validate(actionConfig *CaptchaAction, policyId int
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 加入到白名单
|
// 加入到白名单
|
||||||
SharedIPWhiteList.RecordIP("set:"+strconv.FormatInt(setId, 10), actionConfig.Scope, request.WAFServerId(), request.WAFRemoteIP(), time.Now().Unix()+int64(life), policyId, false, groupId, setId)
|
SharedIPWhiteList.RecordIP("set:"+strconv.FormatInt(setId, 10), actionConfig.Scope, request.WAFServerId(), request.WAFRemoteIP(), time.Now().Unix()+int64(life), policyId, false, groupId, setId, "")
|
||||||
|
|
||||||
http.Redirect(writer, request.WAFRaw(), originURL, http.StatusSeeOther)
|
http.Redirect(writer, request.WAFRaw(), originURL, http.StatusSeeOther)
|
||||||
|
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ func (this *Get302Validator) Run(request requests.Request, writer http.ResponseW
|
|||||||
life = 600 // 默认10分钟
|
life = 600 // 默认10分钟
|
||||||
}
|
}
|
||||||
setId := m.GetString("setId")
|
setId := m.GetString("setId")
|
||||||
SharedIPWhiteList.RecordIP("set:"+setId, m.GetString("scope"), request.WAFServerId(), request.WAFRemoteIP(), time.Now().Unix()+life, m.GetInt64("policyId"), false, m.GetInt64("groupId"), m.GetInt64("setId"))
|
SharedIPWhiteList.RecordIP("set:"+setId, m.GetString("scope"), request.WAFServerId(), request.WAFRemoteIP(), time.Now().Unix()+life, m.GetInt64("policyId"), false, m.GetInt64("groupId"), m.GetInt64("setId"), "")
|
||||||
|
|
||||||
// 返回原始URL
|
// 返回原始URL
|
||||||
var url = m.GetString("url")
|
var url = m.GetString("url")
|
||||||
|
|||||||
@@ -81,7 +81,8 @@ func (this *IPList) RecordIP(ipType string,
|
|||||||
policyId int64,
|
policyId int64,
|
||||||
useLocalFirewall bool,
|
useLocalFirewall bool,
|
||||||
groupId int64,
|
groupId int64,
|
||||||
setId int64) {
|
setId int64,
|
||||||
|
reason string) {
|
||||||
this.Add(ipType, scope, serverId, ip, expiresAt)
|
this.Add(ipType, scope, serverId, ip, expiresAt)
|
||||||
|
|
||||||
if this.listType == IPListTypeDeny {
|
if this.listType == IPListTypeDeny {
|
||||||
@@ -97,6 +98,7 @@ func (this *IPList) RecordIP(ipType string,
|
|||||||
sourceHTTPFirewallPolicyId: policyId,
|
sourceHTTPFirewallPolicyId: policyId,
|
||||||
sourceHTTPFirewallRuleGroupId: groupId,
|
sourceHTTPFirewallRuleGroupId: groupId,
|
||||||
sourceHTTPFirewallRuleSetId: setId,
|
sourceHTTPFirewallRuleSetId: setId,
|
||||||
|
reason: reason,
|
||||||
}:
|
}:
|
||||||
default:
|
default:
|
||||||
|
|
||||||
|
|||||||
@@ -15,14 +15,15 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type WAF struct {
|
type WAF struct {
|
||||||
Id int64 `yaml:"id" json:"id"`
|
Id int64 `yaml:"id" json:"id"`
|
||||||
IsOn bool `yaml:"isOn" json:"isOn"`
|
IsOn bool `yaml:"isOn" json:"isOn"`
|
||||||
Name string `yaml:"name" json:"name"`
|
Name string `yaml:"name" json:"name"`
|
||||||
Inbound []*RuleGroup `yaml:"inbound" json:"inbound"`
|
Inbound []*RuleGroup `yaml:"inbound" json:"inbound"`
|
||||||
Outbound []*RuleGroup `yaml:"outbound" json:"outbound"`
|
Outbound []*RuleGroup `yaml:"outbound" json:"outbound"`
|
||||||
CreatedVersion string `yaml:"createdVersion" json:"createdVersion"`
|
CreatedVersion string `yaml:"createdVersion" json:"createdVersion"`
|
||||||
Mode firewallconfigs.FirewallMode `yaml:"mode" json:"mode"`
|
Mode firewallconfigs.FirewallMode `yaml:"mode" json:"mode"`
|
||||||
UseLocalFirewall bool `yaml:"useLocalFirewall" json:"useLocalFirewall"`
|
UseLocalFirewall bool `yaml:"useLocalFirewall" json:"useLocalFirewall"`
|
||||||
|
SYNFlood *firewallconfigs.SYNFloodConfig `yaml:"synFlood" json:"synFlood"`
|
||||||
|
|
||||||
DefaultBlockAction *BlockAction
|
DefaultBlockAction *BlockAction
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user