feat: proxy rotation for 9proxy.com
This commit is contained in:
parent
4609d34e59
commit
14b9760386
3 changed files with 92 additions and 28 deletions
5
.env.example
Normal file
5
.env.example
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
PROXY_USER=user-ssid-
|
||||||
|
PROXY_PASS=123
|
||||||
|
PROXY_HOST=localhost
|
||||||
|
PROXY_PORT=8080
|
||||||
|
PORT=80
|
||||||
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -1 +1,2 @@
|
||||||
/pp
|
/pp
|
||||||
|
.env
|
||||||
|
|
|
||||||
100
main.go
100
main.go
|
|
@ -2,6 +2,7 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
|
"crypto/rand"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
|
@ -11,9 +12,18 @@ import (
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
proxyUser string
|
||||||
|
proxyPass string
|
||||||
|
proxyHost string
|
||||||
|
sessionID string
|
||||||
|
rotateCounter atomic.Int64
|
||||||
|
)
|
||||||
|
|
||||||
func getEnvDefault(name, def string) string {
|
func getEnvDefault(name, def string) string {
|
||||||
v := os.Getenv(name)
|
v := os.Getenv(name)
|
||||||
if v != "" {
|
if v != "" {
|
||||||
|
|
@ -22,37 +32,85 @@ func getEnvDefault(name, def string) string {
|
||||||
return def
|
return def
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseProxy() *url.URL {
|
func generateSessionID(length int) string {
|
||||||
raw := strings.TrimPrefix(strings.TrimSpace(os.Getenv("PROXY")), "http://")
|
b := make([]byte, length)
|
||||||
if raw == "" {
|
if _, err := rand.Read(b); err != nil {
|
||||||
log.Fatal("missing PROXY (format: login:password@ip:port)")
|
log.Fatalf("failed to generate session ID: %v", err)
|
||||||
|
}
|
||||||
|
return base64.RawURLEncoding.EncodeToString(b)[:length]
|
||||||
}
|
}
|
||||||
|
|
||||||
creds, hostPort, ok := strings.Cut(raw, "@")
|
func getUpstreamURL() *url.URL {
|
||||||
if !ok {
|
counter := rotateCounter.Load()
|
||||||
log.Fatal("expected login:password@ip:port")
|
user := fmt.Sprintf("%s%s%d", proxyUser, sessionID, counter)
|
||||||
|
return &url.URL{
|
||||||
|
Scheme: "http",
|
||||||
|
User: url.UserPassword(user, proxyPass),
|
||||||
|
Host: proxyHost,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
login, password, ok := strings.Cut(creds, ":")
|
func parseProxyConfig() {
|
||||||
if !ok || strings.TrimSpace(login) == "" || strings.TrimSpace(password) == "" {
|
proxyUser = os.Getenv("PROXY_USER")
|
||||||
log.Fatal("expected login:password before @")
|
if proxyUser == "" {
|
||||||
|
log.Fatal("missing PROXY_USER")
|
||||||
}
|
}
|
||||||
|
|
||||||
hostPort = strings.TrimSpace(hostPort)
|
proxyPass = os.Getenv("PROXY_PASS")
|
||||||
if _, _, err := net.SplitHostPort(hostPort); err != nil {
|
if proxyPass == "" {
|
||||||
log.Fatalf("invalid ip:port: %v", err)
|
log.Fatal("missing PROXY_PASS")
|
||||||
}
|
}
|
||||||
|
|
||||||
return &url.URL{Scheme: "http", User: url.UserPassword(login, password), Host: hostPort}
|
proxyHost = os.Getenv("PROXY_HOST")
|
||||||
|
if proxyHost == "" {
|
||||||
|
log.Fatal("missing PROXY_HOST")
|
||||||
}
|
}
|
||||||
|
|
||||||
func proxyHandler(transport *http.Transport, upstream *url.URL) http.HandlerFunc {
|
port := os.Getenv("PROXY_PORT")
|
||||||
|
if port == "" {
|
||||||
|
log.Fatal("missing PROXY_PORT")
|
||||||
|
}
|
||||||
|
proxyHost = net.JoinHostPort(proxyHost, port)
|
||||||
|
|
||||||
|
sessionID = generateSessionID(11)
|
||||||
|
rotateCounter.Store(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getCurrentIP() string {
|
||||||
|
upstream := getUpstreamURL()
|
||||||
|
transport := &http.Transport{Proxy: http.ProxyURL(upstream)}
|
||||||
|
client := &http.Client{Transport: transport, Timeout: 10 * time.Second}
|
||||||
|
|
||||||
|
resp, err := client.Get("https://checkip.amazonaws.com")
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Sprintf("error: %v", err)
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
body, err := io.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Sprintf("error reading: %v", err)
|
||||||
|
}
|
||||||
|
return strings.TrimSpace(string(body))
|
||||||
|
}
|
||||||
|
|
||||||
|
func proxyHandler() http.HandlerFunc {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.URL.Path == "/health" {
|
switch r.URL.Path {
|
||||||
|
case "/health":
|
||||||
w.Write([]byte("ok"))
|
w.Write([]byte("ok"))
|
||||||
return
|
return
|
||||||
|
case "/rotate":
|
||||||
|
newCounter := rotateCounter.Add(1)
|
||||||
|
ip := getCurrentIP()
|
||||||
|
log.Printf("Rotated to counter=%d, IP=%s", newCounter, ip)
|
||||||
|
w.Write([]byte(fmt.Sprintf("%s\n", ip)))
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
upstream := getUpstreamURL()
|
||||||
|
transport := &http.Transport{Proxy: http.ProxyURL(upstream)}
|
||||||
|
|
||||||
if r.Method == http.MethodConnect {
|
if r.Method == http.MethodConnect {
|
||||||
handleTunneling(w, r, upstream)
|
handleTunneling(w, r, upstream)
|
||||||
return
|
return
|
||||||
|
|
@ -173,10 +231,10 @@ func copyHeader(dst, src http.Header) {
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
listenAddr := ":" + getEnvDefault("PORT", "80")
|
listenAddr := ":" + getEnvDefault("PORT", "80")
|
||||||
upstream := parseProxy()
|
parseProxyConfig()
|
||||||
|
|
||||||
transport := &http.Transport{Proxy: http.ProxyURL(upstream)}
|
upstream := getUpstreamURL()
|
||||||
|
ip := getCurrentIP()
|
||||||
log.Printf("Proxy running on %s -> http://%s@%s", listenAddr, upstream.User.Username(), upstream.Host)
|
log.Printf("started listen=%s upstream=http://%s ip=%s", listenAddr, upstream.Host, ip)
|
||||||
log.Fatal(http.ListenAndServe(listenAddr, proxyHandler(transport, upstream)))
|
log.Fatal(http.ListenAndServe(listenAddr, proxyHandler()))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue