github.com/TeaOSLab/EdgeNode@v1.3.8/internal/nodes/client_conn.go (about)

     1  // Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
     2  
     3  package nodes
     4  
     5  import (
     6  	"fmt"
     7  	"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
     8  	"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs"
     9  	"github.com/TeaOSLab/EdgeNode/internal/conns"
    10  	teaconst "github.com/TeaOSLab/EdgeNode/internal/const"
    11  	"github.com/TeaOSLab/EdgeNode/internal/iplibrary"
    12  	"github.com/TeaOSLab/EdgeNode/internal/stats"
    13  	"github.com/TeaOSLab/EdgeNode/internal/utils"
    14  	connutils "github.com/TeaOSLab/EdgeNode/internal/utils/conns"
    15  	"github.com/TeaOSLab/EdgeNode/internal/utils/counters"
    16  	"github.com/TeaOSLab/EdgeNode/internal/utils/fasttime"
    17  	"github.com/TeaOSLab/EdgeNode/internal/waf"
    18  	"github.com/iwind/TeaGo/Tea"
    19  	"github.com/iwind/TeaGo/types"
    20  	"net"
    21  	"os"
    22  	"strings"
    23  	"sync/atomic"
    24  	"time"
    25  )
    26  
    27  // ClientConn 客户端连接
    28  type ClientConn struct {
    29  	BaseClientConn
    30  
    31  	createdAt int64
    32  
    33  	isTLS   bool
    34  	isHTTP  bool
    35  	hasRead bool
    36  
    37  	isLO          bool // 是否为环路
    38  	isNoStat      bool // 是否不统计带宽
    39  	isInAllowList bool
    40  
    41  	hasResetSYNFlood bool
    42  
    43  	lastReadAt  int64
    44  	lastWriteAt int64
    45  	lastErr     error
    46  
    47  	readDeadlineTime int64
    48  	isShortReading   bool // reading header or tls handshake
    49  
    50  	isDebugging      bool
    51  	autoReadTimeout  bool
    52  	autoWriteTimeout bool
    53  }
    54  
    55  func NewClientConn(rawConn net.Conn, isHTTP bool, isTLS bool, isInAllowList bool) net.Conn {
    56  	// 是否为环路
    57  	var remoteAddr = rawConn.RemoteAddr().String()
    58  
    59  	var conn = &ClientConn{
    60  		BaseClientConn: BaseClientConn{rawConn: rawConn},
    61  		isTLS:          isTLS,
    62  		isHTTP:         isHTTP,
    63  		isLO:           strings.HasPrefix(remoteAddr, "127.0.0.1:") || strings.HasPrefix(remoteAddr, "[::1]:"),
    64  		isNoStat:       connutils.IsNoStatConn(remoteAddr),
    65  		isInAllowList:  isInAllowList,
    66  		createdAt:      fasttime.Now().Unix(),
    67  	}
    68  
    69  	if existsLnNodeIP(conn.RawIP()) {
    70  		conn.SetIsPersistent(true)
    71  	}
    72  
    73  	// 超时等设置
    74  	var globalServerConfig = sharedNodeConfig.GlobalServerConfig
    75  	if globalServerConfig != nil {
    76  		var performanceConfig = globalServerConfig.Performance
    77  		conn.isDebugging = performanceConfig.Debug
    78  		conn.autoReadTimeout = performanceConfig.AutoReadTimeout
    79  		conn.autoWriteTimeout = performanceConfig.AutoWriteTimeout
    80  	}
    81  
    82  	if isHTTP {
    83  		// TODO 可以在配置中设置此值
    84  		_ = conn.SetLinger(nodeconfigs.DefaultTCPLinger)
    85  	}
    86  
    87  	// 加入到Map
    88  	conns.SharedMap.Add(conn)
    89  
    90  	return conn
    91  }
    92  
    93  func (this *ClientConn) Read(b []byte) (n int, err error) {
    94  	if this.isDebugging {
    95  		this.lastReadAt = fasttime.Now().Unix()
    96  
    97  		defer func() {
    98  			if err != nil {
    99  				this.lastErr = fmt.Errorf("read error: %w", err)
   100  			} else {
   101  				this.lastErr = nil
   102  			}
   103  		}()
   104  	}
   105  
   106  	// 环路直接读取
   107  	if this.isLO {
   108  		n, err = this.rawConn.Read(b)
   109  		if n > 0 {
   110  			atomic.AddUint64(&teaconst.InTrafficBytes, uint64(n))
   111  		}
   112  		return
   113  	}
   114  
   115  	// 设置读超时时间
   116  	if this.isHTTP && !this.isPersistent && !this.isShortReading && this.autoReadTimeout {
   117  		this.setHTTPReadTimeout()
   118  	}
   119  
   120  	// 开始读取
   121  	n, err = this.rawConn.Read(b)
   122  	if n > 0 {
   123  		atomic.AddUint64(&teaconst.InTrafficBytes, uint64(n))
   124  		this.hasRead = true
   125  	}
   126  
   127  	// 检测是否为超时错误
   128  	var isTimeout = err != nil && os.IsTimeout(err)
   129  	var isHandshakeError = isTimeout && !this.hasRead
   130  
   131  	if err != nil {
   132  		_ = this.SetLinger(nodeconfigs.DefaultTCPLinger)
   133  	}
   134  
   135  	// 忽略白名单和局域网
   136  	if !this.isPersistent && this.isHTTP && !this.isInAllowList && !utils.IsLocalIP(this.RawIP()) {
   137  		// SYN Flood检测
   138  		if this.serverId == 0 || !this.hasResetSYNFlood {
   139  			var synFloodConfig = sharedNodeConfig.SYNFloodConfig()
   140  			if synFloodConfig != nil && synFloodConfig.IsOn {
   141  				if isHandshakeError {
   142  					this.increaseSYNFlood(synFloodConfig)
   143  				} else if err == nil && !this.hasResetSYNFlood {
   144  					this.hasResetSYNFlood = true
   145  					this.resetSYNFlood()
   146  				}
   147  			}
   148  		}
   149  	}
   150  
   151  	return
   152  }
   153  
   154  func (this *ClientConn) Write(b []byte) (n int, err error) {
   155  	if len(b) == 0 {
   156  		return 0, nil
   157  	}
   158  
   159  	if this.isDebugging {
   160  		this.lastWriteAt = fasttime.Now().Unix()
   161  
   162  		defer func() {
   163  			if err != nil {
   164  				this.lastErr = fmt.Errorf("write error: %w", err)
   165  			} else {
   166  				this.lastErr = nil
   167  			}
   168  		}()
   169  	}
   170  
   171  	// 设置写超时时间
   172  	if !this.isPersistent && this.autoWriteTimeout {
   173  		var timeoutSeconds = len(b) / 1024
   174  		if timeoutSeconds < 3 {
   175  			timeoutSeconds = 3
   176  		}
   177  		_ = this.rawConn.SetWriteDeadline(time.Now().Add(time.Duration(timeoutSeconds) * time.Second)) // TODO 时间可以设置
   178  	}
   179  
   180  	// 延长读超时时间
   181  	if this.isHTTP && !this.isPersistent && this.autoReadTimeout {
   182  		this.setHTTPReadTimeout()
   183  	}
   184  
   185  	// 开始写入
   186  	var before = time.Now()
   187  	n, err = this.rawConn.Write(b)
   188  	if n > 0 {
   189  		atomic.AddInt64(&this.totalSentBytes, int64(n))
   190  
   191  		// 统计当前服务带宽
   192  		if this.serverId > 0 {
   193  			// TODO 需要加入在serverId绑定之前的带宽
   194  			if !this.isNoStat || Tea.IsTesting() { // 环路不统计带宽,避免缓存预热等行为产生带宽
   195  				atomic.AddUint64(&teaconst.OutTrafficBytes, uint64(n))
   196  
   197  				var cost = time.Since(before).Seconds()
   198  				if cost > 1 {
   199  					stats.SharedBandwidthStatManager.AddBandwidth(this.userId, this.userPlanId, this.serverId, int64(float64(n)/cost), int64(n))
   200  				} else {
   201  					stats.SharedBandwidthStatManager.AddBandwidth(this.userId, this.userPlanId, this.serverId, int64(n), int64(n))
   202  				}
   203  			}
   204  		}
   205  	}
   206  
   207  	// 如果是写入超时,则立即关闭连接
   208  	if err != nil && os.IsTimeout(err) {
   209  		// TODO 考虑对多次慢连接的IP做出惩罚
   210  		conn, ok := this.rawConn.(LingerConn)
   211  		if ok {
   212  			_ = conn.SetLinger(0)
   213  		}
   214  
   215  		_ = this.Close()
   216  	}
   217  
   218  	return
   219  }
   220  
   221  func (this *ClientConn) Close() error {
   222  	this.isClosed = true
   223  
   224  	err := this.rawConn.Close()
   225  
   226  	// 单个服务并发数限制
   227  	// 不能加条件限制,因为服务配置随时有变化
   228  	sharedClientConnLimiter.Remove(this.rawConn.RemoteAddr().String())
   229  
   230  	// 从conn map中移除
   231  	conns.SharedMap.Remove(this)
   232  
   233  	return err
   234  }
   235  
   236  func (this *ClientConn) LocalAddr() net.Addr {
   237  	return this.rawConn.LocalAddr()
   238  }
   239  
   240  func (this *ClientConn) RemoteAddr() net.Addr {
   241  	return this.rawConn.RemoteAddr()
   242  }
   243  
   244  func (this *ClientConn) SetDeadline(t time.Time) error {
   245  	return this.rawConn.SetDeadline(t)
   246  }
   247  
   248  func (this *ClientConn) SetReadDeadline(t time.Time) error {
   249  	// 如果开启了HTTP自动读超时选项,则自动控制超时时间
   250  	if this.isHTTP && !this.isPersistent && this.autoReadTimeout {
   251  		this.isShortReading = false
   252  
   253  		var unixTime = t.Unix()
   254  		if unixTime < 10 {
   255  			return nil
   256  		}
   257  		if unixTime == this.readDeadlineTime {
   258  			return nil
   259  		}
   260  		this.readDeadlineTime = unixTime
   261  		var seconds = -time.Since(t)
   262  		if seconds <= 0 || seconds > HTTPIdleTimeout {
   263  			return nil
   264  		}
   265  		if seconds < HTTPIdleTimeout-1*time.Second {
   266  			this.isShortReading = true
   267  		}
   268  	}
   269  	return this.rawConn.SetReadDeadline(t)
   270  }
   271  
   272  func (this *ClientConn) SetWriteDeadline(t time.Time) error {
   273  	return this.rawConn.SetWriteDeadline(t)
   274  }
   275  
   276  func (this *ClientConn) CreatedAt() int64 {
   277  	return this.createdAt
   278  }
   279  
   280  func (this *ClientConn) LastReadAt() int64 {
   281  	return this.lastReadAt
   282  }
   283  
   284  func (this *ClientConn) LastWriteAt() int64 {
   285  	return this.lastWriteAt
   286  }
   287  
   288  func (this *ClientConn) LastErr() error {
   289  	return this.lastErr
   290  }
   291  
   292  func (this *ClientConn) resetSYNFlood() {
   293  	counters.SharedCounter.ResetKey("SYN_FLOOD:" + this.RawIP())
   294  }
   295  
   296  func (this *ClientConn) increaseSYNFlood(synFloodConfig *firewallconfigs.SYNFloodConfig) {
   297  	var ip = this.RawIP()
   298  	if len(ip) > 0 && !iplibrary.IsInWhiteList(ip) && (!synFloodConfig.IgnoreLocal || !utils.IsLocalIP(ip)) {
   299  		var result = counters.SharedCounter.IncreaseKey("SYN_FLOOD:"+ip, 60)
   300  		var minAttempts = synFloodConfig.MinAttempts
   301  		if minAttempts < 5 {
   302  			minAttempts = 5
   303  		}
   304  		if !this.isTLS {
   305  			// 非TLS,设置为两倍,防止误封
   306  			minAttempts = 2 * minAttempts
   307  		}
   308  		if result >= types.Uint32(minAttempts) {
   309  			var timeout = synFloodConfig.TimeoutSeconds
   310  			if timeout <= 0 {
   311  				timeout = 600
   312  			}
   313  
   314  			// 关闭当前连接
   315  			_ = this.SetLinger(0)
   316  			_ = this.Close()
   317  
   318  			waf.SharedIPBlackList.RecordIP(waf.IPTypeAll, firewallconfigs.FirewallScopeGlobal, 0, ip, fasttime.Now().Unix()+int64(timeout), 0, true, 0, 0, "疑似SYN Flood攻击,当前1分钟"+types.String(result)+"次空连接")
   319  		}
   320  	}
   321  }
   322  
   323  // 设置读超时时间
   324  func (this *ClientConn) setHTTPReadTimeout() {
   325  	_ = this.SetReadDeadline(time.Now().Add(HTTPIdleTimeout))
   326  }