From 2647f75ed6918f6d03d30bffc62ac3c32f6d6213 Mon Sep 17 00:00:00 2001 From: anonpenguin Date: Sun, 10 Aug 2025 16:34:11 +0300 Subject: [PATCH] added anyone proxy support --- cmd/cli/main.go | 7 + cmd/node/main.go | 295 +++++++++++++++++----------------- go.mod | 4 +- go.sum | 10 -- pkg/anyoneproxy/socks.go | 160 ++++++++++++++++++ pkg/client/client.go | 18 ++- pkg/client/implementations.go | 11 +- pkg/node/node.go | 65 +++++++- 8 files changed, 403 insertions(+), 167 deletions(-) create mode 100644 pkg/anyoneproxy/socks.go diff --git a/cmd/cli/main.go b/cmd/cli/main.go index fe9a097..0caff64 100644 --- a/cmd/cli/main.go +++ b/cmd/cli/main.go @@ -14,6 +14,7 @@ import ( "git.debros.io/DeBros/network/pkg/constants" "github.com/libp2p/go-libp2p/core/crypto" "github.com/libp2p/go-libp2p/core/peer" + "git.debros.io/DeBros/network/pkg/anyoneproxy" ) var ( @@ -21,6 +22,7 @@ var ( timeout = 30 * time.Second format = "table" useProduction = false + disableAnon = false ) // version metadata populated via -ldflags at build time @@ -42,6 +44,9 @@ func main() { // Parse global flags parseGlobalFlags(args) + // Apply disable flag early so all network operations honor it + anyoneproxy.SetDisabled(disableAnon) + switch command { case "version": fmt.Printf("network-cli %s", version) @@ -105,6 +110,8 @@ func parseGlobalFlags(args []string) { } case "--production": useProduction = true + case "--disable-anonrc": + disableAnon = true } } } diff --git a/cmd/node/main.go b/cmd/node/main.go index 82a418e..6700e14 100644 --- a/cmd/node/main.go +++ b/cmd/node/main.go @@ -4,8 +4,8 @@ import ( "context" "flag" "fmt" - "net" "log" + "net" "os" "os/exec" "os/signal" @@ -13,28 +13,33 @@ import ( "strings" "syscall" + "git.debros.io/DeBros/network/pkg/anyoneproxy" + "git.debros.io/DeBros/network/pkg/client" "git.debros.io/DeBros/network/pkg/config" "git.debros.io/DeBros/network/pkg/constants" "git.debros.io/DeBros/network/pkg/logging" "git.debros.io/DeBros/network/pkg/node" - "git.debros.io/DeBros/network/pkg/client" ) func main() { var ( - dataDir = flag.String("data", "", "Data directory (auto-detected if not provided)") - nodeID = flag.String("id", "", "Node identifier (for running multiple local nodes)") - bootstrap = flag.String("bootstrap", "", "Bootstrap peer address (for manual override)") - role = flag.String("role", "auto", "Node role: auto|bootstrap|node (auto detects based on config)") - p2pPort = flag.Int("p2p-port", 4001, "LibP2P listen port") - rqlHTTP = flag.Int("rqlite-http-port", 5001, "RQLite HTTP API port") - rqlRaft = flag.Int("rqlite-raft-port", 7001, "RQLite Raft port") - advertise = flag.String("advertise", "auto", "Advertise mode: auto|localhost|ip") - devLocal = flag.Bool("dev-local", false, "Enable development localhost defaults for the client library (sets NETWORK_DEV_LOCAL=1)") - help = flag.Bool("help", false, "Show help") + dataDir = flag.String("data", "", "Data directory (auto-detected if not provided)") + nodeID = flag.String("id", "", "Node identifier (for running multiple local nodes)") + bootstrap = flag.String("bootstrap", "", "Bootstrap peer address (for manual override)") + role = flag.String("role", "auto", "Node role: auto|bootstrap|node (auto detects based on config)") + p2pPort = flag.Int("p2p-port", 4001, "LibP2P listen port") + rqlHTTP = flag.Int("rqlite-http-port", 5001, "RQLite HTTP API port") + rqlRaft = flag.Int("rqlite-raft-port", 7001, "RQLite Raft port") + advertise = flag.String("advertise", "auto", "Advertise mode: auto|localhost|ip") + devLocal = flag.Bool("dev-local", false, "Enable development localhost defaults for the client library (sets NETWORK_DEV_LOCAL=1)") + disableAnon = flag.Bool("disable-anonrc", false, "Disable Anyone proxy routing (defaults to enabled on 127.0.0.1:9050)") + help = flag.Bool("help", false, "Show help") ) flag.Parse() + // Apply proxy disable flag early + anyoneproxy.SetDisabled(*disableAnon) + if *help { flag.Usage() return @@ -193,84 +198,84 @@ func isBootstrapNode() bool { // getPreferredLocalIP returns a non-loopback IPv4 address of this machine func getPreferredLocalIP() (string, error) { - ifaces, err := net.Interfaces() - if err != nil { - return "", err - } - for _, iface := range ifaces { - if (iface.Flags&net.FlagUp) == 0 || (iface.Flags&net.FlagLoopback) != 0 { - continue - } - addrs, err := iface.Addrs() - if err != nil { - continue - } - for _, addr := range addrs { - var ip net.IP - switch v := addr.(type) { - case *net.IPNet: - ip = v.IP - case *net.IPAddr: - ip = v.IP - } - if ip == nil || ip.IsLoopback() { - continue - } - ip = ip.To4() - if ip == nil { - continue - } - return ip.String(), nil - } - } - return "", fmt.Errorf("no non-loopback IPv4 found") + ifaces, err := net.Interfaces() + if err != nil { + return "", err + } + for _, iface := range ifaces { + if (iface.Flags&net.FlagUp) == 0 || (iface.Flags&net.FlagLoopback) != 0 { + continue + } + addrs, err := iface.Addrs() + if err != nil { + continue + } + for _, addr := range addrs { + var ip net.IP + switch v := addr.(type) { + case *net.IPNet: + ip = v.IP + case *net.IPAddr: + ip = v.IP + } + if ip == nil || ip.IsLoopback() { + continue + } + ip = ip.To4() + if ip == nil { + continue + } + return ip.String(), nil + } + } + return "", fmt.Errorf("no non-loopback IPv4 found") } // isLocalIP checks if the given IP address belongs to this machine func isLocalIP(ip string) bool { - if ip == "127.0.0.1" || strings.EqualFold(ip, "localhost") { - return true - } - ifaces, err := net.Interfaces() - if err != nil { - return false - } - for _, iface := range ifaces { - if (iface.Flags&net.FlagUp) == 0 { - continue - } - addrs, err := iface.Addrs() - if err != nil { - continue - } - for _, addr := range addrs { - var a net.IP - switch v := addr.(type) { - case *net.IPNet: - a = v.IP - case *net.IPAddr: - a = v.IP - } - if a != nil && a.String() == ip { - return true - } - } - } - return false + if ip == "127.0.0.1" || strings.EqualFold(ip, "localhost") { + return true + } + ifaces, err := net.Interfaces() + if err != nil { + return false + } + for _, iface := range ifaces { + if (iface.Flags & net.FlagUp) == 0 { + continue + } + addrs, err := iface.Addrs() + if err != nil { + continue + } + for _, addr := range addrs { + var a net.IP + switch v := addr.(type) { + case *net.IPNet: + a = v.IP + case *net.IPAddr: + a = v.IP + } + if a != nil && a.String() == ip { + return true + } + } + } + return false } // parseHostFromMultiaddr extracts the host from a multiaddr func parseHostFromMultiaddr(multiaddr string) string { - // Simple parsing for /ip4/host/tcp/port/p2p/peerid format - parts := strings.Split(multiaddr, "/") + // Simple parsing for /ip4/host/tcp/port/p2p/peerid format + parts := strings.Split(multiaddr, "/") - // Look for ip4/ip6/dns host in the multiaddr - for i, part := range parts { - if (part == "ip4" || part == "ip6" || part == "dns" || part == "dns4" || part == "dns6") && i+1 < len(parts) { - return parts[i+1] - } - } - return "" + // Look for ip4/ip6/dns host in the multiaddr + for i, part := range parts { + if (part == "ip4" || part == "ip6" || part == "dns" || part == "dns4" || part == "dns6") && i+1 < len(parts) { + return parts[i+1] + } + } + return "" } func startNode(ctx context.Context, cfg *config.Config, port int, isBootstrap bool, logger *logging.StandardLogger) error { @@ -309,73 +314,73 @@ func startNode(ctx context.Context, cfg *config.Config, port int, isBootstrap bo // runNetworkDiagnostics performs network connectivity tests func runNetworkDiagnostics(target string, logger *logging.StandardLogger) { - // If target has scheme, treat as HTTP URL. Otherwise treat as host:port raft. - var host, port string - if strings.HasPrefix(target, "http://") || strings.HasPrefix(target, "https://") { - url := strings.TrimPrefix(strings.TrimPrefix(target, "http://"), "https://") - parts := strings.Split(url, ":") - if len(parts) == 2 { - host, port = parts[0], parts[1] - } - } else { - parts := strings.Split(target, ":") - if len(parts) == 2 { - host, port = parts[0], parts[1] - } - } - if host == "" || port == "" { - logger.Printf("Cannot parse host:port from %s", target) - return - } + // If target has scheme, treat as HTTP URL. Otherwise treat as host:port raft. + var host, port string + if strings.HasPrefix(target, "http://") || strings.HasPrefix(target, "https://") { + url := strings.TrimPrefix(strings.TrimPrefix(target, "http://"), "https://") + parts := strings.Split(url, ":") + if len(parts) == 2 { + host, port = parts[0], parts[1] + } + } else { + parts := strings.Split(target, ":") + if len(parts) == 2 { + host, port = parts[0], parts[1] + } + } + if host == "" || port == "" { + logger.Printf("Cannot parse host:port from %s", target) + return + } - logger.Printf("Testing TCP connectivity to %s:%s", host, port) - if output, err := exec.Command("timeout", "5", "nc", "-z", "-v", host, port).CombinedOutput(); err == nil { - logger.Printf("✅ Port %s:%s is reachable", host, port) - logger.Printf("netcat output: %s", strings.TrimSpace(string(output))) - } else { - logger.Printf("❌ Port %s:%s is NOT reachable", host, port) - logger.Printf("netcat error: %v", err) - logger.Printf("netcat output: %s", strings.TrimSpace(string(output))) - } + logger.Printf("Testing TCP connectivity to %s:%s", host, port) + if output, err := exec.Command("timeout", "5", "nc", "-z", "-v", host, port).CombinedOutput(); err == nil { + logger.Printf("✅ Port %s:%s is reachable", host, port) + logger.Printf("netcat output: %s", strings.TrimSpace(string(output))) + } else { + logger.Printf("❌ Port %s:%s is NOT reachable", host, port) + logger.Printf("netcat error: %v", err) + logger.Printf("netcat output: %s", strings.TrimSpace(string(output))) + } - // Also probe HTTP status on port 5001 of the same host, which is the default HTTP API - httpURL := fmt.Sprintf("http://%s:%d/status", host, 5001) - if output, err := exec.Command("timeout", "5", "curl", "-s", "-o", "/dev/null", "-w", "%{http_code}", httpURL).Output(); err == nil { - httpCode := strings.TrimSpace(string(output)) - if httpCode == "200" { - logger.Printf("✅ HTTP service on %s is responding correctly (status: %s)", httpURL, httpCode) - } else { - logger.Printf("⚠️ HTTP service on %s responded with status: %s", httpURL, httpCode) - } - } else { - logger.Printf("❌ HTTP request to %s failed: %v", httpURL, err) - } + // Also probe HTTP status on port 5001 of the same host, which is the default HTTP API + httpURL := fmt.Sprintf("http://%s:%d/status", host, 5001) + if output, err := exec.Command("timeout", "5", "curl", "-s", "-o", "/dev/null", "-w", "%{http_code}", httpURL).Output(); err == nil { + httpCode := strings.TrimSpace(string(output)) + if httpCode == "200" { + logger.Printf("✅ HTTP service on %s is responding correctly (status: %s)", httpURL, httpCode) + } else { + logger.Printf("⚠️ HTTP service on %s responded with status: %s", httpURL, httpCode) + } + } else { + logger.Printf("❌ HTTP request to %s failed: %v", httpURL, err) + } - // Ping test - if output, err := exec.Command("ping", "-c", "3", "-W", "2", host).Output(); err == nil { - lines := strings.Split(string(output), "\n") - for _, line := range lines { - if strings.Contains(line, "packet loss") { - logger.Printf("🏓 Ping result: %s", strings.TrimSpace(line)) - break - } - } - } else { - logger.Printf("❌ Ping test failed: %v", err) - } + // Ping test + if output, err := exec.Command("ping", "-c", "3", "-W", "2", host).Output(); err == nil { + lines := strings.Split(string(output), "\n") + for _, line := range lines { + if strings.Contains(line, "packet loss") { + logger.Printf("🏓 Ping result: %s", strings.TrimSpace(line)) + break + } + } + } else { + logger.Printf("❌ Ping test failed: %v", err) + } - // DNS resolution - if output, err := exec.Command("nslookup", host).Output(); err == nil { - logger.Printf("🔍 DNS resolution successful") - lines := strings.Split(string(output), "\n") - for _, line := range lines { - if strings.Contains(line, "Address:") && !strings.Contains(line, "#53") { - logger.Printf("DNS result: %s", strings.TrimSpace(line)) - } - } - } else { - logger.Printf("❌ DNS resolution failed: %v", err) - } + // DNS resolution + if output, err := exec.Command("nslookup", host).Output(); err == nil { + logger.Printf("🔍 DNS resolution successful") + lines := strings.Split(string(output), "\n") + for _, line := range lines { + if strings.Contains(line, "Address:") && !strings.Contains(line, "#53") { + logger.Printf("DNS result: %s", strings.TrimSpace(line)) + } + } + } else { + logger.Printf("❌ DNS resolution failed: %v", err) + } - logger.Printf("=== END DIAGNOSTICS ===") + logger.Printf("=== END DIAGNOSTICS ===") } diff --git a/go.mod b/go.mod index a4d054b..0fc8415 100644 --- a/go.mod +++ b/go.mod @@ -5,13 +5,13 @@ go 1.23.8 toolchain go1.24.1 require ( - github.com/joho/godotenv v1.5.1 github.com/libp2p/go-libp2p v0.41.1 github.com/libp2p/go-libp2p-kad-dht v0.33.1 github.com/libp2p/go-libp2p-pubsub v0.14.2 github.com/multiformats/go-multiaddr v0.15.0 github.com/rqlite/gorqlite v0.0.0-20250609141355-ac86a4a1c9a8 go.uber.org/zap v1.27.0 + golang.org/x/net v0.42.0 ) require ( @@ -59,7 +59,6 @@ require ( github.com/libp2p/go-netroute v0.2.2 // indirect github.com/libp2p/go-reuseport v0.4.0 // indirect github.com/libp2p/go-yamux/v5 v5.0.0 // indirect - github.com/libp2p/zeroconf/v2 v2.2.0 // indirect github.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/miekg/dns v1.1.66 // indirect @@ -123,7 +122,6 @@ require ( golang.org/x/crypto v0.40.0 // indirect golang.org/x/exp v0.0.0-20250718183923-645b1fa84792 // indirect golang.org/x/mod v0.26.0 // indirect - golang.org/x/net v0.42.0 // indirect golang.org/x/sync v0.16.0 // indirect golang.org/x/sys v0.34.0 // indirect golang.org/x/text v0.27.0 // indirect diff --git a/go.sum b/go.sum index 602cb9d..858917b 100644 --- a/go.sum +++ b/go.sum @@ -132,8 +132,6 @@ github.com/jackpal/go-nat-pmp v1.0.2/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+ github.com/jbenet/go-temp-err-catcher v0.1.0 h1:zpb3ZH6wIE8Shj2sKS+khgRvf7T7RABoLk/+KKHggpk= github.com/jbenet/go-temp-err-catcher v0.1.0/go.mod h1:0kJRvmDZXNMIiJirNPEYfhpPwbGVtZVWC34vc5WLsDk= github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU= -github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= -github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= @@ -186,8 +184,6 @@ github.com/libp2p/go-reuseport v0.4.0 h1:nR5KU7hD0WxXCJbmw7r2rhRYruNRl2koHw8fQsc github.com/libp2p/go-reuseport v0.4.0/go.mod h1:ZtI03j/wO5hZVDFo2jKywN6bYKWLOy8Se6DrI2E1cLU= github.com/libp2p/go-yamux/v5 v5.0.0 h1:2djUh96d3Jiac/JpGkKs4TO49YhsfLopAoryfPmf+Po= github.com/libp2p/go-yamux/v5 v5.0.0/go.mod h1:en+3cdX51U0ZslwRdRLrvQsdayFt3TSUKvBGErzpWbU= -github.com/libp2p/zeroconf/v2 v2.2.0 h1:Cup06Jv6u81HLhIj1KasuNM/RHHrJ8T7wOTS4+Tv53Q= -github.com/libp2p/zeroconf/v2 v2.2.0/go.mod h1:fuJqLnUwZTshS3U/bMRJ3+ow/v9oid1n0DmyYyNO1Xs= github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI= github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd h1:br0buuQ854V8u83wA0rVZ8ttrq5CpaPZdvrK0LP2lOk= @@ -196,7 +192,6 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4= -github.com/miekg/dns v1.1.43/go.mod h1:+evo5L0630/F6ca/Z9+GAqzhjGyn8/c+TBaOyfEl0V4= github.com/miekg/dns v1.1.66 h1:FeZXOS3VCVsKnEAd+wBkjMC3D2K+ww66Cq3VnCINuJE= github.com/miekg/dns v1.1.66/go.mod h1:jGFzBsSNbJw6z1HYut1RKBKHA9PBdxeHrZG8J+gC2WE= github.com/mikioh/tcp v0.0.0-20190314235350-803a9b46060c h1:bzE/A84HN25pxAuk9Eej1Kz9OUelF97nAc82bDquQI8= @@ -450,7 +445,6 @@ golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210423184538-5f58ad60dda6/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= @@ -488,9 +482,6 @@ golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210426080607-c94f62235c83/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -512,7 +503,6 @@ golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= diff --git a/pkg/anyoneproxy/socks.go b/pkg/anyoneproxy/socks.go new file mode 100644 index 0000000..747429e --- /dev/null +++ b/pkg/anyoneproxy/socks.go @@ -0,0 +1,160 @@ +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 +} diff --git a/pkg/client/client.go b/pkg/client/client.go index 0360031..ad66cf9 100644 --- a/pkg/client/client.go +++ b/pkg/client/client.go @@ -21,6 +21,7 @@ import ( "git.debros.io/DeBros/network/pkg/discovery" "git.debros.io/DeBros/network/pkg/pubsub" "git.debros.io/DeBros/network/pkg/storage" + "git.debros.io/DeBros/network/pkg/anyoneproxy" ) // Client implements the NetworkClient interface @@ -123,14 +124,23 @@ func (c *Client) Connect() error { return nil } - // Create LibP2P host - h, err := libp2p.New( + // Create LibP2P host with optional Anyone proxy for TCP and optional QUIC disable + var opts []libp2p.Option + opts = append(opts, libp2p.ListenAddrStrings("/ip4/0.0.0.0/tcp/0"), // Random port libp2p.Security(noise.ID, noise.New), - libp2p.Transport(tcp.NewTCPTransport), - libp2p.Transport(libp2pquic.NewTransport), libp2p.DefaultMuxers, ) + if anyoneproxy.Enabled() { + opts = append(opts, libp2p.Transport(tcp.NewTCPTransport, tcp.WithDialerForAddr(anyoneproxy.DialerForAddr()))) + } else { + opts = append(opts, libp2p.Transport(tcp.NewTCPTransport)) + } + // Enable QUIC only when not proxying. When proxy is enabled, prefer TCP via SOCKS5. + if !anyoneproxy.Enabled() { + opts = append(opts, libp2p.Transport(libp2pquic.NewTransport)) + } + h, err := libp2p.New(opts...) if err != nil { return fmt.Errorf("failed to create libp2p host: %w", err) } diff --git a/pkg/client/implementations.go b/pkg/client/implementations.go index 6267054..5749a63 100644 --- a/pkg/client/implementations.go +++ b/pkg/client/implementations.go @@ -14,6 +14,7 @@ import ( "github.com/libp2p/go-libp2p/core/peer" "github.com/multiformats/go-multiaddr" "github.com/rqlite/gorqlite" + "git.debros.io/DeBros/network/pkg/anyoneproxy" ) // DatabaseClientImpl implements DatabaseClient @@ -250,7 +251,15 @@ func (d *DatabaseClientImpl) connectToAvailableNode() (*gorqlite.Connection, err var lastErr error for _, rqliteURL := range rqliteNodes { - conn, err := gorqlite.Open(rqliteURL) + var conn *gorqlite.Connection + var err error + // If Anyone proxy is enabled, build a proxy-aware HTTP client + if anyoneproxy.Enabled() { + httpClient := anyoneproxy.NewHTTPClient() + conn, err = gorqlite.OpenWithClient(rqliteURL, httpClient) + } else { + conn, err = gorqlite.Open(rqliteURL) + } if err != nil { lastErr = err continue diff --git a/pkg/node/node.go b/pkg/node/node.go index 30989ef..caae5c4 100644 --- a/pkg/node/node.go +++ b/pkg/node/node.go @@ -14,6 +14,7 @@ import ( "github.com/libp2p/go-libp2p/core/crypto" "github.com/libp2p/go-libp2p/core/host" "github.com/libp2p/go-libp2p/core/peer" + "github.com/libp2p/go-libp2p/core/protocol" noise "github.com/libp2p/go-libp2p/p2p/security/noise" libp2pquic "github.com/libp2p/go-libp2p/p2p/transport/quic" "github.com/libp2p/go-libp2p/p2p/transport/tcp" @@ -22,6 +23,7 @@ import ( "git.debros.io/DeBros/network/pkg/config" "git.debros.io/DeBros/network/pkg/database" + "git.debros.io/DeBros/network/pkg/anyoneproxy" "git.debros.io/DeBros/network/pkg/logging" "git.debros.io/DeBros/network/pkg/storage" ) @@ -132,15 +134,44 @@ func (n *Node) startLibP2P() error { return fmt.Errorf("failed to load identity: %w", err) } + // Log Anyone proxy status before constructing host + n.logger.ComponentInfo(logging.ComponentLibP2P, "Anyone proxy status", + zap.Bool("proxy_enabled", anyoneproxy.Enabled()), + zap.String("proxy_addr", anyoneproxy.Address()), + zap.Bool("proxy_running", anyoneproxy.Running()), + ) + + if anyoneproxy.Enabled() && !anyoneproxy.Running() { + n.logger.Warn("Anyone proxy is enabled but not reachable", + zap.String("addr", anyoneproxy.Address())) + } + // Create LibP2P host with persistent identity - h, err := libp2p.New( + // Build options allowing conditional proxying via Anyone SOCKS5 and optional QUIC disable + var opts []libp2p.Option + opts = append(opts, libp2p.Identity(identity), libp2p.ListenAddrs(listenAddrs...), libp2p.Security(noise.ID, noise.New), - libp2p.Transport(tcp.NewTCPTransport), - libp2p.Transport(libp2pquic.NewTransport), libp2p.DefaultMuxers, ) + + // TCP transport with optional SOCKS5 dialer override + if anyoneproxy.Enabled() { + opts = append(opts, libp2p.Transport(tcp.NewTCPTransport, tcp.WithDialerForAddr(anyoneproxy.DialerForAddr()))) + } else { + opts = append(opts, libp2p.Transport(tcp.NewTCPTransport)) + } + + // QUIC transport: disabled when proxy is enabled (default), + // enabled only when not proxying. + if !anyoneproxy.Enabled() { + opts = append(opts, libp2p.Transport(libp2pquic.NewTransport)) + } else { + n.logger.ComponentDebug(logging.ComponentLibP2P, "QUIC disabled due to proxy being enabled") + } + + h, err := libp2p.New(opts...) if err != nil { return err } @@ -148,12 +179,31 @@ func (n *Node) startLibP2P() error { n.host = h // Create DHT for peer discovery - Use server mode for better peer discovery - kademliaDHT, err := dht.New(context.Background(), h, dht.Mode(dht.ModeServer)) + // Use configured protocol prefix to ensure we discover peers on the correct DHT namespace + dhtPrefix := n.config.Discovery.DHTPrefix + if strings.TrimSpace(dhtPrefix) == "" { + dhtPrefix = "/network/kad/1.0.0" + } + n.logger.ComponentInfo(logging.ComponentDHT, "Using DHT protocol prefix", zap.String("prefix", dhtPrefix)) + kademliaDHT, err := dht.New( + context.Background(), + h, + dht.Mode(dht.ModeServer), + dht.ProtocolPrefix(protocol.ID(dhtPrefix)), + ) if err != nil { return fmt.Errorf("failed to create DHT: %w", err) } n.dht = kademliaDHT + // Log configured bootstrap peers + if len(n.config.Discovery.BootstrapPeers) > 0 { + n.logger.ComponentInfo(logging.ComponentDHT, "Configured bootstrap peers", + zap.Strings("peers", n.config.Discovery.BootstrapPeers)) + } else { + n.logger.ComponentDebug(logging.ComponentDHT, "No bootstrap peers configured") + } + // Connect to LibP2P bootstrap peers if configured if err := n.connectToBootstrapPeers(); err != nil { n.logger.Warn("Failed to connect to bootstrap peers", zap.Error(err)) @@ -308,6 +358,13 @@ func (n *Node) connectToBootstrapPeer(ctx context.Context, addr string) error { return fmt.Errorf("failed to extract peer info: %w", err) } + // Log resolved peer info prior to connect + n.logger.ComponentDebug(logging.ComponentDHT, "Resolved bootstrap peer", + zap.String("peer_id", peerInfo.ID.String()), + zap.String("addr", addr), + zap.Int("addr_count", len(peerInfo.Addrs)), + ) + // Connect to the peer if err := n.host.Connect(ctx, *peerInfo); err != nil { return fmt.Errorf("failed to connect to peer: %w", err)