{"id":46116987,"url":"https://github.com/nao1215/tornago","last_synced_at":"2026-03-01T23:33:56.833Z","repository":{"id":325474325,"uuid":"1099019806","full_name":"nao1215/tornago","owner":"nao1215","description":"simple tor client \u0026 server library in golang","archived":false,"fork":false,"pushed_at":"2026-02-10T14:01:31.000Z","size":7180,"stargazers_count":4,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-02-10T21:07:50.187Z","etag":null,"topics":["client","cross-platform","go","golang","hidden-services","library","onion","server","tor"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/nao1215.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null},"funding":{"github":"nao1215"}},"created_at":"2025-11-18T12:59:02.000Z","updated_at":"2026-02-10T14:01:28.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/nao1215/tornago","commit_stats":null,"previous_names":["nao1215/tornago"],"tags_count":4,"template":false,"template_full_name":null,"purl":"pkg:github/nao1215/tornago","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nao1215%2Ftornago","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nao1215%2Ftornago/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nao1215%2Ftornago/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nao1215%2Ftornago/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/nao1215","download_url":"https://codeload.github.com/nao1215/tornago/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nao1215%2Ftornago/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29987698,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-03-01T22:42:38.399Z","status":"ssl_error","status_checked_at":"2026-03-01T22:41:51.863Z","response_time":124,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["client","cross-platform","go","golang","hidden-services","library","onion","server","tor"],"created_at":"2026-03-01T23:33:56.261Z","updated_at":"2026-03-01T23:33:56.828Z","avatar_url":"https://github.com/nao1215.png","language":"Go","funding_links":["https://github.com/sponsors/nao1215"],"categories":[],"sub_categories":[],"readme":"[![Go Reference](https://pkg.go.dev/badge/github.com/nao1215/tornago.svg)](https://pkg.go.dev/github.com/nao1215/tornago)\n[![Go Report Card](https://goreportcard.com/badge/github.com/nao1215/tornago)](https://goreportcard.com/report/github.com/nao1215/tornago)\n![Coverage](https://raw.githubusercontent.com/nao1215/octocovs-central-repo/main/badges/nao1215/tornago/coverage.svg)\n\n[日本語](./doc/ja/README.md) | [Español](./doc/es/README.md) | [Français](./doc/fr/README.md) | [한국어](./doc/ko/README.md) | [Русский](./doc/ru/README.md) | [中文](./doc/zh-cn/README.md)\n\n# tornago\n\n\u003cimg src=\"./doc/images/tornago-logo-small.png\" alt=\"tornago-logo\" width=\"500\"/\u003e\n\n\nTornago is a lightweight wrapper around the [Tor](https://www.torproject.org/) command-line tool, providing three core functionalities:\n\n- **Tor Daemon Management**: Launch and manage Tor processes programmatically\n- **Tor Client**: Route HTTP/TCP traffic through Tor's SOCKS5 proxy with automatic retries\n- **Tor Server**: Create and manage Hidden Services (onion services) via Tor's ControlPort\n\nThe 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.\n\n## Why tornago?\n\nI 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.\n\nTo 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.\n\n\u003e [!IMPORTANT]\n\u003e **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.\n\n\n## Features\n\n- Zero external Go dependencies. Built on standard library only.\n- `net.Listener`, `net.Addr`, `net.Dialer` compatible interfaces for easy integration.\n- Functional options pattern for configuration.\n- Structured errors with `errors.Is`/`errors.As` support.\n- Automatic retry with exponential backoff.\n- Optional metrics collection and rate limiting.\n- Only requires Tor binary as external dependency.\n\n## How Tor Works\n\nTor (The Onion Router) provides anonymity by routing traffic through multiple encrypted layers. Understanding this mechanism helps you use tornago effectively.\n\n### Onion Routing: Multi-Layer Encryption\n\n```mermaid\nsequenceDiagram\n    participant Client as Your Application\u003cbr/\u003e(tornago)\n    participant Guard as Entry Node\u003cbr/\u003e(Guard)\n    participant Middle as Middle Node\n    participant Exit as Exit Node\n    participant Target as Target Server\u003cbr/\u003e(example.com)\n\n    Note over Client: 1. Build Circuit\n    Client-\u003e\u003eGuard: Encrypted with Guard's key\u003cbr/\u003e[Middle info + Exit info + Request]\n    Note over Guard: Decrypt 1st layer\u003cbr/\u003eSee: Middle node address\n    Guard-\u003e\u003eMiddle: Encrypted with Middle's key\u003cbr/\u003e[Exit info + Request]\n    Note over Middle: Decrypt 2nd layer\u003cbr/\u003eSee: Exit node address\n    Middle-\u003e\u003eExit: Encrypted with Exit's key\u003cbr/\u003e[Request]\n    Note over Exit: Decrypt 3rd layer\u003cbr/\u003eSee: Target address\n\n    Note over Client,Target: 2. Send Request\n    Client-\u003e\u003eGuard: Encrypted data (3 layers)\n    Guard-\u003e\u003eMiddle: Encrypted data (2 layers)\n    Middle-\u003e\u003eExit: Encrypted data (1 layer)\n    Exit-\u003e\u003eTarget: Plain HTTP/HTTPS request\n\n    Note over Client,Target: 3. Receive Response\n    Target-\u003e\u003eExit: Plain HTTP/HTTPS response\n    Exit-\u003e\u003eMiddle: Encrypted response (1 layer)\n    Middle-\u003e\u003eGuard: Encrypted response (2 layers)\n    Guard-\u003e\u003eClient: Encrypted response (3 layers)\n    Note over Client: Decrypt all layers\u003cbr/\u003eSee final response\n```\n\n### Key Security Properties\n\n**Layered Encryption (Onion Layers)**\n- Each relay only knows its immediate predecessor and successor\n- Entry node (Guard) knows your IP but not your destination\n- Exit node knows your destination but not your IP\n- Middle node knows neither your IP nor destination\n\n**Privacy Guarantees**\n- Your ISP sees: You connect to a Tor entry node (but not what you're accessing)\n- Entry node sees: Your IP address (but not your destination)\n- Middle node sees: Only relay traffic (no source or destination)\n- Exit node sees: Your destination (but not your real IP)\n- Target server sees: Exit node's IP (not your real IP)\n\n**Limitations to Understand**\n- Exit node can see unencrypted traffic (use HTTPS for end-to-end encryption)\n- Exit node operators could monitor traffic (but can't trace back to you)\n- Timing analysis might correlate traffic patterns (Tor provides anonymity, not perfect unlinkability)\n- Slower than direct connection (3-hop routing adds latency)\n\n### Tornago's Role\n\nTornago simplifies Tor integration by handling:\n\n1. **SOCKS5 Proxy Communication**: Automatically routes your HTTP/TCP traffic through Tor's SOCKS5 proxy\n2. **Circuit Management**: Uses ControlPort to rotate circuits (get new exit nodes)\n3. **Hidden Service Creation**: Manages .onion addresses via ADD_ONION/DEL_ONION commands\n\n```mermaid\ngraph LR\n    A[Your Go App] --\u003e|tornago| B[Tor Daemon]\n    B --\u003e|SOCKS5 Proxy| C[Tor Network]\n    C --\u003e D[Target Server]\n\n    A --\u003e|ControlPort| B\n    B -.-\u003e|Circuit Control| C\n```\n\n## Requirements\n\n### Go\n\n- **Go Version**: 1.25 or later\n\n### Operating Systems (Tested in GitHub Actions)\n\n- Linux\n- macOS\n- Windows\n- FreeBSD\n- OpenBSD\n- NetBSD\n- DragonFly BSD\n\n### Tor\n\nTornago 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.\n\n**Installation:**\n\n```bash\n# Ubuntu/Debian\nsudo apt update\nsudo apt install tor\n\n# Fedora/RHEL\nsudo dnf install tor\n\n# Arch Linux\nsudo pacman -S tor\n\n# macOS (Homebrew)\nbrew install tor\n```\n\nAfter installation, verify Tor is available:\n\n```bash\ntor --version\n```\n\n**Tor Protocol Version**: Tornago uses the Tor ControlPort protocol and supports SOCKS5 proxy (version 5). It is compatible with Tor protocol versions that support:\n- ControlPort commands: AUTHENTICATE, GETINFO, SIGNAL NEWNYM, ADD_ONION, DEL_ONION\n- Cookie and password authentication methods\n- ED25519-V3 onion addresses\n\n## Quick Start\n\n### Access website using tornago\n\nThis example demonstrates how to start a Tor daemon and fetch a website through Tor (`examples/simple_client/main.go`):\n\n```go\npackage main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"log\"\n\t\"net/http\"\n\t\"time\"\n\n\t\"github.com/nao1215/tornago\"\n)\n\nfunc main() {\n\t// Step 1: Launch Tor daemon\n\tfmt.Println(\"Starting Tor daemon...\")\n\tlaunchCfg, err := tornago.NewTorLaunchConfig(\n\t\ttornago.WithTorSocksAddr(\":0\"),     // Use random available port\n\t\ttornago.WithTorControlAddr(\":0\"),   // Use random available port\n\t\ttornago.WithTorStartupTimeout(60*time.Second),\n\t)\n\tif err != nil {\n\t\tlog.Fatalf(\"Failed to create launch config: %v\", err)\n\t}\n\n\ttorProcess, err := tornago.StartTorDaemon(launchCfg)\n\tif err != nil {\n\t\tlog.Fatalf(\"Failed to start Tor daemon: %v\", err)\n\t}\n\tdefer torProcess.Stop()\n\n\tfmt.Printf(\"Tor daemon started successfully!\\n\")\n\tfmt.Printf(\"  SOCKS address: %s\\n\", torProcess.SocksAddr())\n\tfmt.Printf(\"  Control address: %s\\n\", torProcess.ControlAddr())\n\n\t// Step 2: Create Tor client\n\tclientCfg, err := tornago.NewClientConfig(\n\t\ttornago.WithClientSocksAddr(torProcess.SocksAddr()),\n\t\ttornago.WithClientRequestTimeout(60*time.Second),\n\t)\n\tif err != nil {\n\t\tlog.Fatalf(\"Failed to create client config: %v\", err)\n\t}\n\n\tclient, err := tornago.NewClient(clientCfg)\n\tif err != nil {\n\t\tlog.Fatalf(\"Failed to create client: %v\", err)\n\t}\n\tdefer client.Close()\n\n\t// Step 3: Make HTTP request through Tor\n\tfmt.Println(\"\\nFetching https://example.com through Tor...\")\n\treq, err := http.NewRequestWithContext(context.Background(), http.MethodGet, \"https://example.com\", http.NoBody)\n\tif err != nil {\n\t\tlog.Fatalf(\"Failed to create request: %v\", err)\n\t}\n\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\tlog.Fatalf(\"Request failed: %v\", err)\n\t}\n\tdefer resp.Body.Close()\n\n\tfmt.Printf(\"Status: %s\\n\", resp.Status)\n\n\tbody, err := io.ReadAll(io.LimitReader(resp.Body, 500))\n\tif err != nil {\n\t\tlog.Fatalf(\"Failed to read response: %v\", err)\n\t}\n\n\tfmt.Printf(\"\\nResponse preview (first 500 bytes):\\n%s\\n\", string(body))\n}\n```\n\n**Output:**\n```\nStarting Tor daemon...\nTor daemon started successfully!\n  SOCKS address: 127.0.0.1:42715\n  Control address: 127.0.0.1:35199\n\nFetching https://example.com through Tor...\nStatus: 200 OK\n\nResponse preview (first 500 bytes):\n\u003c!doctype html\u003e\u003chtml lang=\"en\"\u003e\u003chead\u003e\u003ctitle\u003eExample Domain\u003c/title\u003e...\n```\n\n### Access .onion using tornago\n\nThis example demonstrates how to access a .onion site (DuckDuckGo's onion service) through Tor (`examples/onion_client/main.go`):\n\n```go\npackage main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"log\"\n\t\"net/http\"\n\t\"time\"\n\n\t\"github.com/nao1215/tornago\"\n)\n\nfunc main() {\n\t// Step 1: Launch Tor daemon\n\tfmt.Println(\"Starting Tor daemon...\")\n\tlaunchCfg, err := tornago.NewTorLaunchConfig(\n\t\ttornago.WithTorSocksAddr(\":0\"),   // Use random available port\n\t\ttornago.WithTorControlAddr(\":0\"), // Use random available port\n\t\ttornago.WithTorStartupTimeout(60*time.Second),\n\t)\n\tif err != nil {\n\t\tlog.Fatalf(\"Failed to create launch config: %v\", err)\n\t}\n\n\ttorProcess, err := tornago.StartTorDaemon(launchCfg)\n\tif err != nil {\n\t\tlog.Fatalf(\"Failed to start Tor daemon: %v\", err)\n\t}\n\tdefer torProcess.Stop()\n\n\tfmt.Printf(\"Tor daemon started successfully!\\n\")\n\tfmt.Printf(\"  SOCKS address: %s\\n\", torProcess.SocksAddr())\n\tfmt.Printf(\"  Control address: %s\\n\", torProcess.ControlAddr())\n\n\t// Step 2: Create Tor client\n\tclientCfg, err := tornago.NewClientConfig(\n\t\ttornago.WithClientSocksAddr(torProcess.SocksAddr()),\n\t\ttornago.WithClientRequestTimeout(60*time.Second),\n\t)\n\tif err != nil {\n\t\tlog.Fatalf(\"Failed to create client config: %v\", err)\n\t}\n\n\tclient, err := tornago.NewClient(clientCfg)\n\tif err != nil {\n\t\tlog.Fatalf(\"Failed to create client: %v\", err)\n\t}\n\tdefer client.Close()\n\n\t// Step 3: Access .onion site (DuckDuckGo)\n\tonionURL := \"https://duckduckgogg42xjoc72x3sjasowoarfbgcmvfimaftt6twagswzczad.onion/\"\n\tfmt.Printf(\"\\nAccessing DuckDuckGo onion service: %s\\n\", onionURL)\n\n\treq, err := http.NewRequestWithContext(context.Background(), http.MethodGet, onionURL, http.NoBody)\n\tif err != nil {\n\t\tlog.Fatalf(\"Failed to create request: %v\", err)\n\t}\n\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\tlog.Fatalf(\"Request failed: %v\", err)\n\t}\n\tdefer resp.Body.Close()\n\n\tfmt.Printf(\"Status: %s\\n\", resp.Status)\n\n\tbody, err := io.ReadAll(io.LimitReader(resp.Body, 500))\n\tif err != nil {\n\t\tlog.Fatalf(\"Failed to read response: %v\", err)\n\t}\n\n\tfmt.Printf(\"\\nResponse preview (first 500 bytes):\\n%s\\n\", string(body))\n}\n```\n\n**Output:**\n```\nStarting Tor daemon...\nTor daemon started successfully!\n  SOCKS address: 127.0.0.1:42369\n  Control address: 127.0.0.1:46475\n\nAccessing DuckDuckGo onion service: https://duckduckgogg42xjoc72x3sjasowoarfbgcmvfimaftt6twagswzczad.onion/\nStatus: 200 OK\n\nResponse preview (first 500 bytes):\n\u003c!DOCTYPE html\u003e\u003chtml lang=\"en-US\" class=\"\"\u003e\u003chead\u003e\u003cmeta charSet=\"utf-8\"...\n```\n\n### Host .onion using tornago\n\nThis example demonstrates how to create a Hidden Service (.onion) and serve a webpage through Tor (`examples/onion_server/main.go`):\n\n```go\npackage main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"log\"\n\t\"net\"\n\t\"net/http\"\n\t\"os\"\n\t\"os/signal\"\n\t\"syscall\"\n\t\"time\"\n\n\t\"github.com/nao1215/tornago\"\n)\n\nfunc main() {\n\t// Step 1: Launch Tor daemon\n\tfmt.Println(\"Starting Tor daemon...\")\n\tlaunchCfg, err := tornago.NewTorLaunchConfig(\n\t\ttornago.WithTorSocksAddr(\":0\"),   // Use random available port\n\t\ttornago.WithTorControlAddr(\":0\"), // Use random available port\n\t\ttornago.WithTorStartupTimeout(60*time.Second),\n\t)\n\tif err != nil {\n\t\tlog.Fatalf(\"Failed to create launch config: %v\", err)\n\t}\n\n\ttorProcess, err := tornago.StartTorDaemon(launchCfg)\n\tif err != nil {\n\t\tlog.Fatalf(\"Failed to start Tor daemon: %v\", err)\n\t}\n\tdefer torProcess.Stop()\n\n\tfmt.Printf(\"Tor daemon started successfully!\\n\")\n\tfmt.Printf(\"  SOCKS address: %s\\n\", torProcess.SocksAddr())\n\tfmt.Printf(\"  Control address: %s\\n\", torProcess.ControlAddr())\n\n\t// Step 2: Start local HTTP server\n\tlocalAddr := \"127.0.0.1:8080\"\n\tmux := http.NewServeMux()\n\tmux.HandleFunc(\"/\", func(w http.ResponseWriter, r *http.Request) {\n\t\thtml := `\u003c!DOCTYPE html\u003e\n\u003chtml\u003e\n\u003chead\u003e\n    \u003ctitle\u003eTornago Hidden Service\u003c/title\u003e\n    \u003cstyle\u003e\n        body {\n            font-family: Arial, sans-serif;\n            max-width: 800px;\n            margin: 50px auto;\n            padding: 20px;\n            background-color: #f5f5f5;\n        }\n        .container {\n            background-color: white;\n            padding: 30px;\n            border-radius: 10px;\n            box-shadow: 0 2px 10px rgba(0,0,0,0.1);\n        }\n        h1 {\n            color: #7d4698;\n        }\n        .info {\n            background-color: #f0e6f6;\n            padding: 15px;\n            border-radius: 5px;\n            margin: 20px 0;\n        }\n        code {\n            background-color: #e0e0e0;\n            padding: 2px 6px;\n            border-radius: 3px;\n            font-family: monospace;\n        }\n    \u003c/style\u003e\n\u003c/head\u003e\n\u003cbody\u003e\n    \u003cdiv class=\"container\"\u003e\n        \u003ch1\u003e🧅 Welcome to Tornago Hidden Service!\u003c/h1\u003e\n        \u003cp\u003eThis is a simple web page hosted as a Tor Hidden Service (.onion) using the \u003cstrong\u003etornago\u003c/strong\u003e library.\u003c/p\u003e\n\n        \u003cdiv class=\"info\"\u003e\n            \u003ch3\u003eConnection Info:\u003c/h3\u003e\n            \u003cp\u003e\u003cstrong\u003eYour IP:\u003c/strong\u003e \u003ccode\u003e` + r.RemoteAddr + `\u003c/code\u003e\u003c/p\u003e\n            \u003cp\u003e\u003cstrong\u003eRequest Path:\u003c/strong\u003e \u003ccode\u003e` + r.URL.Path + `\u003c/code\u003e\u003c/p\u003e\n            \u003cp\u003e\u003cstrong\u003eUser Agent:\u003c/strong\u003e \u003ccode\u003e` + r.UserAgent() + `\u003c/code\u003e\u003c/p\u003e\n        \u003c/div\u003e\n\n        \u003ch3\u003eAbout Tornago:\u003c/h3\u003e\n        \u003cp\u003eTornago is a lightweight Go wrapper around the Tor command-line tool, providing:\u003c/p\u003e\n        \u003cul\u003e\n            \u003cli\u003eTor Daemon Management\u003c/li\u003e\n            \u003cli\u003eTor Client (SOCKS5 proxy)\u003c/li\u003e\n            \u003cli\u003eTor Server (Hidden Services)\u003c/li\u003e\n        \u003c/ul\u003e\n\n        \u003cp style=\"margin-top: 30px; text-align: center; color: #666;\"\u003e\n            Powered by \u003cstrong\u003etornago\u003c/strong\u003e 🚀\n        \u003c/p\u003e\n    \u003c/div\u003e\n\u003c/body\u003e\n\u003c/html\u003e`\n\t\tw.Header().Set(\"Content-Type\", \"text/html; charset=utf-8\")\n\t\tfmt.Fprint(w, html)\n\t})\n\n\tserver := \u0026http.Server{\n\t\tAddr:              localAddr,\n\t\tHandler:           mux,\n\t\tReadHeaderTimeout: 5 * time.Second,\n\t}\n\n\tlc := net.ListenConfig{}\n\tlistener, err := lc.Listen(context.Background(), \"tcp\", localAddr)\n\tif err != nil {\n\t\tlog.Fatalf(\"Failed to start HTTP server: %v\", err)\n\t}\n\n\tgo func() {\n\t\tif err := server.Serve(listener); err != nil \u0026\u0026 err != http.ErrServerClosed {\n\t\t\tlog.Fatalf(\"HTTP server error: %v\", err)\n\t\t}\n\t}()\n\n\tfmt.Printf(\"\\nLocal HTTP server started on http://%s\\n\", localAddr)\n\n\t// Step 3: Get control authentication and create ControlClient directly\n\tfmt.Println(\"\\nObtaining Tor control authentication...\")\n\tauth, _, err := tornago.ControlAuthFromTor(torProcess.ControlAddr(), 30*time.Second)\n\tif err != nil {\n\t\tlog.Fatalf(\"Failed to get control auth: %v\", err)\n\t}\n\n\t// Step 4: Create ControlClient directly (instead of via tornago.Client)\n\tcontrolClient, err := tornago.NewControlClient(\n\t\ttorProcess.ControlAddr(),\n\t\tauth,\n\t\t30*time.Second,\n\t)\n\tif err != nil {\n\t\tlog.Fatalf(\"Failed to create control client: %v\", err)\n\t}\n\tdefer controlClient.Close()\n\n\tif err := controlClient.Authenticate(); err != nil {\n\t\tlog.Fatalf(\"Failed to authenticate with Tor: %v\", err)\n\t}\n\n\t// Step 5: Create Hidden Service\n\thsCfg, err := tornago.NewHiddenServiceConfig(\n\t\ttornago.WithHiddenServicePort(80, 8080), // Map onion port 80 to local port 8080\n\t)\n\tif err != nil {\n\t\tlog.Fatalf(\"Failed to create hidden service config: %v\", err)\n\t}\n\n\tfmt.Println(\"\\nCreating Hidden Service...\")\n\ths, err := controlClient.CreateHiddenService(context.Background(), hsCfg)\n\tif err != nil {\n\t\tlog.Fatalf(\"Failed to create hidden service: %v\", err)\n\t}\n\tdefer func() {\n\t\tif err := hs.Remove(context.Background()); err != nil {\n\t\t\tlog.Printf(\"Failed to delete hidden service: %v\", err)\n\t\t}\n\t}()\n\n\tfmt.Printf(\"\\n✅ Hidden Service created successfully!\\n\")\n\tfmt.Printf(\"   Onion Address: http://%s\\n\", hs.OnionAddress())\n\tfmt.Printf(\"   Local Address: http://%s\\n\", localAddr)\n\tfmt.Println(\"\\nYou can access this hidden service through Tor using the onion address above.\")\n\tfmt.Println(\"Press Ctrl+C to stop the server...\")\n\n\t// Wait for interrupt signal\n\tsigChan := make(chan os.Signal, 1)\n\tsignal.Notify(sigChan, os.Interrupt, syscall.SIGTERM)\n\t\u003c-sigChan\n\n\tfmt.Println(\"\\n\\nShutting down...\")\n\tctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)\n\tdefer cancel()\n\tif err := server.Shutdown(ctx); err != nil {\n\t\tlog.Printf(\"Server shutdown error: %v\", err)\n\t}\n}\n```\n\n**Output:**\n```\nStarting Tor daemon...\nTor daemon started successfully!\n  SOCKS address: 127.0.0.1:36065\n  Control address: 127.0.0.1:37285\n\nLocal HTTP server started on http://127.0.0.1:8080\n\nObtaining Tor control authentication...\n\nCreating Hidden Service...\n\n✅ Hidden Service created successfully!\n   Onion Address: http://f64ekih3d23wxhdb547wfj7nornjw5nb3ehuu4do45tw2wwmuzhad3yd.onion\n   Local Address: http://127.0.0.1:8080\n\nAccess the hidden service through Tor using the onion address above.\nPress Ctrl+C to stop...\n```\n\nYou can now access your hidden service through Tor Browser or any Tor client using the generated .onion address!\n\n\u003cimg src=\"./doc/images/tornago-server-example.png\" alt=\"onion site\" width=\"500\"/\u003e\n\n## Slow Relay Avoidance\n\nTornago 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.\n\n### Basic Usage (Recommended)\n\n```go\n// Create client with slow relay avoidance enabled\nclient, err := tornago.NewClient(\n    tornago.WithClientSocksAddr(torProcess.SocksAddr()),\n    tornago.WithClientControlAddr(torProcess.ControlAddr()),\n    tornago.WithSlowRelayAvoidance(),  // Enable with defaults\n)\nif err != nil {\n    log.Fatal(err)\n}\ndefer client.Close()\n\n// Make requests normally - everything is handled automatically\nresp, err := client.Do(req)\n\n// Optionally check performance statistics\nstats, ok := client.RelayPerformanceStats()\nif ok {\n    fmt.Printf(\"Tracked: %d, Blocked: %d\\n\", stats.TrackedRelays(), stats.BlockedRelays())\n}\n```\n\n### Custom Threshold Configuration\n\n```go\n// Enable with custom settings for stricter requirements\nclient, err := tornago.NewClient(\n    tornago.WithClientSocksAddr(torProcess.SocksAddr()),\n    tornago.WithClientControlAddr(torProcess.ControlAddr()),\n    tornago.WithSlowRelayAvoidance(\n        tornago.SlowRelayMaxLatency(3*time.Second),   // Block relays slower than 3s\n        tornago.SlowRelayMinSuccessRate(0.9),         // Require 90% success rate\n        tornago.SlowRelayBlockDuration(1*time.Hour),  // Block for 1 hour\n        tornago.SlowRelayMinSamples(5),               // Need 5 samples before judging\n        tornago.SlowRelayMonitorInterval(15*time.Second), // Check every 15s\n    ),\n)\n```\n\n### How It Works\n\n```mermaid\nsequenceDiagram\n    participant App as Your Application\n    participant Client as Tornago Client\n    participant Tor as Tor Daemon\n    participant Network as Tor Network\n\n    Note over App,Network: Phase 1: Normal Operation with Automatic Measurement\n\n    App-\u003e\u003eClient: client.Do(req)\n    Client-\u003e\u003eTor: HTTP Request\n    Tor-\u003e\u003eNetwork: Route through Circuit\u003cbr/\u003e(Guard → Middle → Exit)\n    Network--\u003e\u003eTor: Response\n    Tor--\u003e\u003eClient: Response (latency: 2s)\n    Client-\u003e\u003eClient: Auto-record measurement\u003cbr/\u003efor all relays in circuit\n    Client--\u003e\u003eApp: Response\n\n    Note over App,Network: Phase 2: Slow Relay Detected\n\n    App-\u003e\u003eClient: client.Do(req)\n    Client-\u003e\u003eTor: HTTP Request\n    Tor-\u003e\u003eNetwork: Route through Circuit\n    Network--\u003e\u003eTor: Response (slow)\n    Tor--\u003e\u003eClient: Response (latency: 8s)\n    Client-\u003e\u003eClient: Auto-record measurement\u003cbr/\u003eExit: avg=5s exceeds threshold!\u003cbr/\u003e→ Block slow relay\n\n    alt Auto-Exclude Enabled (default)\n        Client-\u003e\u003eTor: SETCONF ExcludeNodes=$fingerprint\n        Note over Tor: Tor will avoid\u003cbr/\u003ethis relay\n    end\n\n    Client--\u003e\u003eApp: Response\n\n    Note over App,Network: Phase 3: Background Monitor Rotation\n\n    Client-\u003e\u003eTor: GETINFO circuit-status\n    Tor--\u003e\u003eClient: Circuit paths\n    Client-\u003e\u003eClient: Check if circuit uses blocked relay\n    Client-\u003e\u003eTor: SIGNAL NEWNYM\n    Note over Tor: Build new circuit\u003cbr/\u003ewithout slow relay\n\n    Note over App,Network: Phase 4: Improved Performance\n\n    App-\u003e\u003eClient: client.Do(req)\n    Client-\u003e\u003eTor: HTTP Request\n    Tor-\u003e\u003eNetwork: Route through new circuit\n    Network--\u003e\u003eTor: Response (fast)\n    Tor--\u003e\u003eClient: Response (latency: 1.5s)\n    Client--\u003e\u003eApp: Response (OK)\n```\n\n### Default Threshold Values\n\n| Parameter | Default | Description |\n|-----------|---------|-------------|\n| MaxLatency | 5 seconds | Relays slower than this are considered \"slow\" |\n| MinSuccessRate | 80% | Relays with lower success rate are blocked |\n| BlockDuration | 30 minutes | How long slow relays remain blocked |\n| MinSamples | 3 | Minimum measurements needed before evaluation |\n| MonitorInterval | 30 seconds | Background check interval for circuit rotation |\n| AutoExclude | true | Automatically update Tor's ExcludeNodes |\n\nSee [`examples/slow_relay_avoidance`](examples/slow_relay_avoidance/main.go) for a complete working example.\n\n## More Examples\n\nThe `examples/` directory contains additional working examples:\n\n- [`simple_client`](examples/simple_client/main.go) - Basic HTTP requests through Tor\n- [`onion_client`](examples/onion_client/main.go) - Accessing .onion sites\n- [`onion_server`](examples/onion_server/main.go) - Creating a Hidden Service\n- [`existing_tor`](examples/existing_tor/main.go) - Connecting to system Tor daemon\n- [`circuit_rotation`](examples/circuit_rotation/main.go) - Rotating circuits to change exit IP\n- [`error_handling`](examples/error_handling/main.go) - Proper error handling patterns\n- [`metrics_ratelimit`](examples/metrics_ratelimit/main.go) - Metrics collection and rate limiting\n- [`persistent_onion`](examples/persistent_onion/main.go) - Hidden Service with persistent key\n- [`observability`](examples/observability/main.go) - Structured logging, metrics, and health checks\n- [`slow_relay_avoidance`](examples/slow_relay_avoidance/main.go) - Automatic slow relay detection and avoidance\n\nAll examples are tested and ready to run.\n\n## Tool using tornago\n\n- [nao1215/onionlint](https://github.com/nao1215/onionlint): Tor site anonymity linter\n- [nao1215/onionscan](https://github.com/nao1215/onionscan)：Investigating tool the Dark Web\n\n## Contributing\nContributions are welcome! Please see the [Contributing Guide](./CONTRIBUTING.md) for more details.\n\n## Support\nIf you find this project useful, please consider:\n\n- Giving it a star on GitHub - it helps others discover the project\n- [Becoming a sponsor](https://github.com/sponsors/nao1215) - your support keeps the project alive and motivates continued development\n\nYour support, whether through stars, sponsorships, or contributions, is what drives this project forward. Thank you!\n\n## License\n\n[MIT License](./LICENSE)\n\n## Altenative Library, Official references\n\n- [cretz/bine](https://github.com/cretz/bine): Go library for accessing and embedding Tor clients and servers.\n- [wybiral/torgo](https://github.com/wybiral/torgo):  Go library for interacting with Tor over the standard controller interface.\n- [torproject/stem](https://github.com/torproject/stem): Python controller library for Tor\n- [Tor Official Wiki](https://gitlab.torproject.org/tpo/team/-/wikis/home)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnao1215%2Ftornago","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fnao1215%2Ftornago","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnao1215%2Ftornago/lists"}