diff --git a/README.md b/README.md index 6a4ad69..3f0d976 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,7 @@ Features: * Support for `net.Conn` and `net.Listen` style APIs * Supports statically compiled Tor to embed Tor into the binary * Supports both V2 and V3 onion services +* Support for embedded control socket in Tor >= 0.3.5 (non-Windows) See info below, the [API docs](http://godoc.org/github.com/cretz/bine), and the [examples](examples). The project is MIT licensed. The Tor docs/specs and https://github.com/yawning/bulb were great helps when building this. diff --git a/doc.go b/doc.go new file mode 100644 index 0000000..a9aeec1 --- /dev/null +++ b/doc.go @@ -0,0 +1,15 @@ +// Bine is a toolkit to assist in creating Tor clients and servers. Features: +// +// * Full support for the Tor controller API +// +// * Support for `net.Conn` and `net.Listen` style APIs +// +// * Supports statically compiled Tor to embed Tor into the binary +// +// * Supports both V2 and V3 onion services +// +// * Support for embedded control socket in Tor >= 0.3.5 (non-Windows) +// +// Users of this library will usually use the high-level tor package. See +// README at https://github.com/cretz/bine for more info. +package bine diff --git a/process/embedded/process.go b/process/embedded/process.go index 27c13bf..99ef8d9 100644 --- a/process/embedded/process.go +++ b/process/embedded/process.go @@ -15,6 +15,8 @@ // // The default in here is currently for Tor 0.3.3.x which uses the tor-0.3.3 // subdirectory. A different subdirectory can be used for a different version. +// Note that the current version doesn't support +// process.Process.EmbeddedControlConn(). package embedded import ( diff --git a/tor/tor.go b/tor/tor.go index d486169..b046db4 100644 --- a/tor/tor.go +++ b/tor/tor.go @@ -30,7 +30,8 @@ type Tor struct { // It is used by Close and should not be called directly. This can be nil. ProcessCancelFunc context.CancelFunc - // ControlPort is the port that Control is connected on. + // ControlPort is the port that Control is connected on. It is 0 if the + // connection is an embedded control connection. ControlPort int // DataDir is the path to the data directory that Tor is using. @@ -60,8 +61,14 @@ type StartConf struct { // ExePath is ignored. ProcessCreator process.Creator + // UseEmbeddedControlConn can be set to true to use + // process.Process.EmbeddedControlConn() instead of creating a connection + // via ControlPort. Note, this only works when ProcessCreator is an + // embedded Tor creator with version >= 0.3.5.x. + UseEmbeddedControlConn bool + // ControlPort is the port to use for the Tor controller. If it is 0, Tor - // picks a port for use. + // picks a port for use. This is ignored if UseEmbeddedControlConn is true. ControlPort int // DataDir is the directory used by Tor. If it is empty, a temporary @@ -194,23 +201,25 @@ func (t *Tor) startProcess(ctx context.Context, conf *StartConf) error { } } args = append(args, "-f", torrcFileName) - // Create file for Tor to write the control port to if it's not told to us + // Create file for Tor to write the control port to if it's not told to us and we're not embedded var controlPortFileName string var err error - if conf.ControlPort == 0 { - controlPortFile, err := ioutil.TempFile(t.DataDir, "control-port-") - if err != nil { - return err + if !conf.UseEmbeddedControlConn { + if conf.ControlPort == 0 { + controlPortFile, err := ioutil.TempFile(t.DataDir, "control-port-") + if err != nil { + return err + } + controlPortFileName = controlPortFile.Name() + if err = controlPortFile.Close(); err != nil { + return err + } + args = append(args, "--ControlPort", "auto", "--ControlPortWriteToFile", controlPortFile.Name()) + } else { + args = append(args, "--ControlPort", strconv.Itoa(conf.ControlPort)) } - controlPortFileName = controlPortFile.Name() - if err = controlPortFile.Close(); err != nil { - return err - } - args = append(args, "--ControlPort", "auto", "--ControlPortWriteToFile", controlPortFile.Name()) - } else { - args = append(args, "--ControlPort", strconv.Itoa(conf.ControlPort)) } - // Start process with the args + // Create process creator with args var processCtx context.Context processCtx, t.ProcessCancelFunc = context.WithCancel(ctx) args = append(args, conf.ExtraArgs...) @@ -218,39 +227,56 @@ func (t *Tor) startProcess(ctx context.Context, conf *StartConf) error { if err != nil { return err } + // Use the embedded conn if requested + if conf.UseEmbeddedControlConn { + t.Debugf("Using embedded control connection") + conn, err := p.EmbeddedControlConn() + if err != nil { + return fmt.Errorf("Unable to get embedded control conn: %v", err) + } + t.Control = control.NewConn(textproto.NewConn(conn)) + t.Control.DebugWriter = t.DebugWriter + } + // Start process with the args t.Debugf("Starting tor with args %v", args) if err = p.Start(); err != nil { return err } t.Process = p - // Try a few times to read the control port file if we need to - t.ControlPort = conf.ControlPort - if t.ControlPort == 0 { - ControlPortCheck: - for i := 0; i < 10; i++ { - select { - case <-ctx.Done(): - err = ctx.Err() - break ControlPortCheck - default: - // Try to read the controlport file, or wait a bit - var byts []byte - if byts, err = ioutil.ReadFile(controlPortFileName); err != nil { - break ControlPortCheck - } else if t.ControlPort, err = process.ControlPortFromFileContents(string(byts)); err == nil { + // If not embedded, try a few times to read the control port file if we need to + if !conf.UseEmbeddedControlConn { + t.ControlPort = conf.ControlPort + if t.ControlPort == 0 { + ControlPortCheck: + for i := 0; i < 10; i++ { + select { + case <-ctx.Done(): + err = ctx.Err() break ControlPortCheck + default: + // Try to read the controlport file, or wait a bit + var byts []byte + if byts, err = ioutil.ReadFile(controlPortFileName); err != nil { + break ControlPortCheck + } else if t.ControlPort, err = process.ControlPortFromFileContents(string(byts)); err == nil { + break ControlPortCheck + } + time.Sleep(200 * time.Millisecond) } - time.Sleep(200 * time.Millisecond) } - } - if err != nil { - return fmt.Errorf("Unable to read control port file: %v", err) + if err != nil { + return fmt.Errorf("Unable to read control port file: %v", err) + } } } return nil } func (t *Tor) connectController(ctx context.Context, conf *StartConf) error { + // This doesn't apply if already connected (e.g. using embedded conn) + if t.Control != nil { + return nil + } t.Debugf("Connecting to control port %v", t.ControlPort) textConn, err := textproto.Dial("tcp", "127.0.0.1:"+strconv.Itoa(t.ControlPort)) if err != nil {