prox5/validator_engine.go

296 lines
6.2 KiB
Go

package prox5
import (
"bytes"
"crypto/tls"
"errors"
"io"
"net"
"net/http"
"net/url"
"strings"
"sync"
"sync/atomic"
"time"
"git.tcp.direct/kayos/socks"
"golang.org/x/net/proxy"
)
var headerPool = sync.Pool{
New: func() interface{} {
hdr := make(http.Header)
hdr["User-Agent"] = []string{""}
hdr["Accept"] = []string{"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8"}
hdr["Accept-Language"] = []string{"en-US,en;q=0.5"}
hdr["Accept-Encoding"] = []string{"gzip, deflate, br"}
return hdr
},
}
func (p5 *ProxyEngine) prepHTTP() (*http.Client, *http.Transport, *http.Request, error) {
req, err := http.NewRequest("GET", p5.GetRandomEndpoint(), bytes.NewBuffer([]byte("")))
if err != nil {
return nil, nil, nil, err
}
headers := headerPool.Get().(http.Header)
headers["User-Agent"] = []string{p5.RandomUserAgent()}
var client = &http.Client{}
var transporter = &http.Transport{
DisableKeepAlives: true,
TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, //nolint:gosec
TLSHandshakeTimeout: p5.GetValidationTimeout(),
}
return client, transporter, req, err
}
func (sock *Proxy) bad() {
atomic.AddInt64(&sock.timesBad, 1)
}
func (sock *Proxy) good() {
atomic.AddInt64(&sock.timesValidated, 1)
sock.lastValidated = time.Now()
}
func httpEndpoint(hmd *handMeDown) (func(r *http.Request) (*url.URL, error), error) {
s := strs.Get()
defer strs.MustPut(s)
s.MustWriteString("http://")
s.MustWriteString(hmd.sock.Endpoint)
purl, err := url.Parse(s.String())
if err != nil {
return nil, err
}
return http.ProxyURL(purl), nil
}
func (p5 *ProxyEngine) bakeHTTP(hmd *handMeDown) (client *http.Client, req *http.Request, err error) {
builder := strs.Get()
builder.MustWriteString(hmd.protoCheck.String())
builder.MustWriteString("://")
builder.MustWriteString(hmd.sock.Endpoint)
builder.MustWriteString("/?timeout=")
builder.MustWriteString(p5.GetValidationTimeoutStr())
builder.MustWriteString("s")
dialSocks := socks.DialWithConn(builder.String(), hmd.conn)
strs.MustPut(builder)
var transport *http.Transport
client, transport, req, err = p5.prepHTTP()
if err != nil {
if req != nil && req.Header != nil {
headerPool.Put(req.Header)
}
return
}
if hmd.protoCheck != ProtoHTTP {
transport.Dial = dialSocks
client.Transport = transport
return
}
proxyURL, err := httpEndpoint(hmd)
if err != nil {
if req != nil && req.Header != nil {
headerPool.Put(req.Header)
}
return
}
transport.Proxy = proxyURL
return
}
func (p5 *ProxyEngine) validate(hmd *handMeDown) (string, error) {
var (
client *http.Client
req *http.Request
err error
)
client, req, err = p5.bakeHTTP(hmd)
if err != nil {
return "", err
}
resp, err := client.Do(req)
defer func() {
if req != nil && req.Header != nil {
headerPool.Put(req.Header)
}
}()
if err != nil {
return "", err
}
rbody, err := io.ReadAll(resp.Body)
_ = resp.Body.Close()
return string(rbody), err
}
func (p5 *ProxyEngine) anothaOne() {
p5.stats.Checked.Add(1)
}
type handMeDown struct {
sock *Proxy
protoCheck ProxyProtocol
conn net.Conn
under proxy.Dialer
}
func (hmd *handMeDown) Dial(network, addr string) (c net.Conn, err error) {
if hmd.conn.LocalAddr().Network() != network {
return hmd.under.Dial(network, addr)
}
if hmd.conn.RemoteAddr().String() != addr {
return hmd.under.Dial(network, addr)
}
return hmd.conn, nil
}
func (p5 *ProxyEngine) announceValidating(sock *Proxy, presplit string) {
if sock == nil {
return
}
s := strs.Get()
s.MustWriteString("validating ")
s.MustWriteString(sock.GetProto().String())
s.MustWriteString("://")
s.MustWriteString(presplit)
p5.dbgPrint(s)
}
func (p5 *ProxyEngine) singleProxyCheck(sock *Proxy, protocol ProxyProtocol) error {
defer p5.anothaOne()
split := strings.Split(sock.Endpoint, "@")
endpoint := split[0]
if len(split) == 2 {
endpoint = split[1]
}
// p5.announceValidating(sock, endpoint)
conn, err := net.DialTimeout("tcp", endpoint, p5.GetValidationTimeout())
if err != nil {
return err
}
hmd := &handMeDown{sock: sock, conn: conn, under: proxy.Direct, protoCheck: protocol}
resp, err := p5.validate(hmd)
if err != nil {
p5.badProx.Check(sock)
return err
}
if newip := net.ParseIP(resp); newip == nil {
p5.badProx.Check(sock)
return errors.New("bad response from http request: " + resp)
}
sock.ProxiedIP = resp
return nil
}
func (sock *Proxy) validate() {
if sock == nil || sock.parent == nil {
return
}
if !atomic.CompareAndSwapUint32(&sock.lock, stateUnlocked, stateLocked) {
return
}
defer atomic.StoreUint32(&sock.lock, stateUnlocked)
select {
case <-sock.parent.ctx.Done():
return
default:
}
pe := sock.parent
if pe.useProx.Check(sock) {
// s.dbgPrint("useProx ratelimited: " + sock.Endpoint )
return
}
// determined as bad, won't try again until it expires from that cache
if pe.badProx.Peek(sock) {
pe.msgBadProxRate(sock)
return
}
// TODO: consider giving the option for verbose logging of this stuff?
switch {
case sock.timesValidated == 0, sock.protocol.Get() == ProtoNull:
// try to use the proxy with all 3 SOCKS versions
for tryProto := range protoMap {
if tryProto == ProtoNull {
continue
}
select {
case <-pe.ctx.Done():
return
default:
if err := pe.singleProxyCheck(sock, tryProto); err != nil {
// if the proxy is no good, we continue on to the next.
continue
}
sock.protocol.set(tryProto)
break
}
}
default:
if err := pe.singleProxyCheck(sock, sock.GetProto()); err != nil {
sock.bad()
pe.badProx.Check(sock)
return
}
}
switch sock.protocol.Get() {
case ProtoSOCKS4, ProtoSOCKS4a, ProtoSOCKS5, ProtoHTTP:
pe.msgChecked(sock, true)
default:
pe.msgChecked(sock, false)
sock.bad()
pe.badProx.Check(sock)
return
}
sock.good()
pe.tally(sock)
}
func (p5 *ProxyEngine) tally(sock *Proxy) bool {
var target proxyList
switch sock.protocol.Get() {
case ProtoSOCKS4:
p5.stats.v4()
target = p5.Valids.SOCKS4
case ProtoSOCKS4a:
p5.stats.v4a()
target = p5.Valids.SOCKS4a
case ProtoSOCKS5:
p5.stats.v5()
target = p5.Valids.SOCKS5
case ProtoHTTP:
p5.stats.http()
target = p5.Valids.HTTP
default:
return false
}
target.Lock()
target.PushBack(sock)
target.Unlock()
return true
}