1
0

netproxy.go 2.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172
  1. // Package netproxy builds HTTP clients that route the panel's own outbound
  2. // requests through an admin-configured proxy, used to reach GitHub and Telegram
  3. // from servers where those services are filtered.
  4. package netproxy
  5. import (
  6. "context"
  7. "fmt"
  8. "net"
  9. "net/http"
  10. "net/url"
  11. "strings"
  12. "time"
  13. "golang.org/x/net/proxy"
  14. )
  15. // NewHTTPClient returns an *http.Client whose transport honors proxyURL.
  16. //
  17. // An empty proxyURL yields a plain client (unchanged behavior). socks5/socks5h
  18. // URLs are dialed through golang.org/x/net/proxy; http/https URLs use the
  19. // standard library proxy support. Any other scheme returns an error so callers
  20. // can log it and fall back to a direct connection.
  21. //
  22. // The proxy address is intentionally not subjected to SSRF filtering: it is
  23. // admin-configured and is commonly a loopback/private address (for example a
  24. // local Xray SOCKS inbound).
  25. func NewHTTPClient(proxyURL string, timeout time.Duration) (*http.Client, error) {
  26. if proxyURL == "" {
  27. return &http.Client{Timeout: timeout}, nil
  28. }
  29. parsed, err := url.Parse(proxyURL)
  30. if err != nil {
  31. return nil, fmt.Errorf("parse proxy url: %w", err)
  32. }
  33. transport := baseTransport()
  34. switch strings.ToLower(parsed.Scheme) {
  35. case "socks5", "socks5h":
  36. var auth *proxy.Auth
  37. if parsed.User != nil {
  38. password, _ := parsed.User.Password()
  39. auth = &proxy.Auth{User: parsed.User.Username(), Password: password}
  40. }
  41. dialer, err := proxy.SOCKS5("tcp", parsed.Host, auth, proxy.Direct)
  42. if err != nil {
  43. return nil, fmt.Errorf("create socks5 dialer: %w", err)
  44. }
  45. if contextDialer, ok := dialer.(proxy.ContextDialer); ok {
  46. transport.DialContext = contextDialer.DialContext
  47. } else {
  48. transport.DialContext = func(_ context.Context, network, addr string) (net.Conn, error) {
  49. return dialer.Dial(network, addr)
  50. }
  51. }
  52. case "http", "https":
  53. transport.Proxy = http.ProxyURL(parsed)
  54. default:
  55. return nil, fmt.Errorf("unsupported proxy scheme %q", parsed.Scheme)
  56. }
  57. return &http.Client{Timeout: timeout, Transport: transport}, nil
  58. }
  59. func baseTransport() *http.Transport {
  60. if base, ok := http.DefaultTransport.(*http.Transport); ok {
  61. return base.Clone()
  62. }
  63. return &http.Transport{}
  64. }