Skip to content

Commit

Permalink
Merge pull request #11 from alpkeskin/v1.2.1
Browse files Browse the repository at this point in the history
fix(v1.2.1): auth proxy added and rotation fixes
  • Loading branch information
alpkeskin authored Feb 4, 2025
2 parents 78a1348 + fc13d6f commit fa354cd
Show file tree
Hide file tree
Showing 7 changed files with 72 additions and 36 deletions.
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,12 +107,15 @@ Example configuration file can be found in [config.yml](config.yml)

Proxies file should be in the following format:
```
scheme://ip:port
scheme://ip:port or scheme://username:password@ip:port
Examples:
socks5://192.111.137.37:18762
http://192.111.137.37:9911
https://192.111.137.37:9911
socks5://admin:[email protected]:18762
http://admin:[email protected]:8080
https://admin:[email protected]:8081
```

# Quick Start
Expand Down
2 changes: 1 addition & 1 deletion cmd/rota/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import (
)

const (
msgVersion = "rota proxy v1.2.0"
msgVersion = "rota proxy v1.2.1"
msgConfigPathRequired = "config file path is required"
msgFailedToLoadConfig = "failed to load config"
msgConfigLoadedSuccess = "config loaded successfully"
Expand Down
56 changes: 44 additions & 12 deletions internal/proxy/loader.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
package proxy

import (
"context"
"crypto/tls"
"fmt"
"log/slog"
"net"
"net/http"
"net/url"
"os"
"strings"
"time"

"github.com/alpkeskin/rota/internal/config"
"h12.io/socks"
Expand Down Expand Up @@ -64,29 +67,58 @@ func (pl *ProxyLoader) CreateProxy(proxyURL string) (*Proxy, error) {
return nil, err
}

var username, password string
if parsedUrl.User != nil {
username = parsedUrl.User.Username()
password, _ = parsedUrl.User.Password()
}

p := Proxy{
Scheme: parsedUrl.Scheme,
Host: proxyURL,
Url: parsedUrl,
Scheme: parsedUrl.Scheme,
Host: proxyURL,
Url: parsedUrl,
Username: username,
Password: password,
}

tlsConfig := &tls.Config{
InsecureSkipVerify: true,
MinVersion: tls.VersionTLS12,
MaxVersion: tls.VersionTLS13,
CipherSuites: []uint16{
tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
},
}

tr := &http.Transport{
TLSClientConfig: tlsConfig,
DisableKeepAlives: true,
MaxIdleConns: 100,
IdleConnTimeout: 90 * time.Second,
DisableCompression: true,
TLSHandshakeTimeout: 10 * time.Second,
}

tr := &http.Transport{}
switch p.Scheme {
case "socks4", "socks4a", "socks5":
tr = &http.Transport{
Dial: socks.Dial(p.Host),
proxyAddrURL := &url.URL{
Host: parsedUrl.Host,
Scheme: parsedUrl.Scheme,
User: url.UserPassword(username, password),
}
case "http", "https":
tr = &http.Transport{
Proxy: http.ProxyURL(p.Url),
dialSocks := socks.Dial(proxyAddrURL.String())
tr.DialContext = func(ctx context.Context, network, addr string) (net.Conn, error) {
return dialSocks(network, addr)
}
case "http", "https":
tr.Proxy = http.ProxyURL(p.Url)
default:
return nil, fmt.Errorf("%s. URL: %s", msgUnsupportedProxyScheme, proxyURL)
}

tr.DisableKeepAlives = true
tr.TLSClientConfig = &tls.Config{InsecureSkipVerify: true}

p.Transport = tr
return &p, nil
}
10 changes: 3 additions & 7 deletions internal/proxy/proxy_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@ func (ps *ProxyServer) handleRequest(r *http.Request, ctx *goproxy.ProxyCtx) (*h

if r.URL.Scheme == "http" && ps.cfg.Proxy.Authentication.Enabled {
if err := ps.authenticateHttp(ctx, reqInfo); err != nil {
return ps.unauthorizedResponse(reqInfo)
return nil, goproxy.NewResponse(reqInfo.request,
goproxy.ContentTypeText, StatusProxyAuthRequired,
fmt.Sprintf(msgUnauthorized, reqInfo.id))
}
}

Expand Down Expand Up @@ -61,12 +63,6 @@ func (ps *ProxyServer) authenticateHttps(host string, ctx *goproxy.ProxyCtx) (*g
return goproxy.MitmConnect, host
}

func (ps *ProxyServer) unauthorizedResponse(reqInfo requestInfo) (*http.Request, *http.Response) {
return nil, goproxy.NewResponse(reqInfo.request,
goproxy.ContentTypeText, StatusProxyAuthRequired,
fmt.Sprintf(msgUnauthorized, reqInfo.id))
}

func (ps *ProxyServer) badGatewayResponse(reqInfo requestInfo, err error) (*http.Request, *http.Response) {
slog.Error(msgReqRotationError, "error", err, "request_id", reqInfo.id, "url", reqInfo.url)
return nil, goproxy.NewResponse(reqInfo.request,
Expand Down
17 changes: 17 additions & 0 deletions internal/proxy/proxy_rotation.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ import (
)

func (ps *ProxyServer) getProxy() *Proxy {
if len(ps.Proxies) == 0 {
return nil
}

method := ps.cfg.Proxy.Rotation.Method
switch method {
case "random":
Expand Down Expand Up @@ -76,13 +80,25 @@ func (ps *ProxyServer) tryProxy(proxy *Proxy, reqInfo requestInfo) (*http.Respon
client := &http.Client{
Transport: proxy.Transport,
Timeout: time.Duration(ps.cfg.Proxy.Rotation.Timeout) * time.Second,
CheckRedirect: func(req *http.Request, via []*http.Request) error {
if len(via) >= 10 {
return errors.New(msgStoppedAfter10Redirects)
}
return nil
},
}
defer client.CloseIdleConnections()

ps.removeHopHeaders(reqInfo.request)
reqInfo.request.RequestURI = ""

if reqInfo.request.URL != nil {
reqInfo.request.Host = reqInfo.request.URL.Host
}

response, err := client.Do(reqInfo.request)
duration := time.Since(reqInfo.startAt)

if err == nil && response != nil {
go ps.updateProxyUsage(proxy, reqInfo, duration, "success")
slog.Info(msgReqRotationSuccess,
Expand All @@ -93,6 +109,7 @@ func (ps *ProxyServer) tryProxy(proxy *Proxy, reqInfo requestInfo) (*http.Respon
)
return response, nil
}

go ps.updateProxyUsage(proxy, reqInfo, duration, "failed")
slog.Error(msgReqRotationError,
"error", err,
Expand Down
15 changes: 0 additions & 15 deletions internal/proxy/proxy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -128,21 +128,6 @@ func TestRemoveUnhealthyProxy(t *testing.T) {
assert.Equal(t, proxy3, ps.Proxies[1])
}

func TestUnauthorizedResponse(t *testing.T) {
ps := NewProxyServer(&config.Config{})
req, _ := http.NewRequest("GET", "http://example.com", nil)

reqInfo := requestInfo{
id: "test-id",
request: req,
}

_, resp := ps.unauthorizedResponse(reqInfo)

assert.Equal(t, StatusProxyAuthRequired, resp.StatusCode)
assert.Equal(t, "Proxy Authentication Required", resp.Status)
}

func TestBadGatewayResponse(t *testing.T) {
ps := NewProxyServer(&config.Config{})
req, _ := http.NewRequest("GET", "http://example.com", nil)
Expand Down
3 changes: 3 additions & 0 deletions internal/proxy/proxy_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ type Proxy struct {
Host string
Url *url.URL
Transport *http.Transport
Username string
Password string
LatestUsageStatus string
LatestUsageAt string
LatestUsageDuration string
Expand Down Expand Up @@ -65,6 +67,7 @@ const (
msgDeadProxy = "dead proxy"
msgAliveProxy = "alive proxy"
msgFailedToWriteOutputFile = "failed to write output file"
msgStoppedAfter10Redirects = "stopped after 10 redirects"
)

var hopHeaders = []string{
Expand Down

0 comments on commit fa354cd

Please sign in to comment.