161 lines
4.6 KiB
Go

package anyoneproxy
import (
"context"
"net"
"net/http"
"os"
"time"
goproxy "golang.org/x/net/proxy"
"github.com/libp2p/go-libp2p/p2p/transport/tcp"
ma "github.com/multiformats/go-multiaddr"
)
// disabled controls runtime disabling via flags. Default is false (proxy enabled).
var disabled bool
// SetDisabled allows binaries to disable Anyone routing via a flag (e.g. --disable-anonrc).
func SetDisabled(v bool) { disabled = v }
// Enabled reports whether Anyone proxy routing is active.
// Defaults to true, using SOCKS5 at 127.0.0.1:9050, unless explicitly disabled
// via SetDisabled(true) or environment variable ANYONE_DISABLE=1.
// ANYONE_SOCKS5 may override the proxy address.
func Enabled() bool {
if disabled {
return false
}
if os.Getenv("ANYONE_DISABLE") == "1" {
return false
}
// If explicitly enabled via env or custom addr provided, also true.
if os.Getenv("ANYONE_PROXY_ENABLED") == "1" {
return true
}
if os.Getenv("ANYONE_SOCKS5") != "" {
return true
}
// Default: enabled
return true
}
// socksAddr returns the SOCKS5 address to use for proxying (host:port).
func socksAddr() string {
if v := os.Getenv("ANYONE_SOCKS5"); v != "" {
return v
}
return "127.0.0.1:9050"
}
// socksContextDialer implements tcp.ContextDialer over a SOCKS5 proxy.
type socksContextDialer struct{ addr string }
func (d *socksContextDialer) DialContext(ctx context.Context, network, address string) (net.Conn, error) {
// Derive timeout from context deadline if present
var timeout time.Duration
if deadline, ok := ctx.Deadline(); ok {
timeout = time.Until(deadline)
if timeout <= 0 {
return nil, context.DeadlineExceeded
}
}
base := &net.Dialer{Timeout: timeout}
// Create a SOCKS5 dialer using the base dialer
socksDialer, err := goproxy.SOCKS5("tcp", d.addr, nil, base)
if err != nil {
return nil, err
}
return socksDialer.Dial(network, address)
}
// DialerForAddr returns a tcp.DialerForAddr that routes through the Anyone SOCKS5 proxy.
// It automatically BYPASSES the proxy for loopback, private, and link-local addresses
// to allow local/dev networking (e.g. 127.0.0.1, 10.0.0.0/8, 192.168.0.0/16, fc00::/7, fe80::/10).
func DialerForAddr() tcp.DialerForAddr {
return func(raddr ma.Multiaddr) (tcp.ContextDialer, error) {
// Prefer direct dialing for local/private targets
if ip4, err := raddr.ValueForProtocol(ma.P_IP4); err == nil {
if ip := net.ParseIP(ip4); ip != nil {
if ip.IsLoopback() || ip.IsPrivate() || ip.IsLinkLocalUnicast() || ip.IsLinkLocalMulticast() {
return &net.Dialer{}, nil
}
}
}
if ip6, err := raddr.ValueForProtocol(ma.P_IP6); err == nil {
if ip := net.ParseIP(ip6); ip != nil {
if ip.IsLoopback() || ip.IsPrivate() || ip.IsLinkLocalUnicast() || ip.IsLinkLocalMulticast() {
return &net.Dialer{}, nil
}
}
}
if host, err := raddr.ValueForProtocol(ma.P_DNS); err == nil {
if host == "localhost" {
return &net.Dialer{}, nil
}
}
if host, err := raddr.ValueForProtocol(ma.P_DNS4); err == nil {
if host == "localhost" {
return &net.Dialer{}, nil
}
}
if host, err := raddr.ValueForProtocol(ma.P_DNS6); err == nil {
if host == "localhost" {
return &net.Dialer{}, nil
}
}
// Default: use SOCKS dialer
return &socksContextDialer{addr: socksAddr()}, nil
}
}
// NewHTTPClient returns an *http.Client that routes all TCP connections via the Anyone SOCKS5 proxy.
// If Anyone proxy is not enabled, it returns http.DefaultClient.
func NewHTTPClient() *http.Client {
if !Enabled() {
return http.DefaultClient
}
tr := &http.Transport{
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
// Bypass proxy for localhost/private IPs
host, _, err := net.SplitHostPort(addr)
if err != nil {
host = addr
}
if ip := net.ParseIP(host); ip != nil {
if ip.IsLoopback() || ip.IsPrivate() || ip.IsLinkLocalUnicast() || ip.IsLinkLocalMulticast() {
d := &net.Dialer{}
return d.DialContext(ctx, network, addr)
}
}
if host == "localhost" {
d := &net.Dialer{}
return d.DialContext(ctx, network, addr)
}
d := &socksContextDialer{addr: socksAddr()}
return d.DialContext(ctx, network, addr)
},
}
return &http.Client{Transport: tr}
}
// Address returns the SOCKS5 address used for Anyone routing.
func Address() string { return socksAddr() }
// Running returns true if Anyone proxy is enabled and reachable at Address().
// It attempts a short TCP dial and returns false on failure.
func Running() bool {
if !Enabled() {
return false
}
conn, err := net.DialTimeout("tcp", socksAddr(), 200*time.Millisecond)
if err != nil {
return false
}
_ = conn.Close()
return true
}