123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330 |
- // Copyright 2023 The frp Authors
- //
- // Licensed under the Apache License, Version 2.0 (the "License");
- // you may not use this file except in compliance with the License.
- // You may obtain a copy of the License at
- //
- // http://www.apache.org/licenses/LICENSE-2.0
- //
- // Unless required by applicable law or agreed to in writing, software
- // distributed under the License is distributed on an "AS IS" BASIS,
- // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- // See the License for the specific language governing permissions and
- // limitations under the License.
- package nathole
- import (
- "cmp"
- "slices"
- "sync"
- "time"
- "github.com/samber/lo"
- )
- var (
- // mode 0, both EasyNAT, PublicNetwork is always receiver
- // sender | receiver, ttl 7
- // receiver, ttl 7 | sender
- // sender | receiver, ttl 4
- // receiver, ttl 4 | sender
- // sender | receiver
- // receiver | sender
- // sender, sendDelayMs 5000 | receiver
- // sender, sendDelayMs 10000 | receiver
- // receiver | sender, sendDelayMs 5000
- // receiver | sender, sendDelayMs 10000
- mode0Behaviors = []lo.Tuple2[RecommandBehavior, RecommandBehavior]{
- lo.T2(RecommandBehavior{Role: DetectRoleSender}, RecommandBehavior{Role: DetectRoleReceiver, TTL: 7}),
- lo.T2(RecommandBehavior{Role: DetectRoleReceiver, TTL: 7}, RecommandBehavior{Role: DetectRoleSender}),
- lo.T2(RecommandBehavior{Role: DetectRoleSender}, RecommandBehavior{Role: DetectRoleReceiver, TTL: 4}),
- lo.T2(RecommandBehavior{Role: DetectRoleReceiver, TTL: 4}, RecommandBehavior{Role: DetectRoleSender}),
- lo.T2(RecommandBehavior{Role: DetectRoleSender}, RecommandBehavior{Role: DetectRoleReceiver}),
- lo.T2(RecommandBehavior{Role: DetectRoleReceiver}, RecommandBehavior{Role: DetectRoleSender}),
- lo.T2(RecommandBehavior{Role: DetectRoleSender, SendDelayMs: 5000}, RecommandBehavior{Role: DetectRoleReceiver}),
- lo.T2(RecommandBehavior{Role: DetectRoleSender, SendDelayMs: 10000}, RecommandBehavior{Role: DetectRoleReceiver}),
- lo.T2(RecommandBehavior{Role: DetectRoleReceiver}, RecommandBehavior{Role: DetectRoleSender, SendDelayMs: 5000}),
- lo.T2(RecommandBehavior{Role: DetectRoleReceiver}, RecommandBehavior{Role: DetectRoleSender, SendDelayMs: 10000}),
- }
- // mode 1, HardNAT is sender, EasyNAT is receiver, port changes is regular
- // sender | receiver, ttl 7, portsRangeNumber max 10
- // sender, sendDelayMs 2000 | receiver, ttl 7, portsRangeNumber max 10
- // sender | receiver, ttl 4, portsRangeNumber max 10
- // sender, sendDelayMs 2000 | receiver, ttl 4, portsRangeNumber max 10
- // sender | receiver, portsRangeNumber max 10
- // sender, sendDelayMs 2000 | receiver, portsRangeNumber max 10
- mode1Behaviors = []lo.Tuple2[RecommandBehavior, RecommandBehavior]{
- lo.T2(RecommandBehavior{Role: DetectRoleSender}, RecommandBehavior{Role: DetectRoleReceiver, TTL: 7, PortsRangeNumber: 10}),
- lo.T2(RecommandBehavior{Role: DetectRoleSender, SendDelayMs: 2000}, RecommandBehavior{Role: DetectRoleReceiver, TTL: 7, PortsRangeNumber: 10}),
- lo.T2(RecommandBehavior{Role: DetectRoleSender}, RecommandBehavior{Role: DetectRoleReceiver, TTL: 4, PortsRangeNumber: 10}),
- lo.T2(RecommandBehavior{Role: DetectRoleSender, SendDelayMs: 2000}, RecommandBehavior{Role: DetectRoleReceiver, TTL: 4, PortsRangeNumber: 10}),
- lo.T2(RecommandBehavior{Role: DetectRoleSender}, RecommandBehavior{Role: DetectRoleReceiver, PortsRangeNumber: 10}),
- lo.T2(RecommandBehavior{Role: DetectRoleSender, SendDelayMs: 2000}, RecommandBehavior{Role: DetectRoleReceiver, PortsRangeNumber: 10}),
- }
- // mode 2, HardNAT is receiver, EasyNAT is sender
- // sender, portsRandomNumber 1000, sendDelayMs 3000 | receiver, listen 256 ports, ttl 7
- // sender, portsRandomNumber 1000, sendDelayMs 3000 | receiver, listen 256 ports, ttl 4
- // sender, portsRandomNumber 1000, sendDelayMs 3000 | receiver, listen 256 ports
- mode2Behaviors = []lo.Tuple2[RecommandBehavior, RecommandBehavior]{
- lo.T2(
- RecommandBehavior{Role: DetectRoleSender, PortsRandomNumber: 1000, SendDelayMs: 3000},
- RecommandBehavior{Role: DetectRoleReceiver, ListenRandomPorts: 256, TTL: 7},
- ),
- lo.T2(
- RecommandBehavior{Role: DetectRoleSender, PortsRandomNumber: 1000, SendDelayMs: 3000},
- RecommandBehavior{Role: DetectRoleReceiver, ListenRandomPorts: 256, TTL: 4},
- ),
- lo.T2(
- RecommandBehavior{Role: DetectRoleSender, PortsRandomNumber: 1000, SendDelayMs: 3000},
- RecommandBehavior{Role: DetectRoleReceiver, ListenRandomPorts: 256},
- ),
- }
- // mode 3, For HardNAT & HardNAT, both changes in the ports are regular
- // sender, portsRangeNumber 10 | receiver, ttl 7, portsRangeNumber 10
- // sender, portsRangeNumber 10 | receiver, ttl 4, portsRangeNumber 10
- // sender, portsRangeNumber 10 | receiver, portsRangeNumber 10
- // receiver, ttl 7, portsRangeNumber 10 | sender, portsRangeNumber 10
- // receiver, ttl 4, portsRangeNumber 10 | sender, portsRangeNumber 10
- // receiver, portsRangeNumber 10 | sender, portsRangeNumber 10
- mode3Behaviors = []lo.Tuple2[RecommandBehavior, RecommandBehavior]{
- lo.T2(RecommandBehavior{Role: DetectRoleSender, PortsRangeNumber: 10}, RecommandBehavior{Role: DetectRoleReceiver, TTL: 7, PortsRangeNumber: 10}),
- lo.T2(RecommandBehavior{Role: DetectRoleSender, PortsRangeNumber: 10}, RecommandBehavior{Role: DetectRoleReceiver, TTL: 4, PortsRangeNumber: 10}),
- lo.T2(RecommandBehavior{Role: DetectRoleSender, PortsRangeNumber: 10}, RecommandBehavior{Role: DetectRoleReceiver, PortsRangeNumber: 10}),
- lo.T2(RecommandBehavior{Role: DetectRoleReceiver, TTL: 7, PortsRangeNumber: 10}, RecommandBehavior{Role: DetectRoleSender, PortsRangeNumber: 10}),
- lo.T2(RecommandBehavior{Role: DetectRoleReceiver, TTL: 4, PortsRangeNumber: 10}, RecommandBehavior{Role: DetectRoleSender, PortsRangeNumber: 10}),
- lo.T2(RecommandBehavior{Role: DetectRoleReceiver, PortsRangeNumber: 10}, RecommandBehavior{Role: DetectRoleSender, PortsRangeNumber: 10}),
- }
- // mode 4, Regular ports changes are usually the sender.
- // sender, portsRandomNumber 1000, sendDelayMs: 2000 | receiver, listen 256 ports, ttl 7, portsRangeNumber 2
- // sender, portsRandomNumber 1000, sendDelayMs: 2000 | receiver, listen 256 ports, ttl 4, portsRangeNumber 2
- // sender, portsRandomNumber 1000, SendDelayMs: 2000 | receiver, listen 256 ports, portsRangeNumber 2
- mode4Behaviors = []lo.Tuple2[RecommandBehavior, RecommandBehavior]{
- lo.T2(
- RecommandBehavior{Role: DetectRoleSender, PortsRandomNumber: 1000, SendDelayMs: 3000},
- RecommandBehavior{Role: DetectRoleReceiver, ListenRandomPorts: 256, TTL: 7, PortsRangeNumber: 2},
- ),
- lo.T2(
- RecommandBehavior{Role: DetectRoleSender, PortsRandomNumber: 1000, SendDelayMs: 3000},
- RecommandBehavior{Role: DetectRoleReceiver, ListenRandomPorts: 256, TTL: 4, PortsRangeNumber: 2},
- ),
- lo.T2(
- RecommandBehavior{Role: DetectRoleSender, PortsRandomNumber: 1000, SendDelayMs: 3000},
- RecommandBehavior{Role: DetectRoleReceiver, ListenRandomPorts: 256, PortsRangeNumber: 2},
- ),
- }
- )
- func getBehaviorByMode(mode int) []lo.Tuple2[RecommandBehavior, RecommandBehavior] {
- switch mode {
- case 0:
- return mode0Behaviors
- case 1:
- return mode1Behaviors
- case 2:
- return mode2Behaviors
- case 3:
- return mode3Behaviors
- case 4:
- return mode4Behaviors
- }
- // default
- return mode0Behaviors
- }
- func getBehaviorByModeAndIndex(mode int, index int) (RecommandBehavior, RecommandBehavior) {
- behaviors := getBehaviorByMode(mode)
- if index >= len(behaviors) {
- return RecommandBehavior{}, RecommandBehavior{}
- }
- return behaviors[index].A, behaviors[index].B
- }
- func getBehaviorScoresByMode(mode int, defaultScore int) []*BehaviorScore {
- return getBehaviorScoresByMode2(mode, defaultScore, defaultScore)
- }
- func getBehaviorScoresByMode2(mode int, senderScore, receiverScore int) []*BehaviorScore {
- behaviors := getBehaviorByMode(mode)
- scores := make([]*BehaviorScore, 0, len(behaviors))
- for i := 0; i < len(behaviors); i++ {
- score := receiverScore
- if behaviors[i].A.Role == DetectRoleSender {
- score = senderScore
- }
- scores = append(scores, &BehaviorScore{Mode: mode, Index: i, Score: score})
- }
- return scores
- }
- type RecommandBehavior struct {
- Role string
- TTL int
- SendDelayMs int
- PortsRangeNumber int
- PortsRandomNumber int
- ListenRandomPorts int
- }
- type MakeHoleRecords struct {
- mu sync.Mutex
- scores []*BehaviorScore
- LastUpdateTime time.Time
- }
- func NewMakeHoleRecords(c, v *NatFeature) *MakeHoleRecords {
- scores := []*BehaviorScore{}
- easyCount, hardCount, portsChangedRegularCount := ClassifyFeatureCount([]*NatFeature{c, v})
- appendMode0 := func() {
- switch {
- case c.PublicNetwork:
- scores = append(scores, getBehaviorScoresByMode2(DetectMode0, 0, 1)...)
- case v.PublicNetwork:
- scores = append(scores, getBehaviorScoresByMode2(DetectMode0, 1, 0)...)
- default:
- scores = append(scores, getBehaviorScoresByMode(DetectMode0, 0)...)
- }
- }
- switch {
- case easyCount == 2:
- appendMode0()
- case hardCount == 1 && portsChangedRegularCount == 1:
- scores = append(scores, getBehaviorScoresByMode(DetectMode1, 0)...)
- scores = append(scores, getBehaviorScoresByMode(DetectMode2, 0)...)
- appendMode0()
- case hardCount == 1 && portsChangedRegularCount == 0:
- scores = append(scores, getBehaviorScoresByMode(DetectMode2, 0)...)
- scores = append(scores, getBehaviorScoresByMode(DetectMode1, 0)...)
- appendMode0()
- case hardCount == 2 && portsChangedRegularCount == 2:
- scores = append(scores, getBehaviorScoresByMode(DetectMode3, 0)...)
- scores = append(scores, getBehaviorScoresByMode(DetectMode4, 0)...)
- case hardCount == 2 && portsChangedRegularCount == 1:
- scores = append(scores, getBehaviorScoresByMode(DetectMode4, 0)...)
- default:
- // hard to make hole, just trying it out.
- scores = append(scores, getBehaviorScoresByMode(DetectMode0, 1)...)
- scores = append(scores, getBehaviorScoresByMode(DetectMode1, 1)...)
- scores = append(scores, getBehaviorScoresByMode(DetectMode3, 1)...)
- }
- return &MakeHoleRecords{scores: scores, LastUpdateTime: time.Now()}
- }
- func (mhr *MakeHoleRecords) ReportSuccess(mode int, index int) {
- mhr.mu.Lock()
- defer mhr.mu.Unlock()
- mhr.LastUpdateTime = time.Now()
- for i := range mhr.scores {
- score := mhr.scores[i]
- if score.Mode != mode || score.Index != index {
- continue
- }
- score.Score += 2
- score.Score = min(score.Score, 10)
- return
- }
- }
- func (mhr *MakeHoleRecords) Recommand() (mode, index int) {
- mhr.mu.Lock()
- defer mhr.mu.Unlock()
- if len(mhr.scores) == 0 {
- return 0, 0
- }
- maxScore := slices.MaxFunc(mhr.scores, func(a, b *BehaviorScore) int {
- return cmp.Compare(a.Score, b.Score)
- })
- maxScore.Score--
- mhr.LastUpdateTime = time.Now()
- return maxScore.Mode, maxScore.Index
- }
- type BehaviorScore struct {
- Mode int
- Index int
- // between -10 and 10
- Score int
- }
- type Analyzer struct {
- // key is client ip + visitor ip
- records map[string]*MakeHoleRecords
- dataReserveDuration time.Duration
- mu sync.Mutex
- }
- func NewAnalyzer(dataReserveDuration time.Duration) *Analyzer {
- return &Analyzer{
- records: make(map[string]*MakeHoleRecords),
- dataReserveDuration: dataReserveDuration,
- }
- }
- func (a *Analyzer) GetRecommandBehaviors(key string, c, v *NatFeature) (mode, index int, _ RecommandBehavior, _ RecommandBehavior) {
- a.mu.Lock()
- records, ok := a.records[key]
- if !ok {
- records = NewMakeHoleRecords(c, v)
- a.records[key] = records
- }
- a.mu.Unlock()
- mode, index = records.Recommand()
- cBehavior, vBehavior := getBehaviorByModeAndIndex(mode, index)
- switch mode {
- case DetectMode1:
- // HardNAT is always the sender
- if c.NatType == EasyNAT {
- cBehavior, vBehavior = vBehavior, cBehavior
- }
- case DetectMode2:
- // HardNAT is always the receiver
- if c.NatType == HardNAT {
- cBehavior, vBehavior = vBehavior, cBehavior
- }
- case DetectMode4:
- // Regular ports changes is always the sender
- if !c.RegularPortsChange {
- cBehavior, vBehavior = vBehavior, cBehavior
- }
- }
- return mode, index, cBehavior, vBehavior
- }
- func (a *Analyzer) ReportSuccess(key string, mode, index int) {
- a.mu.Lock()
- records, ok := a.records[key]
- a.mu.Unlock()
- if !ok {
- return
- }
- records.ReportSuccess(mode, index)
- }
- func (a *Analyzer) Clean() (int, int) {
- now := time.Now()
- total := 0
- count := 0
- // cleanup 10w records may take 5ms
- a.mu.Lock()
- defer a.mu.Unlock()
- total = len(a.records)
- // clean up records that have not been used for a period of time.
- for key, records := range a.records {
- if now.Sub(records.LastUpdateTime) > a.dataReserveDuration {
- delete(a.records, key)
- count++
- }
- }
- return count, total
- }
|