tornago

package module
v0.4.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 27, 2025 License: MIT Imports: 21 Imported by: 0

README

Go Reference Go Report Card Coverage

日本語 | Español | Français | 한국어 | Русский | 中文

tornago

tornago-logo

Tornago is a lightweight wrapper around the Tor command-line tool, providing three core functionalities:

  • Tor Daemon Management: Launch and manage Tor processes programmatically
  • Tor Client: Route HTTP/TCP traffic through Tor's SOCKS5 proxy with automatic retries
  • Tor Server: Create and manage Hidden Services (onion services) via Tor's ControlPort

The library is designed for both development (launching ephemeral Tor instances) and production (connecting to existing Tor deployments). Tested successfully across linux, macOS, Windows and major BSD variants.

Why tornago?

I created tornago after learning about the need for dark web crawling in credit card fraud prevention contexts -- I belong to the anti-fraud team. While Python is commonly used for Tor-based crawling, I prefer Go for its stability and robustness in production environments, so I wanted a Go library for this purpose.

To prevent potential misuse, tornago is intentionally kept as a thin wrapper around the original Tor command-line tool. I have deliberately limited its convenience features to minimize the risk of abuse.

[!IMPORTANT] Legal Notice: This library is intended for legitimate purposes only, such as privacy protection, security research, and authorized fraud prevention activities. Users are solely responsible for ensuring their use of Tor and this library complies with all applicable laws and regulations. Do not use this tool for any illegal activities.

Features

  • Zero external Go dependencies. Built on standard library only.
  • net.Listener, net.Addr, net.Dialer compatible interfaces for easy integration.
  • Functional options pattern for configuration.
  • Structured errors with errors.Is/errors.As support.
  • Automatic retry with exponential backoff.
  • Optional metrics collection and rate limiting.
  • Only requires Tor binary as external dependency.

How Tor Works

Tor (The Onion Router) provides anonymity by routing traffic through multiple encrypted layers. Understanding this mechanism helps you use tornago effectively.

Onion Routing: Multi-Layer Encryption
sequenceDiagram
    participant Client as Your Application<br/>(tornago)
    participant Guard as Entry Node<br/>(Guard)
    participant Middle as Middle Node
    participant Exit as Exit Node
    participant Target as Target Server<br/>(example.com)

    Note over Client: 1. Build Circuit
    Client->>Guard: Encrypted with Guard's key<br/>[Middle info + Exit info + Request]
    Note over Guard: Decrypt 1st layer<br/>See: Middle node address
    Guard->>Middle: Encrypted with Middle's key<br/>[Exit info + Request]
    Note over Middle: Decrypt 2nd layer<br/>See: Exit node address
    Middle->>Exit: Encrypted with Exit's key<br/>[Request]
    Note over Exit: Decrypt 3rd layer<br/>See: Target address

    Note over Client,Target: 2. Send Request
    Client->>Guard: Encrypted data (3 layers)
    Guard->>Middle: Encrypted data (2 layers)
    Middle->>Exit: Encrypted data (1 layer)
    Exit->>Target: Plain HTTP/HTTPS request

    Note over Client,Target: 3. Receive Response
    Target->>Exit: Plain HTTP/HTTPS response
    Exit->>Middle: Encrypted response (1 layer)
    Middle->>Guard: Encrypted response (2 layers)
    Guard->>Client: Encrypted response (3 layers)
    Note over Client: Decrypt all layers<br/>See final response
Key Security Properties

Layered Encryption (Onion Layers)

  • Each relay only knows its immediate predecessor and successor
  • Entry node (Guard) knows your IP but not your destination
  • Exit node knows your destination but not your IP
  • Middle node knows neither your IP nor destination

Privacy Guarantees

  • Your ISP sees: You connect to a Tor entry node (but not what you're accessing)
  • Entry node sees: Your IP address (but not your destination)
  • Middle node sees: Only relay traffic (no source or destination)
  • Exit node sees: Your destination (but not your real IP)
  • Target server sees: Exit node's IP (not your real IP)

Limitations to Understand

  • Exit node can see unencrypted traffic (use HTTPS for end-to-end encryption)
  • Exit node operators could monitor traffic (but can't trace back to you)
  • Timing analysis might correlate traffic patterns (Tor provides anonymity, not perfect unlinkability)
  • Slower than direct connection (3-hop routing adds latency)
Tornago's Role

Tornago simplifies Tor integration by handling:

  1. SOCKS5 Proxy Communication: Automatically routes your HTTP/TCP traffic through Tor's SOCKS5 proxy
  2. Circuit Management: Uses ControlPort to rotate circuits (get new exit nodes)
  3. Hidden Service Creation: Manages .onion addresses via ADD_ONION/DEL_ONION commands
graph LR
    A[Your Go App] -->|tornago| B[Tor Daemon]
    B -->|SOCKS5 Proxy| C[Tor Network]
    C --> D[Target Server]

    A -->|ControlPort| B
    B -.->|Circuit Control| C

Requirements

Go
  • Go Version: 1.25 or later
Operating Systems (Tested in GitHub Actions)
  • Linux
  • macOS
  • Windows
  • FreeBSD
  • OpenBSD
  • NetBSD
  • DragonFly BSD
Tor

Tornago requires the Tor daemon to be installed on your system. The library has been tested with Tor version 0.4.8.x and should work with newer versions.

Installation:

# Ubuntu/Debian
sudo apt update
sudo apt install tor

# Fedora/RHEL
sudo dnf install tor

# Arch Linux
sudo pacman -S tor

# macOS (Homebrew)
brew install tor

After installation, verify Tor is available:

tor --version

Tor Protocol Version: Tornago uses the Tor ControlPort protocol and supports SOCKS5 proxy (version 5). It is compatible with Tor protocol versions that support:

  • ControlPort commands: AUTHENTICATE, GETINFO, SIGNAL NEWNYM, ADD_ONION, DEL_ONION
  • Cookie and password authentication methods
  • ED25519-V3 onion addresses

Quick Start

Access website using tornago

This example demonstrates how to start a Tor daemon and fetch a website through Tor (examples/simple_client/main.go):

package main

import (
	"context"
	"fmt"
	"io"
	"log"
	"net/http"
	"time"

	"github.com/nao1215/tornago"
)

func main() {
	// Step 1: Launch Tor daemon
	fmt.Println("Starting Tor daemon...")
	launchCfg, err := tornago.NewTorLaunchConfig(
		tornago.WithTorSocksAddr(":0"),     // Use random available port
		tornago.WithTorControlAddr(":0"),   // Use random available port
		tornago.WithTorStartupTimeout(60*time.Second),
	)
	if err != nil {
		log.Fatalf("Failed to create launch config: %v", err)
	}

	torProcess, err := tornago.StartTorDaemon(launchCfg)
	if err != nil {
		log.Fatalf("Failed to start Tor daemon: %v", err)
	}
	defer torProcess.Stop()

	fmt.Printf("Tor daemon started successfully!\n")
	fmt.Printf("  SOCKS address: %s\n", torProcess.SocksAddr())
	fmt.Printf("  Control address: %s\n", torProcess.ControlAddr())

	// Step 2: Create Tor client
	clientCfg, err := tornago.NewClientConfig(
		tornago.WithClientSocksAddr(torProcess.SocksAddr()),
		tornago.WithClientRequestTimeout(60*time.Second),
	)
	if err != nil {
		log.Fatalf("Failed to create client config: %v", err)
	}

	client, err := tornago.NewClient(clientCfg)
	if err != nil {
		log.Fatalf("Failed to create client: %v", err)
	}
	defer client.Close()

	// Step 3: Make HTTP request through Tor
	fmt.Println("\nFetching https://example.com through Tor...")
	req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, "https://example.com", http.NoBody)
	if err != nil {
		log.Fatalf("Failed to create request: %v", err)
	}

	resp, err := client.Do(req)
	if err != nil {
		log.Fatalf("Request failed: %v", err)
	}
	defer resp.Body.Close()

	fmt.Printf("Status: %s\n", resp.Status)

	body, err := io.ReadAll(io.LimitReader(resp.Body, 500))
	if err != nil {
		log.Fatalf("Failed to read response: %v", err)
	}

	fmt.Printf("\nResponse preview (first 500 bytes):\n%s\n", string(body))
}

Output:

Starting Tor daemon...
Tor daemon started successfully!
  SOCKS address: 127.0.0.1:42715
  Control address: 127.0.0.1:35199

Fetching https://example.com through Tor...
Status: 200 OK

Response preview (first 500 bytes):
<!doctype html><html lang="en"><head><title>Example Domain</title>...
Access .onion using tornago

This example demonstrates how to access a .onion site (DuckDuckGo's onion service) through Tor (examples/onion_client/main.go):

package main

import (
	"context"
	"fmt"
	"io"
	"log"
	"net/http"
	"time"

	"github.com/nao1215/tornago"
)

func main() {
	// Step 1: Launch Tor daemon
	fmt.Println("Starting Tor daemon...")
	launchCfg, err := tornago.NewTorLaunchConfig(
		tornago.WithTorSocksAddr(":0"),   // Use random available port
		tornago.WithTorControlAddr(":0"), // Use random available port
		tornago.WithTorStartupTimeout(60*time.Second),
	)
	if err != nil {
		log.Fatalf("Failed to create launch config: %v", err)
	}

	torProcess, err := tornago.StartTorDaemon(launchCfg)
	if err != nil {
		log.Fatalf("Failed to start Tor daemon: %v", err)
	}
	defer torProcess.Stop()

	fmt.Printf("Tor daemon started successfully!\n")
	fmt.Printf("  SOCKS address: %s\n", torProcess.SocksAddr())
	fmt.Printf("  Control address: %s\n", torProcess.ControlAddr())

	// Step 2: Create Tor client
	clientCfg, err := tornago.NewClientConfig(
		tornago.WithClientSocksAddr(torProcess.SocksAddr()),
		tornago.WithClientRequestTimeout(60*time.Second),
	)
	if err != nil {
		log.Fatalf("Failed to create client config: %v", err)
	}

	client, err := tornago.NewClient(clientCfg)
	if err != nil {
		log.Fatalf("Failed to create client: %v", err)
	}
	defer client.Close()

	// Step 3: Access .onion site (DuckDuckGo)
	onionURL := "https://duckduckgogg42xjoc72x3sjasowoarfbgcmvfimaftt6twagswzczad.onion/"
	fmt.Printf("\nAccessing DuckDuckGo onion service: %s\n", onionURL)

	req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, onionURL, http.NoBody)
	if err != nil {
		log.Fatalf("Failed to create request: %v", err)
	}

	resp, err := client.Do(req)
	if err != nil {
		log.Fatalf("Request failed: %v", err)
	}
	defer resp.Body.Close()

	fmt.Printf("Status: %s\n", resp.Status)

	body, err := io.ReadAll(io.LimitReader(resp.Body, 500))
	if err != nil {
		log.Fatalf("Failed to read response: %v", err)
	}

	fmt.Printf("\nResponse preview (first 500 bytes):\n%s\n", string(body))
}

Output:

Starting Tor daemon...
Tor daemon started successfully!
  SOCKS address: 127.0.0.1:42369
  Control address: 127.0.0.1:46475

Accessing DuckDuckGo onion service: https://duckduckgogg42xjoc72x3sjasowoarfbgcmvfimaftt6twagswzczad.onion/
Status: 200 OK

Response preview (first 500 bytes):
<!DOCTYPE html><html lang="en-US" class=""><head><meta charSet="utf-8"...
Host .onion using tornago

This example demonstrates how to create a Hidden Service (.onion) and serve a webpage through Tor (examples/onion_server/main.go):

package main

import (
	"context"
	"fmt"
	"log"
	"net"
	"net/http"
	"os"
	"os/signal"
	"syscall"
	"time"

	"github.com/nao1215/tornago"
)

func main() {
	// Step 1: Launch Tor daemon
	fmt.Println("Starting Tor daemon...")
	launchCfg, err := tornago.NewTorLaunchConfig(
		tornago.WithTorSocksAddr(":0"),   // Use random available port
		tornago.WithTorControlAddr(":0"), // Use random available port
		tornago.WithTorStartupTimeout(60*time.Second),
	)
	if err != nil {
		log.Fatalf("Failed to create launch config: %v", err)
	}

	torProcess, err := tornago.StartTorDaemon(launchCfg)
	if err != nil {
		log.Fatalf("Failed to start Tor daemon: %v", err)
	}
	defer torProcess.Stop()

	fmt.Printf("Tor daemon started successfully!\n")
	fmt.Printf("  SOCKS address: %s\n", torProcess.SocksAddr())
	fmt.Printf("  Control address: %s\n", torProcess.ControlAddr())

	// Step 2: Start local HTTP server
	localAddr := "127.0.0.1:8080"
	mux := http.NewServeMux()
	mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		html := `<!DOCTYPE html>
<html>
<head>
    <title>Tornago Hidden Service</title>
    <style>
        body {
            font-family: Arial, sans-serif;
            max-width: 800px;
            margin: 50px auto;
            padding: 20px;
            background-color: #f5f5f5;
        }
        .container {
            background-color: white;
            padding: 30px;
            border-radius: 10px;
            box-shadow: 0 2px 10px rgba(0,0,0,0.1);
        }
        h1 {
            color: #7d4698;
        }
        .info {
            background-color: #f0e6f6;
            padding: 15px;
            border-radius: 5px;
            margin: 20px 0;
        }
        code {
            background-color: #e0e0e0;
            padding: 2px 6px;
            border-radius: 3px;
            font-family: monospace;
        }
    </style>
</head>
<body>
    <div class="container">
        <h1>🧅 Welcome to Tornago Hidden Service!</h1>
        <p>This is a simple web page hosted as a Tor Hidden Service (.onion) using the <strong>tornago</strong> library.</p>

        <div class="info">
            <h3>Connection Info:</h3>
            <p><strong>Your IP:</strong> <code>` + r.RemoteAddr + `</code></p>
            <p><strong>Request Path:</strong> <code>` + r.URL.Path + `</code></p>
            <p><strong>User Agent:</strong> <code>` + r.UserAgent() + `</code></p>
        </div>

        <h3>About Tornago:</h3>
        <p>Tornago is a lightweight Go wrapper around the Tor command-line tool, providing:</p>
        <ul>
            <li>Tor Daemon Management</li>
            <li>Tor Client (SOCKS5 proxy)</li>
            <li>Tor Server (Hidden Services)</li>
        </ul>

        <p style="margin-top: 30px; text-align: center; color: #666;">
            Powered by <strong>tornago</strong> 🚀
        </p>
    </div>
</body>
</html>`
		w.Header().Set("Content-Type", "text/html; charset=utf-8")
		fmt.Fprint(w, html)
	})

	server := &http.Server{
		Addr:              localAddr,
		Handler:           mux,
		ReadHeaderTimeout: 5 * time.Second,
	}

	lc := net.ListenConfig{}
	listener, err := lc.Listen(context.Background(), "tcp", localAddr)
	if err != nil {
		log.Fatalf("Failed to start HTTP server: %v", err)
	}

	go func() {
		if err := server.Serve(listener); err != nil && err != http.ErrServerClosed {
			log.Fatalf("HTTP server error: %v", err)
		}
	}()

	fmt.Printf("\nLocal HTTP server started on http://%s\n", localAddr)

	// Step 3: Get control authentication and create ControlClient directly
	fmt.Println("\nObtaining Tor control authentication...")
	auth, _, err := tornago.ControlAuthFromTor(torProcess.ControlAddr(), 30*time.Second)
	if err != nil {
		log.Fatalf("Failed to get control auth: %v", err)
	}

	// Step 4: Create ControlClient directly (instead of via tornago.Client)
	controlClient, err := tornago.NewControlClient(
		torProcess.ControlAddr(),
		auth,
		30*time.Second,
	)
	if err != nil {
		log.Fatalf("Failed to create control client: %v", err)
	}
	defer controlClient.Close()

	if err := controlClient.Authenticate(); err != nil {
		log.Fatalf("Failed to authenticate with Tor: %v", err)
	}

	// Step 5: Create Hidden Service
	hsCfg, err := tornago.NewHiddenServiceConfig(
		tornago.WithHiddenServicePort(80, 8080), // Map onion port 80 to local port 8080
	)
	if err != nil {
		log.Fatalf("Failed to create hidden service config: %v", err)
	}

	fmt.Println("\nCreating Hidden Service...")
	hs, err := controlClient.CreateHiddenService(context.Background(), hsCfg)
	if err != nil {
		log.Fatalf("Failed to create hidden service: %v", err)
	}
	defer func() {
		if err := hs.Remove(context.Background()); err != nil {
			log.Printf("Failed to delete hidden service: %v", err)
		}
	}()

	fmt.Printf("\n✅ Hidden Service created successfully!\n")
	fmt.Printf("   Onion Address: http://%s\n", hs.OnionAddress())
	fmt.Printf("   Local Address: http://%s\n", localAddr)
	fmt.Println("\nYou can access this hidden service through Tor using the onion address above.")
	fmt.Println("Press Ctrl+C to stop the server...")

	// Wait for interrupt signal
	sigChan := make(chan os.Signal, 1)
	signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM)
	<-sigChan

	fmt.Println("\n\nShutting down...")
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()
	if err := server.Shutdown(ctx); err != nil {
		log.Printf("Server shutdown error: %v", err)
	}
}

Output:

Starting Tor daemon...
Tor daemon started successfully!
  SOCKS address: 127.0.0.1:36065
  Control address: 127.0.0.1:37285

Local HTTP server started on http://127.0.0.1:8080

Obtaining Tor control authentication...

Creating Hidden Service...

✅ Hidden Service created successfully!
   Onion Address: http://f64ekih3d23wxhdb547wfj7nornjw5nb3ehuu4do45tw2wwmuzhad3yd.onion
   Local Address: http://127.0.0.1:8080

Access the hidden service through Tor using the onion address above.
Press Ctrl+C to stop...

You can now access your hidden service through Tor Browser or any Tor client using the generated .onion address!

onion site

Slow Relay Avoidance

Tornago includes an automatic performance tracking system that detects and avoids slow Tor relays. Simply enable it with one option and the client handles everything internally.

// Create client with slow relay avoidance enabled
client, err := tornago.NewClient(
    tornago.WithClientSocksAddr(torProcess.SocksAddr()),
    tornago.WithClientControlAddr(torProcess.ControlAddr()),
    tornago.WithSlowRelayAvoidance(),  // Enable with defaults
)
if err != nil {
    log.Fatal(err)
}
defer client.Close()

// Make requests normally - everything is handled automatically
resp, err := client.Do(req)

// Optionally check performance statistics
stats, ok := client.RelayPerformanceStats()
if ok {
    fmt.Printf("Tracked: %d, Blocked: %d\n", stats.TrackedRelays(), stats.BlockedRelays())
}
Custom Threshold Configuration
// Enable with custom settings for stricter requirements
client, err := tornago.NewClient(
    tornago.WithClientSocksAddr(torProcess.SocksAddr()),
    tornago.WithClientControlAddr(torProcess.ControlAddr()),
    tornago.WithSlowRelayAvoidance(
        tornago.SlowRelayMaxLatency(3*time.Second),   // Block relays slower than 3s
        tornago.SlowRelayMinSuccessRate(0.9),         // Require 90% success rate
        tornago.SlowRelayBlockDuration(1*time.Hour),  // Block for 1 hour
        tornago.SlowRelayMinSamples(5),               // Need 5 samples before judging
        tornago.SlowRelayMonitorInterval(15*time.Second), // Check every 15s
    ),
)
How It Works
sequenceDiagram
    participant App as Your Application
    participant Client as Tornago Client
    participant Tor as Tor Daemon
    participant Network as Tor Network

    Note over App,Network: Phase 1: Normal Operation with Automatic Measurement

    App->>Client: client.Do(req)
    Client->>Tor: HTTP Request
    Tor->>Network: Route through Circuit<br/>(Guard → Middle → Exit)
    Network-->>Tor: Response
    Tor-->>Client: Response (latency: 2s)
    Client->>Client: Auto-record measurement<br/>for all relays in circuit
    Client-->>App: Response

    Note over App,Network: Phase 2: Slow Relay Detected

    App->>Client: client.Do(req)
    Client->>Tor: HTTP Request
    Tor->>Network: Route through Circuit
    Network-->>Tor: Response (slow)
    Tor-->>Client: Response (latency: 8s)
    Client->>Client: Auto-record measurement<br/>Exit: avg=5s exceeds threshold!<br/>→ Block slow relay

    alt Auto-Exclude Enabled (default)
        Client->>Tor: SETCONF ExcludeNodes=$fingerprint
        Note over Tor: Tor will avoid<br/>this relay
    end

    Client-->>App: Response

    Note over App,Network: Phase 3: Background Monitor Rotation

    Client->>Tor: GETINFO circuit-status
    Tor-->>Client: Circuit paths
    Client->>Client: Check if circuit uses blocked relay
    Client->>Tor: SIGNAL NEWNYM
    Note over Tor: Build new circuit<br/>without slow relay

    Note over App,Network: Phase 4: Improved Performance

    App->>Client: client.Do(req)
    Client->>Tor: HTTP Request
    Tor->>Network: Route through new circuit
    Network-->>Tor: Response (fast)
    Tor-->>Client: Response (latency: 1.5s)
    Client-->>App: Response (OK)
Default Threshold Values
Parameter Default Description
MaxLatency 5 seconds Relays slower than this are considered "slow"
MinSuccessRate 80% Relays with lower success rate are blocked
BlockDuration 30 minutes How long slow relays remain blocked
MinSamples 3 Minimum measurements needed before evaluation
MonitorInterval 30 seconds Background check interval for circuit rotation
AutoExclude true Automatically update Tor's ExcludeNodes

See examples/slow_relay_avoidance for a complete working example.

More Examples

The examples/ directory contains additional working examples:

All examples are tested and ready to run.

Tool using tornago

Contributing

Contributions are welcome! Please see the Contributing Guide for more details.

Support

If you find this project useful, please consider:

  • Giving it a star on GitHub - it helps others discover the project
  • Becoming a sponsor - your support keeps the project alive and motivates continued development

Your support, whether through stars, sponsorships, or contributions, is what drives this project forward. Thank you!

License

MIT License

Altenative Library, Official references

Documentation

Overview

Package tornago provides a Tor client/server helper library that can launch tor for development and connect to existing Tor instances in production.

What is Tor?

Tor (The Onion Router) is a network of relays that anonymizes internet traffic by routing connections through multiple encrypted hops. Key concepts:

  • SocksPort: The SOCKS5 proxy port that applications use to route traffic through Tor. Think of it as the "entrance" to the Tor network for your application's outbound connections.

  • ControlPort: A text-based management interface for controlling a running Tor instance. Used for operations like rotating circuits (NewIdentity), creating hidden services, and querying Tor's internal state.

  • Hidden Service (Onion Service): A service accessible only through the Tor network, identified by a .onion address. This allows you to host servers that are both anonymous and accessible without requiring a public IP address or DNS registration.

  • Circuit: The path your traffic takes through multiple Tor relays. Each circuit typically consists of 3 relays (guard, middle, exit) for outbound connections.

Quick Start

For the simplest use case (making HTTP requests through Tor), you need:

  1. A running Tor instance (installed via package manager or launched by tornago)
  2. A Client configured with the Tor SocksPort address
  3. Use the Client's HTTP() method to get an *http.Client that routes through Tor

See Example_quickStart for a complete minimal example.

Main Use Cases

**Making HTTP requests through Tor** (most common):

  • Create a Client pointing to a Tor SocksPort
  • Use client.HTTP() to get an *http.Client that routes through Tor
  • All HTTP requests automatically go through Tor's anonymizing network

**Launching Tor programmatically** (development/testing):

  • Use StartTorDaemon() to launch a tor process managed by your application
  • tornago handles port allocation, startup synchronization, and cleanup
  • Useful when you don't want to require users to install/configure Tor separately

**Creating Hidden Services** (hosting anonymous servers):

  • Use ControlClient.CreateHiddenService() to create a .onion address
  • Map your local server port to a virtual onion port
  • Your service becomes accessible via Tor without exposing your IP address

**Rotating Tor circuits** (getting a new IP address):

  • Use ControlClient.NewIdentity() to signal Tor to build new circuits
  • Subsequent requests will use different exit nodes (different public IPs)
  • Useful for rate-limiting evasion or additional anonymity

Architecture Overview

tornago provides several components that work together:

  • Client: High-level HTTP/TCP client with automatic Tor routing and retry logic
  • ControlClient: Low-level interface to Tor's ControlPort for management commands
  • TorProcess: Represents a tor daemon launched by StartTorDaemon()
  • Server: Simple wrapper for existing Tor instance addresses
  • HiddenService: Represents a created .onion service

All configurations use functional options pattern for flexibility and immutability.

API Design Principles

**Method Naming Conventions**

tornago follows a consistent naming convention to distinguish between different types of verification operations:

  • Check*() methods perform internal health checks and status verification

  • Check() - Verifies SOCKS and ControlPort connectivity

  • CheckDNSLeak() - Detects if DNS queries are leaking outside Tor

  • CheckTorDaemon() - Checks if the Tor process is running properly

  • These methods are faster but rely on internal heuristics

  • Verify*() methods use external validation via third-party services

  • VerifyTorConnection() - Confirms Tor usage via check.torproject.org

  • These methods are more authoritative but depend on external services

This distinction helps you choose the appropriate method:

  • Use Check*() for quick health monitoring and internal validation
  • Use Verify*() when you need authoritative external confirmation

Authentication

Tor's ControlPort requires authentication. tornago supports:

  • Cookie authentication (default): Tor writes a random cookie file, tornago reads it
  • Password authentication: You configure a hashed password in Tor and provide it to tornago

When using StartTorDaemon(), cookie authentication is configured automatically. When connecting to an existing Tor instance, you must provide appropriate credentials.

Error Handling

All tornago errors are wrapped in TornagoError with a Kind field for programmatic handling. Use errors.Is() to check error kinds:

if errors.Is(err, &tornago.TornagoError{Kind: tornago.ErrSocksDialFailed}) {
    // Handle connection failure
}

Common error kinds:

  • ErrTorBinaryNotFound: tor executable not in PATH (install via package manager)
  • ErrSocksDialFailed: Cannot connect to Tor SocksPort (is Tor running?)
  • ErrControlRequestFail: ControlPort command failed (check authentication)
  • ErrTimeout: Operation exceeded deadline (increase timeout or check network)

Configuration

**Timeout Recommendations**

Tor adds significant latency due to multi-hop routing:

  • Dial timeout: 20-30 seconds for production, 30-60 seconds for .onion sites
  • Request timeout: 60-120 seconds for typical requests, 120-300 seconds for large downloads
  • Startup timeout: 60-120 seconds for first launch, 30-60 seconds with cached state

**Development Environment**

Launch ephemeral Tor daemon for testing:

launchCfg, _ := tornago.NewTorLaunchConfig(
    tornago.WithTorSocksAddr(":0"),  // Random port
    tornago.WithTorControlAddr(":0"),
    tornago.WithTorStartupTimeout(60*time.Second),
)
torProcess, _ := tornago.StartTorDaemon(launchCfg)
defer torProcess.Stop()

clientCfg, _ := tornago.NewClientConfig(
    tornago.WithClientSocksAddr(torProcess.SocksAddr()),
    tornago.WithClientRequestTimeout(30*time.Second),
)

**Production Environment**

Connect to system Tor daemon:

clientCfg, _ := tornago.NewClientConfig(
    tornago.WithClientSocksAddr("127.0.0.1:9050"),
    tornago.WithClientDialTimeout(30*time.Second),
    tornago.WithClientRequestTimeout(120*time.Second),
)

System Tor configuration (/etc/tor/torrc):

SocksPort 127.0.0.1:9050
ControlPort 127.0.0.1:9051
CookieAuthentication 1

**With Metrics and Rate Limiting**

metrics := tornago.NewMetricsCollector()
rateLimiter := tornago.NewRateLimiter(5.0, 10)  // 5 req/s, burst 10

clientCfg, _ := tornago.NewClientConfig(
    tornago.WithClientSocksAddr("127.0.0.1:9050"),
    tornago.WithClientMetrics(metrics),
    tornago.WithClientRateLimiter(rateLimiter),
)

// Check metrics
fmt.Printf("Requests: %d, Success: %d, Avg latency: %v\n",
    metrics.RequestCount(), metrics.SuccessCount(), metrics.AverageLatency())

**With Observability (Logging and Health Checks)**

// Structured logging
logger := slog.New(slog.NewJSONHandler(os.Stdout, nil))
slogAdapter := tornago.NewSlogAdapter(logger)

clientCfg, _ := tornago.NewClientConfig(
    tornago.WithClientSocksAddr("127.0.0.1:9050"),
    tornago.WithClientLogger(slogAdapter),
)

client, _ := tornago.NewClient(clientCfg)

// Health check
health := client.Check(context.Background())
if !health.IsHealthy() {
    log.Printf("Client unhealthy: %s", health.Message())
}

**Hidden Service with Persistent Key**

const keyPath = "/var/lib/myapp/onion.pem"

// Try to load existing key
privateKey, err := tornago.LoadPrivateKey(keyPath)
if err != nil {
    // First run: create new service
    hsCfg, _ := tornago.NewHiddenServiceConfig(
        tornago.WithHiddenServicePort(80, 8080),
    )
    hs, _ := controlClient.CreateHiddenService(ctx, hsCfg)

    // Save key for next time
    tornago.SavePrivateKey(keyPath, hs.PrivateKey())
    os.Chmod(keyPath, 0600)
} else {
    // Reuse existing key - same .onion address
    hsCfg, _ := tornago.NewHiddenServiceConfig(
        tornago.WithHiddenServicePrivateKey(privateKey),
        tornago.WithHiddenServicePort(80, 8080),
    )
    hs, _ := controlClient.CreateHiddenService(ctx, hsCfg)
}

Security Best Practices

**Connection Verification**

Always verify that your application is routing traffic through Tor:

status, err := client.VerifyTorConnection(ctx)
if err != nil {
    log.Fatalf("Verification failed: %v", err)
}
if !status.IsUsingTor() {
    log.Fatalf("CRITICAL: Traffic is NOT going through Tor!")
}

When to verify:

  • On application startup
  • After configuration changes
  • Periodically in long-running services
  • Before handling sensitive operations

**DNS Leak Prevention**

Check if DNS queries are leaking outside Tor:

leakCheck, err := client.CheckDNSLeak(ctx)
if err != nil {
    log.Fatalf("DNS leak check failed: %v", err)
}
if leakCheck.HasLeak() {
    log.Fatalf("WARNING: DNS leak detected! IPs: %v", leakCheck.ResolvedIPs())
}

DNS leaks reveal which domains you're accessing to your ISP or DNS provider.

**Hidden Service Private Key Management**

Private keys determine your .onion address. Keep them secure:

// File permissions
sudo chmod 600 /var/lib/myapp/onion.pem
sudo chown myapp:myapp /var/lib/myapp/onion.pem

// Encrypted backups
openssl enc -aes-256-cbc -salt \
    -in /var/lib/myapp/onion.pem \
    -out onion.pem.enc

Best practices:

  • Store keys in secure directory with restricted permissions (chmod 600)
  • Keep encrypted backups in separate physical location
  • Test restoration regularly
  • Use SELinux/AppArmor for additional protection
  • Monitor key file access with audit logs

**Common Security Pitfalls**

1. Using HTTP instead of HTTPS:

  • Exit nodes can see unencrypted HTTP traffic
  • Always use HTTPS for end-to-end encryption

2. Leaking metadata:

  • Remove identifying headers (User-Agent, X-Forwarded-For)
  • Minimize timestamps and other identifying information

3. Circuit reuse correlation:

  • Rotate circuits for sensitive operations using NewIdentity()
  • Wait 5-10 seconds after rotation for new circuit to build

4. Insufficient timeouts:

  • Use minimum 30s dial timeout, 60-120s request timeout
  • .onion sites require even longer timeouts (60s+ dial, 120s+ request)

5. Not verifying .onion addresses:

  • Hardcode trusted .onion addresses
  • Verify addresses through trusted channels to avoid phishing

**Client Authentication for Hidden Services**

Restrict hidden service access to authorized clients:

// Server side
auth := tornago.HiddenServiceAuth{
    ClientName: "authorized-client-1",
    PublicKey:  "descriptor:x25519:AAAA...base64-public-key",
}
hsCfg, _ := tornago.NewHiddenServiceConfig(
    tornago.WithHiddenServiceClientAuth(auth),
)

Generate x25519 key pairs:

openssl genpkey -algorithm x25519 -out private.pem
openssl pkey -in private.pem -pubout -out public.pem
openssl pkey -in public.pem -pubin -outform DER | tail -c 32 | base64

Security benefits:

  • Only authorized clients can discover the service
  • Protects against descriptor enumeration attacks
  • Provides end-to-end authentication

Troubleshooting

**Tor binary not found**

Error: tor_binary_not_found: tor executable not found in PATH
Solution: Install Tor via package manager
  Ubuntu/Debian: sudo apt install tor
  macOS: brew install tor
  Verify: tor --version

**Cannot connect to Tor daemon**

Error: control_request_failed: failed to dial ControlPort
Solution: Verify Tor is running
  ps aux | grep tor
  sudo netstat -tlnp | grep tor
  Check /etc/tor/torrc for correct SocksPort/ControlPort

**ControlPort authentication failed**

Error: control_auth_failed: AUTHENTICATE failed
Solution: Check authentication method and credentials
  For system Tor with cookie auth:
    auth, _, _ := tornago.ControlAuthFromTor("127.0.0.1:9051", 30*time.Second)
  Verify cookie file permissions:
    ls -l /run/tor/control.authcookie
  Add user to tor group if needed:
    sudo usermod -a -G debian-tor $USER

**Requests timeout**

Error: timeout: context deadline exceeded
Solution: Increase timeouts for slow Tor connections
  .onion sites take 5-30 seconds to connect typically
  Use longer timeouts:
    tornago.WithClientDialTimeout(60*time.Second)
    tornago.WithClientRequestTimeout(120*time.Second)

**Hidden Service not accessible**

Symptoms: .onion address times out, local server works
Solution: Wait for service to establish (30-60 seconds after creation)
  Access through Tor Browser or tornago client
  Verify local service is listening before creating hidden service

**Checking error types**

resp, err := client.Do(req)
if err != nil {
    var torErr *tornago.TornagoError
    if errors.As(err, &torErr) {
        switch torErr.Kind {
        case tornago.ErrTimeout:
            // Increase timeout
        case tornago.ErrSocksDialFailed:
            // Check Tor connection
        case tornago.ErrHTTPFailed:
            // Handle HTTP error
        }
    }
}

**Testing Tor connection manually**

# Test SOCKS proxy
curl --socks5 127.0.0.1:9050 https://check.torproject.org/api/ip

# Test .onion access
curl --socks5 127.0.0.1:9050 http://your-address.onion

Rate Limiting Recommendations

Tor network capacity is limited. Use appropriate rate limits:

// Conservative (respectful scraping)
rateLimiter := tornago.NewRateLimiter(1.0, 3)  // 1 req/s, burst 3

// Moderate (general use)
rateLimiter := tornago.NewRateLimiter(5.0, 10)  // 5 req/s, burst 10

// Aggressive (high volume)
rateLimiter := tornago.NewRateLimiter(10.0, 20)  // 10 req/s, burst 20

Excessive requests may degrade Tor network performance for everyone.

Environment-Specific Configurations

**CI/CD Testing**

Use fast timeouts and ephemeral instances:

launchCfg, _ := tornago.NewTorLaunchConfig(
    tornago.WithTorSocksAddr(":0"),
    tornago.WithTorControlAddr(":0"),
    tornago.WithTorStartupTimeout(120*time.Second), // CI may be slow
)

**Docker Container**

Mount persistent DataDirectory:

launchCfg, _ := tornago.NewTorLaunchConfig(
    tornago.WithTorSocksAddr("127.0.0.1:9050"),
    tornago.WithTorControlAddr("127.0.0.1:9051"),
    tornago.WithTorDataDirectory("/var/lib/tor"),
)

**High-Availability Service**

Use system Tor with health monitoring:

// Verify Tor availability
auth, _, err := tornago.ControlAuthFromTor("127.0.0.1:9051", 5*time.Second)
if err != nil {
    log.Fatal("Tor not available")
}

client, _ := tornago.NewClient(clientCfg)
health := client.Check(ctx)
if !health.IsHealthy() {
    log.Fatalf("Tor unhealthy: %s", health.Message())
}

Additional Resources

For working code examples, see the examples/ directory:

  • examples/simple_client - Basic HTTP requests through Tor
  • examples/onion_client - Accessing .onion sites
  • examples/onion_server - Creating Hidden Services
  • examples/existing_tor - Connecting to system Tor daemon
  • examples/circuit_rotation - Rotating circuits to change exit IP
  • examples/error_handling - Proper error handling patterns
  • examples/metrics_ratelimit - Metrics collection and rate limiting
  • examples/persistent_onion - Hidden Service with persistent key
  • examples/observability - Structured logging, metrics, and health checks
  • examples/security - Tor connection verification and DNS leak detection

Complete API documentation: https://pkg.go.dev/github.com/nao1215/tornago

Example (Client)

Example_client demonstrates how to create a Tor client configuration for making HTTP requests through the Tor network.

package main

import (
	"fmt"
	"log"

	"github.com/nao1215/tornago"
)

func main() {
	// Create a client configuration with default settings
	clientCfg, err := tornago.NewClientConfig(
		tornago.WithClientSocksAddr("127.0.0.1:9050"), // Use local Tor SOCKS proxy
	)
	if err != nil {
		log.Fatalf("failed to create client config: %v", err)
	}

	// Create a new Tor client
	client, err := tornago.NewClient(clientCfg)
	if err != nil {
		log.Fatalf("failed to create client: %v", err)
	}
	defer client.Close()

	fmt.Printf("Client configured with SOCKS: %s\n", clientCfg.SocksAddr())
}
Output:
Client configured with SOCKS: 127.0.0.1:9050
Example (ClientWithRetry)

Example_clientWithRetry demonstrates how to configure retry behavior for HTTP requests through Tor.

package main

import (
	"fmt"
	"log"
	"time"

	"github.com/nao1215/tornago"
)

func main() {
	clientCfg, err := tornago.NewClientConfig(
		tornago.WithClientSocksAddr("127.0.0.1:9050"),
		tornago.WithRetryAttempts(3),
		tornago.WithRetryDelay(2*time.Second),
	)
	if err != nil {
		log.Fatalf("failed to create client config: %v", err)
	}

	client, err := tornago.NewClient(clientCfg)
	if err != nil {
		log.Fatalf("failed to create client: %v", err)
	}
	defer client.Close()

	fmt.Printf("Client configured with 3 retry attempts and 2s delay\n")
}
Output:
Client configured with 3 retry attempts and 2s delay
Example (ConnectToExistingTor)

Example_connectToExistingTor demonstrates connecting to an already-running Tor instance. This is the recommended approach for production environments.

package main

import (
	"fmt"
	"log"

	"github.com/nao1215/tornago"
)

func main() {
	// Configure client to connect to a running Tor instance
	// (Note: This example shows configuration only; actual connection requires Tor to be running)
	clientCfg, err := tornago.NewClientConfig(
		tornago.WithClientSocksAddr("127.0.0.1:9050"),
		// Optionally configure ControlPort for circuit rotation and hidden services
		// tornago.WithClientControlAddr("127.0.0.1:9051"),
		// tornago.WithClientControlCookie("/var/lib/tor/control_auth_cookie"),
	)
	if err != nil {
		log.Fatalf("failed to create config: %v", err)
	}

	// In production, you would create and use the client like this:
	// client, err := tornago.NewClient(clientCfg)
	// if err != nil {
	//     log.Fatalf("failed to create client: %v", err)
	// }
	// defer client.Close()

	fmt.Printf("Configured to connect to Tor at %s\n", clientCfg.SocksAddr())
}
Output:
Configured to connect to Tor at 127.0.0.1:9050
Example (ControlClient)

Example_controlClient demonstrates how to configure a control client for interacting with a running Tor instance.

package main

import (
	"fmt"

	"github.com/nao1215/tornago"
)

func main() {
	// Create a control client configuration with password authentication
	auth := tornago.ControlAuthFromPassword("my-password")

	fmt.Printf("Control auth configured with password\n")
	_ = auth // Use auth variable
}
Output:
Control auth configured with password
Example (ErrorHandling)

Example_errorHandling demonstrates how to handle tornago-specific errors.

package main

import (
	"fmt"
)

func main() {
	fmt.Println("tornago uses TornagoError with Kind field for error classification")
	fmt.Println("Use errors.Is() to check specific error kinds")
	fmt.Println("Common kinds: ErrTorBinaryNotFound, ErrSocksDialFailed, ErrTimeout")
}
Output:
tornago uses TornagoError with Kind field for error classification
Use errors.Is() to check specific error kinds
Common kinds: ErrTorBinaryNotFound, ErrSocksDialFailed, ErrTimeout
Example (HiddenService)

Example_hiddenService demonstrates how to configure a Tor hidden service (onion service) that can be accessed via the Tor network.

package main

import (
	"fmt"
	"log"

	"github.com/nao1215/tornago"
)

func main() {
	// Configure a hidden service that maps port 80 to local port 8080
	hsCfg, err := tornago.NewHiddenServiceConfig(
		tornago.WithHiddenServicePort(80, 8080),
	)
	if err != nil {
		log.Fatalf("failed to create hidden service config: %v", err)
	}

	fmt.Printf("Hidden service configured: virtual port %d -> local port %d\n", 80, 8080)
	fmt.Printf("Key type: %s\n", hsCfg.KeyType())
}
Output:
Hidden service configured: virtual port 80 -> local port 8080
Key type: ED25519-V3
Example (HiddenServiceWithAuth)

Example_hiddenServiceWithAuth demonstrates how to create a hidden service with client authorization for restricted access.

package main

import (
	"fmt"
	"log"

	"github.com/nao1215/tornago"
)

func main() {
	// Configure hidden service with client authorization
	auth := tornago.NewHiddenServiceAuth("alice", "descriptor:x25519:ABCDEF1234567890")

	_, err := tornago.NewHiddenServiceConfig(
		tornago.WithHiddenServicePort(80, 8080),
		tornago.WithHiddenServiceClientAuth(auth),
	)
	if err != nil {
		log.Fatalf("failed to create config: %v", err)
	}

	fmt.Printf("Configured hidden service with auth for: %s\n", auth.ClientName())
}
Output:
Configured hidden service with auth for: alice
Example (LaunchAndUse)

Example_launchAndUse demonstrates launching a Tor daemon and using it for HTTP requests. This is useful when you want your application to manage its own Tor instance.

package main

import (
	"fmt"
	"log"
	"time"

	"github.com/nao1215/tornago"
)

func main() {
	// Launch a Tor daemon with automatic port selection
	launchCfg, err := tornago.NewTorLaunchConfig(
		tornago.WithTorSocksAddr(":0"),   // Let Tor pick a free port
		tornago.WithTorControlAddr(":0"), // Let Tor pick a free port
		tornago.WithTorStartupTimeout(time.Minute),
	)
	if err != nil {
		log.Fatalf("failed to create launch config: %v", err)
	}

	// StartTorDaemon blocks until Tor is ready to accept connections
	// (Note: This example doesn't actually start Tor to keep tests fast)
	// torProc, err := tornago.StartTorDaemon(launchCfg)
	// if err != nil {
	//     log.Fatalf("failed to start tor: %v", err)
	// }
	// defer torProc.Stop()

	// Create a client using the launched Tor instance
	// clientCfg, err := tornago.NewClientConfig(
	//     tornago.WithClientSocksAddr(torProc.SocksAddr()),
	// )
	// client, err := tornago.NewClient(clientCfg)

	fmt.Printf("Configured to launch Tor with timeout: %v\n", launchCfg.StartupTimeout())
}
Output:
Configured to launch Tor with timeout: 1m0s
Example (NewIdentity)

Example_newIdentity demonstrates the concept of requesting a new Tor identity. In practice, you would call client.NewIdentity(ctx) on an authenticated control client to rotate circuits and get a new IP address.

package main

import (
	"fmt"
)

func main() {
	fmt.Println("To request a new identity, use client.NewIdentity(ctx)")
}
Output:
To request a new identity, use client.NewIdentity(ctx)
Example (QuickStart)

Example_quickStart demonstrates the simplest way to make HTTP requests through Tor. This example assumes you have Tor running locally on the default port (9050). To install Tor: apt-get install tor (Ubuntu), brew install tor (macOS), or choco install tor (Windows).

package main

import (
	"fmt"
	"log"

	"github.com/nao1215/tornago"
)

func main() {
	// Create a client that uses your local Tor instance
	clientCfg, err := tornago.NewClientConfig(
		tornago.WithClientSocksAddr("127.0.0.1:9050"),
	)
	if err != nil {
		log.Fatalf("failed to create config: %v", err)
	}

	client, err := tornago.NewClient(clientCfg)
	if err != nil {
		log.Fatalf("failed to create client: %v", err)
	}
	defer client.Close()

	// Get the HTTP client and use it like any http.Client
	// All requests automatically go through Tor
	httpClient := client.HTTP()
	_ = httpClient // Use for http.Get, http.Post, etc.

	fmt.Println("HTTP client ready to make requests through Tor")
}
Output:
HTTP client ready to make requests through Tor
Example (RotateCircuit)

Example_rotateCircuit demonstrates how to request a new Tor identity (new exit node/IP).

package main

import (
	"fmt"
)

func main() {
	// This example shows the concept; actual execution requires a running Tor instance
	fmt.Println("To rotate circuits:")
	fmt.Println("1. Create a Client with ControlAddr configured")
	fmt.Println("2. Call client.Control().NewIdentity(ctx)")
	fmt.Println("3. Subsequent requests use new circuits with different exit IPs")
}
Output:
To rotate circuits:
1. Create a Client with ControlAddr configured
2. Call client.Control().NewIdentity(ctx)
3. Subsequent requests use new circuits with different exit IPs
Example (Server)

Example_server demonstrates how to start a Tor server that can host hidden services and handle incoming connections.

package main

import (
	"fmt"
	"log"

	"github.com/nao1215/tornago"
)

func main() {
	// Create server configuration
	serverCfg, err := tornago.NewServerConfig(
		tornago.WithServerSocksAddr("127.0.0.1:9050"),
		tornago.WithServerControlAddr("127.0.0.1:9051"),
	)
	if err != nil {
		log.Fatalf("failed to create server config: %v", err)
	}

	// Create a server instance
	server, err := tornago.NewServer(serverCfg)
	if err != nil {
		log.Fatalf("failed to create server: %v", err)
	}

	fmt.Printf("Tor server configured with SOCKS: %s, Control: %s\n",
		server.SocksAddr(), server.ControlAddr())
}
Output:
Tor server configured with SOCKS: 127.0.0.1:9050, Control: 127.0.0.1:9051
Example (StartTorDaemon)

Example_startTorDaemon demonstrates how to configure a Tor daemon launch configuration with custom settings.

package main

import (
	"fmt"
	"log"
	"time"

	"github.com/nao1215/tornago"
)

func main() {
	// Create launch configuration with custom settings
	launchCfg, err := tornago.NewTorLaunchConfig(
		tornago.WithTorSocksAddr("127.0.0.1:9050"),
		tornago.WithTorControlAddr("127.0.0.1:9051"),
		tornago.WithTorStartupTimeout(2*time.Minute),
	)
	if err != nil {
		log.Fatalf("failed to create launch config: %v", err)
	}

	fmt.Printf("Tor daemon configured with SOCKS: %s, Control: %s\n",
		launchCfg.SocksAddr(), launchCfg.ControlAddr())
}
Output:
Tor daemon configured with SOCKS: 127.0.0.1:9050, Control: 127.0.0.1:9051

Index

Examples

Constants

View Source
const (

	// CircuitStatusBuilt indicates a circuit has been fully established.
	CircuitStatusBuilt = "BUILT"
)

Variables

This section is empty.

Functions

func As added in v0.3.0

func As(err error, target any) bool

As is a helper function that wraps errors.As for internal use.

func LoadPrivateKey added in v0.2.0

func LoadPrivateKey(path string) (string, error)

LoadPrivateKey reads a private key from a file and returns it as a string suitable for use with WithHiddenServicePrivateKey.

Example:

key, _ := tornago.LoadPrivateKey("/path/to/key")
cfg, _ := tornago.NewHiddenServiceConfig(
    tornago.WithHiddenServicePrivateKey(key),
    tornago.WithHiddenServicePort(80, 8080),
)

func WaitForControlPort

func WaitForControlPort(controlAddr string, timeout time.Duration) error

WaitForControlPort waits until Tor's control port is usable. Tor may accept TCP connections before it can respond to PROTOCOLINFO, because the cookie might not be created yet. This function verifies that PROTOCOLINFO succeeds AND the cookie file exists before returning.

Types

type CircuitInfo added in v0.2.0

type CircuitInfo struct {
	// ID is the circuit identifier.
	ID string
	// Status is the circuit status (e.g., "BUILT", "EXTENDED", "LAUNCHED").
	Status string
	// Path is the list of relay fingerprints in the circuit.
	Path []string
	// BuildFlags contains circuit build flags.
	BuildFlags []string
	// Purpose is the circuit purpose (e.g., "GENERAL", "HS_CLIENT_INTRO").
	Purpose string
	// TimeCreated is when the circuit was created.
	TimeCreated string
}

CircuitInfo represents information about a Tor circuit.

type CircuitManager added in v0.3.0

type CircuitManager struct {
	// contains filtered or unexported fields
}

CircuitManager manages Tor circuits with advanced features like automatic rotation, circuit prewarming, and per-site circuit isolation.

Circuit management is useful for:

  • Automatic IP rotation on a schedule
  • Pre-building circuits before they're needed (prewarming)
  • Isolating circuits for specific destinations (privacy)
  • Recovering from circuit failures

Example usage:

manager := tornago.NewCircuitManager(controlClient)
manager.StartAutoRotation(ctx, 10*time.Minute)  // Rotate every 10 minutes
defer manager.Stop()

func NewCircuitManager added in v0.3.0

func NewCircuitManager(control *ControlClient) *CircuitManager

NewCircuitManager creates a new CircuitManager with the given ControlClient.

func (*CircuitManager) IsRunning added in v0.3.0

func (m *CircuitManager) IsRunning() bool

IsRunning returns true if automatic rotation is currently active.

func (*CircuitManager) PerformanceTracker added in v0.4.0

func (m *CircuitManager) PerformanceTracker() *RelayPerformanceTracker

PerformanceTracker returns the configured RelayPerformanceTracker, or nil if not set.

func (*CircuitManager) PrewarmCircuits added in v0.3.0

func (m *CircuitManager) PrewarmCircuits(ctx context.Context) error

PrewarmCircuits builds new circuits in advance to reduce latency for future requests. This calls NewIdentity() to signal Tor to build fresh circuits.

Prewarming is useful before:

  • Starting a batch of requests
  • After a long idle period
  • When you know you'll need fresh circuits soon

After calling this, wait a few seconds (5-10s) for Tor to build new circuits before making requests.

func (*CircuitManager) RotateNow added in v0.3.0

func (m *CircuitManager) RotateNow(ctx context.Context) error

RotateNow immediately rotates circuits by calling NewIdentity(). This is useful for manual circuit rotation outside of the automatic schedule.

func (*CircuitManager) StartAutoRotation added in v0.3.0

func (m *CircuitManager) StartAutoRotation(ctx context.Context, interval time.Duration) error

StartAutoRotation begins automatic circuit rotation at the specified interval. Circuits will be rotated by calling NewIdentity() at regular intervals.

This is useful for:

  • Changing exit IPs periodically for privacy
  • Avoiding rate limiting by rotating IPs
  • Refreshing circuits that may have become slow

The rotation continues until Stop() is called or the context is canceled.

Example:

manager.StartAutoRotation(ctx, 10*time.Minute)
// Circuits rotate every 10 minutes

func (*CircuitManager) StartPerformanceMonitor added in v0.4.0

func (m *CircuitManager) StartPerformanceMonitor(ctx context.Context, interval time.Duration) error

StartPerformanceMonitor begins monitoring circuit performance and rotating slow circuits. This requires a RelayPerformanceTracker to be set via WithPerformanceTracker.

The monitor periodically checks circuit latency and rotates circuits that use slow relays. This is useful for:

  • Automatically avoiding slow Tor relays
  • Maintaining consistent performance
  • Adapting to network conditions

Example:

tracker := tornago.NewRelayPerformanceTracker()
manager.WithPerformanceTracker(tracker)
manager.StartPerformanceMonitor(ctx, 30*time.Second)

func (*CircuitManager) Stats added in v0.3.0

func (m *CircuitManager) Stats() CircuitStats

Stats returns current statistics about circuit management.

func (*CircuitManager) Stop added in v0.3.0

func (m *CircuitManager) Stop()

Stop stops automatic circuit rotation if it's running.

func (*CircuitManager) StopPerformanceMonitor added in v0.4.0

func (m *CircuitManager) StopPerformanceMonitor()

StopPerformanceMonitor stops the performance monitor if running.

func (*CircuitManager) WithLogger added in v0.3.0

func (m *CircuitManager) WithLogger(logger Logger) *CircuitManager

WithLogger sets a logger for circuit management operations.

func (*CircuitManager) WithPerformanceTracker added in v0.4.0

func (m *CircuitManager) WithPerformanceTracker(tracker *RelayPerformanceTracker) *CircuitManager

WithPerformanceTracker sets a RelayPerformanceTracker for slow relay avoidance. When set, the CircuitManager will monitor circuit performance and automatically rotate circuits that use slow relays.

type CircuitStats added in v0.3.0

type CircuitStats struct {
	// AutoRotationEnabled indicates if automatic rotation is running.
	AutoRotationEnabled bool
	// RotationInterval is the configured rotation interval (0 if not running).
	RotationInterval time.Duration
	// PerformanceMonitorEnabled indicates if performance monitoring is running.
	PerformanceMonitorEnabled bool
	// PerformanceMonitorInterval is the configured performance monitor interval.
	PerformanceMonitorInterval time.Duration
}

CircuitStats provides statistics about circuit management operations.

type Client

type Client struct {
	// contains filtered or unexported fields
}

Client bundles HTTP/TCP over Tor along with optional ControlPort access. It rewrites Dial/HTTP operations to go through Tor's SOCKS5 proxy and automatically retries failures based on ClientConfig.

Client is the main entry point for making HTTP requests or TCP connections through Tor. It handles:

  • Automatic SOCKS5 proxying through Tor's SocksPort
  • Exponential backoff retry logic for failed requests
  • Optional ControlPort access for circuit rotation and hidden services
  • Thread-safe operation for concurrent requests

Example usage:

cfg, _ := tornago.NewClientConfig(
    tornago.WithClientSocksAddr("127.0.0.1:9050"),
)
client, _ := tornago.NewClient(cfg)
defer client.Close()

// Make HTTP requests through Tor
resp, err := client.HTTP().Get("https://check.torproject.org")

// Make raw TCP connections through Tor
conn, err := client.Dial("tcp", "example.onion:80")

func NewClient

func NewClient(cfg ClientConfig) (*Client, error)

NewClient builds a Client that routes traffic through the configured Tor server. The client is ready to use immediately after creation - all connections will automatically be routed through Tor's SOCKS5 proxy.

If cfg includes a ControlAddr, the client will also connect to Tor's ControlPort for management operations (e.g., circuit rotation, hidden service creation).

Always call Close() when done to clean up resources.

func NewDefaultClient added in v0.3.0

func NewDefaultClient() (*Client, error)

NewDefaultClient creates a Client with default settings for connecting to a local Tor daemon running on localhost:9050 (the default Tor SOCKS port).

This is a convenience function equivalent to:

cfg, _ := tornago.NewClientConfig()
client, _ := tornago.NewClient(cfg)

For custom configuration (timeouts, control port, metrics, etc.), use NewClient with a custom ClientConfig instead.

Example:

client, err := tornago.NewDefaultClient()
if err != nil {
    log.Fatal(err)
}
defer client.Close()

resp, err := client.HTTP().Get("https://check.torproject.org")

func (*Client) Check added in v0.3.0

func (c *Client) Check(ctx context.Context) HealthCheck

Check performs a health check on the Tor connection. It verifies that:

  • SOCKS proxy is reachable
  • ControlPort is accessible (if configured)
  • Authentication is valid (if configured)

The check includes a timeout to prevent hanging on unresponsive services.

Example:

client, _ := tornago.NewClient(cfg)
health := client.Check(context.Background())
if !health.IsHealthy() {
    log.Printf("Tor unhealthy: %s", health.Message())
}

func (*Client) CheckDNSLeak added in v0.3.0

func (c *Client) CheckDNSLeak(ctx context.Context) (DNSLeakCheck, error)

CheckDNSLeak verifies that DNS queries are going through Tor and not leaking to your local DNS resolver. It does this by resolving a hostname through the Tor SOCKS proxy and comparing it with what Tor's DNS resolution returns.

DNS leaks occur when your system's DNS resolver is used instead of Tor's, potentially revealing which domains you're accessing to your ISP or DNS provider.

This check resolves "check.torproject.org" through Tor and verifies the result.

Example:

client, _ := tornago.NewClient(cfg)
leakCheck, err := client.CheckDNSLeak(context.Background())
if err != nil {
    log.Fatalf("DNS leak check failed: %v", err)
}
if leakCheck.HasLeak {
    log.Printf("WARNING: DNS leak detected! IPs: %v", leakCheck.ResolvedIPs)
}

func (*Client) Close

func (c *Client) Close() error

Close closes the ControlClient and underlying HTTP transport resources. If slow relay avoidance is enabled, the background monitor is also stopped.

func (*Client) Control

func (c *Client) Control() *ControlClient

Control returns the ControlClient, which may be nil if ControlAddr was empty.

func (*Client) Dial

func (c *Client) Dial(network, addr string) (net.Conn, error)

Dial establishes a TCP connection via Tor's SOCKS5 proxy. This is equivalent to DialContext with context.Background().

func (*Client) DialContext added in v0.2.0

func (c *Client) DialContext(ctx context.Context, network, addr string) (net.Conn, error)

DialContext establishes a TCP connection via Tor's SOCKS5 proxy with context support. The context can be used for cancellation and deadlines.

func (*Client) Dialer added in v0.2.0

func (c *Client) Dialer() func(ctx context.Context, network, addr string) (net.Conn, error)

Dialer returns a net.Dialer-compatible function that routes connections through Tor. This can be used with libraries that accept a custom dial function.

Example:

dialer := client.Dialer()
conn, err := dialer(ctx, "tcp", "example.onion:80")

func (*Client) Do

func (c *Client) Do(req *http.Request) (*http.Response, error)

Do performs an HTTP request via Tor with retry support.

func (*Client) HTTP

func (c *Client) HTTP() *http.Client

HTTP returns the configured *http.Client that routes through Tor.

func (*Client) Listen added in v0.2.0

func (c *Client) Listen(ctx context.Context, virtualPort, localPort int) (*TorListener, error)

Listen creates a TorListener that exposes a local TCP listener as a Tor Hidden Service. The virtualPort is the port exposed on the .onion address, and localPort is the local port that accepts connections.

This method requires a ControlClient to be configured (via WithClientControlAddr).

Example:

client, _ := tornago.NewClient(cfg)
listener, _ := client.Listen(ctx, 80, 8080) // onion:80 -> local:8080
defer listener.Close()

fmt.Printf("Listening at: %s\n", listener.OnionAddress())
for {
    conn, _ := listener.Accept()
    go handleConnection(conn)
}

func (*Client) ListenWithConfig added in v0.2.0

func (c *Client) ListenWithConfig(ctx context.Context, hsCfg HiddenServiceConfig, localPort int) (*TorListener, error)

ListenWithConfig creates a TorListener using a custom HiddenServiceConfig. This allows for advanced configurations like persistent keys or client authorization.

The HiddenServiceConfig must have exactly one port mapping, and its target port must match the localPort parameter.

Example:

hsCfg, _ := tornago.NewHiddenServiceConfig(
    tornago.WithHiddenServicePrivateKey(savedKey),
    tornago.WithHiddenServicePort(80, 8080),
)
listener, _ := client.ListenWithConfig(ctx, hsCfg, 8080)

func (*Client) Metrics added in v0.2.0

func (c *Client) Metrics() *MetricsCollector

Metrics returns the metrics collector, which may be nil if not configured.

func (*Client) RelayPerformanceStats added in v0.4.0

func (c *Client) RelayPerformanceStats() (RelayPerformanceStats, bool)

RelayPerformanceStats returns statistics about relay performance tracking. The second return value indicates whether slow relay avoidance is enabled. If not enabled, returns a zero-value RelayPerformanceStats and false.

The returned struct is a copy and can be safely modified without affecting internal state.

Example:

stats, ok := client.RelayPerformanceStats()
if ok {
    fmt.Printf("Tracked: %d, Blocked: %d\n", stats.TrackedRelays, stats.BlockedRelays)
}

func (*Client) VerifyTorConnection added in v0.3.0

func (c *Client) VerifyTorConnection(ctx context.Context) (TorConnectionStatus, error)

VerifyTorConnection checks if the client is actually routing traffic through Tor by connecting to check.torproject.org. This service returns whether the connection came from a known Tor exit node.

This is useful for:

  • Verifying Tor configuration is working correctly
  • Detecting if traffic is leaking outside Tor
  • Getting the current exit node IP address

Example:

client, _ := tornago.NewClient(cfg)
status, err := client.VerifyTorConnection(context.Background())
if err != nil {
    log.Fatalf("Verification failed: %v", err)
}
if !status.UsingTor {
    log.Printf("WARNING: Not using Tor! Exit IP: %s", status.ExitIP)
}

type ClientConfig

type ClientConfig struct {
	// contains filtered or unexported fields
}

ClientConfig bundles all knobs for creating a Client. It is immutable after construction via NewClientConfig.

func NewClientConfig

func NewClientConfig(opts ...ClientOption) (ClientConfig, error)

NewClientConfig returns a validated, immutable client config.

func (ClientConfig) ControlAddr

func (c ClientConfig) ControlAddr() string

ControlAddr is the ControlPort address used for optional control commands.

func (ClientConfig) ControlAuth

func (c ClientConfig) ControlAuth() ControlAuth

ControlAuth carries credentials for the ControlPort.

func (ClientConfig) DialTimeout

func (c ClientConfig) DialTimeout() time.Duration

DialTimeout is the timeout for establishing TCP connections via SOCKS5.

func (ClientConfig) Logger added in v0.3.0

func (c ClientConfig) Logger() Logger

Logger returns the optional logger instance.

func (ClientConfig) Metrics added in v0.2.0

func (c ClientConfig) Metrics() *MetricsCollector

Metrics returns the optional metrics collector.

func (ClientConfig) PerformanceTracker added in v0.4.0

func (c ClientConfig) PerformanceTracker() *RelayPerformanceTracker

PerformanceTracker returns the optional relay performance tracker.

func (ClientConfig) RateLimiter added in v0.2.0

func (c ClientConfig) RateLimiter() *RateLimiter

RateLimiter returns the optional rate limiter.

func (ClientConfig) RequestTimeout

func (c ClientConfig) RequestTimeout() time.Duration

RequestTimeout sets the overall timeout for HTTP requests.

func (ClientConfig) RetryAttempts

func (c ClientConfig) RetryAttempts() uint

RetryAttempts is the maximum number of retries when RetryOnError returns true.

func (ClientConfig) RetryDelay

func (c ClientConfig) RetryDelay() time.Duration

RetryDelay is the initial backoff delay used by retry-go.

func (ClientConfig) RetryMaxDelay

func (c ClientConfig) RetryMaxDelay() time.Duration

RetryMaxDelay caps backoff delay used by retry-go.

func (ClientConfig) RetryOnError

func (c ClientConfig) RetryOnError() func(error) bool

RetryOnError decides whether an error should trigger a retry.

func (ClientConfig) SlowRelayAvoidanceEnabled added in v0.4.0

func (c ClientConfig) SlowRelayAvoidanceEnabled() bool

SlowRelayAvoidanceEnabled returns whether slow relay avoidance is enabled.

func (ClientConfig) SlowRelayOptions added in v0.4.0

func (c ClientConfig) SlowRelayOptions() []SlowRelayOption

SlowRelayOptions returns the options for slow relay avoidance.

func (ClientConfig) SocksAddr

func (c ClientConfig) SocksAddr() string

SocksAddr is the target SocksPort address used for outbound traffic.

type ClientOption

type ClientOption func(*ClientConfig)

ClientOption customizes ClientConfig creation.

func WithClientControlAddr

func WithClientControlAddr(addr string) ClientOption

WithClientControlAddr sets the ControlPort address for the client.

func WithClientControlCookie

func WithClientControlCookie(path string) ClientOption

WithClientControlCookie sets cookie-based ControlPort authentication.

func WithClientControlCookieBytes added in v0.2.0

func WithClientControlCookieBytes(data []byte) ClientOption

WithClientControlCookieBytes sets cookie-based ControlPort authentication using raw cookie bytes.

func WithClientControlPassword

func WithClientControlPassword(password string) ClientOption

WithClientControlPassword sets password-based ControlPort authentication.

func WithClientDialTimeout

func WithClientDialTimeout(timeout time.Duration) ClientOption

WithClientDialTimeout sets the timeout for dialing via SOCKS5.

func WithClientLogger added in v0.3.0

func WithClientLogger(logger Logger) ClientOption

WithClientLogger sets a structured logger for debugging and monitoring.

Example with slog:

logger := slog.New(slog.NewJSONHandler(os.Stderr, &slog.HandlerOptions{
    Level: slog.LevelDebug,
}))
cfg, _ := tornago.NewClientConfig(
    tornago.WithClientSocksAddr("127.0.0.1:9050"),
    tornago.WithClientLogger(tornago.NewSlogAdapter(logger)),
)

func WithClientMetrics added in v0.2.0

func WithClientMetrics(m *MetricsCollector) ClientOption

WithClientMetrics sets the metrics collector for the client.

func WithClientPerformanceTracker added in v0.4.0

func WithClientPerformanceTracker(tracker *RelayPerformanceTracker) ClientOption

WithClientPerformanceTracker sets a RelayPerformanceTracker for slow relay avoidance. When set, the client can automatically track relay performance and avoid slow relays.

Example with default settings (recommended for most users):

tracker := tornago.NewRelayPerformanceTracker()
client, err := tornago.NewClient(
    tornago.WithClientPerformanceTracker(tracker),
)

Example with custom thresholds:

threshold := tornago.NewRelayThreshold().
    WithMaxLatency(3 * time.Second).
    WithMinSuccessRate(0.9)
tracker := tornago.NewRelayPerformanceTracker(
    tornago.WithTrackerThreshold(threshold),
)
client, err := tornago.NewClient(
    tornago.WithClientPerformanceTracker(tracker),
)

func WithClientRateLimiter added in v0.2.0

func WithClientRateLimiter(r *RateLimiter) ClientOption

WithClientRateLimiter sets the rate limiter for the client.

func WithClientRequestTimeout

func WithClientRequestTimeout(timeout time.Duration) ClientOption

WithClientRequestTimeout sets the overall HTTP request timeout.

func WithClientSocksAddr

func WithClientSocksAddr(addr string) ClientOption

WithClientSocksAddr sets the SocksPort address for the client.

func WithRetryAttempts

func WithRetryAttempts(attempts uint) ClientOption

WithRetryAttempts sets the maximum number of retries.

func WithRetryDelay

func WithRetryDelay(delay time.Duration) ClientOption

WithRetryDelay sets the initial backoff delay.

func WithRetryMaxDelay

func WithRetryMaxDelay(delay time.Duration) ClientOption

WithRetryMaxDelay caps the backoff delay.

func WithRetryOnError

func WithRetryOnError(fn func(error) bool) ClientOption

WithRetryOnError registers a predicate to decide retry eligibility.

func WithSlowRelayAvoidance added in v0.4.0

func WithSlowRelayAvoidance(opts ...SlowRelayOption) ClientOption

WithSlowRelayAvoidance enables automatic slow relay avoidance in the Client. When enabled, the Client automatically tracks relay performance during requests, blocks slow or unreliable relays, and triggers circuit rotation when needed.

This is the recommended way to enable slow relay avoidance for most users. The feature works transparently - just make requests normally and the Client handles everything internally.

Options can be provided to customize behavior:

  • SlowRelayMaxLatency: Maximum acceptable latency (default: 5s)
  • SlowRelayMinSuccessRate: Minimum required success rate (default: 0.8)
  • SlowRelayBlockDuration: How long to block slow relays (default: 30m)
  • SlowRelayMinSamples: Samples needed before judging (default: 3)
  • SlowRelayMonitorInterval: Background check interval (default: 30s)
  • SlowRelayAutoExclude: Auto-update Tor's ExcludeNodes (default: true)

Note: This feature requires ControlPort access. Use WithClientControlAddr() to specify the Tor ControlPort address.

Example:

// Enable with defaults
client, err := tornago.NewClient(
    tornago.WithClientSocksAddr(torProcess.SocksAddr()),
    tornago.WithClientControlAddr(torProcess.ControlAddr()),
    tornago.WithSlowRelayAvoidance(),
)

// Enable with custom settings
client, err := tornago.NewClient(
    tornago.WithClientSocksAddr(torProcess.SocksAddr()),
    tornago.WithClientControlAddr(torProcess.ControlAddr()),
    tornago.WithSlowRelayAvoidance(
        tornago.SlowRelayMaxLatency(3*time.Second),
        tornago.SlowRelayMinSuccessRate(0.9),
    ),
)

// Get performance statistics
stats, ok := client.RelayPerformanceStats()
if ok {
    fmt.Printf("Tracked relays: %d, Blocked: %d\n", stats.TrackedRelays(), stats.BlockedRelays())
}

type ControlAuth

type ControlAuth struct {
	// contains filtered or unexported fields
}

ControlAuth holds ControlPort authentication values. It is immutable after creation via the helper functions below.

func ControlAuthFromCookie

func ControlAuthFromCookie(path string) ControlAuth

ControlAuthFromCookie builds ControlAuth for cookie-based auth. ControlAuthFromCookie constructs ControlAuth for cookie-based auth.

func ControlAuthFromCookieBytes

func ControlAuthFromCookieBytes(data []byte) ControlAuth

ControlAuthFromCookieBytes constructs ControlAuth from raw cookie data.

func ControlAuthFromPassword

func ControlAuthFromPassword(password string) ControlAuth

ControlAuthFromPassword builds ControlAuth for password-based auth. ControlAuthFromPassword constructs ControlAuth for password-based auth.

func ControlAuthFromTor

func ControlAuthFromTor(controlAddr string, timeout time.Duration) (ControlAuth, string, error)

ControlAuthFromTor queries Tor for the control cookie path and returns the ControlAuth that uses the corresponding cookie bytes.

func (ControlAuth) CookieBytes

func (a ControlAuth) CookieBytes() []byte

CookieBytes returns the raw cookie data if configured.

func (ControlAuth) CookiePath

func (a ControlAuth) CookiePath() string

CookiePath returns the configured control cookie path.

func (ControlAuth) Password

func (a ControlAuth) Password() string

Password returns the configured control password.

type ControlClient

type ControlClient struct {
	// contains filtered or unexported fields
}

ControlClient talks to Tor's ControlPort (a text-based management interface where Tor accepts commands like AUTHENTICATE/GETINFO/SIGNAL NEWNYM). It is provided as a standalone client so tools that only need ControlPort access (e.g. circuit rotation or Hidden Service management) can use it without constructing the higher-level HTTP/TCP Client.

The ControlPort allows you to:

  • Rotate circuits to get new exit IPs (NewIdentity)
  • Create and manage hidden services (CreateHiddenService)
  • Query Tor's internal state (GetInfo)
  • Monitor Tor events and status

Authentication is required before most commands. Use either cookie-based authentication (automatic with StartTorDaemon) or password authentication (for existing Tor instances).

Example usage:

auth := tornago.ControlAuthFromCookie("/var/lib/tor/control_auth_cookie")
ctrl, _ := tornago.NewControlClient("127.0.0.1:9051", auth, 5*time.Second)
defer ctrl.Close()

ctrl.Authenticate()
ctrl.NewIdentity(context.Background())  // Request new circuits

func NewControlClient

func NewControlClient(addr string, auth ControlAuth, timeout time.Duration) (*ControlClient, error)

NewControlClient dials the ControlPort at addr with the given timeout.

func (*ControlClient) Authenticate

func (c *ControlClient) Authenticate() error

Authenticate performs AUTHENTICATE using ControlAuth credentials.

func (*ControlClient) ClearExcludeNodes added in v0.4.0

func (c *ControlClient) ClearExcludeNodes(ctx context.Context) error

ClearExcludeNodes removes all entries from the ExcludeNodes configuration.

func (*ControlClient) Close

func (c *ControlClient) Close() error

Close closes the underlying ControlPort connection.

func (*ControlClient) CreateHiddenService

func (c *ControlClient) CreateHiddenService(ctx context.Context, cfg HiddenServiceConfig) (HiddenService, error)

CreateHiddenService issues ADD_ONION and returns a HiddenService handle.

func (*ControlClient) ExcludeNodes added in v0.4.0

func (c *ControlClient) ExcludeNodes(ctx context.Context, fingerprints []string) error

ExcludeNodes sets the ExcludeNodes configuration to avoid specific relays. The fingerprints should be relay fingerprints (40-character hex strings), optionally prefixed with '$'.

Example:

err := ctrl.ExcludeNodes(ctx, []string{"$AAAA...", "$BBBB..."})

func (*ControlClient) GetCircuitStatus added in v0.2.0

func (c *ControlClient) GetCircuitStatus(ctx context.Context) ([]CircuitInfo, error)

GetCircuitStatus retrieves information about all current Tor circuits. This is useful for monitoring circuit health and debugging connectivity issues.

func (*ControlClient) GetConf added in v0.2.0

func (c *ControlClient) GetConf(ctx context.Context, key string) (string, error)

GetConf retrieves the current value of a Tor configuration option. The key should be a valid Tor configuration option name (e.g., "SocksPort", "ORPort").

Example:

socksPort, err := ctrl.GetConf(ctx, "SocksPort")

func (*ControlClient) GetHiddenServiceStatus added in v0.2.0

func (c *ControlClient) GetHiddenServiceStatus(ctx context.Context) ([]HiddenServiceStatus, error)

GetHiddenServiceStatus retrieves information about all active hidden services. This is useful for monitoring and debugging hidden service configurations.

func (*ControlClient) GetInfo

func (c *ControlClient) GetInfo(ctx context.Context, key string) (string, error)

GetInfo runs GETINFO and returns the associated value.

func (*ControlClient) GetInfoNoAuth

func (c *ControlClient) GetInfoNoAuth(ctx context.Context, key string) (string, error)

GetInfoNoAuth runs GETINFO without authenticating first.

func (*ControlClient) GetStreamStatus added in v0.2.0

func (c *ControlClient) GetStreamStatus(ctx context.Context) ([]StreamInfo, error)

GetStreamStatus retrieves information about all current Tor streams. This is useful for monitoring active connections through Tor.

func (*ControlClient) MapAddress added in v0.2.0

func (c *ControlClient) MapAddress(ctx context.Context, fromAddr, toAddr string) (string, error)

MapAddress creates a mapping from a virtual address to a target address. This allows you to access services using custom addresses through Tor.

Example:

// Map "mysite" to an onion address
mapped, err := ctrl.MapAddress(ctx, "mysite.virtual", "abcdef...onion")

func (*ControlClient) NewIdentity

func (c *ControlClient) NewIdentity(ctx context.Context) error

NewIdentity issues SIGNAL NEWNYM to rotate Tor circuits, causing Tor to close existing circuits and build new ones. This effectively gives you a new exit IP address for subsequent requests.

This is useful for:

  • Avoiding rate limiting or IP-based blocks
  • Getting a fresh identity for privacy reasons
  • Testing behavior with different exit nodes

Note: Tor rate-limits NEWNYM requests to once per 10 seconds by default. Calling this more frequently will not create new circuits.

func (*ControlClient) ResetConf added in v0.2.0

func (c *ControlClient) ResetConf(ctx context.Context, key string) error

ResetConf resets a Tor configuration option to its default value.

Example:

err := ctrl.ResetConf(ctx, "MaxCircuitDirtiness")

func (*ControlClient) SaveConf added in v0.2.0

func (c *ControlClient) SaveConf(ctx context.Context) error

SaveConf saves the current configuration to the torrc file. This persists any changes made with SetConf.

func (*ControlClient) SetConf added in v0.2.0

func (c *ControlClient) SetConf(ctx context.Context, key, value string) error

SetConf sets a Tor configuration option to the specified value. The change takes effect immediately but is not persisted to the torrc file. To persist changes, call SaveConf after SetConf.

Example:

err := ctrl.SetConf(ctx, "MaxCircuitDirtiness", "600")

type DNSLeakCheck added in v0.3.0

type DNSLeakCheck struct {
	// contains filtered or unexported fields
}

DNSLeakCheck represents the result of a DNS leak detection test. It is an immutable value object that provides methods to query leak status.

func (DNSLeakCheck) HasLeak added in v0.3.0

func (d DNSLeakCheck) HasLeak() bool

HasLeak returns true if DNS queries are leaking outside Tor.

func (DNSLeakCheck) Latency added in v0.3.0

func (d DNSLeakCheck) Latency() time.Duration

Latency returns how long the check took.

func (DNSLeakCheck) Message added in v0.3.0

func (d DNSLeakCheck) Message() string

Message provides human-readable details about the check.

func (DNSLeakCheck) ResolvedIPs added in v0.3.0

func (d DNSLeakCheck) ResolvedIPs() []string

ResolvedIPs returns a defensive copy of the IP addresses returned by DNS resolution.

func (DNSLeakCheck) String added in v0.3.0

func (d DNSLeakCheck) String() string

String returns a human-readable representation of the DNS leak check.

type ErrorKind

type ErrorKind string

ErrorKind classifies Tornago errors for easier handling and retry decisions.

const (
	// ErrInvalidConfig indicates user-supplied configuration is invalid.
	ErrInvalidConfig ErrorKind = "invalid_config"
	// ErrTorBinaryNotFound indicates the tor executable could not be located.
	ErrTorBinaryNotFound ErrorKind = "tor_binary_not_found"
	// ErrTorLaunchFailed indicates tor failed to launch or exited unexpectedly.
	ErrTorLaunchFailed ErrorKind = "tor_launch_failed"
	// ErrSocksDialFailed indicates SOCKS5 dialing failed.
	ErrSocksDialFailed ErrorKind = "socks_dial_failed"
	// ErrControlAuthFailed indicates ControlPort authentication failed.
	ErrControlAuthFailed ErrorKind = "control_auth_failed"
	// ErrControlRequestFail indicates a ControlPort request returned an error.
	ErrControlRequestFail ErrorKind = "control_request_failed"
	// ErrHTTPFailed indicates an HTTP request via Tor failed.
	ErrHTTPFailed ErrorKind = "http_failed"
	// ErrTimeout indicates an operation exceeded its deadline.
	ErrTimeout ErrorKind = "timeout"
	// ErrIO wraps generic I/O errors.
	ErrIO ErrorKind = "io_error"
	// ErrHiddenServiceFailed indicates Hidden Service creation/removal failed.
	ErrHiddenServiceFailed ErrorKind = "hidden_service_failed"
	// ErrListenerClosed indicates an operation was attempted on a closed listener.
	ErrListenerClosed ErrorKind = "listener_closed"
	// ErrListenerCloseFailed indicates the listener failed to close properly.
	ErrListenerCloseFailed ErrorKind = "listener_close_failed"
	// ErrAcceptFailed indicates Accept() failed on a listener.
	ErrAcceptFailed ErrorKind = "accept_failed"
	// ErrUnknown is used when no specific classification is available.
	ErrUnknown ErrorKind = "unknown"
)

ErrorKind values classify tornago errors by their category.

type HealthCheck added in v0.3.0

type HealthCheck struct {
	// contains filtered or unexported fields
}

HealthCheck contains the result of a health check operation. It is an immutable value object that provides methods to query health status.

func CheckTorDaemon added in v0.3.0

func CheckTorDaemon(ctx context.Context, proc *TorProcess) HealthCheck

CheckTorDaemon performs a health check on a TorProcess. It verifies that:

  • The Tor process is running
  • SOCKS and ControlPort are responsive

Example:

torProcess, _ := tornago.StartTorDaemon(cfg)
health := tornago.CheckTorDaemon(context.Background(), torProcess)
if !health.IsHealthy() {
    log.Printf("Tor daemon unhealthy: %s", health.Message())
}

func (HealthCheck) IsDegraded added in v0.3.0

func (h HealthCheck) IsDegraded() bool

IsDegraded returns true if the service is operational but experiencing issues.

func (HealthCheck) IsHealthy added in v0.3.0

func (h HealthCheck) IsHealthy() bool

IsHealthy returns true if all components are functioning normally.

func (HealthCheck) IsUnhealthy added in v0.3.0

func (h HealthCheck) IsUnhealthy() bool

IsUnhealthy returns true if the service is not functioning.

func (HealthCheck) Latency added in v0.3.0

func (h HealthCheck) Latency() time.Duration

Latency returns how long the health check took.

func (HealthCheck) Message added in v0.3.0

func (h HealthCheck) Message() string

Message provides human-readable context about the health status.

func (HealthCheck) Status added in v0.3.0

func (h HealthCheck) Status() HealthStatus

Status returns the overall health status.

func (HealthCheck) String added in v0.3.0

func (h HealthCheck) String() string

String returns a human-readable representation of the health check.

func (HealthCheck) Timestamp added in v0.3.0

func (h HealthCheck) Timestamp() time.Time

Timestamp returns when the health check was performed.

type HealthStatus added in v0.3.0

type HealthStatus string

HealthStatus represents the health state of a Tor connection or service.

const (
	// HealthStatusHealthy indicates the service is functioning normally.
	HealthStatusHealthy HealthStatus = "healthy"
	// HealthStatusDegraded indicates the service is operational but experiencing issues.
	HealthStatusDegraded HealthStatus = "degraded"
	// HealthStatusUnhealthy indicates the service is not functioning.
	HealthStatusUnhealthy HealthStatus = "unhealthy"
)

type HiddenService

type HiddenService interface {
	// OnionAddress returns the .onion address where the service is accessible.
	OnionAddress() string
	// PrivateKey returns the private key in Tor's format for re-registering this service.
	PrivateKey() string
	// Ports returns the virtual port to local port mapping.
	Ports() map[int]int
	// ClientAuth returns the client authorization entries if configured.
	ClientAuth() []HiddenServiceAuth
	// Remove deletes this hidden service from Tor. The .onion address becomes inaccessible.
	Remove(ctx context.Context) error
	// SavePrivateKey saves the private key to a file for later reuse.
	SavePrivateKey(path string) error
}

HiddenService represents a provisioned Hidden Service (also known as an onion service). A hidden service allows you to host a server that's accessible only through the Tor network, identified by a .onion address.

Benefits of hidden services:

  • No need for public IP address or DNS registration
  • Server location and IP remain anonymous
  • End-to-end encryption through Tor network
  • Censorship resistance (difficult to block .onion addresses)

Example usage:

// Create hidden service mapping port 80 to local port 8080
cfg, _ := tornago.NewHiddenServiceConfig(
    tornago.WithHiddenServicePort(80, 8080),
)
hs, _ := controlClient.CreateHiddenService(context.Background(), cfg)
defer hs.Remove(context.Background())

fmt.Printf("Your service is at: %s\n", hs.OnionAddress())
// Example output: "abc123xyz456.onion"

type HiddenServiceAuth

type HiddenServiceAuth struct {
	// contains filtered or unexported fields
}

HiddenServiceAuth describes Tor v3 client authorization information.

func NewHiddenServiceAuth

func NewHiddenServiceAuth(clientName, key string) HiddenServiceAuth

NewHiddenServiceAuth returns a client auth entry.

func (HiddenServiceAuth) ClientName

func (a HiddenServiceAuth) ClientName() string

ClientName returns the configured auth client name.

func (HiddenServiceAuth) Key

func (a HiddenServiceAuth) Key() string

Key returns the authorization key.

type HiddenServiceConfig

type HiddenServiceConfig struct {
	// contains filtered or unexported fields
}

HiddenServiceConfig describes the desired onion service to create via Tor.

func NewHiddenServiceConfig

func NewHiddenServiceConfig(opts ...HiddenServiceOption) (HiddenServiceConfig, error)

NewHiddenServiceConfig returns a validated, immutable configuration.

func (HiddenServiceConfig) ClientAuth

func (c HiddenServiceConfig) ClientAuth() []HiddenServiceAuth

ClientAuth returns a copy of the configured client authorization entries.

func (HiddenServiceConfig) KeyType

func (c HiddenServiceConfig) KeyType() string

KeyType returns the key type (e.g. "ED25519-V3").

func (HiddenServiceConfig) Ports

func (c HiddenServiceConfig) Ports() map[int]int

Ports returns a copy of the configured virtual -> target port mapping.

func (HiddenServiceConfig) PrivateKey

func (c HiddenServiceConfig) PrivateKey() string

PrivateKey returns the optional private key blob.

type HiddenServiceOption

type HiddenServiceOption func(*HiddenServiceConfig)

HiddenServiceOption customizes HiddenServiceConfig creation.

func WithHiddenServiceClientAuth

func WithHiddenServiceClientAuth(auth ...HiddenServiceAuth) HiddenServiceOption

WithHiddenServiceClientAuth appends client authorization entries.

func WithHiddenServiceHTTP added in v0.2.0

func WithHiddenServiceHTTP(localPort int) HiddenServiceOption

WithHiddenServiceHTTP maps port 80 to the specified local port. This is a convenience for hosting HTTP services.

func WithHiddenServiceHTTPS added in v0.2.0

func WithHiddenServiceHTTPS(localPort int) HiddenServiceOption

WithHiddenServiceHTTPS maps port 443 to the specified local port. This is a convenience for hosting HTTPS services.

func WithHiddenServiceKeyType

func WithHiddenServiceKeyType(keyType string) HiddenServiceOption

WithHiddenServiceKeyType sets the key type (default: "ED25519-V3").

func WithHiddenServicePort

func WithHiddenServicePort(virtualPort, targetPort int) HiddenServiceOption

WithHiddenServicePort maps a virtual port to a local target port.

func WithHiddenServicePorts

func WithHiddenServicePorts(ports map[int]int) HiddenServiceOption

WithHiddenServicePorts sets the entire virtual -> target port mapping.

func WithHiddenServicePrivateKey

func WithHiddenServicePrivateKey(privateKey string) HiddenServiceOption

WithHiddenServicePrivateKey uses an existing private key blob.

func WithHiddenServicePrivateKeyFile added in v0.2.0

func WithHiddenServicePrivateKeyFile(path string) HiddenServiceOption

WithHiddenServicePrivateKeyFile loads a private key from a file and uses it. This is a convenience option that combines LoadPrivateKey and WithHiddenServicePrivateKey.

func WithHiddenServiceSamePort added in v0.2.0

func WithHiddenServiceSamePort(port int) HiddenServiceOption

WithHiddenServiceSamePort maps a port to itself (virtualPort == targetPort). This is a convenience for common cases where you don't need port translation.

type HiddenServiceStatus added in v0.2.0

type HiddenServiceStatus struct {
	// ServiceID is the onion address without .onion suffix.
	ServiceID string
	// Ports lists the configured port mappings.
	Ports []string
}

HiddenServiceStatus represents the status of a hidden service.

type Latency added in v0.4.0

type Latency struct {
	// contains filtered or unexported fields
}

Latency represents a duration measurement for relay performance.

func NewLatency added in v0.4.0

func NewLatency(d time.Duration) Latency

NewLatency creates a new Latency from a duration.

func (Latency) Duration added in v0.4.0

func (l Latency) Duration() time.Duration

Duration returns the latency as a time.Duration.

func (Latency) ExceedsThreshold added in v0.4.0

func (l Latency) ExceedsThreshold(threshold Latency) bool

ExceedsThreshold returns true if this latency exceeds the given threshold.

func (Latency) IsZero added in v0.4.0

func (l Latency) IsZero() bool

IsZero returns true if the latency is zero.

type Logger added in v0.3.0

type Logger interface {
	// Log logs a message at the specified level with optional key-value pairs.
	// Level should be one of: "debug", "info", "warn", "error".
	// The keysAndValues are interpreted as alternating key-value pairs.
	Log(level string, msg string, keysAndValues ...any)
}

Logger defines a minimal structured logging interface for tornago. This interface is intentionally simple to support various logging libraries (slog, logrus, zap, zerolog, etc.) through adapters.

The default logger discards all log messages. Users can provide their own logger implementation using WithLogger configuration option.

Example with slog:

logger := slog.New(slog.NewJSONHandler(os.Stderr, &slog.HandlerOptions{
    Level: slog.LevelDebug,
}))
adapter := tornago.NewSlogAdapter(logger)
cfg, _ := tornago.NewClientConfig(
    tornago.WithClientSocksAddr("127.0.0.1:9050"),
    tornago.WithLogger(adapter),
)

For other logging libraries, implement this interface by wrapping your preferred logger. The keysAndValues parameter should be interpreted as alternating key-value pairs (e.g., "key1", value1, "key2", value2).

func NewSlogAdapter added in v0.3.0

func NewSlogAdapter(logger *slog.Logger) Logger

NewSlogAdapter creates a Logger from *slog.Logger.

Example:

logger := slog.New(slog.NewTextHandler(os.Stderr, nil))
adapter := tornago.NewSlogAdapter(logger)
cfg, _ := tornago.NewClientConfig(
    tornago.WithClientSocksAddr("127.0.0.1:9050"),
    tornago.WithLogger(adapter),
)

type Metrics added in v0.2.0

type Metrics interface {
	// RequestCount returns the total number of requests made.
	RequestCount() uint64
	// SuccessCount returns the number of successful requests.
	SuccessCount() uint64
	// ErrorCount returns the number of failed requests.
	ErrorCount() uint64
	// TotalLatency returns the sum of all request latencies.
	TotalLatency() time.Duration
	// AverageLatency returns the average request latency.
	AverageLatency() time.Duration
	// Reset clears all metrics.
	Reset()
}

Metrics provides access to client operation statistics. All methods are safe for concurrent use.

type MetricsCollector added in v0.2.0

type MetricsCollector struct {
	// contains filtered or unexported fields
}

MetricsCollector tracks request statistics for the Client. It is thread-safe and can be shared across goroutines.

func NewMetricsCollector added in v0.2.0

func NewMetricsCollector() *MetricsCollector

NewMetricsCollector creates a new MetricsCollector.

func (*MetricsCollector) AverageLatency added in v0.2.0

func (m *MetricsCollector) AverageLatency() time.Duration

AverageLatency returns the average request latency. Returns 0 if no requests have been made.

func (*MetricsCollector) ConnectionReuseCount added in v0.3.0

func (m *MetricsCollector) ConnectionReuseCount() uint64

ConnectionReuseCount returns the number of times an existing connection was reused. This is calculated as the difference between total requests and total dials.

func (*MetricsCollector) ConnectionReuseRate added in v0.3.0

func (m *MetricsCollector) ConnectionReuseRate() float64

ConnectionReuseRate returns the percentage of requests that reused existing connections. Returns 0.0 if no requests have been made. A higher rate (closer to 1.0) indicates better connection pooling efficiency.

func (*MetricsCollector) DialCount added in v0.3.0

func (m *MetricsCollector) DialCount() uint64

DialCount returns the total number of dial operations performed. This includes both new connections and connection reuse attempts.

func (*MetricsCollector) ErrorCount added in v0.2.0

func (m *MetricsCollector) ErrorCount() uint64

ErrorCount returns the number of failed requests.

func (*MetricsCollector) ErrorsByKind added in v0.3.0

func (m *MetricsCollector) ErrorsByKind() map[ErrorKind]uint64

ErrorsByKind returns a copy of error counts grouped by error kind.

func (*MetricsCollector) MaxLatency added in v0.3.0

func (m *MetricsCollector) MaxLatency() time.Duration

MaxLatency returns the maximum request latency observed. Returns 0 if no requests have been made.

func (*MetricsCollector) MinLatency added in v0.3.0

func (m *MetricsCollector) MinLatency() time.Duration

MinLatency returns the minimum request latency observed. Returns 0 if no requests have been made.

func (*MetricsCollector) RequestCount added in v0.2.0

func (m *MetricsCollector) RequestCount() uint64

RequestCount returns the total number of requests made.

func (*MetricsCollector) Reset added in v0.2.0

func (m *MetricsCollector) Reset()

Reset clears all metrics to zero.

func (*MetricsCollector) SuccessCount added in v0.2.0

func (m *MetricsCollector) SuccessCount() uint64

SuccessCount returns the number of successful requests.

func (*MetricsCollector) TotalLatency added in v0.2.0

func (m *MetricsCollector) TotalLatency() time.Duration

TotalLatency returns the sum of all request latencies.

type OnionAddr added in v0.2.0

type OnionAddr struct {
	// contains filtered or unexported fields
}

OnionAddr represents a .onion address that implements net.Addr.

func (*OnionAddr) Network added in v0.2.0

func (a *OnionAddr) Network() string

Network returns the network type, always "onion".

func (*OnionAddr) Port added in v0.2.0

func (a *OnionAddr) Port() int

Port returns the virtual port number.

func (*OnionAddr) String added in v0.2.0

func (a *OnionAddr) String() string

String returns the full address in "host:port" format.

type RateLimiter added in v0.2.0

type RateLimiter struct {
	// contains filtered or unexported fields
}

RateLimiter controls the rate of requests to prevent overloading Tor circuits. It implements a token bucket algorithm.

func NewRateLimiter added in v0.2.0

func NewRateLimiter(rate float64, burst int) *RateLimiter

NewRateLimiter creates a rate limiter with the specified rate (requests/second) and burst size.

func (*RateLimiter) Allow added in v0.2.0

func (r *RateLimiter) Allow() bool

Allow returns true if a request can proceed immediately without waiting.

func (*RateLimiter) Burst added in v0.2.0

func (r *RateLimiter) Burst() int

Burst returns the configured burst size.

func (*RateLimiter) Rate added in v0.2.0

func (r *RateLimiter) Rate() float64

Rate returns the configured rate (requests per second).

func (*RateLimiter) Wait added in v0.2.0

func (r *RateLimiter) Wait(ctx context.Context) error

Wait blocks until a token is available or the context is canceled.

type RelayFingerprint added in v0.4.0

type RelayFingerprint struct {
	// contains filtered or unexported fields
}

RelayFingerprint represents a Tor relay's unique identifier. It is a 40-character hexadecimal string (SHA-1 hash of the relay's identity key).

func NewRelayFingerprint added in v0.4.0

func NewRelayFingerprint(fingerprint string) RelayFingerprint

NewRelayFingerprint creates a new RelayFingerprint from a string. The fingerprint should be a 40-character hexadecimal string, optionally prefixed with '$'.

func (RelayFingerprint) Equal added in v0.4.0

func (f RelayFingerprint) Equal(other RelayFingerprint) bool

Equal returns true if two fingerprints are equal.

func (RelayFingerprint) IsEmpty added in v0.4.0

func (f RelayFingerprint) IsEmpty() bool

IsEmpty returns true if the fingerprint is empty.

func (RelayFingerprint) String added in v0.4.0

func (f RelayFingerprint) String() string

String returns the fingerprint as a string.

type RelayMeasurement added in v0.4.0

type RelayMeasurement struct {
	// contains filtered or unexported fields
}

RelayMeasurement represents a single performance measurement for a relay.

func NewRelayMeasurement added in v0.4.0

func NewRelayMeasurement(latency time.Duration, success bool) RelayMeasurement

NewRelayMeasurement creates a new measurement.

func (RelayMeasurement) IsExpired added in v0.4.0

func (m RelayMeasurement) IsExpired(window time.Duration) bool

IsExpired returns true if the measurement is older than the given window.

func (RelayMeasurement) Latency added in v0.4.0

func (m RelayMeasurement) Latency() Latency

Latency returns the measured latency.

func (RelayMeasurement) Success added in v0.4.0

func (m RelayMeasurement) Success() bool

Success returns whether the measurement was successful.

func (RelayMeasurement) Timestamp added in v0.4.0

func (m RelayMeasurement) Timestamp() time.Time

Timestamp returns when the measurement was taken.

type RelayPerformanceStats added in v0.4.0

type RelayPerformanceStats struct {
	// contains filtered or unexported fields
}

RelayPerformanceStats provides detailed statistics about relay performance. This is an immutable Value Object - use accessor methods to retrieve values.

func (RelayPerformanceStats) BlockedRelayList added in v0.4.0

func (s RelayPerformanceStats) BlockedRelayList() []string

BlockedRelayList returns a copy of the blocked relay fingerprints.

func (RelayPerformanceStats) BlockedRelays added in v0.4.0

func (s RelayPerformanceStats) BlockedRelays() int

BlockedRelays returns the number of currently blocked relays.

func (RelayPerformanceStats) Enabled added in v0.4.0

func (s RelayPerformanceStats) Enabled() bool

Enabled returns true if slow relay avoidance is active.

func (RelayPerformanceStats) Threshold added in v0.4.0

Threshold returns the current threshold configuration.

func (RelayPerformanceStats) TrackedRelays added in v0.4.0

func (s RelayPerformanceStats) TrackedRelays() int

TrackedRelays returns the number of relays being monitored.

type RelayPerformanceTracker added in v0.4.0

type RelayPerformanceTracker struct {
	// contains filtered or unexported fields
}

RelayPerformanceTracker tracks relay performance and manages slow relay exclusion.

func NewRelayPerformanceTracker added in v0.4.0

func NewRelayPerformanceTracker(opts ...RelayTrackerOption) *RelayPerformanceTracker

NewRelayPerformanceTracker creates a new tracker with optional configuration.

func (*RelayPerformanceTracker) BlockedRelays added in v0.4.0

func (t *RelayPerformanceTracker) BlockedRelays() []RelayFingerprint

BlockedRelays returns a list of currently blocked relay fingerprints.

func (*RelayPerformanceTracker) Clear added in v0.4.0

func (t *RelayPerformanceTracker) Clear()

Clear removes all tracked statistics and blocks.

func (*RelayPerformanceTracker) ClearExcludeNodes added in v0.4.0

func (t *RelayPerformanceTracker) ClearExcludeNodes(ctx context.Context) error

ClearExcludeNodes removes all ExcludeNodes from Tor configuration.

func (*RelayPerformanceTracker) GetStats added in v0.4.0

func (t *RelayPerformanceTracker) GetStats(fingerprint RelayFingerprint) *RelayStats

GetStats returns statistics for a specific relay.

func (*RelayPerformanceTracker) IsBlocked added in v0.4.0

func (t *RelayPerformanceTracker) IsBlocked(fingerprint RelayFingerprint) bool

IsBlocked returns true if the relay is currently blocked.

func (*RelayPerformanceTracker) RecordCircuitMeasurement added in v0.4.0

func (t *RelayPerformanceTracker) RecordCircuitMeasurement(path []string, latency time.Duration, success bool)

RecordCircuitMeasurement records a measurement for all relays in a circuit path.

func (*RelayPerformanceTracker) RecordMeasurement added in v0.4.0

func (t *RelayPerformanceTracker) RecordMeasurement(fingerprint RelayFingerprint, latency time.Duration, success bool)

RecordMeasurement records a performance measurement for a relay in a circuit.

func (*RelayPerformanceTracker) Stats added in v0.4.0

Stats returns current tracker statistics.

type RelayStats added in v0.4.0

type RelayStats struct {
	// contains filtered or unexported fields
}

RelayStats aggregates performance statistics for a single relay.

func NewRelayStats added in v0.4.0

func NewRelayStats(fingerprint RelayFingerprint, measureWindow time.Duration) *RelayStats

NewRelayStats creates a new RelayStats for the given fingerprint.

func (*RelayStats) AddMeasurement added in v0.4.0

func (s *RelayStats) AddMeasurement(m RelayMeasurement)

AddMeasurement adds a new measurement and recalculates statistics.

func (*RelayStats) AverageLatency added in v0.4.0

func (s *RelayStats) AverageLatency() Latency

AverageLatency returns the average latency.

func (*RelayStats) Fingerprint added in v0.4.0

func (s *RelayStats) Fingerprint() RelayFingerprint

Fingerprint returns the relay's fingerprint.

func (*RelayStats) IsSlow added in v0.4.0

func (s *RelayStats) IsSlow(threshold RelayThreshold) bool

IsSlow returns true if the relay is considered slow based on the threshold.

func (*RelayStats) SampleCount added in v0.4.0

func (s *RelayStats) SampleCount() int

SampleCount returns the number of samples in the current window.

func (*RelayStats) SuccessRate added in v0.4.0

func (s *RelayStats) SuccessRate() SuccessRate

SuccessRate returns the success rate.

type RelayThreshold added in v0.4.0

type RelayThreshold struct {
	// contains filtered or unexported fields
}

RelayThreshold defines thresholds for determining slow relays. All fields are immutable after creation.

func NewRelayThreshold added in v0.4.0

func NewRelayThreshold() RelayThreshold

NewRelayThreshold creates a RelayThreshold with default values. Use With* methods to customize.

func (RelayThreshold) BlockDuration added in v0.4.0

func (t RelayThreshold) BlockDuration() time.Duration

BlockDuration returns how long slow relays are blocked.

func (RelayThreshold) MaxLatency added in v0.4.0

func (t RelayThreshold) MaxLatency() Latency

MaxLatency returns the maximum acceptable latency.

func (RelayThreshold) MinSamples added in v0.4.0

func (t RelayThreshold) MinSamples() int

MinSamples returns the minimum number of samples required for evaluation.

func (RelayThreshold) MinSuccessRate added in v0.4.0

func (t RelayThreshold) MinSuccessRate() SuccessRate

MinSuccessRate returns the minimum acceptable success rate.

func (RelayThreshold) WithBlockDuration added in v0.4.0

func (t RelayThreshold) WithBlockDuration(d time.Duration) RelayThreshold

WithBlockDuration returns a new RelayThreshold with the given block duration.

func (RelayThreshold) WithMaxLatency added in v0.4.0

func (t RelayThreshold) WithMaxLatency(d time.Duration) RelayThreshold

WithMaxLatency returns a new RelayThreshold with the given max latency.

func (RelayThreshold) WithMinSamples added in v0.4.0

func (t RelayThreshold) WithMinSamples(n int) RelayThreshold

WithMinSamples returns a new RelayThreshold with the given minimum sample count.

func (RelayThreshold) WithMinSuccessRate added in v0.4.0

func (t RelayThreshold) WithMinSuccessRate(rate float64) RelayThreshold

WithMinSuccessRate returns a new RelayThreshold with the given min success rate.

type RelayThresholdStats added in v0.4.0

type RelayThresholdStats struct {
	// contains filtered or unexported fields
}

RelayThresholdStats provides threshold configuration details. This is an immutable Value Object - use accessor methods to retrieve values.

func (RelayThresholdStats) BlockDuration added in v0.4.0

func (s RelayThresholdStats) BlockDuration() time.Duration

BlockDuration returns how long slow relays remain blocked.

func (RelayThresholdStats) MaxLatency added in v0.4.0

func (s RelayThresholdStats) MaxLatency() time.Duration

MaxLatency returns the maximum acceptable latency.

func (RelayThresholdStats) MinSamples added in v0.4.0

func (s RelayThresholdStats) MinSamples() int

MinSamples returns the minimum measurements needed before evaluation.

func (RelayThresholdStats) MinSuccessRate added in v0.4.0

func (s RelayThresholdStats) MinSuccessRate() float64

MinSuccessRate returns the minimum acceptable success rate.

type RelayTrackerOption added in v0.4.0

type RelayTrackerOption func(*RelayPerformanceTracker)

RelayTrackerOption configures a RelayPerformanceTracker.

func WithTrackerAutoExclude added in v0.4.0

func WithTrackerAutoExclude(enabled bool) RelayTrackerOption

WithTrackerAutoExclude enables/disables automatic relay exclusion via Tor.

func WithTrackerControl added in v0.4.0

func WithTrackerControl(control *ControlClient) RelayTrackerOption

WithTrackerControl sets the control client for automatic ExcludeNodes.

func WithTrackerLogger added in v0.4.0

func WithTrackerLogger(logger Logger) RelayTrackerOption

WithTrackerLogger sets the logger.

func WithTrackerMeasureWindow added in v0.4.0

func WithTrackerMeasureWindow(d time.Duration) RelayTrackerOption

WithTrackerMeasureWindow sets the measurement window.

func WithTrackerThreshold added in v0.4.0

func WithTrackerThreshold(threshold RelayThreshold) RelayTrackerOption

WithTrackerThreshold sets the threshold configuration.

type Server

type Server interface {
	// SocksAddr returns the Tor SocksPort address.
	SocksAddr() string
	// ControlAddr returns the Tor ControlPort address.
	ControlAddr() string
}

Server exposes Tor SocksPort and ControlPort addresses for clients to use.

func NewServer

func NewServer(cfg ServerConfig) (Server, error)

NewServer builds a Server from the given configuration.

type ServerConfig

type ServerConfig struct {
	// contains filtered or unexported fields
}

ServerConfig represents addresses of an existing Tor instance. It is immutable after construction via NewServerConfig.

func NewServerConfig

func NewServerConfig(opts ...ServerOption) (ServerConfig, error)

NewServerConfig returns a validated, immutable server config.

func (ServerConfig) ControlAddr

func (c ServerConfig) ControlAddr() string

ControlAddr is the address of an already running Tor ControlPort.

func (ServerConfig) SocksAddr

func (c ServerConfig) SocksAddr() string

SocksAddr is the address of an already running Tor SocksPort.

type ServerOption

type ServerOption func(*ServerConfig)

ServerOption customizes ServerConfig creation.

func WithServerControlAddr

func WithServerControlAddr(addr string) ServerOption

WithServerControlAddr sets the ControlPort address. WithServerControlAddr sets the ControlPort address on ServerConfig.

func WithServerSocksAddr

func WithServerSocksAddr(addr string) ServerOption

WithServerSocksAddr sets the SocksPort address. WithServerSocksAddr sets the SocksPort address on ServerConfig.

type SlowRelayOption added in v0.4.0

type SlowRelayOption func(*slowRelayConfig)

SlowRelayOption configures slow relay avoidance behavior.

func SlowRelayAutoExclude added in v0.4.0

func SlowRelayAutoExclude(enabled bool) SlowRelayOption

SlowRelayAutoExclude enables/disables automatic Tor ExcludeNodes updates. When enabled, blocked relays are added to Tor's ExcludeNodes configuration. Default: true.

func SlowRelayBlockDuration added in v0.4.0

func SlowRelayBlockDuration(d time.Duration) SlowRelayOption

SlowRelayBlockDuration sets how long slow relays remain blocked. Default: 30 minutes.

func SlowRelayMaxLatency added in v0.4.0

func SlowRelayMaxLatency(d time.Duration) SlowRelayOption

SlowRelayMaxLatency sets the maximum acceptable latency. Relays with average latency exceeding this will be blocked. Default: 5 seconds.

func SlowRelayMinSamples added in v0.4.0

func SlowRelayMinSamples(n int) SlowRelayOption

SlowRelayMinSamples sets the minimum measurements needed before evaluation. Default: 3.

func SlowRelayMinSuccessRate added in v0.4.0

func SlowRelayMinSuccessRate(rate float64) SlowRelayOption

SlowRelayMinSuccessRate sets the minimum acceptable success rate (0.0-1.0). Relays with lower success rates will be blocked. Default: 0.8 (80%).

func SlowRelayMonitorInterval added in v0.4.0

func SlowRelayMonitorInterval(d time.Duration) SlowRelayOption

SlowRelayMonitorInterval sets how often to check for slow circuits. Default: 30 seconds.

type StreamInfo added in v0.2.0

type StreamInfo struct {
	// ID is the stream identifier.
	ID string
	// Status is the stream status (e.g., "SUCCEEDED", "NEW", "SENTCONNECT").
	Status string
	// CircuitID is the circuit this stream is attached to.
	CircuitID string
	// Target is the destination address:port.
	Target string
	// Purpose is the stream purpose.
	Purpose string
}

StreamInfo represents information about a Tor stream.

type SuccessRate added in v0.4.0

type SuccessRate struct {
	// contains filtered or unexported fields
}

SuccessRate represents a success rate between 0.0 and 1.0.

func NewSuccessRate added in v0.4.0

func NewSuccessRate(rate float64) SuccessRate

NewSuccessRate creates a new SuccessRate, clamping to [0.0, 1.0].

func (SuccessRate) BelowThreshold added in v0.4.0

func (s SuccessRate) BelowThreshold(threshold SuccessRate) bool

BelowThreshold returns true if this rate is below the given threshold.

func (SuccessRate) Float64 added in v0.4.0

func (s SuccessRate) Float64() float64

Float64 returns the success rate as a float64.

type TestServer

type TestServer struct {
	// Process points to the TorProcess launched for tests.
	Process *TorProcess
	// Server exposes the Socks/Control addresses of the launched Tor instance.
	Server Server
	// contains filtered or unexported fields
}

TestServer wraps a TorProcess and Server for integration tests.

func StartTestServer

func StartTestServer(t *testing.T) *TestServer

StartTestServer launches a Tor daemon for tests using a project-local DataDirectory and dedicated ports, skipping if tor is unavailable.

func (*TestServer) Client

func (ts *TestServer) Client(t *testing.T) *Client

Client returns a Client configured to use the started Tor instance.

func (*TestServer) Close

func (ts *TestServer) Close()

Close shuts down the client and Tor process launched for tests.

func (*TestServer) ControlAuth

func (ts *TestServer) ControlAuth(t *testing.T) ControlAuth

ControlAuth returns ControlPort credentials for this TestServer.

type TorConnectionStatus added in v0.3.0

type TorConnectionStatus struct {
	// contains filtered or unexported fields
}

TorConnectionStatus represents the result of verifying Tor connectivity. It is an immutable value object that provides methods to query connection status.

func (TorConnectionStatus) ExitIP added in v0.3.0

func (s TorConnectionStatus) ExitIP() string

ExitIP returns the IP address as seen by the target server (Tor exit node IP).

func (TorConnectionStatus) IsUsingTor added in v0.3.0

func (s TorConnectionStatus) IsUsingTor() bool

IsUsingTor returns true if the connection is going through Tor.

func (TorConnectionStatus) Latency added in v0.3.0

func (s TorConnectionStatus) Latency() time.Duration

Latency returns how long the verification took.

func (TorConnectionStatus) Message added in v0.3.0

func (s TorConnectionStatus) Message() string

Message provides human-readable details about the check.

func (TorConnectionStatus) String added in v0.3.0

func (s TorConnectionStatus) String() string

String returns a human-readable representation of the Tor connection status.

type TorLaunchConfig

type TorLaunchConfig struct {
	// contains filtered or unexported fields
}

TorLaunchConfig controls how the Tor daemon is started by Tornago. It is immutable after construction via NewTorLaunchConfig.

func NewTorLaunchConfig

func NewTorLaunchConfig(opts ...TorLaunchOption) (TorLaunchConfig, error)

NewTorLaunchConfig returns a validated, immutable launch config.

func (TorLaunchConfig) ControlAddr

func (c TorLaunchConfig) ControlAddr() string

ControlAddr is the address for Tor's ControlPort; ":0" lets Tor pick a free port.

func (TorLaunchConfig) DataDir

func (c TorLaunchConfig) DataDir() string

DataDir is the Tor DataDirectory path when explicitly configured.

func (TorLaunchConfig) ExtraArgs

func (c TorLaunchConfig) ExtraArgs() []string

ExtraArgs are passed through to the tor process at launch.

func (TorLaunchConfig) LogReporter

func (c TorLaunchConfig) LogReporter() func(string)

LogReporter returns the callback registered for Tor log output.

func (TorLaunchConfig) Logger added in v0.3.0

func (c TorLaunchConfig) Logger() Logger

Logger returns the structured logger for Tor daemon operations.

func (TorLaunchConfig) SocksAddr

func (c TorLaunchConfig) SocksAddr() string

SocksAddr is the address for Tor's SocksPort; ":0" lets Tor pick a free port.

func (TorLaunchConfig) StartupTimeout

func (c TorLaunchConfig) StartupTimeout() time.Duration

StartupTimeout bounds how long Tornago waits for tor to become ready.

func (TorLaunchConfig) TorBinary

func (c TorLaunchConfig) TorBinary() string

TorBinary is the tor executable path; defaults to LookPath("tor") when empty.

func (TorLaunchConfig) TorConfigFile

func (c TorLaunchConfig) TorConfigFile() string

TorConfigFile is the optional tor configuration file path passed with "-f".

type TorLaunchOption

type TorLaunchOption func(*TorLaunchConfig)

TorLaunchOption customizes TorLaunchConfig creation.

func WithTorBinary

func WithTorBinary(path string) TorLaunchOption

WithTorBinary sets the tor executable path.

func WithTorConfigFile

func WithTorConfigFile(path string) TorLaunchOption

WithTorConfigFile sets the torrc path passed to tor via "-f".

func WithTorControlAddr

func WithTorControlAddr(addr string) TorLaunchOption

WithTorControlAddr sets the ControlPort listen address.

func WithTorDataDir

func WithTorDataDir(path string) TorLaunchOption

WithTorDataDir forces Tor to use the provided DataDirectory path.

func WithTorExtraArgs

func WithTorExtraArgs(args ...string) TorLaunchOption

WithTorExtraArgs appends additional CLI args passed to tor.

func WithTorLogReporter

func WithTorLogReporter(fn func(string)) TorLaunchOption

WithTorLogReporter registers a callback to receive Tor startup logs.

func WithTorLogger added in v0.3.0

func WithTorLogger(logger Logger) TorLaunchOption

WithTorLogger sets the structured logger for Tor daemon operations.

func WithTorSocksAddr

func WithTorSocksAddr(addr string) TorLaunchOption

WithTorSocksAddr sets the SocksPort listen address.

func WithTorStartupTimeout

func WithTorStartupTimeout(timeout time.Duration) TorLaunchOption

WithTorStartupTimeout sets how long Tornago waits for tor to start.

type TorListener added in v0.2.0

type TorListener struct {
	// contains filtered or unexported fields
}

TorListener implements net.Listener for Tor Hidden Services. It wraps a local TCP listener and exposes it as a Tor onion service.

Example usage:

client, _ := tornago.NewClient(tornago.NewClientConfig(...))
listener, _ := client.Listen(ctx, 80, 8080) // onion:80 -> local:8080
defer listener.Close()

for {
    conn, err := listener.Accept()
    if err != nil {
        break
    }
    go handleConnection(conn)
}

func (*TorListener) Accept added in v0.2.0

func (l *TorListener) Accept() (net.Conn, error)

Accept waits for and returns the next connection to the listener. This implements net.Listener.

func (*TorListener) Addr added in v0.2.0

func (l *TorListener) Addr() net.Addr

Addr returns the .onion address of the listener. This implements net.Listener.

func (*TorListener) Close added in v0.2.0

func (l *TorListener) Close() error

Close stops listening and removes the hidden service from Tor. This implements net.Listener.

func (*TorListener) HiddenService added in v0.2.0

func (l *TorListener) HiddenService() HiddenService

HiddenService returns the underlying HiddenService. This can be used to access the private key or other hidden service details.

func (*TorListener) OnionAddress added in v0.2.0

func (l *TorListener) OnionAddress() string

OnionAddress returns the full .onion address (e.g., "abc123.onion").

func (*TorListener) VirtualPort added in v0.2.0

func (l *TorListener) VirtualPort() int

VirtualPort returns the port exposed on the .onion address.

type TorProcess

type TorProcess struct {
	// contains filtered or unexported fields
}

TorProcess represents a running tor daemon launched by Tornago. It is immutable and exposes read-only accessors for its properties.

func StartTorDaemon

func StartTorDaemon(cfg TorLaunchConfig) (_ *TorProcess, err error)

StartTorDaemon launches the tor binary as a child process using the provided configuration. It waits until both the SocksPort and ControlPort become reachable or until StartupTimeout elapses.

This function is useful when you want your application to manage its own Tor instance rather than relying on a system-wide Tor daemon. StartTorDaemon handles:

  • Finding the tor binary in PATH (install via: apt install tor, brew install tor, choco install tor)
  • Allocating free ports when using ":0" addresses
  • Configuring cookie authentication automatically
  • Waiting for Tor to become ready before returning
  • Creating/managing the Tor DataDirectory

The returned TorProcess must be stopped via Stop() to cleanly terminate Tor and clean up resources. Use defer torProc.Stop() to ensure cleanup.

Example usage:

cfg, _ := tornago.NewTorLaunchConfig(
    tornago.WithTorSocksAddr(":0"),     // Auto-select free port
    tornago.WithTorControlAddr(":0"),   // Auto-select free port
)
torProc, err := tornago.StartTorDaemon(cfg)
if err != nil {
    log.Fatalf("failed to start tor: %v", err)
}
defer torProc.Stop()

// Use torProc.SocksAddr() and torProc.ControlAddr() to connect
clientCfg, _ := tornago.NewClientConfig(
    tornago.WithClientSocksAddr(torProc.SocksAddr()),
)
client, _ := tornago.NewClient(clientCfg)
defer client.Close()

func (TorProcess) ControlAddr

func (p TorProcess) ControlAddr() string

ControlAddr returns the resolved ControlPort address of the launched tor daemon.

func (TorProcess) DataDir

func (p TorProcess) DataDir() string

DataDir returns the Tor data directory path used by this process.

func (TorProcess) PID

func (p TorProcess) PID() int

PID returns the process identifier of the launched tor daemon.

func (TorProcess) SocksAddr

func (p TorProcess) SocksAddr() string

SocksAddr returns the resolved SocksPort address of the launched tor daemon.

func (*TorProcess) Stop

func (p *TorProcess) Stop() error

Stop terminates the tor process and cleans up temporary resources.

type TornagoError

type TornagoError struct {
	// Kind classifies the error for programmatic handling.
	Kind ErrorKind
	// Op names the operation during which the error occurred.
	Op string
	// Msg carries an optional human-readable description.
	Msg string
	// Err stores the wrapped underlying error.
	Err error
}

TornagoError wraps an underlying error with a Kind and an optional operation label so callers can branch on error type while retaining context.

func (*TornagoError) Error

func (e *TornagoError) Error() string

Error returns a formatted string that includes Kind, Op, and the wrapped error.

func (*TornagoError) Is

func (e *TornagoError) Is(target error) bool

Is reports whether target has the same ErrorKind, enabling errors.Is checks.

func (*TornagoError) Unwrap

func (e *TornagoError) Unwrap() error

Unwrap exposes the underlying error for errors.Is / errors.As compatibility.

type TrackerStats added in v0.4.0

type TrackerStats struct {
	// TrackedRelays is the number of relays being tracked.
	TrackedRelays int
	// BlockedRelays is the number of currently blocked relays.
	BlockedRelays int
	// Threshold is the current threshold configuration.
	Threshold RelayThreshold
}

TrackerStats provides statistics about the tracker.

Directories

Path Synopsis
examples
circuit_management command
Package main demonstrates advanced circuit management features including automatic circuit rotation, manual rotation, and circuit prewarming.
Package main demonstrates advanced circuit management features including automatic circuit rotation, manual rotation, and circuit prewarming.
circuit_rotation command
Package main demonstrates Tor circuit rotation using NEWNYM signal.
Package main demonstrates Tor circuit rotation using NEWNYM signal.
error_handling command
Package main demonstrates proper error handling with tornago.
Package main demonstrates proper error handling with tornago.
existing_tor command
Package main demonstrates connecting to an existing Tor daemon.
Package main demonstrates connecting to an existing Tor daemon.
metrics_ratelimit command
Package main demonstrates metrics collection and rate limiting.
Package main demonstrates metrics collection and rate limiting.
observability command
Package main demonstrates observability features: logging, metrics, and health checks.
Package main demonstrates observability features: logging, metrics, and health checks.
onion_client command
Package main provides an example client that connects to an onion service through Tor.
Package main provides an example client that connects to an onion service through Tor.
onion_server command
Package main provides an example HTTP server accessible via Tor hidden service.
Package main provides an example HTTP server accessible via Tor hidden service.
persistent_onion command
Package main demonstrates creating a Hidden Service with a persistent private key.
Package main demonstrates creating a Hidden Service with a persistent private key.
security command
Package main demonstrates security features: Tor connection verification, DNS leak detection, and Hidden Service client authentication.
Package main demonstrates security features: Tor connection verification, DNS leak detection, and Hidden Service client authentication.
simple_client command
Package main provides a simple example of making HTTP requests through Tor.
Package main provides a simple example of making HTTP requests through Tor.
slow_relay_avoidance command
Package main demonstrates the slow relay avoidance feature.
Package main demonstrates the slow relay avoidance feature.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL