github.com/TeaOSLab/EdgeNode@v1.3.8/internal/stats/http_request_stat_manager.go (about)

     1  package stats
     2  
     3  import (
     4  	iplib "github.com/TeaOSLab/EdgeCommon/pkg/iplibrary"
     5  	"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
     6  	"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
     7  	"github.com/TeaOSLab/EdgeNode/internal/events"
     8  	"github.com/TeaOSLab/EdgeNode/internal/goman"
     9  	"github.com/TeaOSLab/EdgeNode/internal/monitor"
    10  	"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
    11  	"github.com/TeaOSLab/EdgeNode/internal/rpc"
    12  	"github.com/TeaOSLab/EdgeNode/internal/trackers"
    13  	"github.com/TeaOSLab/EdgeNode/internal/utils"
    14  	"github.com/TeaOSLab/EdgeNode/internal/utils/agents"
    15  	"github.com/TeaOSLab/EdgeNode/internal/waf"
    16  	"github.com/iwind/TeaGo/Tea"
    17  	"github.com/iwind/TeaGo/maps"
    18  	"github.com/iwind/TeaGo/types"
    19  	timeutil "github.com/iwind/TeaGo/utils/time"
    20  	"sort"
    21  	"strconv"
    22  	"strings"
    23  	"sync"
    24  	"time"
    25  )
    26  
    27  type StatItem struct {
    28  	Bytes               int64
    29  	CountRequests       int64
    30  	CountAttackRequests int64
    31  	AttackBytes         int64
    32  }
    33  
    34  var SharedHTTPRequestStatManager = NewHTTPRequestStatManager()
    35  
    36  // HTTPRequestStatManager HTTP请求相关的统计
    37  // 这里的统计是一个辅助统计,注意不要因为统计而影响服务工作性能
    38  type HTTPRequestStatManager struct {
    39  	ipChan                chan string
    40  	userAgentChan         chan string
    41  	firewallRuleGroupChan chan string
    42  
    43  	cityMap     map[string]*StatItem // serverId@country@province@city => *StatItem ,不需要加锁,因为我们是使用channel依次执行的
    44  	providerMap map[string]int64     // serverId@provider => count
    45  	systemMap   map[string]int64     // serverId@system@version => count
    46  	browserMap  map[string]int64     // serverId@browser@version => count
    47  
    48  	dailyFirewallRuleGroupMap map[string]int64 // serverId@firewallRuleGroupId@action => count
    49  
    50  	serverCityCountMap    map[string]int16 // serverIdString => count cities
    51  	serverSystemCountMap  map[string]int16 // serverIdString => count systems
    52  	serverBrowserCountMap map[string]int16 // serverIdString => count browsers
    53  
    54  	totalAttackRequests int64
    55  
    56  	locker sync.Mutex
    57  
    58  	monitorTicker *time.Ticker
    59  	uploadTicker  *time.Ticker
    60  }
    61  
    62  // NewHTTPRequestStatManager 获取新对象
    63  func NewHTTPRequestStatManager() *HTTPRequestStatManager {
    64  	return &HTTPRequestStatManager{
    65  		ipChan:                    make(chan string, 10_000), // TODO 将来可以配置容量
    66  		userAgentChan:             make(chan string, 10_000), // TODO 将来可以配置容量
    67  		firewallRuleGroupChan:     make(chan string, 10_000), // TODO 将来可以配置容量
    68  		cityMap:                   map[string]*StatItem{},
    69  		providerMap:               map[string]int64{},
    70  		systemMap:                 map[string]int64{},
    71  		browserMap:                map[string]int64{},
    72  		dailyFirewallRuleGroupMap: map[string]int64{},
    73  
    74  		serverCityCountMap:    map[string]int16{},
    75  		serverSystemCountMap:  map[string]int16{},
    76  		serverBrowserCountMap: map[string]int16{},
    77  	}
    78  }
    79  
    80  // Start 启动
    81  func (this *HTTPRequestStatManager) Start() {
    82  	// 上传请求总数
    83  	this.monitorTicker = time.NewTicker(1 * time.Minute)
    84  	events.OnKey(events.EventQuit, this, func() {
    85  		this.monitorTicker.Stop()
    86  	})
    87  	goman.New(func() {
    88  		for range this.monitorTicker.C {
    89  			if this.totalAttackRequests > 0 {
    90  				monitor.SharedValueQueue.Add(nodeconfigs.NodeValueItemAttackRequests, maps.Map{"total": this.totalAttackRequests})
    91  				this.totalAttackRequests = 0
    92  			}
    93  		}
    94  	})
    95  
    96  	this.uploadTicker = time.NewTicker(30 * time.Minute)
    97  	if Tea.IsTesting() {
    98  		this.uploadTicker = time.NewTicker(10 * time.Second) // 在测试环境下缩短Ticker时间,以方便我们调试
    99  	}
   100  	remotelogs.Println("HTTP_REQUEST_STAT_MANAGER", "start ...")
   101  	events.OnKey(events.EventQuit, this, func() {
   102  		remotelogs.Println("HTTP_REQUEST_STAT_MANAGER", "quit")
   103  		this.uploadTicker.Stop()
   104  	})
   105  
   106  	// 上传Ticker
   107  	goman.New(func() {
   108  		for range this.uploadTicker.C {
   109  			var tr = trackers.Begin("UPLOAD_REQUEST_STATS")
   110  			err := this.Upload()
   111  			tr.End()
   112  			if err != nil {
   113  				if !rpc.IsConnError(err) {
   114  					remotelogs.Error("HTTP_REQUEST_STAT_MANAGER", "upload failed: "+err.Error())
   115  				} else {
   116  					remotelogs.Warn("HTTP_REQUEST_STAT_MANAGER", "upload failed: "+err.Error())
   117  				}
   118  			}
   119  
   120  		}
   121  	})
   122  
   123  	// 分析Ticker
   124  	for {
   125  		err := this.Loop()
   126  		if err != nil {
   127  			if rpc.IsConnError(err) {
   128  				remotelogs.Warn("HTTP_REQUEST_STAT_MANAGER", err.Error())
   129  			} else {
   130  				remotelogs.Error("HTTP_REQUEST_STAT_MANAGER", err.Error())
   131  			}
   132  		}
   133  	}
   134  }
   135  
   136  // AddRemoteAddr 添加客户端地址
   137  func (this *HTTPRequestStatManager) AddRemoteAddr(serverId int64, remoteAddr string, bytes int64, isAttack bool) {
   138  	if len(remoteAddr) == 0 {
   139  		return
   140  	}
   141  	if remoteAddr[0] == '[' { // 排除IPv6
   142  		return
   143  	}
   144  	var index = strings.Index(remoteAddr, ":")
   145  	var ip string
   146  	if index < 0 {
   147  		ip = remoteAddr
   148  	} else {
   149  		ip = remoteAddr[:index]
   150  	}
   151  	if len(ip) > 0 {
   152  		var s string
   153  		if isAttack {
   154  			s = strconv.FormatInt(serverId, 10) + "@" + ip + "@" + types.String(bytes) + "@1"
   155  		} else {
   156  			s = strconv.FormatInt(serverId, 10) + "@" + ip + "@" + types.String(bytes) + "@0"
   157  		}
   158  		select {
   159  		case this.ipChan <- s:
   160  		default:
   161  			// 超出容量我们就丢弃
   162  		}
   163  	}
   164  }
   165  
   166  // AddUserAgent 添加UserAgent
   167  func (this *HTTPRequestStatManager) AddUserAgent(serverId int64, userAgent string, ip string) {
   168  	if len(userAgent) == 0 || strings.ContainsRune(userAgent, '@') /** 非常重要,防止后面组合字符串时出现异常 **/ {
   169  		return
   170  	}
   171  
   172  	// 是否包含一些知名Agent
   173  	if len(userAgent) > 0 && len(ip) > 0 && agents.IsAgentFromUserAgent(userAgent) {
   174  		agents.SharedQueue.Push(ip)
   175  	}
   176  
   177  	select {
   178  	case this.userAgentChan <- strconv.FormatInt(serverId, 10) + "@" + userAgent:
   179  	default:
   180  		// 超出容量我们就丢弃
   181  	}
   182  }
   183  
   184  // AddFirewallRuleGroupId 添加防火墙拦截动作
   185  func (this *HTTPRequestStatManager) AddFirewallRuleGroupId(serverId int64, firewallRuleGroupId int64, actions []*waf.ActionConfig) {
   186  	if firewallRuleGroupId <= 0 {
   187  		return
   188  	}
   189  
   190  	this.totalAttackRequests++
   191  
   192  	for _, action := range actions {
   193  		select {
   194  		case this.firewallRuleGroupChan <- strconv.FormatInt(serverId, 10) + "@" + strconv.FormatInt(firewallRuleGroupId, 10) + "@" + action.Code:
   195  		default:
   196  			// 超出容量我们就丢弃
   197  		}
   198  	}
   199  }
   200  
   201  // Loop 单个循环
   202  func (this *HTTPRequestStatManager) Loop() error {
   203  	select {
   204  	case ipString := <-this.ipChan:
   205  		// serverId@ip@bytes@isAttack
   206  		var pieces = strings.Split(ipString, "@")
   207  		if len(pieces) < 4 {
   208  			return nil
   209  		}
   210  		var serverIdString = pieces[0]
   211  		var ip = pieces[1]
   212  
   213  		var result = iplib.LookupIP(ip)
   214  		if result != nil && result.IsOk() {
   215  			this.locker.Lock()
   216  			if result.CountryId() > 0 {
   217  				var key = serverIdString + "@" + types.String(result.CountryId()) + "@" + types.String(result.ProvinceId()) + "@" + types.String(result.CityId())
   218  				stat, ok := this.cityMap[key]
   219  				if !ok {
   220  					// 检查数量
   221  					if this.serverCityCountMap[serverIdString] > 128 { // 限制单个服务的城市数量,防止数量过多
   222  						this.locker.Unlock()
   223  						return nil
   224  					}
   225  					this.serverCityCountMap[serverIdString]++ // 需要放在限制之后,因为使用的是int16
   226  
   227  					stat = &StatItem{}
   228  					this.cityMap[key] = stat
   229  				}
   230  				stat.Bytes += types.Int64(pieces[2])
   231  				stat.CountRequests++
   232  				if types.Int8(pieces[3]) == 1 {
   233  					stat.AttackBytes += types.Int64(pieces[2])
   234  					stat.CountAttackRequests++
   235  				}
   236  			}
   237  
   238  			if result.ProviderId() > 0 {
   239  				this.providerMap[serverIdString+"@"+types.String(result.ProviderId())]++
   240  			} else if utils.IsLocalIP(ip) { // 局域网IP
   241  				this.providerMap[serverIdString+"@258"]++
   242  			}
   243  			this.locker.Unlock()
   244  		}
   245  	case userAgentString := <-this.userAgentChan:
   246  		var atIndex = strings.Index(userAgentString, "@")
   247  		if atIndex < 0 {
   248  			return nil
   249  		}
   250  		var serverIdString = userAgentString[:atIndex]
   251  		var userAgent = userAgentString[atIndex+1:]
   252  
   253  		var result = SharedUserAgentParser.Parse(userAgent)
   254  		var osInfo = result.OS
   255  		if len(osInfo.Name) > 0 {
   256  			dotIndex := strings.Index(osInfo.Version, ".")
   257  			if dotIndex > -1 {
   258  				osInfo.Version = osInfo.Version[:dotIndex]
   259  			}
   260  			this.locker.Lock()
   261  
   262  			var systemKey = serverIdString + "@" + osInfo.Name + "@" + osInfo.Version
   263  			_, ok := this.systemMap[systemKey]
   264  			if !ok {
   265  				if this.serverSystemCountMap[serverIdString] < 128 { // 限制最大数据,防止攻击
   266  					this.serverSystemCountMap[serverIdString]++
   267  					ok = true
   268  				}
   269  			}
   270  			if ok {
   271  				this.systemMap[systemKey]++
   272  			}
   273  			this.locker.Unlock()
   274  		}
   275  
   276  		var browser, browserVersion = result.BrowserName, result.BrowserVersion
   277  		if len(browser) > 0 {
   278  			dotIndex := strings.Index(browserVersion, ".")
   279  			if dotIndex > -1 {
   280  				browserVersion = browserVersion[:dotIndex]
   281  			}
   282  			this.locker.Lock()
   283  
   284  			var browserKey = serverIdString + "@" + browser + "@" + browserVersion
   285  			_, ok := this.browserMap[browserKey]
   286  			if !ok {
   287  				if this.serverBrowserCountMap[serverIdString] < 256 { // 限制最大数据,防止攻击
   288  					this.serverBrowserCountMap[serverIdString]++
   289  					ok = true
   290  				}
   291  			}
   292  			if ok {
   293  				this.browserMap[browserKey]++
   294  			}
   295  			this.locker.Unlock()
   296  		}
   297  	case firewallRuleGroupString := <-this.firewallRuleGroupChan:
   298  		this.locker.Lock()
   299  		this.dailyFirewallRuleGroupMap[firewallRuleGroupString]++
   300  		this.locker.Unlock()
   301  	}
   302  
   303  	return nil
   304  }
   305  
   306  // Upload 上传数据
   307  func (this *HTTPRequestStatManager) Upload() error {
   308  	// 上传统计数据
   309  	rpcClient, err := rpc.SharedRPC()
   310  	if err != nil {
   311  		return err
   312  	}
   313  
   314  	// 拷贝数据
   315  	this.locker.Lock()
   316  	var cityMap = this.cityMap
   317  	var providerMap = this.providerMap
   318  	var systemMap = this.systemMap
   319  	var browserMap = this.browserMap
   320  	var dailyFirewallRuleGroupMap = this.dailyFirewallRuleGroupMap
   321  
   322  	this.cityMap = map[string]*StatItem{}
   323  	this.providerMap = map[string]int64{}
   324  	this.systemMap = map[string]int64{}
   325  	this.browserMap = map[string]int64{}
   326  	this.dailyFirewallRuleGroupMap = map[string]int64{}
   327  
   328  	this.serverCityCountMap = map[string]int16{}
   329  	this.serverSystemCountMap = map[string]int16{}
   330  	this.serverBrowserCountMap = map[string]int16{}
   331  
   332  	this.locker.Unlock()
   333  
   334  	// 上传限制
   335  	var maxCities int16 = 32
   336  	var maxProviders int16 = 32
   337  	var maxSystems int16 = 64
   338  	var maxBrowsers int16 = 64
   339  	nodeConfig, _ := nodeconfigs.SharedNodeConfig()
   340  	if nodeConfig != nil {
   341  		var serverConfig = nodeConfig.GlobalServerConfig // 复制是为了防止在中途修改
   342  		if serverConfig != nil {
   343  			var uploadConfig = serverConfig.Stat.Upload
   344  			if uploadConfig.MaxCities > 0 {
   345  				maxCities = uploadConfig.MaxCities
   346  			}
   347  			if uploadConfig.MaxProviders > 0 {
   348  				maxProviders = uploadConfig.MaxProviders
   349  			}
   350  			if uploadConfig.MaxSystems > 0 {
   351  				maxSystems = uploadConfig.MaxSystems
   352  			}
   353  			if uploadConfig.MaxBrowsers > 0 {
   354  				maxBrowsers = uploadConfig.MaxBrowsers
   355  			}
   356  		}
   357  	}
   358  
   359  	var pbCities = []*pb.UploadServerHTTPRequestStatRequest_RegionCity{}
   360  	var pbProviders = []*pb.UploadServerHTTPRequestStatRequest_RegionProvider{}
   361  	var pbSystems = []*pb.UploadServerHTTPRequestStatRequest_System{}
   362  	var pbBrowsers = []*pb.UploadServerHTTPRequestStatRequest_Browser{}
   363  
   364  	// 城市
   365  	for k, stat := range cityMap {
   366  		var pieces = strings.SplitN(k, "@", 4)
   367  		var serverId = types.Int64(pieces[0])
   368  		pbCities = append(pbCities, &pb.UploadServerHTTPRequestStatRequest_RegionCity{
   369  			ServerId:            serverId,
   370  			CountryId:           types.Int64(pieces[1]),
   371  			ProvinceId:          types.Int64(pieces[2]),
   372  			CityId:              types.Int64(pieces[3]),
   373  			CountRequests:       stat.CountRequests,
   374  			CountAttackRequests: stat.CountAttackRequests,
   375  			Bytes:               stat.Bytes,
   376  			AttackBytes:         stat.AttackBytes,
   377  		})
   378  	}
   379  	if len(cityMap) > int(maxCities) {
   380  		var newPBCities = []*pb.UploadServerHTTPRequestStatRequest_RegionCity{}
   381  		sort.Slice(pbCities, func(i, j int) bool {
   382  			return pbCities[i].CountRequests > pbCities[j].CountRequests
   383  		})
   384  		var serverCountMap = map[int64]int16{} // serverId => count
   385  		for _, city := range pbCities {
   386  			serverCountMap[city.ServerId]++
   387  			if serverCountMap[city.ServerId] > maxCities {
   388  				continue
   389  			}
   390  			newPBCities = append(newPBCities, city)
   391  		}
   392  		if len(pbCities) != len(newPBCities) {
   393  			pbCities = newPBCities
   394  		}
   395  	}
   396  
   397  	// 运营商
   398  	for k, count := range providerMap {
   399  		var pieces = strings.SplitN(k, "@", 2)
   400  		var serverId = types.Int64(pieces[0])
   401  		pbProviders = append(pbProviders, &pb.UploadServerHTTPRequestStatRequest_RegionProvider{
   402  			ServerId:   serverId,
   403  			ProviderId: types.Int64(pieces[1]),
   404  			Count:      count,
   405  		})
   406  	}
   407  	if len(providerMap) > int(maxProviders) {
   408  		var newPBProviders = []*pb.UploadServerHTTPRequestStatRequest_RegionProvider{}
   409  		sort.Slice(pbProviders, func(i, j int) bool {
   410  			return pbProviders[i].Count > pbProviders[j].Count
   411  		})
   412  		var serverCountMap = map[int64]int16{}
   413  		for _, provider := range pbProviders {
   414  			serverCountMap[provider.ServerId]++
   415  			if serverCountMap[provider.ServerId] > maxProviders {
   416  				continue
   417  			}
   418  			newPBProviders = append(newPBProviders, provider)
   419  		}
   420  		if len(pbProviders) != len(newPBProviders) {
   421  			pbProviders = newPBProviders
   422  		}
   423  	}
   424  
   425  	// 操作系统
   426  	for k, count := range systemMap {
   427  		var pieces = strings.SplitN(k, "@", 3)
   428  		var serverId = types.Int64(pieces[0])
   429  		pbSystems = append(pbSystems, &pb.UploadServerHTTPRequestStatRequest_System{
   430  			ServerId: serverId,
   431  			Name:     pieces[1],
   432  			Version:  pieces[2],
   433  			Count:    count,
   434  		})
   435  	}
   436  	if len(systemMap) > int(maxSystems) {
   437  		var newPBSystems = []*pb.UploadServerHTTPRequestStatRequest_System{}
   438  		sort.Slice(pbSystems, func(i, j int) bool {
   439  			return pbSystems[i].Count > pbSystems[j].Count
   440  		})
   441  		var serverCountMap = map[int64]int16{}
   442  		for _, system := range pbSystems {
   443  			serverCountMap[system.ServerId]++
   444  			if serverCountMap[system.ServerId] > maxSystems {
   445  				continue
   446  			}
   447  			newPBSystems = append(newPBSystems, system)
   448  		}
   449  		if len(pbSystems) != len(newPBSystems) {
   450  			pbSystems = newPBSystems
   451  		}
   452  	}
   453  
   454  	// 浏览器
   455  	for k, count := range browserMap {
   456  		var pieces = strings.SplitN(k, "@", 3)
   457  		var serverId = types.Int64(pieces[0])
   458  		pbBrowsers = append(pbBrowsers, &pb.UploadServerHTTPRequestStatRequest_Browser{
   459  			ServerId: serverId,
   460  			Name:     pieces[1],
   461  			Version:  pieces[2],
   462  			Count:    count,
   463  		})
   464  	}
   465  	if len(browserMap) > int(maxBrowsers) {
   466  		var newPBBrowsers = []*pb.UploadServerHTTPRequestStatRequest_Browser{}
   467  		sort.Slice(pbBrowsers, func(i, j int) bool {
   468  			return pbBrowsers[i].Count > pbBrowsers[j].Count
   469  		})
   470  		var serverCountMap = map[int64]int16{}
   471  		for _, browser := range pbBrowsers {
   472  			serverCountMap[browser.ServerId]++
   473  			if serverCountMap[browser.ServerId] > maxBrowsers {
   474  				continue
   475  			}
   476  			newPBBrowsers = append(newPBBrowsers, browser)
   477  		}
   478  		if len(pbBrowsers) != len(newPBBrowsers) {
   479  			pbBrowsers = newPBBrowsers
   480  		}
   481  	}
   482  
   483  	// 防火墙相关
   484  	var pbFirewallRuleGroups = []*pb.UploadServerHTTPRequestStatRequest_HTTPFirewallRuleGroup{}
   485  	for k, count := range dailyFirewallRuleGroupMap {
   486  		var pieces = strings.SplitN(k, "@", 3)
   487  		pbFirewallRuleGroups = append(pbFirewallRuleGroups, &pb.UploadServerHTTPRequestStatRequest_HTTPFirewallRuleGroup{
   488  			ServerId:                types.Int64(pieces[0]),
   489  			HttpFirewallRuleGroupId: types.Int64(pieces[1]),
   490  			Action:                  pieces[2],
   491  			Count:                   count,
   492  		})
   493  	}
   494  
   495  	// 检查是否有数据
   496  	if len(pbCities) == 0 &&
   497  		len(pbProviders) == 0 &&
   498  		len(pbSystems) == 0 &&
   499  		len(pbBrowsers) == 0 &&
   500  		len(pbFirewallRuleGroups) == 0 {
   501  		return nil
   502  	}
   503  
   504  	// 上传数据
   505  	_, err = rpcClient.ServerRPC.UploadServerHTTPRequestStat(rpcClient.Context(), &pb.UploadServerHTTPRequestStatRequest{
   506  		Month:                  timeutil.Format("Ym"),
   507  		Day:                    timeutil.Format("Ymd"),
   508  		RegionCities:           pbCities,
   509  		RegionProviders:        pbProviders,
   510  		Systems:                pbSystems,
   511  		Browsers:               pbBrowsers,
   512  		HttpFirewallRuleGroups: pbFirewallRuleGroups,
   513  	})
   514  	if err != nil {
   515  		// 是否包含了invalid UTF-8
   516  		if strings.Contains(err.Error(), "string field contains invalid UTF-8") {
   517  			for _, system := range pbSystems {
   518  				system.Name = utils.ToValidUTF8string(system.Name)
   519  				system.Version = utils.ToValidUTF8string(system.Version)
   520  			}
   521  			for _, browser := range pbBrowsers {
   522  				browser.Name = utils.ToValidUTF8string(browser.Name)
   523  				browser.Version = utils.ToValidUTF8string(browser.Version)
   524  			}
   525  
   526  			// 再次尝试
   527  			_, err = rpcClient.ServerRPC.UploadServerHTTPRequestStat(rpcClient.Context(), &pb.UploadServerHTTPRequestStatRequest{
   528  				Month:                  timeutil.Format("Ym"),
   529  				Day:                    timeutil.Format("Ymd"),
   530  				RegionCities:           pbCities,
   531  				RegionProviders:        pbProviders,
   532  				Systems:                pbSystems,
   533  				Browsers:               pbBrowsers,
   534  				HttpFirewallRuleGroups: pbFirewallRuleGroups,
   535  			})
   536  			if err != nil {
   537  				return err
   538  			}
   539  		}
   540  	}
   541  
   542  	return nil
   543  }