Projects STRLCPY prox5 Commits 94a2c381
🤬
  • ■ ■ ■ ■ ■ ■
    .gitignore
    1 1  .idea/
    2 2  *.txt
    3 3  *.list
     4 +*.swp
     5 +*.save
    4 6   
  • ■ ■ ■ ■ ■ ■
    conductor.go
    skipped 2 lines
    3 3  import (
    4 4   "context"
    5 5   "errors"
     6 + "sync/atomic"
    6 7  )
    7 8   
    8 9  // SwampStatus represents the current state of our Swamp.
    9 10  type SwampStatus uint32
    10 11   
    11 12  const (
    12  - // Running means the proxy pool is currently taking in proxys and validating them, and is available to dispense proxies.
    13  - Running SwampStatus = iota
    14  - // Paused means the proxy pool has been with Swamp.Pause() and may be resumed with Swamp.Resume()
    15  - Paused
    16  - // New means the proxy pool has never been started.
    17  - New
     13 + // StateRunning means the proxy pool is currently taking in proxys and validating them, and is available to dispense proxies.
     14 + StateRunning SwampStatus = iota
     15 + // StatePaused means the proxy pool has been with Swamp.Pause() and may be resumed with Swamp.Resume()
     16 + StatePaused
     17 + // StateNew means the proxy pool has never been started.
     18 + StateNew
    18 19  )
    19 20   
    20 21  // Start starts our proxy pool operations. Trying to start a running Swamp will return an error.
    21  -func (s *Swamp) Start() error {
    22  - if s.Status.Load().(SwampStatus) != New {
    23  - return s.Resume()
     22 +func (pe *Swamp) Start() error {
     23 + if atomic.LoadUint32(&pe.Status) != uint32(StateNew) {
     24 + return pe.Resume()
    24 25   }
    25  - s.startDaemons()
     26 + pe.startDaemons()
    26 27   return nil
    27 28  }
    28 29   
    29 30  /*
    30 31  Pause will cease the creation of any new proxy validation operations.
    31  - * You will be able to start the proxy pool again with Swamp.Resume(), it will have the same Statistics, options, and ratelimits.
    32  - * During pause you are still able to dispense proxies.
    33  - * Options may be changed and proxy lists may be loaded when paused.
    34  - * Pausing an already paused Swamp is a nonop.
     32 + - You will be able to start the proxy pool again with Swamp.Resume(), it will have the same Statistics, options, and ratelimits.
     33 + - During pause you are still able to dispense proxies.
     34 + - Options may be changed and proxy lists may be loaded when paused.
     35 + - Pausing an already paused Swamp is a nonop.
    35 36  */
    36  -func (s *Swamp) Pause() error {
    37  - if !s.IsRunning() {
     37 +func (pe *Swamp) Pause() error {
     38 + if !pe.IsRunning() {
    38 39   return errors.New("not running")
    39 40   }
    40 41   
    41  - s.dbgPrint("pausing...")
     42 + pe.dbgPrint(simpleString("pausing proxy pool"))
    42 43   
    43  - s.quit()
     44 + pe.quit()
    44 45   
    45  - s.Status.Store(Paused)
     46 + atomic.StoreUint32(&pe.Status, uint32(StatePaused))
    46 47   return nil
    47 48  }
    48 49   
    49  -func (s *Swamp) startDaemons() {
    50  - go s.mapBuilder()
    51  - <-s.conductor
    52  - s.svcUp()
    53  - go s.jobSpawner()
     50 +func (pe *Swamp) startDaemons() {
     51 + go pe.mapBuilder()
     52 + <-pe.conductor
     53 + pe.svcUp()
     54 + go pe.jobSpawner()
    54 55   
    55 56   for {
    56  - if s.IsRunning() {
    57  - s.Status.Store(Running)
     57 + if pe.IsRunning() {
     58 + atomic.StoreUint32(&pe.Status, uint32(StateRunning))
    58 59   break
    59 60   }
    60 61   }
    61 62  }
    62 63   
    63 64  // Resume will resume pause proxy pool operations, attempting to resume a running Swamp is returns an error.
    64  -func (s *Swamp) Resume() error {
    65  - if s.IsRunning() {
     65 +func (pe *Swamp) Resume() error {
     66 + if pe.IsRunning() {
    66 67   return errors.New("already running")
    67 68   }
    68  - s.ctx, s.quit = context.WithCancel(context.Background())
    69  - s.startDaemons()
     69 + pe.ctx, pe.quit = context.WithCancel(context.Background())
     70 + pe.startDaemons()
    70 71   return nil
    71 72  }
    72 73   
  • ■ ■ ■ ■ ■ ■
    daemons.go
    skipped 2 lines
    3 3  import (
    4 4   "errors"
    5 5   "strconv"
     6 + "strings"
    6 7   "sync"
    7 8   "sync/atomic"
    8 9   "time"
     10 + 
     11 + "git.tcp.direct/kayos/prox5/internal/pools"
    9 12  )
    10 13   
    11  -func (s *Swamp) svcUp() {
    12  - atomic.AddInt32(&s.runningdaemons, 1)
     14 +func (p5 *Swamp) svcUp() {
     15 + atomic.AddInt32(&p5.runningdaemons, 1)
    13 16  }
    14 17   
    15  -func (s *Swamp) svcDown() {
    16  - atomic.AddInt32(&s.runningdaemons, -1)
     18 +func (p5 *Swamp) svcDown() {
     19 + atomic.AddInt32(&p5.runningdaemons, -1)
    17 20  }
    18 21   
    19 22  type swampMap struct {
    skipped 11 lines
    31 34   }
    32 35   
    33 36   sm.plot[sock] = &Proxy{
    34  - Endpoint: sock,
    35  - lock: stateUnlocked,
    36  - parent: sm.parent,
     37 + Endpoint: sock,
     38 + protocol: newImmutableProto(),
     39 + lastValidated: time.UnixMilli(0),
     40 + timesValidated: 0,
     41 + timesBad: 0,
     42 + parent: sm.parent,
     43 + lock: stateUnlocked,
    37 44   }
    38 45   
    39  - sm.plot[sock].timesValidated.Store(0)
    40  - sm.plot[sock].timesBad.Store(0)
    41 46   return sm.plot[sock], true
    42 47  }
    43 48   
    skipped 25 lines
    69 74   }
    70 75  }
    71 76   
    72  -func (s *Swamp) mapBuilder() {
    73  - if s.pool.IsClosed() {
    74  - s.pool.Reboot()
     77 +func (p5 *Swamp) mapBuilder() {
     78 + if p5.pool.IsClosed() {
     79 + p5.pool.Reboot()
    75 80   }
    76 81   
    77  - s.dbgPrint("map builder started")
     82 + p5.dbgPrint(simpleString("map builder started"))
    78 83   
    79 84   go func() {
    80  - defer s.dbgPrint("map builder paused")
     85 + defer p5.dbgPrint(simpleString("map builder paused"))
    81 86   for {
    82 87   select {
    83  - case <-s.ctx.Done():
    84  - s.svcDown()
     88 + case <-p5.ctx.Done():
     89 + p5.svcDown()
    85 90   return
    86 91   case in := <-inChan:
    87  - if p, ok := s.swampmap.add(in); !ok {
     92 + if p, ok := p5.swampmap.add(in); !ok {
    88 93   continue
    89 94   } else {
    90  - s.Pending <- p
     95 + p5.Pending <- p
    91 96   }
     97 + default:
     98 + p5.recycling()
    92 99   }
    93 100   }
    94 101   }()
    95  - s.conductor <- true
     102 + p5.conductor <- true
    96 103  }
    97 104   
    98  -func (s *Swamp) recycling() int {
    99  - s.mu.Lock()
    100  - defer s.mu.Unlock()
    101  - if !s.GetRecyclingStatus() {
     105 +func (p5 *Swamp) recycling() int {
     106 + if !p5.GetRecyclingStatus() {
    102 107   return 0
    103 108   }
    104 109   
    105  - if len(s.swampmap.plot) < 1 {
     110 + if len(p5.swampmap.plot) < 1 {
    106 111   return 0
    107 112   }
    108 113   
    109 114   var count int
    110 115   
    111  - s.swampmap.mu.RLock()
    112  - defer s.swampmap.mu.RUnlock()
     116 + p5.swampmap.mu.RLock()
     117 + defer p5.swampmap.mu.RUnlock()
    113 118   
    114  - for _, sock := range s.swampmap.plot {
     119 + for _, sock := range p5.swampmap.plot {
    115 120   select {
    116  - case <-s.ctx.Done():
     121 + case <-p5.ctx.Done():
    117 122   return 0
    118  - case s.Pending <- sock:
     123 + case p5.Pending <- sock:
    119 124   count++
    120 125   default:
     126 + continue
    121 127   }
    122 128   }
    123 129   
    124 130   return count
    125 131  }
    126 132   
    127  -func (s *Swamp) jobSpawner() {
    128  - if s.pool.IsClosed() {
    129  - s.pool.Reboot()
     133 +func (p5 *Swamp) jobSpawner() {
     134 + if p5.pool.IsClosed() {
     135 + p5.pool.Reboot()
    130 136   }
    131 137   
    132  - s.dbgPrint("job spawner started")
    133  - defer s.dbgPrint("job spawner paused")
     138 + p5.dbgPrint(simpleString("job spawner started"))
     139 + defer p5.dbgPrint(simpleString("job spawner paused"))
    134 140   
    135 141   q := make(chan bool)
    136 142   
    137 143   go func() {
    138  - var count = 0
    139 144   for {
    140 145   select {
    141  - case <-s.ctx.Done():
     146 + case <-p5.ctx.Done():
    142 147   q <- true
    143  - s.svcDown()
     148 + p5.svcDown()
    144 149   return
    145  - case sock := <-s.Pending:
    146  - if err := s.pool.Submit(sock.validate); err != nil {
    147  - s.dbgPrint(ylw + err.Error() + rst)
     150 + case sock := <-p5.Pending:
     151 + if err := p5.pool.Submit(sock.validate); err != nil {
     152 + p5.dbgPrint(simpleString(err.Error()))
    148 153   }
    149 154   
    150 155   default:
    151 156   time.Sleep(25 * time.Millisecond)
    152  - if count == 0 {
    153  - time.Sleep(5 * time.Second)
    154  - }
    155  - count++
    156  - if count > 100 {
    157  - rcount := s.recycling()
    158  - if rcount > 0 {
    159  - s.dbgPrint(ylw + "recycled " + strconv.Itoa(rcount) + " proxies from our map" + rst)
    160  - }
    161  - count = 0
    162  - }
     157 + count := p5.recycling()
     158 + buf := pools.CopABuffer.Get().(*strings.Builder)
     159 + buf.WriteString("recycled ")
     160 + buf.WriteString(strconv.Itoa(count))
     161 + buf.WriteString(" proxies from our map")
     162 + p5.dbgPrint(buf)
    163 163   }
    164 164   }
    165 165   }()
    166 166   
    167  - s.svcUp()
     167 + p5.svcUp()
    168 168   <-q
    169  - s.pool.Release()
     169 + p5.pool.Release()
    170 170  }
    171 171   
  • ■ ■ ■ ■ ■
    debug.go
    1 1  package prox5
    2 2   
    3 3  import (
     4 + "fmt"
     5 + "strings"
    4 6   "sync"
     7 + "sync/atomic"
     8 + 
     9 + "git.tcp.direct/kayos/prox5/internal/pools"
    5 10  )
    6 11   
    7 12  var (
    8  - useDebugChannel = false
    9  - debugChan chan string
    10  - debugMutex *sync.RWMutex
     13 + debugStatus *uint32
     14 + debugHardLock = &sync.RWMutex{}
    11 15  )
    12 16   
    13 17  func init() {
    14  - debugMutex = &sync.RWMutex{}
     18 + dd := debugDisabled
     19 + debugStatus = &dd
    15 20  }
    16 21   
    17  -// DebugChannel will return a channel which will receive debug messages once debug is enabled.
    18  -// This will alter the flow of debug messages, they will no longer print to console, they will be pushed into this channel.
    19  -// Make sure you pull from the channel eventually to avoid build up of blocked goroutines.
    20  -func (s *Swamp) DebugChannel() chan string {
    21  - debugChan = make(chan string, 1000000)
    22  - useDebugChannel = true
    23  - return debugChan
     22 +const (
     23 + debugEnabled uint32 = iota
     24 + debugDisabled
     25 +)
     26 + 
     27 +type SocksLogger struct {
     28 + parent *Swamp
    24 29  }
    25 30   
    26  -// DebugEnabled returns the current state of our debug switch.
    27  -func (s *Swamp) DebugEnabled() bool {
    28  - return s.swampopt.debug.Load().(bool)
     31 +// Printf is used to handle socks server logging.
     32 +func (s SocksLogger) Printf(format string, a ...interface{}) {
     33 + buf := pools.CopABuffer.Get().(*strings.Builder)
     34 + buf.WriteString(fmt.Sprintf(format, a...))
     35 + s.parent.dbgPrint(buf)
    29 36  }
    30 37   
    31  -// DisableDebugChannel redirects debug messages back to the console.
    32  -// DisableProxyChannel does not disable debug, use DisableDebug().
    33  -func (s *Swamp) DisableDebugChannel() {
    34  - debugMutex.Lock()
    35  - defer debugMutex.Unlock()
    36  - useDebugChannel = false
     38 +type basicPrinter struct{}
     39 + 
     40 +func (b *basicPrinter) Print(str string) {
     41 + println("[prox5] " + str)
     42 +}
     43 + 
     44 +func (b *basicPrinter) Printf(format string, items ...any) {
     45 + println(fmt.Sprintf("prox5: "+format, items))
     46 +}
     47 + 
     48 +// DebugEnabled returns the current state of our debug switch.
     49 +func (pe *Swamp) DebugEnabled() bool {
     50 + debugHardLock.RLock()
     51 + defer debugHardLock.RUnlock()
     52 + return atomic.CompareAndSwapUint32(debugStatus, debugEnabled, debugEnabled)
    37 53  }
    38 54   
    39 55  // EnableDebug enables printing of verbose messages during operation
    40  -func (s *Swamp) EnableDebug() {
    41  - s.swampopt.debug.Store(true)
     56 +func (pe *Swamp) EnableDebug() {
     57 + atomic.StoreUint32(debugStatus, debugEnabled)
    42 58  }
    43 59   
    44 60  // DisableDebug enables printing of verbose messages during operation.
    45 61  // WARNING: if you are using a DebugChannel, you must read all of the messages in the channel's cache or this will block.
    46  -func (s *Swamp) DisableDebug() {
    47  - s.swampopt.debug.Store(false)
     62 +func (pe *Swamp) DisableDebug() {
     63 + atomic.StoreUint32(debugStatus, debugDisabled)
    48 64  }
    49 65   
    50  -func (s *Swamp) dbgPrint(str string) {
    51  - if !s.swampopt.debug.Load().(bool) {
     66 +func simpleString(s string) *strings.Builder {
     67 + buf := pools.CopABuffer.Get().(*strings.Builder)
     68 + buf.WriteString(s)
     69 + return buf
     70 +}
     71 + 
     72 +func (pe *Swamp) dbgPrint(builder *strings.Builder) {
     73 + defer pools.DiscardBuffer(builder)
     74 + if !pe.DebugEnabled() {
    52 75   return
    53 76   }
     77 + pe.DebugLogger.Print(builder.String())
     78 + return
     79 +}
    54 80   
    55  - if useDebugChannel {
    56  - select {
    57  - case debugChan <- str:
    58  - return
    59  - default:
    60  - println("prox5 overflow: " + str)
    61  - return
    62  - }
     81 +func (pe *Swamp) msgUnableToReach(socksString, target string, err error) {
     82 + if !pe.DebugEnabled() {
     83 + return
    63 84   }
    64  - println("prox5: " + str)
     85 + buf := pools.CopABuffer.Get().(*strings.Builder)
     86 + buf.WriteString("unable to reach ")
     87 + if pe.swampopt.redact {
     88 + buf.WriteString("[redacted]")
     89 + } else {
     90 + buf.WriteString(target)
     91 + }
     92 + buf.WriteString(" with ")
     93 + buf.WriteString(socksString)
     94 + if !pe.swampopt.redact {
     95 + buf.WriteString(": ")
     96 + buf.WriteString(err.Error())
     97 + }
     98 + buf.WriteString(", cycling...")
     99 + pe.dbgPrint(buf)
     100 +}
     101 + 
     102 +func (pe *Swamp) msgUsingProxy(socksString string) {
     103 + if !pe.DebugEnabled() {
     104 + return
     105 + }
     106 + buf := pools.CopABuffer.Get().(*strings.Builder)
     107 + buf.WriteString("MysteryDialer using socks: ")
     108 + buf.WriteString(socksString)
     109 + pe.dbgPrint(buf)
     110 +}
     111 + 
     112 +func (pe *Swamp) msgFailedMiddleware(socksString string) {
     113 + if !pe.DebugEnabled() {
     114 + return
     115 + }
     116 + buf := pools.CopABuffer.Get().(*strings.Builder)
     117 + buf.WriteString("failed middleware check, ")
     118 + buf.WriteString(socksString)
     119 + buf.WriteString(", cycling...")
     120 + pe.dbgPrint(buf)
     121 +}
     122 + 
     123 +func (pe *Swamp) msgTry(socksString string) {
     124 + if !pe.DebugEnabled() {
     125 + return
     126 + }
     127 + buf := pools.CopABuffer.Get().(*strings.Builder)
     128 + buf.WriteString("try dial with: ")
     129 + buf.WriteString(socksString)
     130 + pe.dbgPrint(buf)
     131 +}
     132 + 
     133 +func (pe *Swamp) msgCantGetLock(socksString string, putback bool) {
     134 + if !pe.DebugEnabled() {
     135 + return
     136 + }
     137 + buf := pools.CopABuffer.Get().(*strings.Builder)
     138 + buf.WriteString("can't get lock for ")
     139 + buf.WriteString(socksString)
     140 + if putback {
     141 + buf.WriteString(", putting back in queue")
     142 + }
     143 + pe.dbgPrint(buf)
     144 +}
     145 + 
     146 +func (pe *Swamp) msgGotLock(socksString string) {
     147 + if !pe.DebugEnabled() {
     148 + return
     149 + }
     150 + buf := pools.CopABuffer.Get().(*strings.Builder)
     151 + buf.WriteString("got lock for ")
     152 + buf.WriteString(socksString)
     153 + pe.dbgPrint(buf)
     154 +}
     155 + 
     156 +func (pe *Swamp) msgChecked(sock *Proxy, success bool) {
     157 + if !pe.DebugEnabled() {
     158 + return
     159 + }
     160 + buf := pools.CopABuffer.Get().(*strings.Builder)
     161 + if success {
     162 + buf.WriteString("verified ")
     163 + buf.WriteString(sock.Endpoint)
     164 + buf.WriteString(" as ")
     165 + buf.WriteString(sock.protocol.Get().String())
     166 + buf.WriteString(" proxy")
     167 + pe.dbgPrint(buf)
     168 + return
     169 + }
     170 + buf.WriteString("failed to verify: ")
     171 + buf.WriteString(sock.Endpoint)
     172 + pe.dbgPrint(buf)
     173 +}
     174 + 
     175 +func (pe *Swamp) msgBadProxRate(sock *Proxy) {
     176 + if !pe.DebugEnabled() {
     177 + return
     178 + }
     179 + buf := pools.CopABuffer.Get().(*strings.Builder)
     180 + buf.WriteString("badProx ratelimited: ")
     181 + buf.WriteString(sock.Endpoint)
     182 + pe.dbgPrint(buf)
    65 183  }
    66 184   
  • ■ ■ ■ ■ ■ ■
    defs.go
    skipped 1 lines
    2 2   
    3 3  import (
    4 4   "context"
    5  - "fmt"
    6  - "net/http"
     5 + "strings"
    7 6   "sync"
    8 7   "sync/atomic"
    9 8   "time"
    10 9   
    11 10   "github.com/panjf2000/ants/v2"
    12 11   rl "github.com/yunginnanet/Rate5"
     12 + 
     13 + "git.tcp.direct/kayos/prox5/internal/pools"
     14 + "git.tcp.direct/kayos/prox5/logger"
    13 15  )
    14 16   
     17 +type ProxyChannels struct {
     18 + // SOCKS5 is a constant stream of verified SOCKS5 proxies
     19 + SOCKS5 chan *Proxy
     20 + // SOCKS4 is a constant stream of verified SOCKS4 proxies
     21 + SOCKS4 chan *Proxy
     22 + // SOCKS4a is a constant stream of verified SOCKS5 proxies
     23 + SOCKS4a chan *Proxy
     24 + // HTTP is a constant stream of verified SOCKS5 proxies
     25 + HTTP chan *Proxy
     26 +}
     27 + 
    15 28  // Swamp represents a proxy pool
    16 29  type Swamp struct {
    17  - // ValidSocks5 is a constant stream of verified ValidSocks5 proxies
    18  - ValidSocks5 chan *Proxy
    19  - // ValidSocks4 is a constant stream of verified ValidSocks4 proxies
    20  - ValidSocks4 chan *Proxy
    21  - // ValidSocks4a is a constant stream of verified ValidSocks5 proxies
    22  - ValidSocks4a chan *Proxy
    23  - // ValidHTTP is a constant stream of verified ValidSocks5 proxies
    24  - ValidHTTP chan *Proxy
    25  - 
    26  - socksServerLogger socksLogger
     30 + Valids ProxyChannels
     31 + DebugLogger logger.Logger
    27 32   
    28  - // Stats holds the Statistics for our swamp
    29  - Stats *Statistics
     33 + // stats holds the statistics for our swamp
     34 + stats *statistics
    30 35   
    31  - Status atomic.Value
     36 + Status uint32
    32 37   
    33 38   // Pending is a constant stream of proxy strings to be verified
    34 39   Pending chan *Proxy
    skipped 2 lines
    37 42   useProx *rl.Limiter
    38 43   badProx *rl.Limiter
    39 44   
    40  - socks5ServerAuth socksCreds
     45 + dispenseMiddleware func(*Proxy) (*Proxy, bool)
    41 46   
    42 47   ctx context.Context
    43 48   quit context.CancelFunc
    skipped 4 lines
    48 53   
    49 54   mu *sync.RWMutex
    50 55   pool *ants.Pool
    51  - swampopt *swampOptions
    52  - httpClient *http.Client
     56 + swampopt *config
    53 57   runningdaemons int32
    54 58   conductor chan bool
    55 59  }
    56 60   
    57 61  var (
    58  - defaultStaleTime = 1 * time.Hour
    59  - defWorkers = 100
    60  - defBailout = 5
     62 + defaultStaleTime = 30 * time.Minute
     63 + defaultWorkerCount = 20
     64 + defaultBailout = 20
     65 + defaultRemoveAfter = 25
    61 66   // Note: I've chosen to use https here exclusively assuring all validated proxies are SSL capable.
    62 67   defaultChecks = []string{
    63 68   "https://wtfismyip.com/text",
    skipped 7 lines
    71 76   }
    72 77  )
    73 78   
    74  -// https://pkg.go.dev/github.com/yunginnanet/Rate5#Policy
    75  -var defUseProx = rl.Policy{
    76  - Window: 60,
    77  - Burst: 2,
    78  -}
    79  - 
    80  -var defBadProx = rl.Policy{
    81  - Window: 60,
    82  - Burst: 3,
    83  -}
    84  - 
    85 79  // Returns a pointer to our default options (modified and accessed later through concurrent safe getters and setters)
    86  -func defOpt() *swampOptions {
    87  - sm := &swampOptions{
    88  - useProxConfig: defUseProx,
    89  - badProxConfig: defBadProx,
     80 +func defOpt() *config {
     81 + sm := &config{
     82 + useProxConfig: defaultUseProxyRatelimiter,
     83 + badProxConfig: defaultBadProxyRateLimiter,
    90 84   
    91 85   checkEndpoints: defaultChecks,
    92 86   userAgents: defaultUserAgents,
     87 + RWMutex: &sync.RWMutex{},
     88 + removeafter: defaultRemoveAfter,
     89 + recycle: true,
     90 + debug: true,
     91 + dialerBailout: defaultBailout,
     92 + stale: defaultStaleTime,
     93 + maxWorkers: defaultWorkerCount,
     94 + redact: true,
    93 95   }
    94  - 
    95  - sm.removeafter.Store(5)
    96  - sm.recycle.Store(true)
    97  - sm.debug.Store(false)
    98  - sm.validationTimeout.Store(time.Duration(12) * time.Second)
    99  - sm.serverTimeout.Store(time.Duration(60) * time.Second)
    100  - 
    101  - sm.dialerBailout.Store(defBailout)
    102  - sm.stale.Store(defaultStaleTime)
    103  - sm.maxWorkers = defWorkers
    104  - 
     96 + sm.validationTimeout = time.Duration(18) * time.Second
     97 + sm.serverTimeout = time.Duration(180) * time.Second
    105 98   return sm
    106 99  }
    107 100   
    108  -/*type connPoolOptions struct {
    109  - dialer func() (net.Conn, error)
    110  - deathFunc func(*Conn) error
    111  -}
    112  -*/
    113  - 
    114  -/*// scvm is a pooled net.Conn
    115  -type scvm struct {
    116  - moss net.Conn
    117  - used atomic.Value
    118  -}
    119  - 
    120  -func getScvm(moss net.Conn) *scvm {
    121  - s := &scvm{
    122  - moss: moss,
    123  - }
    124  - s.used.Store(time.Now())
    125  - return s
    126  -}*/
    127  - 
    128  -// swampOptions holds our configuration for Swamp instances.
     101 +// config holds our configuration for Swamp instances.
    129 102  // This is implemented as a pointer, and should be interacted with via the setter and getter functions.
    130  -type swampOptions struct {
     103 +type config struct {
    131 104   // stale is the amount of time since verification that qualifies a proxy going stale.
    132 105   // if a stale proxy is drawn during the use of our getter functions, it will be skipped.
    133  - stale atomic.Value
    134  - 
     106 + stale time.Duration
    135 107   // userAgents contains a list of userAgents to be randomly drawn from for proxied requests, this should be supplied via SetUserAgents
    136 108   userAgents []string
    137  - 
    138 109   // debug when enabled will print results as they come in
    139  - debug atomic.Value
    140  - 
     110 + debug bool
    141 111   // checkEndpoints includes web services that respond with (just) the WAN IP of the connection for validation purposes
    142 112   checkEndpoints []string
    143  - 
    144 113   // maxWorkers determines the maximum amount of workers used for checking proxies
    145 114   maxWorkers int
    146  - 
    147 115   // validationTimeout defines the timeout for proxy validation operations.
    148 116   // This will apply for both the initial quick check (dial), and the second check (HTTP GET).
    149  - validationTimeout atomic.Value
    150  - 
     117 + validationTimeout time.Duration
    151 118   // serverTimeout defines the timeout for outgoing connections made with the MysteryDialer.
    152  - serverTimeout atomic.Value
    153  - 
    154  - dialerBailout atomic.Value
    155  - 
     119 + serverTimeout time.Duration
     120 + // dialerBailout defines the amount of times a dial atttempt can fail before giving up and returning an error.
     121 + dialerBailout int
     122 + // redact when enabled will redact the target string from the debug output
     123 + redact bool
    156 124   // recycle determines whether or not we recycle proxies pack into the pending channel after we dispense them
    157  - recycle atomic.Value
     125 + recycle bool
    158 126   // remove proxy from recycling after being marked bad this many times
    159  - removeafter atomic.Value
     127 + removeafter int
     128 + // shuffle determines whether or not we shuffle proxies before we validate and dispense them.
     129 + shuffle bool
    160 130   
    161 131   // TODO: make getters and setters for these
    162 132   useProxConfig rl.Policy
    163 133   badProxConfig rl.Policy
    164  -}
    165  - 
    166  -const (
    167  - stateUnlocked uint32 = iota
    168  - stateLocked
    169  -)
    170  - 
    171  -// Proxy represents an individual proxy
    172  -type Proxy struct {
    173  - // Endpoint is the address:port of the proxy that we connect to
    174  - Endpoint string
    175  - // ProxiedIP is the address that we end up having when making proxied requests through this proxy
    176  - ProxiedIP string
    177  - // Proto is the version/Protocol (currently SOCKS* only) of the proxy
    178  - Proto atomic.Value
    179  - // lastValidated is the time this proxy was last verified working
    180  - lastValidated atomic.Value
    181  - // timesValidated is the amount of times the proxy has been validated.
    182  - timesValidated atomic.Value
    183  - // timesBad is the amount of times the proxy has been marked as bad.
    184  - timesBad atomic.Value
    185 134   
    186  - parent *Swamp
    187  - lock uint32
    188  - hardlock *sync.Mutex
    189  -}
    190  - 
    191  -// UniqueKey is an implementation of the Identity interface from Rate5.
    192  -// See: https://pkg.go.dev/github.com/yunginnanet/Rate5#Identity
    193  -func (sock *Proxy) UniqueKey() string {
    194  - return sock.Endpoint
     135 + *sync.RWMutex
    195 136  }
    196 137   
    197  -// NewDefaultSwamp returns a Swamp with basic options.
    198  -// After calling this you can use the various "setters" to change the options before calling Swamp.Start().
    199  -func NewDefaultSwamp() *Swamp {
    200  - s := &Swamp{
    201  - ValidSocks5: make(chan *Proxy, 1000000),
    202  - ValidSocks4: make(chan *Proxy, 1000000),
    203  - ValidSocks4a: make(chan *Proxy, 1000000),
    204  - Pending: make(chan *Proxy, 1000000),
    205  - 
    206  - Stats: &Statistics{
    207  - Valid4: 0,
    208  - Valid4a: 0,
    209  - Valid5: 0,
    210  - Dispensed: 0,
    211  - birthday: time.Now(),
    212  - mu: &sync.Mutex{},
    213  - },
     138 +// NewProxyEngine returns a Swamp with default options.
     139 +// After calling this you may use the various "setters" to change the options before calling Swamp.Start().
     140 +func NewProxyEngine() *Swamp {
     141 + pe := &Swamp{
     142 + stats: &statistics{birthday: time.Now()},
     143 + DebugLogger: &basicPrinter{},
    214 144   
    215 145   swampopt: defOpt(),
    216 146   
    217 147   conductor: make(chan bool),
    218 148   mu: &sync.RWMutex{},
    219  - Status: atomic.Value{},
     149 + Status: uint32(StateNew),
    220 150   }
    221 151   
    222  - s.ctx, s.quit = context.WithCancel(context.Background())
    223  - 
    224  - s.Status.Store(New)
     152 + stats := []int64{pe.stats.Valid4, pe.stats.Valid4a, pe.stats.Valid5, pe.stats.ValidHTTP, pe.stats.Dispensed}
     153 + for i := range stats {
     154 + atomic.StoreInt64(&stats[i], 0)
     155 + }
    225 156   
    226  - s.swampmap = swampMap{
    227  - plot: make(map[string]*Proxy),
    228  - mu: &sync.RWMutex{},
    229  - parent: s,
     157 + chans := []*chan *Proxy{&pe.Valids.SOCKS5, &pe.Valids.SOCKS4, &pe.Valids.SOCKS4a, &pe.Valids.HTTP, &pe.Pending}
     158 + for _, c := range chans {
     159 + *c = make(chan *Proxy, 500)
    230 160   }
    231 161   
    232  - s.socksServerLogger = socksLogger{parent: s}
     162 + pe.dispenseMiddleware = func(p *Proxy) (*Proxy, bool) {
     163 + return p, true
     164 + }
     165 + pe.ctx, pe.quit = context.WithCancel(context.Background())
     166 + pe.swampmap = newSwampMap(pe)
    233 167   
    234  - atomic.StoreInt32(&s.runningdaemons, 0)
     168 + atomic.StoreUint32(&pe.Status, uint32(StateNew))
     169 + atomic.StoreInt32(&pe.runningdaemons, 0)
    235 170   
    236  - s.useProx = rl.NewCustomLimiter(s.swampopt.useProxConfig)
    237  - s.badProx = rl.NewCustomLimiter(s.swampopt.badProxConfig)
     171 + pe.useProx = rl.NewCustomLimiter(pe.swampopt.useProxConfig)
     172 + pe.badProx = rl.NewCustomLimiter(pe.swampopt.badProxConfig)
    238 173   
    239 174   var err error
    240  - s.pool, err = ants.NewPool(s.swampopt.maxWorkers, ants.WithOptions(ants.Options{
     175 + pe.pool, err = ants.NewPool(pe.swampopt.maxWorkers, ants.WithOptions(ants.Options{
    241 176   ExpiryDuration: 2 * time.Minute,
    242  - PanicHandler: s.pondPanic,
     177 + PanicHandler: pe.pondPanic,
    243 178   }))
    244 179   
    245 180   if err != nil {
    246  - s.dbgPrint(red + "CRITICAL: " + err.Error() + rst)
     181 + buf := pools.CopABuffer.Get().(*strings.Builder)
     182 + buf.WriteString("CRITICAL: ")
     183 + buf.WriteString(err.Error())
     184 + pe.dbgPrint(buf)
    247 185   panic(err)
    248 186   }
    249 187   
    250  - /* s.reaper = sync.Pool{
    251  - New: func() interface{} {
    252  - clock := time.NewTimer(time.Duration(s.swampopt.validationTimeout) * time.Second)
    253  - clock.Stop()
    254  - return clock
    255  - },
    256  - }
    257  - */
    258  - return s
     188 + return pe
     189 +}
     190 + 
     191 +func newSwampMap(pe *Swamp) swampMap {
     192 + return swampMap{
     193 + plot: make(map[string]*Proxy),
     194 + mu: &sync.RWMutex{},
     195 + parent: pe,
     196 + }
    259 197  }
    260 198   
    261  -func (s *Swamp) pondPanic(p interface{}) {
    262  - fmt.Println("WORKER PANIC! ", p)
    263  - s.dbgPrint(red + "PANIC! " + fmt.Sprintf("%v", p))
     199 +func (p5 *Swamp) pondPanic(p interface{}) {
     200 + panic(p)
     201 + // pe.dbgPrint("Worker panic: " + fmt.Sprintf("%v", p))
    264 202  }
    265 203   
    266 204  // defaultUserAgents is a small list of user agents to use during validation.
    skipped 23 lines
  • ■ ■ ■ ■ ■ ■
    dispense.go
    1 1  package prox5
    2 2   
    3 3  import (
    4  - "strconv"
     4 + "strings"
    5 5   "sync/atomic"
    6 6   "time"
     7 + 
     8 + "git.tcp.direct/kayos/prox5/internal/pools"
    7 9  )
    8 10   
    9 11  // Socks5Str gets a SOCKS5 proxy that we have fully verified (dialed and then retrieved our IP address from a what-is-my-ip endpoint.
    10 12  // Will block if one is not available!
    11  -func (s *Swamp) Socks5Str() string {
     13 +func (p5 *Swamp) Socks5Str() string {
    12 14   for {
    13 15   select {
    14  - case sock := <-s.ValidSocks5:
    15  - if !s.stillGood(sock) {
     16 + case sock := <-p5.Valids.SOCKS5:
     17 + if !p5.stillGood(sock) {
    16 18   continue
    17 19   }
    18  - s.Stats.dispense()
     20 + p5.stats.dispense()
    19 21   return sock.Endpoint
    20 22   default:
    21  - count := s.recycling()
    22  - if count > 0 {
    23  - s.dbgPrint(ylw + "recycled " + strconv.Itoa(count) + " proxies from our map" + rst)
    24  - }
    25  - time.Sleep(1 * time.Second)
     23 + p5.recycling()
    26 24   }
    27 25   }
    28 26  }
    29 27   
    30 28  // Socks4Str gets a SOCKS4 proxy that we have fully verified.
    31 29  // Will block if one is not available!
    32  -func (s *Swamp) Socks4Str() string {
    33  - defer s.Stats.dispense()
     30 +func (p5 *Swamp) Socks4Str() string {
     31 + defer p5.stats.dispense()
    34 32   for {
    35 33   select {
    36  - case sock := <-s.ValidSocks4:
    37  - if !s.stillGood(sock) {
     34 + case sock := <-p5.Valids.SOCKS4:
     35 + if !p5.stillGood(sock) {
    38 36   continue
    39 37   }
    40 38   return sock.Endpoint
    41 39   default:
    42  - count := s.recycling()
    43  - if count > 0 {
    44  - s.dbgPrint(ylw + "recycled " + strconv.Itoa(count) + " proxies from our map" + rst)
    45  - }
    46  - time.Sleep(1 * time.Second)
     40 + p5.recycling()
    47 41   }
    48 42   }
    49 43  }
    50 44   
    51 45  // Socks4aStr gets a SOCKS4 proxy that we have fully verified.
    52 46  // Will block if one is not available!
    53  -func (s *Swamp) Socks4aStr() string {
    54  - defer s.Stats.dispense()
     47 +func (p5 *Swamp) Socks4aStr() string {
     48 + defer p5.stats.dispense()
    55 49   for {
    56 50   select {
    57  - case sock := <-s.ValidSocks4a:
    58  - if !s.stillGood(sock) {
     51 + case sock := <-p5.Valids.SOCKS4a:
     52 + if !p5.stillGood(sock) {
    59 53   continue
    60 54   }
    61 55   return sock.Endpoint
    62 56   default:
    63  - count := s.recycling()
    64  - if count > 0 {
    65  - s.dbgPrint(ylw + "recycled " + strconv.Itoa(count) + " proxies from our map" + rst)
    66  - }
    67  - time.Sleep(1 * time.Second)
     57 + p5.recycling()
    68 58   }
    69 59   }
    70 60  }
    71 61   
     62 +// GetHTTPTunnel checks for an available HTTP CONNECT proxy in our pool.
     63 +// For now, this function does not loop forever like the GetAnySOCKS does.
     64 +// Alternatively it can be included within the for loop by passing true to GetAnySOCKS.
     65 +// If there is an HTTP proxy available, ok will be true. If not, it will return false without delay.
     66 +func (p5 *Swamp) GetHTTPTunnel() (p *Proxy, ok bool) {
     67 + select {
     68 + case httptunnel := <-p5.Valids.HTTP:
     69 + return httptunnel, true
     70 + default:
     71 + return nil, false
     72 + }
     73 +}
     74 + 
    72 75  // GetAnySOCKS retrieves any version SOCKS proxy as a Proxy type
    73 76  // Will block if one is not available!
    74  -func (s *Swamp) GetAnySOCKS() *Proxy {
    75  - defer s.Stats.dispense()
     77 +func (p5 *Swamp) GetAnySOCKS() *Proxy {
     78 + defer p5.stats.dispense()
    76 79   for {
     80 + var sock *Proxy
    77 81   select {
    78  - case sock := <-s.ValidSocks4:
    79  - if s.stillGood(sock) {
    80  - return sock
    81  - }
    82  - continue
    83  - case sock := <-s.ValidSocks4a:
    84  - if s.stillGood(sock) {
    85  - return sock
    86  - }
    87  - continue
    88  - case sock := <-s.ValidSocks5:
    89  - if s.stillGood(sock) {
    90  - return sock
    91  - }
    92  - continue
     82 + case sock = <-p5.Valids.SOCKS4:
     83 + break
     84 + case sock = <-p5.Valids.SOCKS4a:
     85 + break
     86 + case sock = <-p5.Valids.SOCKS5:
     87 + break
    93 88   default:
    94  - // s.dbgPrint(red + "no valid proxies in channels, sleeping" + rst)
    95  - count := s.recycling()
    96  - if count > 0 {
    97  - s.dbgPrint(ylw + "recycled " + strconv.Itoa(count) + " proxies from our map" + rst)
    98  - }
    99  - time.Sleep(1 * time.Second)
     89 + p5.recycling()
     90 + }
     91 + if p5.stillGood(sock) {
     92 + return sock
    100 93   }
     94 + continue
    101 95   }
    102 96  }
    103 97   
    104  -func (s *Swamp) stillGood(sock *Proxy) bool {
    105  - for !atomic.CompareAndSwapUint32(&sock.lock, stateUnlocked, stateLocked) {
    106  - randSleep()
     98 +func (p5 *Swamp) stillGood(sock *Proxy) bool {
     99 + if sock == nil {
     100 + return false
     101 + }
     102 + if !atomic.CompareAndSwapUint32(&sock.lock, stateUnlocked, stateLocked) {
     103 + return false
    107 104   }
    108 105   defer atomic.StoreUint32(&sock.lock, stateUnlocked)
    109 106   
    110  - if sock.timesBad.Load().(int) > s.GetRemoveAfter() && s.GetRemoveAfter() != -1 {
    111  - s.dbgPrint(red + "deleting from map (too many failures): " + sock.Endpoint + rst)
    112  - if err := s.swampmap.delete(sock.Endpoint); err != nil {
    113  - s.dbgPrint(red + err.Error() + rst)
     107 + if atomic.LoadInt64(&sock.timesBad) > int64(p5.GetRemoveAfter()) && p5.GetRemoveAfter() != -1 {
     108 + buf := pools.CopABuffer.Get().(*strings.Builder)
     109 + buf.WriteString("deleting from map (too many failures): ")
     110 + buf.WriteString(sock.Endpoint)
     111 + p5.dbgPrint(buf)
     112 + if err := p5.swampmap.delete(sock.Endpoint); err != nil {
     113 + p5.dbgPrint(simpleString(err.Error()))
    114 114   }
    115 115   }
    116 116   
    117  - if s.badProx.Peek(sock) {
    118  - s.dbgPrint(ylw + "badProx dial ratelimited: " + sock.Endpoint + rst)
     117 + if p5.badProx.Peek(sock) {
     118 + buf := pools.CopABuffer.Get().(*strings.Builder)
     119 + buf.WriteString("badProx dial ratelimited: ")
     120 + buf.WriteString(sock.Endpoint)
     121 + p5.dbgPrint(buf)
    119 122   return false
    120 123   }
    121 124   
    122  - if s.swampopt.stale.Load().(time.Duration) == 0 {
    123  - return true
    124  - }
    125  - since := time.Since(sock.lastValidated.Load().(time.Time))
    126  - if since > s.swampopt.stale.Load().(time.Duration) {
    127  - s.dbgPrint("proxy stale: " + sock.Endpoint)
    128  - go s.Stats.stale()
     125 + if time.Since(sock.lastValidated) > p5.swampopt.stale {
     126 + buf := pools.CopABuffer.Get().(*strings.Builder)
     127 + buf.WriteString("proxy stale: ")
     128 + buf.WriteString(sock.Endpoint)
     129 + p5.dbgPrint(buf)
     130 + go p5.stats.stale()
    129 131   return false
    130 132   }
    131 133   
    skipped 3 lines
  • ■ ■ ■ ■ ■ ■
    example/main.go
    skipped 8 lines
    9 9   "syscall"
    10 10   "time"
    11 11   
    12  - "git.tcp.direct/kayos/prox5"
    13 12   "github.com/haxii/socks5"
    14 13   "github.com/mattn/go-tty"
     14 + 
     15 + "git.tcp.direct/kayos/prox5"
    15 16  )
    16 17   
    17 18  var (
    skipped 28 lines
    46 47   
    47 48  func init() {
    48 49   quit = make(chan bool)
    49  - swamp = prox5.NewDefaultSwamp()
     50 + swamp = prox5.NewProxyEngine()
    50 51   swamp.SetMaxWorkers(5)
    51 52   swamp.EnableDebug()
    52 53   go StartUpstreamProxy("127.0.0.1:1555")
    skipped 116 lines
    169 170   
    170 171   go func() {
    171 172   for {
    172  - fmt.Printf("4: %d, 4a: %d, 5: %d \n", swamp.Stats.Valid4, swamp.Stats.Valid4a, swamp.Stats.Valid5)
     173 + stats := swamp.GetStatistics()
     174 + fmt.Printf("4: %d, 4a: %d, 5: %d \n", stats.Valid4, stats.Valid4a, stats.Valid5)
    173 175   time.Sleep(5 * time.Second)
    174 176   }
    175 177   }()
    skipped 4 lines
  • ■ ■ ■ ■ ■ ■
    getters.go
    1 1  package prox5
    2 2   
    3 3  import (
    4  - "fmt"
    5 4   "strconv"
    6 5   "sync/atomic"
    7 6   "time"
    8  -)
    9 7   
    10  -// GetProto safely retrieves the protocol value of the Proxy.
    11  -func (sock *Proxy) GetProto() string {
    12  - return sock.Proto.Load().(string)
    13  -}
    14  - 
    15  -// GetProto safely retrieves the protocol value of the Proxy.
    16  -func (sock *Proxy) String() string {
    17  - tout := ""
    18  - if sock.parent.GetServerTimeoutStr() != "-1" {
    19  - tout = fmt.Sprintf("?timeout=%ss", sock.parent.GetServerTimeoutStr())
    20  - }
    21  - return fmt.Sprintf("socks%s://%s%s", sock.GetProto(), sock.Endpoint, tout)
    22  -}
     8 + "git.tcp.direct/kayos/common/entropy"
     9 +)
    23 10   
    24 11  // GetStatistics returns all current statistics.
    25 12  // * This is a pointer, do not modify it!
    26  -func (s *Swamp) GetStatistics() *Statistics {
    27  - return s.Stats
     13 +func (pe *Swamp) GetStatistics() *statistics {
     14 + return pe.stats
    28 15  }
    29 16   
    30 17  // RandomUserAgent retrieves a random user agent from our list in string form.
    31  -func (s *Swamp) RandomUserAgent() string {
    32  - s.mu.RLock()
    33  - defer s.mu.RUnlock()
    34  - return randStrChoice(s.swampopt.userAgents)
     18 +func (pe *Swamp) RandomUserAgent() string {
     19 + pe.mu.RLock()
     20 + defer pe.mu.RUnlock()
     21 + return entropy.RandomStrChoice(pe.swampopt.userAgents)
    35 22  }
    36 23   
    37 24  // GetRandomEndpoint returns a random whatismyip style endpoint from our Swamp's options
    38  -func (s *Swamp) GetRandomEndpoint() string {
    39  - s.mu.RLock()
    40  - defer s.mu.RUnlock()
    41  - return randStrChoice(s.swampopt.checkEndpoints)
     25 +func (pe *Swamp) GetRandomEndpoint() string {
     26 + pe.mu.RLock()
     27 + defer pe.mu.RUnlock()
     28 + return entropy.RandomStrChoice(pe.swampopt.checkEndpoints)
    42 29  }
    43 30   
    44 31  // GetStaleTime returns the duration of time after which a proxy will be considered "stale".
    45  -func (s *Swamp) GetStaleTime() time.Duration {
    46  - return s.swampopt.stale.Load().(time.Duration)
     32 +func (pe *Swamp) GetStaleTime() time.Duration {
     33 + pe.swampopt.RLock()
     34 + defer pe.swampopt.RLock()
     35 + return pe.swampopt.stale
    47 36  }
    48 37   
    49 38  // GetValidationTimeout returns the current value of validationTimeout.
    50  -func (s *Swamp) GetValidationTimeout() time.Duration {
    51  - return s.swampopt.validationTimeout.Load().(time.Duration)
     39 +func (pe *Swamp) GetValidationTimeout() time.Duration {
     40 + pe.swampopt.RLock()
     41 + defer pe.swampopt.RLock()
     42 + return pe.swampopt.validationTimeout
    52 43  }
    53 44   
    54 45  // GetValidationTimeoutStr returns the current value of validationTimeout (in seconds string).
    55  -func (s *Swamp) GetValidationTimeoutStr() string {
    56  - timeout := s.swampopt.validationTimeout.Load().(time.Duration)
     46 +func (pe *Swamp) GetValidationTimeoutStr() string {
     47 + pe.swampopt.RLock()
     48 + defer pe.swampopt.RLock()
     49 + timeout := pe.swampopt.validationTimeout
    57 50   return strconv.Itoa(int(timeout / time.Second))
    58 51  }
    59 52   
    60 53  // GetServerTimeout returns the current value of serverTimeout.
    61  -func (s *Swamp) GetServerTimeout() time.Duration {
    62  - return s.swampopt.serverTimeout.Load().(time.Duration)
     54 +func (pe *Swamp) GetServerTimeout() time.Duration {
     55 + pe.swampopt.RLock()
     56 + defer pe.swampopt.RLock()
     57 + return pe.swampopt.serverTimeout
    63 58  }
    64 59   
    65 60  // GetServerTimeoutStr returns the current value of serverTimeout (in seconds string).
    66  -func (s *Swamp) GetServerTimeoutStr() string {
    67  - timeout := s.swampopt.serverTimeout.Load().(time.Duration)
     61 +func (pe *Swamp) GetServerTimeoutStr() string {
     62 + pe.swampopt.RLock()
     63 + defer pe.swampopt.RLock()
     64 + timeout := pe.swampopt.serverTimeout
    68 65   if timeout == time.Duration(0) {
    69 66   return "-1"
    70 67   }
    skipped 1 lines
    72 69  }
    73 70   
    74 71  // GetMaxWorkers returns maximum amount of workers that validate proxies concurrently. Note this is read-only during runtime.
    75  -func (s *Swamp) GetMaxWorkers() int {
    76  - return s.pool.Cap()
     72 +func (pe *Swamp) GetMaxWorkers() int {
     73 + return pe.pool.Cap()
    77 74  }
    78 75   
    79 76  // IsRunning returns true if our background goroutines defined in daemons.go are currently operational
    80  -func (s *Swamp) IsRunning() bool {
    81  - return atomic.LoadInt32(&s.runningdaemons) == 2
     77 +func (pe *Swamp) IsRunning() bool {
     78 + return atomic.LoadInt32(&pe.runningdaemons) == 2
    82 79  }
    83 80   
    84 81  // GetRecyclingStatus retrieves the current recycling status, see EnableRecycling.
    85  -func (s *Swamp) GetRecyclingStatus() bool {
    86  - return s.swampopt.recycle.Load().(bool)
     82 +func (pe *Swamp) GetRecyclingStatus() bool {
     83 + pe.swampopt.RLock()
     84 + defer pe.swampopt.RLock()
     85 + return pe.swampopt.recycle
    87 86  }
    88 87   
    89 88  // GetWorkers retrieves pond worker statistics:
    90  -// * return MaxWorkers, RunningWorkers, IdleWorkers
    91  -func (s *Swamp) GetWorkers() (maxWorkers, runningWorkers, idleWorkers int) {
    92  - s.mu.RLock()
    93  - defer s.mu.RUnlock()
    94  - return s.pool.Cap(), s.pool.Running(), s.pool.Free()
     89 +// - return MaxWorkers, RunningWorkers, IdleWorkers
     90 +func (pe *Swamp) GetWorkers() (maxWorkers, runningWorkers, idleWorkers int) {
     91 + pe.mu.RLock()
     92 + defer pe.mu.RUnlock()
     93 + return pe.pool.Cap(), pe.pool.Running(), pe.pool.Free()
    95 94  }
    96 95   
    97 96  // GetRemoveAfter retrieves the removeafter policy, the amount of times a recycled proxy is marked as bad until it is removed entirely.
    98  -// * returns -1 if recycling is disabled.
    99  -func (s *Swamp) GetRemoveAfter() int {
    100  - s.mu.RLock()
    101  - defer s.mu.RUnlock()
    102  - if !s.swampopt.recycle.Load().(bool) {
     97 +// - returns -1 if recycling is disabled.
     98 +func (pe *Swamp) GetRemoveAfter() int {
     99 + pe.mu.RLock()
     100 + defer pe.mu.RUnlock()
     101 + if !pe.swampopt.recycle {
    103 102   return -1
    104 103   }
    105  - return s.swampopt.removeafter.Load().(int)
     104 + return pe.swampopt.removeafter
    106 105  }
    107 106   
    108 107  // GetDialerBailout retrieves the dialer bailout policy. See SetDialerBailout for more info.
    109  -func (s *Swamp) GetDialerBailout() int {
    110  - return s.swampopt.dialerBailout.Load().(int)
     108 +func (pe *Swamp) GetDialerBailout() int {
     109 + pe.mu.RLock()
     110 + defer pe.mu.RUnlock()
     111 + return pe.swampopt.dialerBailout
     112 +}
     113 + 
     114 +// TODO: Document middleware concept
     115 + 
     116 +func (pe *Swamp) GetDispenseMiddleware() func(*Proxy) (*Proxy, bool) {
     117 + pe.mu.RLock()
     118 + defer pe.mu.RUnlock()
     119 + return pe.dispenseMiddleware
     120 +}
     121 + 
     122 +func (pe *Swamp) GetShuffleStatus() bool {
     123 + pe.mu.RLock()
     124 + defer pe.mu.RUnlock()
     125 + return pe.swampopt.shuffle
    111 126  }
    112 127   
  • ■ ■ ■ ■ ■ ■
    go.mod
    1 1  module git.tcp.direct/kayos/prox5
    2 2   
    3  -go 1.18
     3 +go 1.19
    4 4   
    5 5  require (
    6  - git.tcp.direct/kayos/common v0.7.1
     6 + git.tcp.direct/kayos/common/entropy v0.0.0-20220210125455-40e3d2190a52
    7 7   git.tcp.direct/kayos/go-socks5 v1.0.1
     8 + git.tcp.direct/kayos/socks v0.0.0-20220828111753-f9f7cd3e7ee7
    8 9   github.com/haxii/socks5 v1.0.0
    9 10   github.com/mattn/go-tty v0.0.4
    10 11   github.com/miekg/dns v1.1.50
    11  - github.com/panjf2000/ants/v2 v2.5.0
     12 + github.com/ooni/oohttp v0.3.0
     13 + github.com/refraction-networking/utls v1.1.2
    12 14   github.com/yunginnanet/Rate5 v1.1.0
    13  - golang.org/x/net v0.0.0-20220630215102-69896b714898
    14  - h12.io/socks v1.0.3
     15 + golang.org/x/net v0.0.0-20220921203646-d300de134e69
    15 16   inet.af/netaddr v0.0.0-20220811202034-502d2d690317
    16 17  )
    17 18   
    18 19  require (
    19  - github.com/mattn/go-isatty v0.0.14 // indirect
     20 + github.com/andybalholm/brotli v1.0.4 // indirect
     21 + github.com/klauspost/compress v1.13.6 // indirect
     22 + github.com/mattn/go-isatty v0.0.10 // indirect
    20 23   github.com/patrickmn/go-cache v2.1.0+incompatible // indirect
    21 24   go4.org/intern v0.0.0-20211027215823-ae77deb06f29 // indirect
    22 25   go4.org/unsafe/assume-no-moving-gc v0.0.0-20220617031537-928513b29760 // indirect
     26 + golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa // indirect
    23 27   golang.org/x/mod v0.4.2 // indirect
    24  - golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a // indirect
     28 + golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10 // indirect
     29 + golang.org/x/text v0.3.7 // indirect
    25 30   golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2 // indirect
    26 31   golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
    27  - nullprogram.com/x/rng v1.1.0 // indirect
    28 32  )
    29 33   
  • ■ ■ ■ ■ ■ ■
    go.sum
    1  -git.tcp.direct/kayos/common v0.7.1 h1:srCbByDmcvItXrz82FRhDOv9w+LvWl4sYQzy1bup7rw=
    2  -git.tcp.direct/kayos/common v0.7.1/go.mod h1:Xq+Ln4uK48HttJFNp0uCofNGWaRHSmvAUeB3jlwcv/o=
     1 +git.tcp.direct/kayos/common/entropy v0.0.0-20220210125455-40e3d2190a52 h1:W0cnyiO2VA/QvtxlAjnL2SKUgY7K5waOSFIJgKVKNXA=
     2 +git.tcp.direct/kayos/common/entropy v0.0.0-20220210125455-40e3d2190a52/go.mod h1:TM+NcOy0W2vfTkNIXMhcnJKdDW6YmvRRXNcvh2jCgEA=
    3 3  git.tcp.direct/kayos/go-socks5 v1.0.1 h1:Pe9PlSXofibIJyWkrr9rwWcgyfUxSdUcDCQ//6fAi0U=
    4 4  git.tcp.direct/kayos/go-socks5 v1.0.1/go.mod h1:I9xU/uzFAZKukMJgEgWPrfC6rDlcPQe8wXMibF3qvhE=
    5  -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
     5 +git.tcp.direct/kayos/socks v0.0.0-20220828111753-f9f7cd3e7ee7 h1:zuN2VWun3lu34Lz+LAt/ZbY6YJ0SqzQf2d00YQUfNao=
     6 +git.tcp.direct/kayos/socks v0.0.0-20220828111753-f9f7cd3e7ee7/go.mod h1:KmN5oa1od8tMHmRIr9GOqWKx9MR0oGZVtAj+ARxiPwo=
     7 +github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY=
     8 +github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
    6 9  github.com/dvyukov/go-fuzz v0.0.0-20210103155950-6a8e9d1f2415/go.mod h1:11Gm+ccJnvAhCNLlf5+cS9KjtbaD5I5zaZpFMsTHWTw=
    7 10  github.com/h12w/go-socks5 v0.0.0-20200522160539-76189e178364 h1:5XxdakFhqd9dnXoAZy1Mb2R/DZ6D1e+0bGC/JhucGYI=
    8  -github.com/h12w/go-socks5 v0.0.0-20200522160539-76189e178364/go.mod h1:eDJQioIyy4Yn3MVivT7rv/39gAJTrA7lgmYr8EW950c=
    9 11  github.com/haxii/socks5 v1.0.0 h1:78BIzd4lHibdRNOKdMwKCnnsgYLW9SeotqU+nMhWSSo=
    10 12  github.com/haxii/socks5 v1.0.0/go.mod h1:6O9Ba2yrLlvuSe/L1e84eZI8cPw6H+q1Ilr4hjgm4uY=
     13 +github.com/klauspost/compress v1.13.6 h1:P76CopJELS0TiO2mebmnzgWaajssP/EszplttgQxcgc=
     14 +github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
    11 15  github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
    12 16  github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
     17 +github.com/mattn/go-isatty v0.0.10 h1:qxFzApOv4WsAL965uUPIsXzAKCZxN2p9UqdhFS4ZW10=
    13 18  github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84=
    14  -github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
    15  -github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
    16 19  github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
    17 20  github.com/mattn/go-tty v0.0.4 h1:NVikla9X8MN0SQAqCYzpGyXv0jY7MNl3HOWD2dkle7E=
    18 21  github.com/mattn/go-tty v0.0.4/go.mod h1:u5GGXBtZU6RQoKV8gY5W6UhMudbR5vXnUe7j3pxse28=
    19 22  github.com/miekg/dns v1.1.50 h1:DQUfb9uc6smULcREF09Uc+/Gd46YWqJd5DbpPE9xkcA=
    20 23  github.com/miekg/dns v1.1.50/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME=
    21  -github.com/panjf2000/ants/v2 v2.5.0 h1:1rWGWSnxCsQBga+nQbA4/iY6VMeNoOIAM0ZWh9u3q2Q=
    22  -github.com/panjf2000/ants/v2 v2.5.0/go.mod h1:cU93usDlihJZ5CfRGNDYsiBYvoilLvBF5Qp/BT2GNRE=
     24 +github.com/ooni/oohttp v0.3.0 h1:75OsZKelkLXl6p2UD53dTJyIv+9owWqaL6sMT26LN8w=
     25 +github.com/ooni/oohttp v0.3.0/go.mod h1:fgNDPYw+nsgEKCDBpT/4R06bgnrCRtvgNmAWOCmm4JE=
    23 26  github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
    24 27  github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
    25  -github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2 h1:JhzVVoYvbOACxoUmOs6V/G4D5nPVUW73rKvXxP4XUJc=
    26  -github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2/go.mod h1:iIss55rKnNBTvrwdmkUpLnDpZoAHvWaiq5+iMmen4AE=
    27  -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
    28  -github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY=
     28 +github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5 h1:Ii+DKncOVM8Cu1Hc+ETb5K+23HdAMvESYE3ZJ5b5cMI=
     29 +github.com/refraction-networking/utls v1.1.2 h1:a7GQauRt72VG+wtNm0lnrAaCGlyX47gEi1++dSsDBpw=
     30 +github.com/refraction-networking/utls v1.1.2/go.mod h1:+D89TUtA8+NKVFj1IXWr0p3tSdX1+SqUB7rL0QnGqyg=
    29 31  github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
    30 32  github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
    31 33  github.com/yunginnanet/Rate5 v1.1.0 h1:FGp+IwKju0cTrrM3VffZGZiFgRt1jFXOWRCPwB1HPek=
    skipped 6 lines
    38 40  golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
    39 41  golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
    40 42  golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
     43 +golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa h1:idItI2DDfCokpg0N51B2VtiLdJ4vAuXC9fnCb2gACo4=
     44 +golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
    41 45  golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
    42 46  golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo=
    43 47  golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
    44 48  golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
    45 49  golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
    46 50  golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
     51 +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
    47 52  golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
    48 53  golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
    49  -golang.org/x/net v0.0.0-20220630215102-69896b714898 h1:K7wO6V1IrczY9QOQ2WkVpw4JQSwCd52UsxVEirZUfiw=
    50  -golang.org/x/net v0.0.0-20220630215102-69896b714898/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
     54 +golang.org/x/net v0.0.0-20211111160137-58aab5ef257a/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
     55 +golang.org/x/net v0.0.0-20220921203646-d300de134e69 h1:hUJpGDpnfwdJW8iNypFjmSY0sCBEL+spFTZ2eO+Sfps=
     56 +golang.org/x/net v0.0.0-20220921203646-d300de134e69/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
    51 57  golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
    52 58  golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
    53 59  golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
    skipped 9 lines
    63 69  golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
    64 70  golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
    65 71  golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
     72 +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
    66 73  golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
    67  -golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a h1:dGzPydgVsqGcTRVwiLJ1jVbufYwmzD3LfVPLKsKg+0k=
    68  -golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
     74 +golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10 h1:WIoqL4EROvwiPdUtaip4VcDdpZ4kha7wBWZrbVKCIZg=
     75 +golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
    69 76  golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
    70 77  golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
    71 78  golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
    72 79  golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
     80 +golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
     81 +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
    73 82  golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
    74 83  golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
    75 84  golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
    skipped 3 lines
    79 88  golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
    80 89  golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
    81 90  golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
    82  -gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
    83  -h12.io/socks v1.0.3 h1:Ka3qaQewws4j4/eDQnOdpr4wXsC//dXtWvftlIcCQUo=
    84  -h12.io/socks v1.0.3/go.mod h1:AIhxy1jOId/XCz9BO+EIgNL2rQiPTBNnOfnVnQ+3Eck=
    85 91  inet.af/netaddr v0.0.0-20220811202034-502d2d690317 h1:U2fwK6P2EqmopP/hFLTOAjWTki0qgd4GMJn5X8wOleU=
    86 92  inet.af/netaddr v0.0.0-20220811202034-502d2d690317/go.mod h1:OIezDfdzOgFhuw4HuWapWq2e9l0H9tK4F1j+ETRtF3k=
    87  -nullprogram.com/x/rng v1.1.0 h1:SMU7DHaQSWtKJNTpNFIFt8Wd/KSmOuSDPXrMFp/UMro=
    88  -nullprogram.com/x/rng v1.1.0/go.mod h1:glGw6V87vyfawxCzqOABL3WfL95G65az9Z2JZCylCkg=
    89 93   
  • ■ ■ ■ ■ ■ ■
    internal/randtls/shim.go
     1 +package randtls
     2 + 
     3 +import (
     4 + "context"
     5 + "crypto/tls"
     6 + "net"
     7 + 
     8 + uhttp "github.com/ooni/oohttp"
     9 + utls "github.com/refraction-networking/utls"
     10 +)
     11 + 
     12 +// See: https://github.com/ooni/oohttp/blob/main/example/example-utls/tls.go
     13 + 
     14 +type adapter struct {
     15 + *utls.UConn
     16 + conn net.Conn
     17 +}
     18 + 
     19 +// Asserts that we follow the interface.
     20 +var _ uhttp.TLSConn = &adapter{}
     21 + 
     22 +// ConnectionState implements the tls.ConnectionState interface.
     23 +func (c *adapter) ConnectionState() tls.ConnectionState {
     24 + ustate := c.UConn.ConnectionState()
     25 + return tls.ConnectionState{
     26 + Version: ustate.Version,
     27 + HandshakeComplete: ustate.HandshakeComplete,
     28 + DidResume: ustate.DidResume,
     29 + CipherSuite: ustate.CipherSuite,
     30 + NegotiatedProtocol: ustate.NegotiatedProtocol,
     31 + NegotiatedProtocolIsMutual: ustate.NegotiatedProtocolIsMutual,
     32 + ServerName: ustate.ServerName,
     33 + PeerCertificates: ustate.PeerCertificates,
     34 + VerifiedChains: ustate.VerifiedChains,
     35 + SignedCertificateTimestamps: ustate.SignedCertificateTimestamps,
     36 + OCSPResponse: ustate.OCSPResponse,
     37 + TLSUnique: ustate.TLSUnique,
     38 + }
     39 +}
     40 + 
     41 +// HandshakeContext implements TLSConn's HandshakeContext.
     42 +func (c *adapter) HandshakeContext(ctx context.Context) error {
     43 + errch := make(chan error, 1)
     44 + go func() {
     45 + errch <- c.UConn.Handshake()
     46 + }()
     47 + select {
     48 + case err := <-errch:
     49 + return err
     50 + case <-ctx.Done():
     51 + return ctx.Err()
     52 + }
     53 +}
     54 + 
     55 +// NetConn implements TLSConn's NetConn
     56 +func (c *adapter) NetConn() net.Conn {
     57 + return c.conn
     58 +}
     59 + 
     60 +// utlsFactory creates a new uTLS connection.
     61 +func utlsFactory(conn net.Conn, config *tls.Config) uhttp.TLSConn {
     62 + uConfig := &utls.Config{
     63 + RootCAs: config.RootCAs,
     64 + NextProtos: config.NextProtos,
     65 + ServerName: config.ServerName,
     66 + InsecureSkipVerify: config.InsecureSkipVerify,
     67 + DynamicRecordSizingDisabled: config.DynamicRecordSizingDisabled,
     68 + }
     69 + return &adapter{
     70 + UConn: utls.UClient(conn, uConfig, utls.HelloFirefox_55),
     71 + conn: conn,
     72 + }
     73 +}
     74 + 
  • ■ ■ ■ ■ ■ ■
    list_management.go
    skipped 3 lines
    4 4   "bufio"
    5 5   "io"
    6 6   "os"
    7  - "strconv"
    8 7   "strings"
    9  - "sync/atomic"
    10 8   "time"
    11 9  )
    12 10   
    skipped 6 lines
    19 17  }
    20 18   
    21 19  // LoadProxyTXT loads proxies from a given seed file and feeds them to the mapBuilder to be later queued automatically for validation.
    22  -// Expects the following formats:
    23  -// * 127.0.0.1:1080
    24  -// * 127.0.0.1:1080:user:pass
    25  -// * yeet.com:1080
    26  -// * yeet.com:1080:user:pass
    27  -// * [fe80::2ef0:5dff:fe7f:c299]:1080
    28  -// * [fe80::2ef0:5dff:fe7f:c299]:1080:user:pass
    29  -func (s *Swamp) LoadProxyTXT(seedFile string) int {
    30  - var count = &atomic.Value{}
    31  - count.Store(0)
    32  - 
     20 +// Expects one of the following formats for each line:
     21 +// * 127.0.0.1:1080
     22 +// * 127.0.0.1:1080:user:pass
     23 +// * yeet.com:1080
     24 +// * yeet.com:1080:user:pass
     25 +// * [fe80::2ef0:5dff:fe7f:c299]:1080
     26 +// * [fe80::2ef0:5dff:fe7f:c299]:1080:user:pass
     27 +func (p5 *Swamp) LoadProxyTXT(seedFile string) (count int) {
    33 28   f, err := os.Open(seedFile)
    34 29   if err != nil {
    35  - s.dbgPrint(red + err.Error() + rst)
     30 + p5.dbgPrint(simpleString(err.Error()))
    36 31   return 0
    37 32   }
    38 33   
    39  - s.dbgPrint("LoadProxyTXT start: " + seedFile)
    40 34   defer func() {
    41  - s.dbgPrint("LoadProxyTXT finished: " + strconv.Itoa(count.Load().(int)))
    42 35   if err := f.Close(); err != nil {
    43  - s.dbgPrint(red + err.Error() + rst)
     36 + p5.dbgPrint(simpleString(err.Error()))
    44 37   }
    45 38   }()
    46 39   
    47 40   bs, err := io.ReadAll(f)
    48 41   if err != nil {
    49  - s.dbgPrint(red + err.Error() + rst)
     42 + p5.dbgPrint(simpleString(err.Error()))
    50 43   return 0
    51 44   }
    52 45   sockstr := string(bs)
    53 46   
    54  - count.Store(s.LoadMultiLineString(sockstr))
    55  - return count.Load().(int)
     47 + return p5.LoadMultiLineString(sockstr)
    56 48  }
    57 49   
    58  -// LoadSingleProxy loads a SOCKS proxy into our map. Uses the format: 127.0.0.1:1080 (host:port).
    59  -func (s *Swamp) LoadSingleProxy(sock string) (ok bool) {
     50 +// LoadSingleProxy loads a SOCKS proxy into our map.
     51 +// Expects one of the following formats:
     52 +// * 127.0.0.1:1080
     53 +// * 127.0.0.1:1080:user:pass
     54 +// * yeet.com:1080
     55 +// * yeet.com:1080:user:pass
     56 +// * [fe80::2ef0:5dff:fe7f:c299]:1080
     57 +// * [fe80::2ef0:5dff:fe7f:c299]:1080:user:pass
     58 +func (p5 *Swamp) LoadSingleProxy(sock string) (ok bool) {
    60 59   if sock, ok = filter(sock); !ok {
    61 60   return
    62 61   }
    63  - go s.loadSingleProxy(sock)
     62 + go p5.loadSingleProxy(sock)
    64 63   return
    65 64  }
    66 65   
    67  -func (s *Swamp) loadSingleProxy(sock string) {
     66 +func (p5 *Swamp) loadSingleProxy(sock string) {
    68 67   for {
    69 68   select {
    70 69   case inChan <- sock:
    skipped 4 lines
    75 74   }
    76 75  }
    77 76   
    78  -// LoadMultiLineString loads a multiine string object with one (host:port) SOCKS proxy per line.
    79  -func (s *Swamp) LoadMultiLineString(socks string) int {
     77 +// LoadMultiLineString loads a multiine string object with proxy per line.
     78 +// Expects one of the following formats for each line:
     79 +// * 127.0.0.1:1080
     80 +// * 127.0.0.1:1080:user:pass
     81 +// * yeet.com:1080
     82 +// * yeet.com:1080:user:pass
     83 +// * [fe80::2ef0:5dff:fe7f:c299]:1080
     84 +// * [fe80::2ef0:5dff:fe7f:c299]:1080:user:pass
     85 +func (p5 *Swamp) LoadMultiLineString(socks string) int {
    80 86   var count int
    81 87   scan := bufio.NewScanner(strings.NewReader(socks))
    82 88   for scan.Scan() {
    83  - if s.LoadSingleProxy(scan.Text()) {
     89 + if p5.LoadSingleProxy(scan.Text()) {
    84 90   count++
    85 91   }
    86 92   }
    skipped 2 lines
    89 95   
    90 96  // ClearSOCKSList clears the map of proxies that we have on record.
    91 97  // Other operations (proxies that are still in buffered channels) will continue.
    92  -func (s *Swamp) ClearSOCKSList() {
    93  - s.swampmap.clear()
     98 +func (p5 *Swamp) ClearSOCKSList() {
     99 + p5.swampmap.clear()
    94 100  }
    95 101   
  • ■ ■ ■ ■ ■ ■
    logger/logger.go
     1 +package logger
     2 + 
     3 +type Logger interface {
     4 + Print(str string)
     5 + Printf(format string, a ...interface{})
     6 +}
     7 + 
  • ■ ■ ■ ■ ■ ■
    mr_worldwide.go
     1 +package prox5
     2 + 
     3 +import (
     4 + "context"
     5 + "crypto/tls"
     6 + "net"
     7 + "net/http"
     8 +)
     9 + 
     10 +// GetHTTPClient retrieves a pointer to an http.Client powered by MysteryDialer.
     11 +func (pe *Swamp) GetHTTPClient() *http.Client {
     12 + // var htp func(*http.Request) (*url.URL, error)
     13 + var dctx func(ctx context.Context, network string, addr string) (net.Conn, error)
     14 + // if httun, htok := pe.GetHTTPTunnel(); htok {
     15 + // httprox, uerr := url.Parse("http://" + httun.Endpoint)
     16 + // if uerr == nil {
     17 + // htp = http.ProxyURL(httprox)
     18 + // }
     19 + // }
     20 + // if htp == nil {
     21 + dctx = pe.DialContext
     22 + // }
     23 + return &http.Client{
     24 + Transport: &http.Transport{
     25 + // Proxy: htp,
     26 + DialContext: dctx,
     27 + TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
     28 + TLSHandshakeTimeout: pe.GetServerTimeout(),
     29 + DisableKeepAlives: true,
     30 + DisableCompression: false,
     31 + MaxIdleConnsPerHost: 5,
     32 + IdleConnTimeout: pe.GetServerTimeout(),
     33 + ResponseHeaderTimeout: pe.GetServerTimeout(),
     34 + },
     35 + Timeout: pe.GetServerTimeout(),
     36 + }
     37 +}
     38 + 
     39 +// RoundTrip is Mr. WorldWide. Obviously. See: https://pkg.go.dev/net/http#RoundTripper
     40 +func (pe *Swamp) RoundTrip(req *http.Request) (*http.Response, error) {
     41 + return pe.GetHTTPClient().Do(req)
     42 +}
     43 + 
  • ■ ■ ■ ■ ■ ■
    mrworldwide.go
    1  -package prox5
    2  - 
    3  -import (
    4  - "crypto/tls"
    5  - "net/http"
    6  -)
    7  - 
    8  -// GetHTTPClient retrieves a pointer to an http.Client powered by MysteryDialer.
    9  -func (s *Swamp) GetHTTPClient() *http.Client {
    10  - if s.httpClient != nil {
    11  - return s.httpClient
    12  - }
    13  - s.httpClient = &http.Client{
    14  - Transport: &http.Transport{
    15  - DialContext: s.DialContext,
    16  - DisableKeepAlives: true,
    17  - TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
    18  - TLSHandshakeTimeout: s.GetServerTimeout(),
    19  - },
    20  - Timeout: s.GetServerTimeout(),
    21  - }
    22  - return s.httpClient
    23  -}
    24  - 
    25  -// RoundTrip is Mr. WorldWide. Obviously. See: https://pkg.go.dev/net/http#RoundTripper
    26  -func (s *Swamp) RoundTrip(req *http.Request) (*http.Response, error) {
    27  - return s.GetHTTPClient().Do(req)
    28  -}
    29  - 
  • ■ ■ ■ ■ ■ ■
    mystery_dialer.go
    skipped 1 lines
    2 2   
    3 3  import (
    4 4   "context"
    5  - "errors"
    6 5   "fmt"
    7 6   "net"
    8  - "strconv"
     7 + "strings"
    9 8   "sync/atomic"
    10 9   "time"
    11 10   
    12  - "h12.io/socks"
     11 + "git.tcp.direct/kayos/socks"
     12 + 
     13 + "git.tcp.direct/kayos/prox5/internal/pools"
    13 14  )
    14 15   
    15  -// DialContext is a simple stub adapter to implement a net.Dialer with context.
    16  -func (s *Swamp) DialContext(ctx context.Context, network, addr string) (net.Conn, error) {
    17  - return s.MysteryDialer(ctx, network, addr)
     16 +// DialContext is a simple stub adapter to implement a net.Dialer.
     17 +func (p5 *Swamp) DialContext(ctx context.Context, network, addr string) (net.Conn, error) {
     18 + return p5.MysteryDialer(ctx, network, addr)
    18 19  }
    19 20   
    20 21  // Dial is a simple stub adapter to implement a net.Dialer.
    21  -func (s *Swamp) Dial(network, addr string) (net.Conn, error) {
    22  - return s.DialContext(context.Background(), network, addr)
     22 +func (p5 *Swamp) Dial(network, addr string) (net.Conn, error) {
     23 + return p5.MysteryDialer(context.Background(), network, addr)
    23 24  }
    24 25   
    25 26  // DialTimeout is a simple stub adapter to implement a net.Dialer with a timeout.
    26  -func (s *Swamp) DialTimeout(network, addr string, timeout time.Duration) (net.Conn, error) {
     27 +func (p5 *Swamp) DialTimeout(network, addr string, timeout time.Duration) (net.Conn, error) {
    27 28   ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(timeout))
    28  - go func() {
     29 + go func() { // this is a goroutine that calls cancel() upon the deadline expiring to avoid context leaks
     30 + <-ctx.Done()
     31 + cancel()
     32 + }()
     33 + return p5.MysteryDialer(ctx, network, addr)
     34 +}
     35 + 
     36 +func (p5 *Swamp) addTimeout(socksString string) string {
     37 + tout := pools.CopABuffer.Get().(*strings.Builder)
     38 + tout.WriteString(socksString)
     39 + tout.WriteString("?timeout=")
     40 + tout.WriteString(p5.GetServerTimeoutStr())
     41 + tout.WriteRune('s')
     42 + socksString = tout.String()
     43 + pools.DiscardBuffer(tout)
     44 + return socksString
     45 +}
     46 + 
     47 +func (p5 *Swamp) popSockAndLockIt(ctx context.Context) (*Proxy, error) {
     48 + sock := p5.GetAnySOCKS(false)
     49 + socksString := sock.String()
     50 + select {
     51 + case <-ctx.Done():
     52 + return nil, fmt.Errorf("context done: %w", ctx.Err())
     53 + default:
     54 + if atomic.CompareAndSwapUint32(&sock.lock, stateUnlocked, stateLocked) {
     55 + p5.msgGotLock(socksString)
     56 + return sock, nil
     57 + }
    29 58   select {
    30  - case <-ctx.Done():
    31  - cancel()
     59 + case p5.Pending <- sock:
     60 + p5.msgCantGetLock(socksString, true)
     61 + return nil, nil
     62 + default:
     63 + p5.msgCantGetLock(socksString, false)
     64 + return nil, nil
    32 65   }
    33  - }()
    34  - return s.MysteryDialer(ctx, network, addr)
     66 + }
    35 67  }
    36 68   
    37 69  // MysteryDialer is a dialer function that will use a different proxy for every request.
    38  -func (s *Swamp) MysteryDialer(ctx context.Context, network, addr string) (net.Conn, error) {
    39  - var sock *Proxy
    40  - var conn net.Conn
    41  - var count int
     70 +func (p5 *Swamp) MysteryDialer(ctx context.Context, network, addr string) (net.Conn, error) {
    42 71   // pull down proxies from channel until we get a proxy good enough for our spoiled asses
     72 + var count = 0
    43 73   for {
    44  - select {
    45  - case <-ctx.Done():
    46  - return nil, ctx.Err()
    47  - default:
    48  - }
    49  - max := s.GetDialerBailout()
     74 + max := p5.GetDialerBailout()
    50 75   if count > max {
    51  - return nil, errors.New("giving up after " + strconv.Itoa(max) + " tries")
     76 + return nil, fmt.Errorf("giving up after %d tries", max)
    52 77   }
    53 78   if err := ctx.Err(); err != nil {
    54  - return nil, fmt.Errorf("context error: %v", err)
     79 + return nil, fmt.Errorf("context error: %w", err)
    55 80   }
    56  - 
    57  - sock = s.GetAnySOCKS()
    58  - for !atomic.CompareAndSwapUint32(&sock.lock, stateUnlocked, stateLocked) {
    59  - if sock == nil {
     81 + var sock *Proxy
     82 + for {
     83 + var err error
     84 + sock, err = p5.popSockAndLockIt(ctx)
     85 + if err != nil {
     86 + return nil, err
     87 + }
     88 + if sock != nil {
    60 89   break
    61 90   }
    62  - randSleep()
    63 91   }
    64  - if sock == nil {
     92 + socksString := sock.String()
     93 + var ok bool
     94 + if sock, ok = p5.dispenseMiddleware(sock); !ok {
     95 + atomic.StoreUint32(&sock.lock, stateUnlocked)
     96 + p5.msgFailedMiddleware(socksString)
    65 97   continue
    66 98   }
    67  - 
    68  - s.dbgPrint("dialer trying: " + sock.Endpoint + "...")
     99 + p5.msgTry(socksString)
    69 100   atomic.StoreUint32(&sock.lock, stateUnlocked)
    70  - dialSocks := socks.Dial(sock.String())
    71  - var err error
    72  - if conn, err = dialSocks(network, addr); err != nil {
     101 + dialSocks := socks.Dial(socksString)
     102 + conn, err := dialSocks(network, addr)
     103 + if err != nil {
    73 104   count++
    74  - s.dbgPrint(ylw + "unable to reach [redacted] with " + sock.String() + ", cycling..." + rst)
     105 + p5.msgUnableToReach(socksString, addr, err)
    75 106   continue
    76 107   }
    77  - break
     108 + p5.msgUsingProxy(socksString)
     109 + return conn, nil
    78 110   }
    79  - s.dbgPrint(grn + "MysteryDialer using socks: " + sock.String() + rst)
    80  - return conn, nil
    81 111  }
    82 112   
  • ■ ■ ■ ■ ■ ■
    mystery_resolver.go
     1 +package prox5
     2 + 
     3 +/*
     4 +import (
     5 + "context"
     6 + "net"
     7 + 
     8 + "inet.af/netaddr"
     9 +)
     10 + 
     11 +type dnsCacheEntry []netaddr.IP
     12 + 
     13 +var dnsCache = make(map[string]dnsCacheEntry)
     14 + 
     15 +func (pe *Swamp) Resolve(ctx context.Context, name string) (context.Context, net.IP, error) {
     16 + var result net.IP
     17 + for {
     18 + select {
     19 + case <-ctx.Done():
     20 + return ctx, nil, ctx.Err()
     21 + }
     22 + }
     23 +}
     24 +*/
     25 + 
  • ■ ■ ■ ■ ■ ■
    proto.go
     1 +package prox5
     2 + 
     3 +import (
     4 + "strings"
     5 + "sync"
     6 + "sync/atomic"
     7 +)
     8 + 
     9 +type ProxyProtocol int8
     10 + 
     11 +const (
     12 + // ProtoNull is a null value for ProxyProtocol.
     13 + ProtoNull ProxyProtocol = iota
     14 + ProtoSOCKS4
     15 + ProtoSOCKS4a
     16 + ProtoSOCKS5
     17 + ProtoHTTP
     18 +)
     19 + 
     20 +var protoMap = map[ProxyProtocol]string{
     21 + ProtoSOCKS5: "socks5", ProtoNull: "", ProtoSOCKS4: "socks4", ProtoSOCKS4a: "socks4a",
     22 +}
     23 + 
     24 +func (p ProxyProtocol) String() string {
     25 + return protoMap[p]
     26 +}
     27 + 
     28 +type proto struct {
     29 + proto *atomic.Value
     30 + // immutable
     31 + *sync.Once
     32 +}
     33 + 
     34 +func newImmutableProto() proto {
     35 + p := proto{
     36 + proto: &atomic.Value{},
     37 + Once: &sync.Once{},
     38 + }
     39 + p.proto.Store(ProtoNull)
     40 + return p
     41 +}
     42 + 
     43 +func (p *proto) Get() ProxyProtocol {
     44 + return p.proto.Load().(ProxyProtocol)
     45 +}
     46 + 
     47 +func (p *proto) set(proxyproto ProxyProtocol) {
     48 + p.Do(func() {
     49 + p.proto.Store(proxyproto)
     50 + })
     51 +}
     52 + 
     53 +func (p ProxyProtocol) writeProtoString(builder *strings.Builder) {
     54 + builder.WriteString(p.String())
     55 +}
     56 + 
     57 +func (p ProxyProtocol) writeProtoURI(builder *strings.Builder) {
     58 + p.writeProtoString(builder)
     59 + builder.WriteString("://")
     60 +}
     61 + 
  • ■ ■ ■ ■ ■ ■
    proxy.go
     1 +package prox5
     2 + 
     3 +import (
     4 + "strings"
     5 + "time"
     6 + 
     7 + rl "github.com/yunginnanet/Rate5"
     8 + 
     9 + "git.tcp.direct/kayos/prox5/internal/pools"
     10 +)
     11 + 
     12 +// https://pkg.go.dev/github.com/yunginnanet/Rate5#Policy
     13 +var defaultUseProxyRatelimiter = rl.Policy{
     14 + Window: 55,
     15 + Burst: 55,
     16 +}
     17 + 
     18 +var defaultBadProxyRateLimiter = rl.Policy{
     19 + Window: 55,
     20 + Burst: 25,
     21 +}
     22 + 
     23 +const (
     24 + stateUnlocked uint32 = iota
     25 + stateLocked
     26 +)
     27 + 
     28 +// Proxy represents an individual proxy
     29 +type Proxy struct {
     30 + // Endpoint is the address:port of the proxy that we connect to
     31 + Endpoint string
     32 + // ProxiedIP is the address that we end up having when making proxied requests through this proxy
     33 + ProxiedIP string
     34 + // protocol is the version/Protocol (currently SOCKS* only) of the proxy
     35 + protocol proto
     36 + // lastValidated is the time this proxy was last verified working
     37 + lastValidated time.Time
     38 + // timesValidated is the amount of times the proxy has been validated.
     39 + timesValidated int64
     40 + // timesBad is the amount of times the proxy has been marked as bad.
     41 + timesBad int64
     42 + 
     43 + parent *Swamp
     44 + lock uint32
     45 +}
     46 + 
     47 +// UniqueKey is an implementation of the Identity interface from Rate5.
     48 +// See: https://pkg.go.dev/github.com/yunginnanet/Rate5#Identity
     49 +func (sock *Proxy) UniqueKey() string {
     50 + return sock.Endpoint
     51 +}
     52 + 
     53 +// GetProto retrieves the known protocol value of the Proxy.
     54 +func (sock *Proxy) GetProto() ProxyProtocol {
     55 + return sock.protocol.Get()
     56 +}
     57 + 
     58 +// GetProto safely retrieves the protocol value of the Proxy.
     59 +func (sock *Proxy) String() string {
     60 + tout := ""
     61 + if sock.parent.GetServerTimeoutStr() != "-1" {
     62 + tbuf := pools.CopABuffer.Get().(*strings.Builder)
     63 + tbuf.WriteString("?timeout=")
     64 + tbuf.WriteString(sock.parent.GetServerTimeoutStr())
     65 + tbuf.WriteString("s")
     66 + tout = tbuf.String()
     67 + pools.DiscardBuffer(tbuf)
     68 + }
     69 + buf := pools.CopABuffer.Get().(*strings.Builder)
     70 + buf.WriteString(sock.GetProto().String())
     71 + buf.WriteString("://")
     72 + buf.WriteString(sock.Endpoint)
     73 + if tout != "" {
     74 + buf.WriteString(tout)
     75 + }
     76 + out := buf.String()
     77 + pools.DiscardBuffer(buf)
     78 + return out
     79 +}
     80 + 
  • ■ ■ ■ ■ ■ ■
    setters.go
    skipped 1 lines
    2 2   
    3 3  import (
    4 4   "time"
     5 + 
     6 + "git.tcp.direct/kayos/prox5/logger"
    5 7  )
    6 8   
    7 9  // AddUserAgents appends to the list of useragents we randomly choose from during proxied requests
    8  -func (s *Swamp) AddUserAgents(uagents []string) {
    9  - s.mu.Lock()
    10  - defer s.mu.Unlock()
    11  - s.swampopt.userAgents = append(s.swampopt.userAgents, uagents...)
     10 +func (pe *Swamp) AddUserAgents(uagents []string) {
     11 + pe.mu.Lock()
     12 + defer pe.mu.Unlock()
     13 + pe.swampopt.userAgents = append(pe.swampopt.userAgents, uagents...)
    12 14  }
    13 15   
    14 16  // SetUserAgents sets the list of useragents we randomly choose from during proxied requests
    15  -func (s *Swamp) SetUserAgents(uagents []string) {
    16  - s.mu.Lock()
    17  - defer s.mu.Unlock()
    18  - s.swampopt.userAgents = uagents
     17 +func (pe *Swamp) SetUserAgents(uagents []string) {
     18 + pe.mu.Lock()
     19 + defer pe.mu.Unlock()
     20 + pe.swampopt.userAgents = uagents
    19 21  }
    20 22   
    21 23  // SetCheckEndpoints replaces the running list of whatismyip style endpoitns for validation. (must return only the WAN IP)
    22  -func (s *Swamp) SetCheckEndpoints(newendpoints []string) {
    23  - s.mu.Lock()
    24  - defer s.mu.Unlock()
    25  - s.swampopt.checkEndpoints = newendpoints
     24 +func (pe *Swamp) SetCheckEndpoints(newendpoints []string) {
     25 + pe.mu.Lock()
     26 + defer pe.mu.Unlock()
     27 + pe.swampopt.checkEndpoints = newendpoints
    26 28  }
    27 29   
    28 30  // AddCheckEndpoints appends entries to the running list of whatismyip style endpoitns for validation. (must return only the WAN IP)
    29  -func (s *Swamp) AddCheckEndpoints(endpoints []string) {
    30  - s.mu.Lock()
    31  - defer s.mu.Unlock()
    32  - s.swampopt.checkEndpoints = append(s.swampopt.checkEndpoints, endpoints...)
     31 +func (pe *Swamp) AddCheckEndpoints(endpoints []string) {
     32 + pe.mu.Lock()
     33 + defer pe.mu.Unlock()
     34 + pe.swampopt.checkEndpoints = append(pe.swampopt.checkEndpoints, endpoints...)
    33 35  }
    34 36   
    35 37  // SetStaleTime replaces the duration of time after which a proxy will be considered "stale". stale proxies will be skipped upon retrieval.
    36  -func (s *Swamp) SetStaleTime(newtime time.Duration) {
    37  - s.swampopt.stale.Store(newtime)
     38 +func (pe *Swamp) SetStaleTime(newtime time.Duration) {
     39 + pe.swampopt.Lock()
     40 + defer pe.swampopt.Unlock()
     41 + pe.swampopt.stale = newtime
    38 42  }
    39 43   
    40 44  // SetValidationTimeout sets the validationTimeout option.
    41  -func (s *Swamp) SetValidationTimeout(timeout time.Duration) {
    42  - s.swampopt.validationTimeout.Store(timeout)
     45 +func (pe *Swamp) SetValidationTimeout(timeout time.Duration) {
     46 + pe.swampopt.Lock()
     47 + defer pe.swampopt.Unlock()
     48 + pe.swampopt.validationTimeout = timeout
    43 49  }
    44 50   
    45 51  // SetServerTimeout sets the serverTimeout option.
    46 52  // * serverTimeout defines the timeout for outgoing connections made with the MysteryDialer.
    47 53  // * To disable timeout on outgoing MysteryDialer connections, set this to time.Duration(0).
    48  -func (s *Swamp) SetServerTimeout(timeout time.Duration) {
    49  - s.swampopt.serverTimeout.Store(timeout)
     54 +func (pe *Swamp) SetServerTimeout(timeout time.Duration) {
     55 + pe.swampopt.Lock()
     56 + defer pe.swampopt.Unlock()
     57 + pe.swampopt.serverTimeout = timeout
    50 58  }
    51 59   
    52 60  // SetMaxWorkers set the maximum workers for proxy checking and clears the current proxy map and worker pool jobs.
    53  -func (s *Swamp) SetMaxWorkers(num int) {
    54  - s.pool.Tune(num)
     61 +func (pe *Swamp) SetMaxWorkers(num int) {
     62 + pe.pool.Tune(num)
    55 63  }
    56 64   
    57 65  // EnableRecycling enables recycling used proxies back into the pending channel for revalidation after dispensed.
    58  -func (s *Swamp) EnableRecycling() {
    59  - s.swampopt.recycle.Store(true)
     66 +func (pe *Swamp) EnableRecycling() {
     67 + pe.swampopt.Lock()
     68 + defer pe.swampopt.Unlock()
     69 + pe.swampopt.recycle = true
    60 70  }
    61 71   
    62 72  // DisableRecycling disables recycling used proxies back into the pending channel for revalidation after dispensed.
    63  -func (s *Swamp) DisableRecycling() {
    64  - s.swampopt.recycle.Store(false)
     73 +func (pe *Swamp) DisableRecycling() {
     74 + pe.swampopt.Lock()
     75 + defer pe.swampopt.Unlock()
     76 + pe.swampopt.recycle = false
    65 77  }
    66 78   
    67 79  // SetRemoveAfter sets the removeafter policy, the amount of times a recycled proxy is marked as bad before it is removed entirely.
    68  -// * Default is 5
    69  -// * To disable deleting entirely, set this value to -1
    70  -// * Only applies when recycling is enabled
    71  -func (s *Swamp) SetRemoveAfter(timesfailed int) {
    72  - s.swampopt.removeafter.Store(timesfailed)
     80 +// - Default is 10
     81 +// - To disable deleting entirely, set this value to -1
     82 +// - Only applies when recycling is enabled
     83 +func (pe *Swamp) SetRemoveAfter(timesfailed int) {
     84 + pe.swampopt.Lock()
     85 + defer pe.swampopt.Unlock()
     86 + pe.swampopt.removeafter = timesfailed
    73 87  }
    74 88   
    75 89  // SetDialerBailout sets the amount of times the MysteryDialer will dial out and fail before it bails out.
    76  -// * The dialer will attempt to redial a destination with a different proxy a specified amount of times before it gives up
    77  -func (s *Swamp) SetDialerBailout(dialattempts int) {
    78  - s.swampopt.dialerBailout.Store(dialattempts)
     90 +// - The dialer will attempt to redial a destination with a different proxy a specified amount of times before it gives up
     91 +func (pe *Swamp) SetDialerBailout(dialattempts int) {
     92 + pe.swampopt.Lock()
     93 + defer pe.swampopt.Unlock()
     94 + pe.swampopt.dialerBailout = dialattempts
     95 +}
     96 + 
     97 +// SetDispenseMiddleware will add a function that sits within the dialing process of the MysteryDialer and anyhing using it.
     98 +// This means this function will be called mid-dial during connections. Return true to approve proxy, false to skip it.
     99 +// Take care modiying the proxy in-flight as it is a pointer.
     100 +func (pe *Swamp) SetDispenseMiddleware(f func(*Proxy) (*Proxy, bool)) {
     101 + pe.mu.Lock()
     102 + defer pe.mu.Unlock()
     103 + pe.dispenseMiddleware = f
     104 +}
     105 + 
     106 +// SetDebugLogger sets the debug logger for the Swamp. See the Logger interface for implementation details.
     107 +func (pe *Swamp) SetDebugLogger(l logger.Logger) {
     108 + debugHardLock.Lock()
     109 + pe.mu.Lock()
     110 + pe.DebugLogger = l
     111 + pe.mu.Unlock()
     112 + debugHardLock.Unlock()
     113 +}
     114 + 
     115 +func (pe *Swamp) SetShuffle(shuffle bool) {
     116 + pe.mu.Lock()
     117 + defer pe.mu.Unlock()
     118 + pe.swampopt.shuffle = shuffle
    79 119  }
    80 120   
  • ■ ■ ■ ■ ■ ■
    socks5_server.go
    1 1  package prox5
    2 2   
    3 3  import (
    4  - "fmt"
     4 + "strings"
    5 5   
    6 6   "git.tcp.direct/kayos/go-socks5"
     7 + 
     8 + "git.tcp.direct/kayos/prox5/internal/pools"
    7 9  )
    8  - 
    9  -type socksLogger struct {
    10  - parent *Swamp
    11  -}
    12  - 
    13  -// Printf is used to handle socks server logging.
    14  -func (s socksLogger) Printf(format string, a ...interface{}) {
    15  - s.parent.dbgPrint(fmt.Sprintf(format, a...))
    16  -}
    17 10   
    18 11  type socksCreds struct {
    19 12   username string
    skipped 11 lines
    31 24  // StartSOCKS5Server starts our rotating proxy SOCKS5 server.
    32 25  // listen is standard Go listen string, e.g: "127.0.0.1:1080".
    33 26  // username and password are used for authenticatig to the SOCKS5 server.
    34  -func (s *Swamp) StartSOCKS5Server(listen, username, password string) error {
    35  - s.socks5ServerAuth = socksCreds{username: username, password: password}
     27 +func (pe *Swamp) StartSOCKS5Server(listen, username, password string) error {
    36 28   
    37 29   conf := &socks5.Config{
    38  - Credentials: s.socks5ServerAuth,
    39  - Logger: s.socksServerLogger,
    40  - Dial: s.MysteryDialer,
     30 + Credentials: socksCreds{username: username, password: password},
     31 + Logger: pe.DebugLogger,
     32 + Dial: pe.MysteryDialer,
     33 + // Resolver: pe.MysteryResolver,
    41 34   }
    42 35   
    43  - s.dbgPrint("listening for SOCKS5 connections on " + listen)
     36 + buf := pools.CopABuffer.Get().(*strings.Builder)
     37 + buf.WriteString("listening for SOCKS5 connections on ")
     38 + buf.WriteString(listen)
     39 + pe.dbgPrint(buf)
    44 40   
    45 41   server, err := socks5.New(conf)
    46 42   if err != nil {
    skipped 6 lines
  • ■ ■ ■ ■ ■ ■
    stats.go
    1 1  package prox5
    2 2   
    3 3  import (
    4  - "sync"
    5 4   "time"
    6 5  )
    7 6   
    8  -// Statistics is used to encapsulate various swampy stats
    9  -type Statistics struct {
     7 +// statistics is used to encapsulate various swampy stats
     8 +type statistics struct {
    10 9   // Valid4 is the amount of SOCKS4 proxies validated
    11  - Valid4 int
     10 + Valid4 int64
    12 11   // Valid4a is the amount of SOCKS4a proxies validated
    13  - Valid4a int
     12 + Valid4a int64
    14 13   // Valid5 is the amount of SOCKS5 proxies validated
    15  - Valid5 int
     14 + Valid5 int64
    16 15   // ValidHTTP is the amount of HTTP proxies validated
    17  - ValidHTTP int
     16 + ValidHTTP int64
    18 17   // Dispensed is a simple ticker to keep track of proxies dispensed via our getters
    19  - Dispensed int
     18 + Dispensed int64
    20 19   // Stale is the amount of proxies that failed our stale policy upon dispensing
    21  - Stale int
     20 + Stale int64
    22 21   // Checked is the amount of proxies we've checked.
    23  - Checked int
     22 + Checked int64
    24 23   // birthday represents the time we started checking proxies with this pool
    25 24   birthday time.Time
    26  - mu *sync.Mutex
    27 25  }
    28 26   
    29  -func (stats *Statistics) dispense() {
     27 +func (stats *statistics) dispense() {
    30 28   stats.Dispensed++
    31 29  }
    32 30   
    33  -func (stats *Statistics) stale() {
     31 +func (stats *statistics) stale() {
    34 32   stats.Stale++
    35 33  }
    36 34   
    37  -func (stats *Statistics) v4() {
     35 +func (stats *statistics) v4() {
    38 36   stats.Valid4++
    39 37  }
    40 38   
    41  -func (stats *Statistics) v4a() {
     39 +func (stats *statistics) v4a() {
    42 40   stats.Valid4a++
    43 41  }
    44 42   
    45  -func (stats *Statistics) v5() {
     43 +func (stats *statistics) v5() {
    46 44   stats.Valid5++
    47 45  }
    48 46   
    49  -func (stats *Statistics) http() {
     47 +func (stats *statistics) http() {
    50 48   stats.ValidHTTP++
    51 49  }
    52 50   
    53 51  // GetTotalValidated retrieves our grand total validated proxy count.
    54  -func (s *Swamp) GetTotalValidated() int {
    55  - return s.Stats.Valid4a + s.Stats.Valid4 + s.Stats.Valid5 + s.Stats.ValidHTTP
     52 +func (pe *Swamp) GetTotalValidated() int {
     53 + stats := pe.GetStatistics()
     54 + return int(stats.Valid4a + stats.Valid4 + stats.Valid5 + stats.ValidHTTP)
    56 55  }
    57 56   
    58 57  // GetUptime returns the total lifetime duration of our pool.
    59  -func (stats *Statistics) GetUptime() time.Duration {
     58 +func (stats *statistics) GetUptime() time.Duration {
    60 59   return time.Since(stats.birthday)
    61 60  }
    62 61   
  • ■ ■ ■ ■ ■ ■
    util.go
    1  -package prox5
    2  - 
    3  -import (
    4  - "git.tcp.direct/kayos/common/entropy"
    5  -)
    6  - 
    7  -const (
    8  - grn = "\033[32m"
    9  - red = "\033[31m"
    10  - ylw = "\033[33m"
    11  - rst = "\033[0m"
    12  -)
    13  - 
    14  -// randStrChoice returns a random element from the given string slice.
    15  -func randStrChoice(choices []string) string {
    16  - return entropy.RandomStrChoice(choices)
    17  -}
    18  - 
    19  -func randSleep() {
    20  - entropy.RandSleepMS(200)
    21  -}
    22  - 
  • ■ ■ ■ ■ ■ ■
    validator_engine.go
    skipped 3 lines
    4 4   "bytes"
    5 5   "crypto/tls"
    6 6   "errors"
    7  - "fmt"
    8 7   "io"
    9 8   "net"
    10 9   "net/http"
     10 + "net/url"
    11 11   "strings"
    12  - // "net/url"
    13 12   "sync/atomic"
    14 13   "time"
    15 14   
     15 + "git.tcp.direct/kayos/socks"
    16 16   "golang.org/x/net/proxy"
    17  - "h12.io/socks"
     17 + 
     18 + "git.tcp.direct/kayos/prox5/internal/pools"
    18 19  )
    19 20   
    20  -func (s *Swamp) prepHTTP() (*http.Client, *http.Transport, *http.Request, error) {
    21  - req, err := http.NewRequest("GET", s.GetRandomEndpoint(), bytes.NewBuffer([]byte("")))
     21 +func (pe *Swamp) prepHTTP() (*http.Client, *http.Transport, *http.Request, error) {
     22 + req, err := http.NewRequest("GET", pe.GetRandomEndpoint(), bytes.NewBuffer([]byte("")))
    22 23   if err != nil {
    23 24   return nil, nil, nil, err
    24 25   }
    25 26   headers := make(map[string]string)
    26  - headers["User-Agent"] = s.RandomUserAgent()
     27 + headers["User-Agent"] = pe.RandomUserAgent()
    27 28   headers["Accept"] = "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8"
    28 29   headers["Accept-Language"] = "en-US,en;q=0.5"
    29 30   headers["'Accept-Encoding'"] = "gzip, deflate, br"
    30  - headers["Connection"] = "keep-alive"
     31 + // headers["Connection"] = "keep-alive"
    31 32   for header, value := range headers {
    32 33   req.Header.Set(header, value)
    33 34   }
    34  - var client *http.Client
     35 + var client = &http.Client{}
    35 36   var transporter = &http.Transport{
    36 37   DisableKeepAlives: true,
    37  - TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
    38  - TLSHandshakeTimeout: s.swampopt.validationTimeout.Load().(time.Duration),
     38 + TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, //nolint:gosec
     39 + TLSHandshakeTimeout: pe.GetValidationTimeout(),
    39 40   }
    40 41   
    41 42   return client, transporter, req, err
    42 43  }
    43 44   
    44 45  func (sock *Proxy) bad() {
    45  - sock.timesBad.Store(sock.timesBad.Load().(int) + 1)
     46 + atomic.AddInt64(&sock.timesBad, 1)
    46 47  }
    47 48   
    48 49  func (sock *Proxy) good() {
    49  - sock.timesValidated.Store(sock.timesValidated.Load().(int) + 1)
    50  - sock.lastValidated.Store(time.Now())
     50 + atomic.AddInt64(&sock.timesValidated, 1)
     51 + sock.lastValidated = time.Now()
    51 52  }
    52 53   
    53  -func (s *Swamp) checkHTTP(sock *Proxy) (string, error) {
     54 +func (pe *Swamp) bakeHTTP(hmd *HandMeDown) (client *http.Client, req *http.Request, err error) {
     55 + builder := pools.CopABuffer.Get().(*strings.Builder)
     56 + builder.WriteString(hmd.protoCheck.String())
     57 + builder.WriteString("://")
     58 + builder.WriteString(hmd.sock.Endpoint)
     59 + builder.WriteString("/?timeout=")
     60 + builder.WriteString(pe.GetValidationTimeoutStr())
     61 + builder.WriteString("s")
     62 + dialSocks := socks.DialWithConn(builder.String(), hmd.conn)
     63 + pools.DiscardBuffer(builder)
     64 + 
    54 65   var (
    55  - client *http.Client
    56  - transporter *http.Transport
    57  - req *http.Request
    58  - err error
     66 + purl *url.URL
     67 + transport *http.Transport
    59 68   )
    60 69   
    61  - if client, transporter, req, err = s.prepHTTP(); err != nil {
    62  - return "", err
     70 + if client, transport, req, err = pe.prepHTTP(); err != nil {
     71 + return
    63 72   }
    64 73   
    65  - var dialSocks = socks.Dial(fmt.Sprintf(
    66  - "socks%s://%s/?timeout=%ss",
    67  - sock.Proto.Load().(string),
    68  - sock.Endpoint,
    69  - s.GetValidationTimeoutStr()),
    70  - )
    71  - 
    72  - var transportDialer = dialSocks
    73  - if sock.Proto.Load().(string) == "none" {
    74  - transportDialer = proxy.Direct.Dial
     74 + if hmd.protoCheck != ProtoHTTP {
     75 + transport.Dial = dialSocks
     76 + client.Transport = transport
     77 + return
    75 78   }
     79 + if purl, err = url.Parse("http://" + hmd.sock.Endpoint); err != nil {
     80 + return
     81 + }
     82 + transport.Proxy = http.ProxyURL(purl)
     83 + return
     84 +}
    76 85   
    77  - // if sock.Proto.Load().(string) != "http" {
    78  - transporter.Dial = transportDialer
    79  - 
    80  - // } else {
    81  - // if purl, err := url.Parse("http://" + sock.Endpoint); err == nil {
    82  - // transporter.Proxy = http.ProxyURL(purl)
    83  - // } else {
    84  - // return "", err
    85  - // }
    86  - // }
     86 +func (pe *Swamp) validate(hmd *HandMeDown) (string, error) {
     87 + var (
     88 + client *http.Client
     89 + req *http.Request
     90 + err error
     91 + )
    87 92   
    88  - client = &http.Client{
    89  - Transport: transporter,
    90  - Timeout: s.swampopt.validationTimeout.Load().(time.Duration),
     93 + client, req, err = pe.bakeHTTP(hmd)
     94 + if err != nil {
     95 + return "", err
    91 96   }
    92 97   
    93 98   resp, err := client.Do(req)
    skipped 1 lines
    95 100   return "", err
    96 101   }
    97 102   
    98  - defer func(Body io.ReadCloser) {
    99  - err := Body.Close()
    100  - if err != nil {
    101  - panic(err)
    102  - }
    103  - }(resp.Body)
    104  - 
    105 103   rbody, err := io.ReadAll(resp.Body)
     104 + _ = resp.Body.Close()
    106 105   return string(rbody), err
    107 106  }
    108 107   
    109  -func (s *Swamp) anothaOne() {
    110  - s.Stats.Checked++
     108 +func (pe *Swamp) anothaOne() {
     109 + pe.stats.Checked++
     110 +}
     111 + 
     112 +type HandMeDown struct {
     113 + sock *Proxy
     114 + protoCheck ProxyProtocol
     115 + conn net.Conn
     116 + under proxy.Dialer
    111 117  }
    112 118   
    113  -func (s *Swamp) singleProxyCheck(sock *Proxy) error {
    114  - defer s.anothaOne()
     119 +func (hmd *HandMeDown) Dial(network, addr string) (c net.Conn, err error) {
     120 + if hmd.conn.LocalAddr().Network() != network {
     121 + return hmd.under.Dial(network, addr)
     122 + }
     123 + if hmd.conn.RemoteAddr().String() != addr {
     124 + return hmd.under.Dial(network, addr)
     125 + }
     126 + return hmd.conn, nil
     127 +}
     128 + 
     129 +func (pe *Swamp) singleProxyCheck(sock *Proxy, protocol ProxyProtocol) error {
     130 + defer pe.anothaOne()
    115 131   split := strings.Split(sock.Endpoint, "@")
    116 132   endpoint := split[0]
    117 133   if len(split) == 2 {
    118 134   endpoint = split[1]
    119 135   }
    120  - if _, err := net.DialTimeout("tcp", endpoint,
    121  - s.swampopt.validationTimeout.Load().(time.Duration)); err != nil {
    122  - s.badProx.Check(sock)
     136 + conn, err := net.DialTimeout("tcp", endpoint, pe.GetValidationTimeout())
     137 + if err != nil {
    123 138   return err
    124 139   }
    125 140   
    126  - resp, err := s.checkHTTP(sock)
     141 + hmd := &HandMeDown{sock: sock, conn: conn, under: proxy.Direct, protoCheck: protocol}
     142 + 
     143 + resp, err := pe.validate(hmd)
    127 144   if err != nil {
    128  - s.badProx.Check(sock)
     145 + pe.badProx.Check(sock)
    129 146   return err
    130 147   }
    131 148   
    132 149   if newip := net.ParseIP(resp); newip == nil {
    133  - s.badProx.Check(sock)
     150 + pe.badProx.Check(sock)
    134 151   return errors.New("bad response from http request: " + resp)
    135 152   }
    136 153   
    skipped 3 lines
    140 157  }
    141 158   
    142 159  func (sock *Proxy) validate() {
    143  - var sversions = []string{"4", "5", "4a"}
     160 + if !atomic.CompareAndSwapUint32(&sock.lock, stateUnlocked, stateLocked) {
     161 + return
     162 + }
     163 + defer atomic.StoreUint32(&sock.lock, stateUnlocked)
    144 164   
    145  - s := sock.parent
    146  - if s.useProx.Check(sock) {
    147  - // s.dbgPrint(ylw + "useProx ratelimited: " + sock.Endpoint + rst)
    148  - atomic.StoreUint32(&sock.lock, stateUnlocked)
     165 + pe := sock.parent
     166 + if pe.useProx.Check(sock) {
     167 + // s.dbgPrint("useProx ratelimited: " + sock.Endpoint )
    149 168   return
    150 169   }
    151 170   
    152 171   // determined as bad, won't try again until it expires from that cache
    153  - if s.badProx.Peek(sock) {
    154  - s.dbgPrint(ylw + "badProx ratelimited: " + sock.Endpoint + rst)
    155  - atomic.StoreUint32(&sock.lock, stateUnlocked)
     172 + if pe.badProx.Peek(sock) {
     173 + pe.msgBadProxRate(sock)
    156 174   return
    157 175   }
    158 176   
    159  - // try to use the proxy with all 3 SOCKS versions
    160  - var good = false
    161  - for _, sver := range sversions {
    162  - if s.Status.Load().(SwampStatus) == Paused {
    163  - return
    164  - }
     177 + // TODO: consider giving the option for verbose logging of this stuff?
    165 178   
    166  - sock.Proto.Store(sver)
    167  - if err := s.singleProxyCheck(sock); err == nil {
    168  - // if sock.Proto != "http" {
    169  - s.dbgPrint(grn + "verified " + sock.Endpoint + " as SOCKS" + sver + rst)
    170  - // } else {
    171  - // s.dbgPrint(ylw + "verified " + sock.Endpoint + " as http (not usable yet)" + rst)
    172  - // }
    173  - good = true
    174  - break
     179 + if sock.timesValidated == 0 || sock.protocol.Get() == ProtoNull {
     180 + // try to use the proxy with all 3 SOCKS versions
     181 + for tryProto := range protoMap {
     182 + select {
     183 + case <-pe.ctx.Done():
     184 + return
     185 + default:
     186 + if err := pe.singleProxyCheck(sock, tryProto); err != nil {
     187 + // if the proxy is no good, we continue on to the next.
     188 + continue
     189 + }
     190 + sock.protocol.set(tryProto)
     191 + break
     192 + }
     193 + }
     194 + } else {
     195 + if err := pe.singleProxyCheck(sock, sock.GetProto()); err != nil {
     196 + sock.bad()
     197 + pe.badProx.Check(sock)
     198 + return
    175 199   }
    176 200   }
    177 201   
    178  - if !good {
    179  - s.dbgPrint(red + "failed to verify: " + sock.Endpoint + rst)
     202 + switch sock.protocol.Get() {
     203 + case ProtoSOCKS4, ProtoSOCKS4a, ProtoSOCKS5, ProtoHTTP:
     204 + pe.msgChecked(sock, true)
     205 + default:
     206 + pe.msgChecked(sock, false)
    180 207   sock.bad()
    181  - s.badProx.Check(sock)
    182  - atomic.StoreUint32(&sock.lock, stateUnlocked)
     208 + pe.badProx.Check(sock)
    183 209   return
    184 210   }
    185 211   
    186 212   sock.good()
    187  - atomic.StoreUint32(&sock.lock, stateUnlocked)
     213 + pe.tally(sock)
     214 +}
    188 215   
    189  - switch sock.Proto.Load().(string) {
    190  - case "4":
    191  - go func() {
    192  - s.Stats.v4()
    193  - s.ValidSocks4 <- sock
    194  - }()
    195  - return
    196  - case "4a":
    197  - go func() {
    198  - s.Stats.v4a()
    199  - s.ValidSocks4a <- sock
    200  - }()
    201  - return
    202  - case "5":
    203  - go func() {
    204  - s.Stats.v5()
    205  - s.ValidSocks5 <- sock
    206  - }()
    207  - return
     216 +func (pe *Swamp) tally(sock *Proxy) {
     217 + switch sock.protocol.Get() {
     218 + case ProtoSOCKS4:
     219 + pe.stats.v4()
     220 + pe.Valids.SOCKS4 <- sock
     221 + case ProtoSOCKS4a:
     222 + pe.stats.v4a()
     223 + pe.Valids.SOCKS4a <- sock
     224 + case ProtoSOCKS5:
     225 + pe.stats.v5()
     226 + pe.Valids.SOCKS5 <- sock
     227 + case ProtoHTTP:
     228 + pe.stats.http()
     229 + pe.Valids.HTTP <- sock
    208 230   default:
    209 231   return
    210 232   }
    skipped 2 lines
Please wait...
Page is in error, reload to recover