| 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 |