Work on tor iface
This commit is contained in:
parent
2c4f45c285
commit
a0a929fbc3
|
@ -9,7 +9,7 @@ import (
|
||||||
"github.com/cretz/bine/util"
|
"github.com/cretz/bine/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
// EventCode represents an asynchronous event code (ref control spec 4.1)
|
// EventCode represents an asynchronous event code (ref control spec 4.1).
|
||||||
type EventCode string
|
type EventCode string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"github.com/cretz/bine/util"
|
"github.com/cretz/bine/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// ProtocolInfo is the protocol info result of Conn.ProtocolInfo.
|
||||||
type ProtocolInfo struct {
|
type ProtocolInfo struct {
|
||||||
AuthMethods []string
|
AuthMethods []string
|
||||||
CookieFile string
|
CookieFile string
|
||||||
|
@ -13,6 +14,7 @@ type ProtocolInfo struct {
|
||||||
RawResponse *Response
|
RawResponse *Response
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// HasAuthMethod checks if ProtocolInfo contains the requested auth method.
|
||||||
func (p *ProtocolInfo) HasAuthMethod(authMethod string) bool {
|
func (p *ProtocolInfo) HasAuthMethod(authMethod string) bool {
|
||||||
for _, m := range p.AuthMethods {
|
for _, m := range p.AuthMethods {
|
||||||
if m == authMethod {
|
if m == authMethod {
|
||||||
|
@ -22,6 +24,7 @@ func (p *ProtocolInfo) HasAuthMethod(authMethod string) bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ProtocolInfo invokes PROTOCOLINFO on first invocation and returns a cached result on all others.
|
||||||
func (c *Conn) ProtocolInfo() (*ProtocolInfo, error) {
|
func (c *Conn) ProtocolInfo() (*ProtocolInfo, error) {
|
||||||
var err error
|
var err error
|
||||||
if c.protocolInfo == nil {
|
if c.protocolInfo == nil {
|
||||||
|
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"strconv"
|
"strconv"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// AttachStream invokes ATTACHSTREAM.
|
||||||
func (c *Conn) AttachStream(streamID string, circuitID string, hopNum int) error {
|
func (c *Conn) AttachStream(streamID string, circuitID string, hopNum int) error {
|
||||||
if circuitID == "" {
|
if circuitID == "" {
|
||||||
circuitID = "0"
|
circuitID = "0"
|
||||||
|
@ -15,6 +16,7 @@ func (c *Conn) AttachStream(streamID string, circuitID string, hopNum int) error
|
||||||
return c.sendRequestIgnoreResponse(cmd)
|
return c.sendRequestIgnoreResponse(cmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RedirectStream invokes REDIRECTSTREAM.
|
||||||
func (c *Conn) RedirectStream(streamID string, address string, port int) error {
|
func (c *Conn) RedirectStream(streamID string, address string, port int) error {
|
||||||
cmd := "REDIRECTSTREAM " + streamID + " " + address
|
cmd := "REDIRECTSTREAM " + streamID + " " + address
|
||||||
if port > 0 {
|
if port > 0 {
|
||||||
|
@ -23,6 +25,7 @@ func (c *Conn) RedirectStream(streamID string, address string, port int) error {
|
||||||
return c.sendRequestIgnoreResponse(cmd)
|
return c.sendRequestIgnoreResponse(cmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CloseStream invokes CLOSESTREAM.
|
||||||
func (c *Conn) CloseStream(streamID string, reason string) error {
|
func (c *Conn) CloseStream(streamID string, reason string) error {
|
||||||
return c.sendRequestIgnoreResponse("CLOSESTREAM %v %v", streamID, reason)
|
return c.sendRequestIgnoreResponse("CLOSESTREAM %v %v", streamID, reason)
|
||||||
}
|
}
|
||||||
|
|
|
@ -68,13 +68,6 @@ func (c *Conn) SendRequest(format string, args ...interface{}) (*Response, error
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Conn) Close() error {
|
func (c *Conn) Close() error {
|
||||||
// We'll close all the chans first
|
|
||||||
c.asyncChansLock.Lock()
|
|
||||||
for _, ch := range c.asyncChans {
|
|
||||||
close(ch)
|
|
||||||
}
|
|
||||||
c.asyncChans = nil
|
|
||||||
c.asyncChansLock.Unlock()
|
|
||||||
// Ignore the response and ignore the error
|
// Ignore the response and ignore the error
|
||||||
c.Quit()
|
c.Quit()
|
||||||
return c.conn.Close()
|
return c.conn.Close()
|
||||||
|
|
|
@ -1,35 +1,42 @@
|
||||||
package controltest
|
package controltest
|
||||||
|
|
||||||
import "testing"
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/cretz/bine/tor"
|
||||||
|
)
|
||||||
|
|
||||||
func TestAuthenticateNull(t *testing.T) {
|
func TestAuthenticateNull(t *testing.T) {
|
||||||
ctx, conn := NewTestContextConnected(t)
|
ctx := NewTestContext(t, &tor.StartConf{DisableCookieAuth: true, DisableEagerAuth: true})
|
||||||
defer ctx.CloseConnected(conn)
|
defer ctx.Close()
|
||||||
// Verify auth methods before auth
|
// Verify auth methods before auth
|
||||||
info, err := conn.ProtocolInfo()
|
info, err := ctx.Control.ProtocolInfo()
|
||||||
ctx.Require.NoError(err)
|
ctx.Require.NoError(err)
|
||||||
ctx.Require.ElementsMatch([]string{"NULL"}, info.AuthMethods)
|
ctx.Require.ElementsMatch([]string{"NULL"}, info.AuthMethods)
|
||||||
ctx.Require.NoError(conn.Authenticate(""))
|
ctx.Require.NoError(ctx.Control.Authenticate(""))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAuthenticateSafeCookie(t *testing.T) {
|
func TestAuthenticateSafeCookie(t *testing.T) {
|
||||||
ctx, conn := NewTestContextConnected(t, "--CookieAuthentication", "1")
|
ctx := NewTestContext(t, &tor.StartConf{DisableEagerAuth: true})
|
||||||
defer ctx.CloseConnected(conn)
|
defer ctx.Close()
|
||||||
// Verify auth methods before auth
|
// Verify auth methods before auth
|
||||||
info, err := conn.ProtocolInfo()
|
info, err := ctx.Control.ProtocolInfo()
|
||||||
ctx.Require.NoError(err)
|
ctx.Require.NoError(err)
|
||||||
ctx.Require.ElementsMatch([]string{"COOKIE", "SAFECOOKIE"}, info.AuthMethods)
|
ctx.Require.ElementsMatch([]string{"COOKIE", "SAFECOOKIE"}, info.AuthMethods)
|
||||||
ctx.Require.NoError(conn.Authenticate(""))
|
ctx.Require.NoError(ctx.Control.Authenticate(""))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAuthenticateHashedPassword(t *testing.T) {
|
func TestAuthenticateHashedPassword(t *testing.T) {
|
||||||
// "testpass" - 16:5417AE717521511A609921392778FFA8518EC089BF2162A199241AEB4A
|
// "testpass" - 16:5417AE717521511A609921392778FFA8518EC089BF2162A199241AEB4A
|
||||||
ctx, conn := NewTestContextConnected(t, "--HashedControlPassword",
|
ctx := NewTestContext(t, &tor.StartConf{
|
||||||
"16:5417AE717521511A609921392778FFA8518EC089BF2162A199241AEB4A")
|
DisableCookieAuth: true,
|
||||||
defer ctx.CloseConnected(conn)
|
DisableEagerAuth: true,
|
||||||
|
ExtraArgs: []string{"--HashedControlPassword", "16:5417AE717521511A609921392778FFA8518EC089BF2162A199241AEB4A"},
|
||||||
|
})
|
||||||
|
defer ctx.Close()
|
||||||
// Verify auth methods before auth
|
// Verify auth methods before auth
|
||||||
info, err := conn.ProtocolInfo()
|
info, err := ctx.Control.ProtocolInfo()
|
||||||
ctx.Require.NoError(err)
|
ctx.Require.NoError(err)
|
||||||
ctx.Require.ElementsMatch([]string{"HASHEDPASSWORD"}, info.AuthMethods)
|
ctx.Require.ElementsMatch([]string{"HASHEDPASSWORD"}, info.AuthMethods)
|
||||||
ctx.Require.NoError(conn.Authenticate("testpass"))
|
ctx.Require.NoError(ctx.Control.Authenticate("testpass"))
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,11 +8,11 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestGetSetAndResetConf(t *testing.T) {
|
func TestGetSetAndResetConf(t *testing.T) {
|
||||||
ctx, conn := NewTestContextAuthenticated(t)
|
ctx := NewTestContext(t, nil)
|
||||||
defer ctx.CloseConnected(conn)
|
defer ctx.Close()
|
||||||
// Simple get conf
|
// Simple get conf
|
||||||
assertConfVals := func(val string) {
|
assertConfVals := func(val string) {
|
||||||
entries, err := conn.GetConf("LogMessageDomains", "ProtocolWarnings")
|
entries, err := ctx.Control.GetConf("LogMessageDomains", "ProtocolWarnings")
|
||||||
ctx.Require.NoError(err)
|
ctx.Require.NoError(err)
|
||||||
ctx.Require.Len(entries, 2)
|
ctx.Require.Len(entries, 2)
|
||||||
ctx.Require.Contains(entries, control.NewKeyVal("LogMessageDomains", val))
|
ctx.Require.Contains(entries, control.NewKeyVal("LogMessageDomains", val))
|
||||||
|
@ -21,22 +21,22 @@ func TestGetSetAndResetConf(t *testing.T) {
|
||||||
assertConfVals("0")
|
assertConfVals("0")
|
||||||
// Change em both to 1
|
// Change em both to 1
|
||||||
one := "1"
|
one := "1"
|
||||||
err := conn.SetConf(control.KeyVals("LogMessageDomains", "1", "ProtocolWarnings", "1")...)
|
err := ctx.Control.SetConf(control.KeyVals("LogMessageDomains", "1", "ProtocolWarnings", "1")...)
|
||||||
ctx.Require.NoError(err)
|
ctx.Require.NoError(err)
|
||||||
// Check again
|
// Check again
|
||||||
assertConfVals(one)
|
assertConfVals(one)
|
||||||
// Reset em both
|
// Reset em both
|
||||||
err = conn.ResetConf(control.KeyVals("LogMessageDomains", "", "ProtocolWarnings", "")...)
|
err = ctx.Control.ResetConf(control.KeyVals("LogMessageDomains", "", "ProtocolWarnings", "")...)
|
||||||
ctx.Require.NoError(err)
|
ctx.Require.NoError(err)
|
||||||
// Make sure both back to zero
|
// Make sure both back to zero
|
||||||
assertConfVals("0")
|
assertConfVals("0")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLoadConf(t *testing.T) {
|
func TestLoadConf(t *testing.T) {
|
||||||
ctx, conn := NewTestContextAuthenticated(t)
|
ctx := NewTestContext(t, nil)
|
||||||
defer ctx.CloseConnected(conn)
|
defer ctx.Close()
|
||||||
// Get entire conf text
|
// Get entire conf text
|
||||||
vals, err := conn.GetInfo("config-text")
|
vals, err := ctx.Control.GetInfo("config-text")
|
||||||
ctx.Require.NoError(err)
|
ctx.Require.NoError(err)
|
||||||
ctx.Require.Len(vals, 1)
|
ctx.Require.Len(vals, 1)
|
||||||
ctx.Require.Equal("config-text", vals[0].Key)
|
ctx.Require.Equal("config-text", vals[0].Key)
|
||||||
|
@ -44,9 +44,9 @@ func TestLoadConf(t *testing.T) {
|
||||||
// Append new conf val and load
|
// Append new conf val and load
|
||||||
ctx.Require.NotContains(confText, "LogMessageDomains")
|
ctx.Require.NotContains(confText, "LogMessageDomains")
|
||||||
confText += "\r\nLogMessageDomains 1"
|
confText += "\r\nLogMessageDomains 1"
|
||||||
ctx.Require.NoError(conn.LoadConf(confText))
|
ctx.Require.NoError(ctx.Control.LoadConf(confText))
|
||||||
// Check the new val
|
// Check the new val
|
||||||
vals, err = conn.GetInfo("config-text")
|
vals, err = ctx.Control.GetInfo("config-text")
|
||||||
ctx.Require.NoError(err)
|
ctx.Require.NoError(err)
|
||||||
ctx.Require.Len(vals, 1)
|
ctx.Require.Len(vals, 1)
|
||||||
ctx.Require.Equal("config-text", vals[0].Key)
|
ctx.Require.Equal("config-text", vals[0].Key)
|
||||||
|
@ -54,18 +54,18 @@ func TestLoadConf(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSaveConf(t *testing.T) {
|
func TestSaveConf(t *testing.T) {
|
||||||
ctx, conn := NewTestContextAuthenticated(t)
|
ctx := NewTestContext(t, nil)
|
||||||
defer ctx.CloseConnected(conn)
|
defer ctx.Close()
|
||||||
// Get conf filename
|
// Get conf filename
|
||||||
vals, err := conn.GetInfo("config-file")
|
vals, err := ctx.Control.GetInfo("config-file")
|
||||||
ctx.Require.NoError(err)
|
ctx.Require.NoError(err)
|
||||||
ctx.Require.Len(vals, 1)
|
ctx.Require.Len(vals, 1)
|
||||||
ctx.Require.Equal("config-file", vals[0].Key)
|
ctx.Require.Equal("config-file", vals[0].Key)
|
||||||
confFile := vals[0].Val
|
confFile := vals[0].Val
|
||||||
// Save it
|
// Save it
|
||||||
ctx.Require.NoError(conn.SaveConf(false))
|
ctx.Require.NoError(ctx.Control.SaveConf(false))
|
||||||
// Read and make sure, say, the DataDirectory is accurate
|
// Read and make sure, say, the DataDirectory is accurate
|
||||||
confText, err := ioutil.ReadFile(confFile)
|
confText, err := ioutil.ReadFile(confFile)
|
||||||
ctx.Require.NoError(err)
|
ctx.Require.NoError(err)
|
||||||
ctx.Require.Contains(string(confText), "DataDirectory "+ctx.TestTor.DataDir)
|
ctx.Require.Contains(string(confText), "DataDirectory "+ctx.DataDir)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +1,6 @@
|
||||||
package controltest
|
package controltest
|
||||||
|
|
||||||
import (
|
/*
|
||||||
"context"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/cretz/bine/control"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestEvents(t *testing.T) {
|
func TestEvents(t *testing.T) {
|
||||||
SkipIfNotRunningSpecifically(t)
|
SkipIfNotRunningSpecifically(t)
|
||||||
ctx, conn := NewTestContextAuthenticated(t)
|
ctx, conn := NewTestContextAuthenticated(t)
|
||||||
|
@ -44,3 +37,4 @@ MainLoop:
|
||||||
ctx.Debugf("Event %v seen? %v", event, ok)
|
ctx.Debugf("Event %v seen? %v", event, ok)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
package controltest
|
package controltest
|
||||||
|
|
||||||
import (
|
/*
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestGetHiddenServiceDescriptorAsync(t *testing.T) {
|
func TestGetHiddenServiceDescriptorAsync(t *testing.T) {
|
||||||
ctx, conn := NewTestContextAuthenticated(t)
|
ctx, conn := NewTestContextAuthenticated(t)
|
||||||
defer ctx.CloseConnected(conn)
|
defer ctx.CloseConnected(conn)
|
||||||
t.Skip("TODO")
|
t.Skip("TODO")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
|
@ -3,7 +3,7 @@ package controltest
|
||||||
import "testing"
|
import "testing"
|
||||||
|
|
||||||
func TestSignal(t *testing.T) {
|
func TestSignal(t *testing.T) {
|
||||||
ctx, conn := NewTestContextAuthenticated(t)
|
ctx := NewTestContext(t, nil)
|
||||||
defer ctx.CloseConnected(conn)
|
defer ctx.Close()
|
||||||
ctx.Require.NoError(conn.Signal("HEARTBEAT"))
|
ctx.Require.NoError(ctx.Control.Signal("HEARTBEAT"))
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,10 +6,10 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestProtocolInfo(t *testing.T) {
|
func TestProtocolInfo(t *testing.T) {
|
||||||
ctx, conn := NewTestContextConnected(t)
|
ctx := NewTestContext(t, nil)
|
||||||
defer ctx.CloseConnected(conn)
|
defer ctx.Close()
|
||||||
info, err := conn.ProtocolInfo()
|
info, err := ctx.Control.ProtocolInfo()
|
||||||
ctx.Require.NoError(err)
|
ctx.Require.NoError(err)
|
||||||
ctx.Require.Contains(info.AuthMethods, "NULL")
|
ctx.Require.Contains(info.AuthMethods, "SAFECOOKIE")
|
||||||
ctx.Require.True(strings.HasPrefix(info.TorVersion, "0.3"))
|
ctx.Require.True(strings.HasPrefix(info.TorVersion, "0.3"))
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,99 +3,54 @@ package controltest
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"net/textproto"
|
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/cretz/bine/tor"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
"github.com/cretz/bine/control"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type TestContext struct {
|
type TestContext struct {
|
||||||
context.Context
|
context.Context
|
||||||
*testing.T
|
*testing.T
|
||||||
ExtraTorArgs []string
|
*tor.Tor
|
||||||
Require *require.Assertions
|
Require *require.Assertions
|
||||||
TestTor *TestTor
|
|
||||||
DebugWriter io.Writer
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewTestContext(ctx context.Context, t *testing.T, extraTorArgs ...string) *TestContext {
|
var torExePath string
|
||||||
ret := &TestContext{Context: ctx, T: t, ExtraTorArgs: extraTorArgs, Require: require.New(t)}
|
|
||||||
testVerboseFlag := flag.Lookup("test.v")
|
func init() {
|
||||||
if testVerboseFlag != nil && testVerboseFlag.Value != nil && testVerboseFlag.Value.String() == "true" {
|
flag.StringVar(&torExePath, "tor.path", "tor", "The TOR exe path")
|
||||||
ret.DebugWriter = os.Stdout
|
flag.Parse()
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTestContext(t *testing.T, conf *tor.StartConf) *TestContext {
|
||||||
|
// Build start conf
|
||||||
|
if conf == nil {
|
||||||
|
conf = &tor.StartConf{}
|
||||||
|
}
|
||||||
|
conf.ExePath = torExePath
|
||||||
|
if f := flag.Lookup("test.v"); f != nil && f.Value != nil && f.Value.String() == "true" {
|
||||||
|
conf.DebugWriter = os.Stdout
|
||||||
} else {
|
} else {
|
||||||
ret.ExtraTorArgs = append(append([]string{}, ret.ExtraTorArgs...), "--quiet")
|
conf.ExtraArgs = append(conf.ExtraArgs, "--quiet")
|
||||||
|
}
|
||||||
|
ret := &TestContext{Context: context.Background(), T: t, Require: require.New(t)}
|
||||||
|
// Start tor
|
||||||
|
var err error
|
||||||
|
if ret.Tor, err = tor.Start(ret.Context, conf); err != nil {
|
||||||
|
defer ret.Close()
|
||||||
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewTestContextConnected(t *testing.T, extraTorArgs ...string) (*TestContext, *control.Conn) {
|
|
||||||
ctx := NewTestContext(context.Background(), t, extraTorArgs...)
|
|
||||||
conn, err := ctx.ConnectTestTor()
|
|
||||||
if err != nil {
|
|
||||||
ctx.Close()
|
|
||||||
ctx.Fatal(err)
|
|
||||||
}
|
|
||||||
return ctx, conn
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewTestContextAuthenticated(t *testing.T, extraTorArgs ...string) (*TestContext, *control.Conn) {
|
|
||||||
ctx, conn := NewTestContextConnected(t, extraTorArgs...)
|
|
||||||
if err := conn.Authenticate(""); err != nil {
|
|
||||||
conn.Close()
|
|
||||||
ctx.Close()
|
|
||||||
ctx.Fatal(err)
|
|
||||||
}
|
|
||||||
return ctx, conn
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *TestContext) EnsureTestTorStarted() {
|
|
||||||
if t.TestTor == nil {
|
|
||||||
var err error
|
|
||||||
if t.TestTor, err = StartTestTor(t, t.ExtraTorArgs...); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *TestContext) Close() {
|
func (t *TestContext) Close() {
|
||||||
if t.TestTor != nil {
|
if err := t.Tor.Close(); err != nil {
|
||||||
if err := t.TestTor.Close(); err != nil {
|
if t.Failed() {
|
||||||
fmt.Printf("Warning, close failed on tor inst: %v", err)
|
t.Logf("Failure on close: %v", err)
|
||||||
|
} else {
|
||||||
|
t.Errorf("Failure on close: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *TestContext) CloseConnected(conn *control.Conn) {
|
|
||||||
if err := conn.Close(); err != nil {
|
|
||||||
fmt.Printf("Warning, close failed on tor conn: %v", err)
|
|
||||||
}
|
|
||||||
t.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *TestContext) ConnectTestTor() (*control.Conn, error) {
|
|
||||||
t.EnsureTestTorStarted()
|
|
||||||
textConn, err := textproto.Dial("tcp", "127.0.0.1:"+strconv.Itoa(t.TestTor.ControlPort))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
conn := control.NewConn(textConn)
|
|
||||||
conn.DebugWriter = t.DebugWriter
|
|
||||||
return conn, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *TestContext) DebugEnabled() bool {
|
|
||||||
return t.DebugWriter != nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *TestContext) Debugf(format string, args ...interface{}) {
|
|
||||||
if w := t.DebugWriter; w != nil {
|
|
||||||
fmt.Fprintf(w, format+"\n", args...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,103 +0,0 @@
|
||||||
package controltest
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"flag"
|
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/cretz/bine/process"
|
|
||||||
)
|
|
||||||
|
|
||||||
var torExePath string
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
flag.StringVar(&torExePath, "tor.path", "tor", "The TOR exe path")
|
|
||||||
flag.Parse()
|
|
||||||
}
|
|
||||||
|
|
||||||
type TestTor struct {
|
|
||||||
DataDir string
|
|
||||||
OrigArgs []string
|
|
||||||
ControlPort int
|
|
||||||
|
|
||||||
processCancelFn context.CancelFunc
|
|
||||||
}
|
|
||||||
|
|
||||||
func StartTestTor(ctx context.Context, extraArgs ...string) (*TestTor, error) {
|
|
||||||
dataDir, err := ioutil.TempDir(".", "test-data-dir-")
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
controlPortFile := filepath.Join(dataDir, "control-port")
|
|
||||||
// We have to touch the torrc
|
|
||||||
torrcFile := filepath.Join(dataDir, "test-torrc")
|
|
||||||
if err = ioutil.WriteFile(torrcFile, nil, os.FileMode(0600)); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
ret := &TestTor{
|
|
||||||
DataDir: dataDir,
|
|
||||||
OrigArgs: append([]string{
|
|
||||||
"-f", torrcFile,
|
|
||||||
"--DisableNetwork", "1",
|
|
||||||
"--ControlPort", "auto",
|
|
||||||
"--ControlPortWriteToFile", controlPortFile,
|
|
||||||
"--DataDirectory", dataDir,
|
|
||||||
}, extraArgs...),
|
|
||||||
}
|
|
||||||
errCh := make(chan error, 1)
|
|
||||||
var processCtx context.Context
|
|
||||||
processCtx, ret.processCancelFn = context.WithCancel(ctx)
|
|
||||||
go func() {
|
|
||||||
p, err := process.NewProcessCreator(torExePath).New(processCtx, ret.OrigArgs...)
|
|
||||||
if err == nil {
|
|
||||||
err = p.Run()
|
|
||||||
}
|
|
||||||
errCh <- err
|
|
||||||
}()
|
|
||||||
err = nil
|
|
||||||
for err == nil {
|
|
||||||
select {
|
|
||||||
case err = <-errCh:
|
|
||||||
if err == nil {
|
|
||||||
err = fmt.Errorf("Process returned earlier than expected")
|
|
||||||
}
|
|
||||||
case <-processCtx.Done():
|
|
||||||
err = ctx.Err()
|
|
||||||
default:
|
|
||||||
// Try to read the controlport file, or wait a bit
|
|
||||||
var byts []byte
|
|
||||||
if byts, err = ioutil.ReadFile(controlPortFile); err == nil {
|
|
||||||
if ret.ControlPort, err = process.ControlPortFromFileContents(string(byts)); err == nil {
|
|
||||||
return ret, nil
|
|
||||||
}
|
|
||||||
} else if os.IsNotExist(err) {
|
|
||||||
// Wait a bit
|
|
||||||
err = nil
|
|
||||||
time.Sleep(100 * time.Millisecond)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Delete the data dir and stop the process since we errored
|
|
||||||
if closeErr := ret.Close(); closeErr != nil {
|
|
||||||
fmt.Printf("Warning, unable to remove data dir %v: %v", dataDir, closeErr)
|
|
||||||
}
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *TestTor) Close() (err error) {
|
|
||||||
if t.processCancelFn != nil {
|
|
||||||
t.processCancelFn()
|
|
||||||
}
|
|
||||||
// Try this twice while waiting a bit between each
|
|
||||||
for i := 0; i < 2; i++ {
|
|
||||||
if err = os.RemoveAll(t.DataDir); err == nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
time.Sleep(300 * time.Millisecond)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
|
@ -5,7 +5,8 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type Process interface {
|
type Process interface {
|
||||||
Run() error
|
Start() error
|
||||||
|
Wait() error
|
||||||
}
|
}
|
||||||
|
|
||||||
type exeProcess struct {
|
type exeProcess struct {
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
package tor
|
||||||
|
|
||||||
|
import "net"
|
||||||
|
|
||||||
|
type OnionConf struct {
|
||||||
|
Port int
|
||||||
|
TargetPort int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Tor) Listen(conf *OnionConf) (net.Listener, error) {
|
||||||
|
panic("TODO")
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
package tor
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
func (t *Tor) DebugEnabled() bool {
|
||||||
|
return t.DebugWriter != nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Tor) Debugf(format string, args ...interface{}) {
|
||||||
|
if w := t.DebugWriter; w != nil {
|
||||||
|
fmt.Fprintf(w, format+"\n", args...)
|
||||||
|
}
|
||||||
|
}
|
231
tor/tor.go
231
tor/tor.go
|
@ -2,25 +2,45 @@ package tor
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"io/ioutil"
|
||||||
|
"net/textproto"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/cretz/bine/control"
|
||||||
|
|
||||||
|
"github.com/cretz/bine/process"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Tor struct {
|
type Tor struct {
|
||||||
|
Process process.Process
|
||||||
|
Control *control.Conn
|
||||||
|
|
||||||
|
ProcessCancelFunc context.CancelFunc
|
||||||
|
ControlPort int
|
||||||
|
DataDir string
|
||||||
|
DeleteDataDirOnClose bool
|
||||||
|
DebugWriter io.Writer
|
||||||
}
|
}
|
||||||
|
|
||||||
type StartConf struct {
|
type StartConf struct {
|
||||||
// TODO: docs...Nil means contet.Background
|
|
||||||
Context context.Context
|
|
||||||
// TODO: docs...Empty string means just "tor" either locally or on PATH
|
// TODO: docs...Empty string means just "tor" either locally or on PATH
|
||||||
ExePath string
|
ExePath string
|
||||||
// TODO: docs...If true, doesn't use exe path, uses statically compiled Tor
|
// TODO: docs...If true, doesn't use exe path, uses statically compiled Tor
|
||||||
Embedded bool
|
Embedded bool
|
||||||
// TODO: docs...If 0, Tor is asked to store the control port in a temporary file in the data directory that is
|
// TODO: docs...If 0, Tor is asked to store the control port in a temporary file in the data directory
|
||||||
// deleted after read
|
|
||||||
ControlPort int
|
ControlPort int
|
||||||
// TODO: docs...If not empty, this is the data directory used and *TempDataDir* fields are unused
|
// TODO: docs...If not empty, this is the data directory used and *TempDataDir* fields are unused
|
||||||
DataDir string
|
DataDir string
|
||||||
|
// TODO: docs...by default we do cookie auth, this disables it
|
||||||
|
DisableCookieAuth bool
|
||||||
|
// TODO: docs...by default this authenticates
|
||||||
|
DisableEagerAuth bool
|
||||||
|
// TODO: docs...by default network is disabled
|
||||||
|
EnableNetwork bool
|
||||||
// TODO: docs...If not empty, this is the parent directory that a child dir is created for data. If empty, the
|
// TODO: docs...If not empty, this is the parent directory that a child dir is created for data. If empty, the
|
||||||
// current dir is assumed. This has no effect if DataDir is set.
|
// current dir is assumed. This has no effect if DataDir is set.
|
||||||
TempDataDirBase string
|
TempDataDirBase string
|
||||||
|
@ -28,20 +48,205 @@ type StartConf struct {
|
||||||
RetainTempDataDir bool
|
RetainTempDataDir bool
|
||||||
// TODO: docs...Any extra CLI arguments to pass to Tor. This are applied after other CLI args.
|
// TODO: docs...Any extra CLI arguments to pass to Tor. This are applied after other CLI args.
|
||||||
ExtraArgs []string
|
ExtraArgs []string
|
||||||
|
// TODO: docs...If not present, a blank torrc file is placed in the data dir and used
|
||||||
|
TorrcFile string
|
||||||
// TODO: docs...
|
// TODO: docs...
|
||||||
DebugWriter io.Writer
|
DebugWriter io.Writer
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Tor) Start(conf *StartConf) error {
|
// TODO: docs...conf can be nil for defaults, note on error the process could still be running
|
||||||
// actualConf := *conf
|
func Start(ctx context.Context, conf *StartConf) (*Tor, error) {
|
||||||
panic("TODO")
|
if ctx == nil {
|
||||||
|
ctx = context.Background()
|
||||||
|
}
|
||||||
|
if conf == nil {
|
||||||
|
conf = &StartConf{}
|
||||||
|
}
|
||||||
|
tor := &Tor{DataDir: conf.DataDir, DebugWriter: conf.DebugWriter}
|
||||||
|
// Create the data dir
|
||||||
|
if tor.DataDir == "" {
|
||||||
|
tempBase := conf.TempDataDirBase
|
||||||
|
if tempBase == "" {
|
||||||
|
tempBase = "."
|
||||||
|
}
|
||||||
|
var err error
|
||||||
|
if tor.DataDir, err = ioutil.TempDir(tempBase, "data-dir-"); err != nil {
|
||||||
|
return nil, fmt.Errorf("Unable to create temp data dir: %v", err)
|
||||||
|
}
|
||||||
|
tor.Debugf("Created temp data directory at: %v", tor.DataDir)
|
||||||
|
tor.DeleteDataDirOnClose = !conf.RetainTempDataDir
|
||||||
|
} else if err := os.MkdirAll(tor.DataDir, 0600); err != nil {
|
||||||
|
return nil, fmt.Errorf("Unable to create data dir: %v", err)
|
||||||
|
}
|
||||||
|
// From this point on, we must close tor if we error
|
||||||
|
// Start tor
|
||||||
|
err := tor.startProcess(ctx, conf)
|
||||||
|
// Connect the controller
|
||||||
|
if err == nil {
|
||||||
|
err = tor.connectController(ctx, conf)
|
||||||
|
}
|
||||||
|
// Attempt eager auth w/ no password
|
||||||
|
if err == nil && !conf.DisableEagerAuth {
|
||||||
|
err = tor.Control.Authenticate("")
|
||||||
|
}
|
||||||
|
// If there was an error, we have to try to close here but it may leave the process open
|
||||||
|
if err != nil {
|
||||||
|
if closeErr := tor.Close(); closeErr != nil {
|
||||||
|
err = fmt.Errorf("Error on start: %v (also got error trying to close: %v)", err, closeErr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return tor, err
|
||||||
}
|
}
|
||||||
|
|
||||||
type OnionConf struct {
|
func (t *Tor) startProcess(ctx context.Context, conf *StartConf) error {
|
||||||
Port int
|
// Get the creator
|
||||||
TargetPort int
|
var creator process.ProcessCreator
|
||||||
|
if conf.Embedded {
|
||||||
|
return fmt.Errorf("Embedded Tor not yet supported")
|
||||||
|
} else {
|
||||||
|
torPath := conf.ExePath
|
||||||
|
if torPath == "" {
|
||||||
|
torPath = "tor"
|
||||||
|
}
|
||||||
|
creator = process.NewProcessCreator(torPath)
|
||||||
|
}
|
||||||
|
// Build the args
|
||||||
|
args := []string{"--DataDirectory", t.DataDir}
|
||||||
|
if !conf.DisableCookieAuth {
|
||||||
|
args = append(args, "--CookieAuthentication", "1")
|
||||||
|
}
|
||||||
|
if !conf.EnableNetwork {
|
||||||
|
args = append(args, "--DisableNetwork", "1")
|
||||||
|
}
|
||||||
|
// If there is no Torrc file, create a blank temp one
|
||||||
|
torrcFileName := conf.TorrcFile
|
||||||
|
if torrcFileName == "" {
|
||||||
|
torrcFile, err := ioutil.TempFile(t.DataDir, "torrc-")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
torrcFileName = torrcFile.Name()
|
||||||
|
if err = torrcFile.Close(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
args = append(args, "-f", torrcFileName)
|
||||||
|
// Create file for Tor to write the control port to if it's not told to us
|
||||||
|
var controlPortFileName string
|
||||||
|
var err error
|
||||||
|
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())
|
||||||
|
}
|
||||||
|
// Start process with the args
|
||||||
|
var processCtx context.Context
|
||||||
|
processCtx, t.ProcessCancelFunc = context.WithCancel(ctx)
|
||||||
|
args = append(args, conf.ExtraArgs...)
|
||||||
|
p, err := creator.New(processCtx, args...)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
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 {
|
||||||
|
break ControlPortCheck
|
||||||
|
}
|
||||||
|
time.Sleep(200 * time.Millisecond)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Unable to read control port file: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Tor) Listen(conf *OnionConf) (net.Listener, error) {
|
func (t *Tor) connectController(ctx context.Context, conf *StartConf) error {
|
||||||
panic("TODO")
|
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 {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
t.Control = control.NewConn(textConn)
|
||||||
|
t.Control.DebugWriter = t.DebugWriter
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Tor) Close() error {
|
||||||
|
errs := []error{}
|
||||||
|
// If controller is authenticated, send the quit signal to the process. Otherwise, just close the controller.
|
||||||
|
sentHalt := false
|
||||||
|
if t.Control != nil {
|
||||||
|
if t.Control.Authenticated {
|
||||||
|
if err := t.Control.Signal("HALT"); err != nil {
|
||||||
|
errs = append(errs, fmt.Errorf("Unable to signal halt: %v", err))
|
||||||
|
} else {
|
||||||
|
sentHalt = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Now close the controller
|
||||||
|
if err := t.Control.Close(); err != nil {
|
||||||
|
errs = append(errs, fmt.Errorf("Unable to close contrlller: %v", err))
|
||||||
|
} else {
|
||||||
|
t.Control = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if t.Process != nil {
|
||||||
|
// If we didn't halt, we have to force kill w/ the cancel func
|
||||||
|
if !sentHalt {
|
||||||
|
t.ProcessCancelFunc()
|
||||||
|
}
|
||||||
|
// Wait for a bit to make sure it stopped
|
||||||
|
errCh := make(chan error, 1)
|
||||||
|
var waitErr error
|
||||||
|
go func() { errCh <- t.Process.Wait() }()
|
||||||
|
select {
|
||||||
|
case waitErr = <-errCh:
|
||||||
|
if waitErr != nil {
|
||||||
|
errs = append(errs, fmt.Errorf("Process wait failed: %v", waitErr))
|
||||||
|
}
|
||||||
|
case <-time.After(300 * time.Millisecond):
|
||||||
|
errs = append(errs, fmt.Errorf("Process did not exit after 300 ms"))
|
||||||
|
}
|
||||||
|
if waitErr == nil {
|
||||||
|
t.Process = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Get rid of the entire data dir
|
||||||
|
if t.DeleteDataDirOnClose {
|
||||||
|
if err := os.RemoveAll(t.DataDir); err != nil {
|
||||||
|
errs = append(errs, fmt.Errorf("Failed to remove data dir %v: %v", t.DataDir, err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Combine the errors if present
|
||||||
|
if len(errs) == 0 {
|
||||||
|
return nil
|
||||||
|
} else if len(errs) == 1 {
|
||||||
|
return errs[0]
|
||||||
|
}
|
||||||
|
return fmt.Errorf("Got %v errors while closing - %v", len(errs), errs)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue