mirror of
https://github.com/anotherhadi/iknowyou.git
synced 2026-04-12 08:57:26 +02:00
155 lines
4.0 KiB
Go
155 lines
4.0 KiB
Go
package proxy
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"math/rand/v2"
|
|
"net/http"
|
|
"net/url"
|
|
"os"
|
|
"strings"
|
|
|
|
"github.com/anotherhadi/iknowyou/config"
|
|
)
|
|
|
|
type httpClientKey struct{}
|
|
type proxychainsConfKey struct{}
|
|
|
|
// WithClient injects a proxy-aware HTTP client into the context.
|
|
func WithClient(ctx context.Context, client *http.Client) context.Context {
|
|
return context.WithValue(ctx, httpClientKey{}, client)
|
|
}
|
|
|
|
// ClientFromContext returns the proxy-aware HTTP client stored in ctx,
|
|
// or http.DefaultClient if none was set.
|
|
func ClientFromContext(ctx context.Context) *http.Client {
|
|
if client, ok := ctx.Value(httpClientKey{}).(*http.Client); ok && client != nil {
|
|
return client
|
|
}
|
|
return http.DefaultClient
|
|
}
|
|
|
|
// WithProxychainsConf injects a proxychains config file path into the context.
|
|
func WithProxychainsConf(ctx context.Context, confPath string) context.Context {
|
|
return context.WithValue(ctx, proxychainsConfKey{}, confPath)
|
|
}
|
|
|
|
// ProxychainsConfFromContext returns the proxychains config file path stored in
|
|
// ctx, or an empty string if none was set.
|
|
func ProxychainsConfFromContext(ctx context.Context) string {
|
|
if path, ok := ctx.Value(proxychainsConfKey{}).(string); ok {
|
|
return path
|
|
}
|
|
return ""
|
|
}
|
|
|
|
// fallbackTransport is an http.RoundTripper that tries proxies in random order
|
|
// and falls back to the next one on network error.
|
|
type fallbackTransport struct {
|
|
transports []*http.Transport
|
|
}
|
|
|
|
func (t *fallbackTransport) RoundTrip(req *http.Request) (*http.Response, error) {
|
|
order := rand.Perm(len(t.transports))
|
|
var lastErr error
|
|
for _, i := range order {
|
|
resp, err := t.transports[i].RoundTrip(req)
|
|
if err == nil {
|
|
return resp, nil
|
|
}
|
|
lastErr = err
|
|
}
|
|
return nil, lastErr
|
|
}
|
|
|
|
// NewClient builds an *http.Client that routes requests through the given
|
|
// proxies, trying them in random order and falling back on network error.
|
|
// Returns nil if proxies is empty.
|
|
func NewClient(proxies []config.ProxyEntry) (*http.Client, error) {
|
|
if len(proxies) == 0 {
|
|
return nil, nil
|
|
}
|
|
transports := make([]*http.Transport, 0, len(proxies))
|
|
for _, p := range proxies {
|
|
u, err := url.Parse(p.URL)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("proxy: invalid URL %q: %w", p.URL, err)
|
|
}
|
|
transports = append(transports, &http.Transport{
|
|
Proxy: http.ProxyURL(u),
|
|
})
|
|
}
|
|
return &http.Client{
|
|
Transport: &fallbackTransport{transports: transports},
|
|
}, nil
|
|
}
|
|
|
|
// WriteProxychainsConf generates a temporary proxychains4 config file from the
|
|
// given proxy list and returns its path along with a cleanup function.
|
|
// Returns ("", nil, nil) if proxies is empty.
|
|
func WriteProxychainsConf(proxies []config.ProxyEntry) (string, func(), error) {
|
|
if len(proxies) == 0 {
|
|
return "", func() {}, nil
|
|
}
|
|
|
|
var sb strings.Builder
|
|
sb.WriteString("dynamic_chain\nproxy_dns\n\n[ProxyList]\n")
|
|
|
|
for _, p := range proxies {
|
|
u, err := url.Parse(p.URL)
|
|
if err != nil {
|
|
return "", nil, fmt.Errorf("proxy: invalid URL %q: %w", p.URL, err)
|
|
}
|
|
|
|
scheme := u.Scheme
|
|
// proxychains only knows socks4, socks5, http
|
|
if scheme != "socks4" && scheme != "socks5" && scheme != "http" {
|
|
scheme = "socks5"
|
|
}
|
|
|
|
host := u.Hostname()
|
|
port := u.Port()
|
|
if port == "" {
|
|
port = defaultPort(scheme)
|
|
}
|
|
|
|
line := fmt.Sprintf("%s %s %s", scheme, host, port)
|
|
if u.User != nil {
|
|
user := u.User.Username()
|
|
pass, _ := u.User.Password()
|
|
if user != "" {
|
|
line += " " + user
|
|
if pass != "" {
|
|
line += " " + pass
|
|
}
|
|
}
|
|
}
|
|
sb.WriteString(line + "\n")
|
|
}
|
|
|
|
f, err := os.CreateTemp("", "iky-proxychains-*.conf")
|
|
if err != nil {
|
|
return "", nil, fmt.Errorf("proxy: create temp conf: %w", err)
|
|
}
|
|
if _, err := f.WriteString(sb.String()); err != nil {
|
|
_ = f.Close()
|
|
_ = os.Remove(f.Name())
|
|
return "", nil, fmt.Errorf("proxy: write conf: %w", err)
|
|
}
|
|
_ = f.Close()
|
|
|
|
path := f.Name()
|
|
cleanup := func() { _ = os.Remove(path) }
|
|
return path, cleanup, nil
|
|
}
|
|
|
|
func defaultPort(scheme string) string {
|
|
switch scheme {
|
|
case "socks4", "socks5":
|
|
return "1080"
|
|
case "http":
|
|
return "8080"
|
|
}
|
|
return "1080"
|
|
}
|