diff --git a/dialer.go b/dialer.go
new file mode 100644
index 0000000..3b3c0cb
--- /dev/null
+++ b/dialer.go
@@ -0,0 +1,54 @@
+// dialer.go - Tor backed proxy.Dialer.
+//
+// To the extent possible under law, Yawning Angel waived all copyright
+// and related or neighboring rights to bulb, using the creative
+// commons "cc0" public domain dedication. See LICENSE or
+// for full details.
+
+package bulb
+
+import (
+ "strconv"
+ "strings"
+
+ "golang.org/x/net/proxy"
+)
+
+// Dialer returns a proxy.Dialer for the given Tor instance.
+func (c *Conn) Dialer(auth *proxy.Auth) (proxy.Dialer, error) {
+ const (
+ cmdGetInfo = "GETINFO"
+ socksListeners = "net/listeners/socks"
+ unixPrefix = "unix:"
+ )
+
+ // Query for the SOCKS listeners via a GETINFO request.
+ resp, err := c.Request("%s %s", cmdGetInfo, socksListeners)
+ if err != nil {
+ return nil, err
+ }
+
+ if len(resp.Data) != 1 {
+ return nil, newProtocolError("no SOCKS listeners configured")
+ }
+ splitResp := strings.Split(resp.Data[0], " ")
+ if len(splitResp) < 1 {
+ return nil, newProtocolError("no SOCKS listeners configured")
+ }
+
+ // The first listener will have a "net/listeners/socks=" prefix, and all
+ // entries are QuotedStrings.
+ laddrStr := strings.TrimPrefix(splitResp[0], socksListeners+"=")
+ if laddrStr == splitResp[0] {
+ return nil, newProtocolError("failed to parse SOCKS listener")
+ }
+ laddrStr, _ = strconv.Unquote(laddrStr)
+
+ // Construct the proxyDialer.
+ if strings.HasPrefix(laddrStr, unixPrefix) {
+ unixPath := strings.TrimPrefix(laddrStr, unixPrefix)
+ return proxy.SOCKS5("unix", unixPath, auth, proxy.Direct)
+ }
+
+ return proxy.SOCKS5("tcp", laddrStr, auth, proxy.Direct)
+}
diff --git a/examples/dialer/dialer.go b/examples/dialer/dialer.go
new file mode 100644
index 0000000..f08a636
--- /dev/null
+++ b/examples/dialer/dialer.go
@@ -0,0 +1,55 @@
+// Dialer example.
+//
+// To the extent possible under law, Yawning Angel waived all copyright
+// and related or neighboring rights to bulb, using the creative
+// commons "cc0" public domain dedication. See LICENSE or
+// for full details.
+
+package main
+
+import (
+ "io/ioutil"
+ "log"
+ "net/http"
+
+ "github.com/yawning/bulb"
+)
+
+func main() {
+ // Connect to a running tor instance.
+ // * TCP: c, err := bulb.Dial("tcp4", "127.0.0.1:9051")
+ c, err := bulb.Dial("unix", "/var/run/tor/control")
+ if err != nil {
+ log.Fatalf("failed to connect to control port: %v", err)
+ }
+ defer c.Close()
+
+ // See what's really going on under the hood.
+ // Do not enable in production.
+ c.Debug(true)
+
+ // Authenticate with the control port. The password argument
+ // here can be "" if no password is set (CookieAuth, no auth).
+ if err := c.Authenticate("ExamplePassword"); err != nil {
+ log.Fatalf("Authentication failed: %v", err)
+ }
+
+ // Get a proxy.Dialer that will use the given Tor instance for outgoing
+ // connections.
+ dialer, err := c.Dialer(nil)
+ if err != nil {
+ log.Fatalf("Failed to get Dialer: %v", err)
+ }
+
+ // Try using the Dialer for something...
+ orTransport := &http.Transport{Dial: dialer.Dial}
+ orHTTPClient := &http.Client{Transport: orTransport}
+ resp, err := orHTTPClient.Get("https://check.torproject.org/api/ip")
+ if err != nil {
+ log.Fatalf("Failed https GET: %v", err)
+ }
+ defer resp.Body.Close()
+ body, _ := ioutil.ReadAll(resp.Body)
+ log.Printf("HTTPS GET via Tor: %v", resp)
+ log.Printf(" Body: %s\n", body)
+}