diff --git a/tests/tor_dialer_test.go b/tests/tor_dialer_test.go index ae3ff6a..4f5003e 100644 --- a/tests/tor_dialer_test.go +++ b/tests/tor_dialer_test.go @@ -8,12 +8,13 @@ import ( "testing" "time" + "github.com/cretz/bine/tor" "golang.org/x/net/context/ctxhttp" ) func TestDialerSimpleHTTP(t *testing.T) { ctx := GlobalEnabledNetworkContext(t) - httpClient := httpClient(ctx) + httpClient := httpClient(ctx, nil) // IsTor check byts := httpGet(ctx, httpClient, "https://check.torproject.org/api/ip") jsn := map[string]interface{}{} @@ -21,12 +22,12 @@ func TestDialerSimpleHTTP(t *testing.T) { ctx.Require.True(jsn["IsTor"].(bool)) } -func httpClient(ctx *TestContext) *http.Client { +func httpClient(ctx *TestContext, conf *tor.DialConf) *http.Client { // 15 seconds max to dial dialCtx, dialCancel := context.WithTimeout(ctx, 15*time.Second) defer dialCancel() // Make connection - dialer, err := ctx.Dialer(dialCtx, nil) + dialer, err := ctx.Dialer(dialCtx, conf) ctx.Require.NoError(err) return &http.Client{Transport: &http.Transport{DialContext: dialer.DialContext}} } diff --git a/tests/tor_isolate_socks_auth_test.go b/tests/tor_isolate_socks_auth_test.go new file mode 100644 index 0000000..a080206 --- /dev/null +++ b/tests/tor_isolate_socks_auth_test.go @@ -0,0 +1,72 @@ +package tests + +import ( + "context" + "strconv" + "strings" + "testing" + "time" + + "github.com/cretz/bine/tor" + "golang.org/x/net/proxy" +) + +// TestIsolateSocksAuth simply confirms the functionality of IsolateSOCKSAuth, +// namely that it uses a different circuit for different SOCKS credentials. +func TestIsolateSocksAuth(t *testing.T) { + // Create context w/ no isolate + ctx := NewTestContext(t, &tor.StartConf{ + NoAutoSocksPort: true, + ExtraArgs: []string{"--SocksPort", "auto NoIsolateSOCKSAuth"}, + }) + // Make sure it reused the circuit (i.e. only has one) for both separate-auth calls + uniqueCircuitIDs := doSeparateAuthHttpCalls(ctx) + ctx.Debugf("Unique IDs without isolate: %v", uniqueCircuitIDs) + ctx.Require.Len(uniqueCircuitIDs, 1) + // Create context w/ isolate + ctx = NewTestContext(t, nil) + // Make sure it made a new circuit (i.e. has two) for each separate-auth call + uniqueCircuitIDs = doSeparateAuthHttpCalls(ctx) + ctx.Debugf("Unique IDs with isolate: %v", uniqueCircuitIDs) + ctx.Require.Len(uniqueCircuitIDs, 2) +} + +// Returns the map keyed with unique circuit IDs after second separate-auth HTTP call +func doSeparateAuthHttpCalls(ctx *TestContext) map[int]struct{} { + defer ctx.Close() + enableCtx, enableCancel := context.WithTimeout(ctx, 100*time.Second) + defer enableCancel() + ctx.Require.NoError(ctx.EnableNetwork(enableCtx, true)) + // Make HTTP call w/ an auth + client := httpClient(ctx, &tor.DialConf{ProxyAuth: &proxy.Auth{"foo", "bar"}}) + byts := httpGet(ctx, client, "https://check.torproject.org/api/ip") + ctx.Debugf("Read bytes: %v", string(byts)) + // Confirm just size 1 + ids := uniqueStreamCircuitIDs(ctx) + ctx.Require.Len(ids, 1) + // Now make call with another auth and just return circuit IDs + client = httpClient(ctx, &tor.DialConf{ProxyAuth: &proxy.Auth{"baz", "qux"}}) + byts = httpGet(ctx, client, "https://check.torproject.org/api/ip") + ctx.Debugf("Read bytes: %v", string(byts)) + return uniqueStreamCircuitIDs(ctx) +} + +// Return each stream circuit as a key of an empty-val map +func uniqueStreamCircuitIDs(ctx *TestContext) map[int]struct{} { + ret := map[int]struct{}{} + vals, err := ctx.Control.GetInfo("stream-status") + ctx.Require.NoError(err) + for _, val := range vals { + ctx.Require.Equal("stream-status", val.Key) + for _, line := range strings.Split(val.Val, "\n") { + pieces := strings.Split(strings.TrimSpace(line), " ") + if len(pieces) < 3 { + continue + } + i, err := strconv.Atoi(pieces[2]) + ctx.Require.NoError(err) + ret[i] = struct{}{} + } + } + return ret +} diff --git a/tests/tor_listen_test.go b/tests/tor_listen_test.go index eade197..0feed99 100644 --- a/tests/tor_listen_test.go +++ b/tests/tor_listen_test.go @@ -50,7 +50,7 @@ func startHTTPServer( handlePattern string, handler func(http.ResponseWriter, *http.Request), ) (*http.Client, *http.Server, *tor.OnionService) { - httpClient := httpClient(ctx) + httpClient := httpClient(ctx, nil) // Wait at most a few minutes for the entire test listenCtx, listenCancel := context.WithTimeout(context.Background(), 4*time.Minute) defer listenCancel() diff --git a/tor/dialer.go b/tor/dialer.go index 038bc95..9ef211e 100644 --- a/tor/dialer.go +++ b/tor/dialer.go @@ -25,8 +25,13 @@ type DialConf struct { // is empty and ProxyAddress is not empty, it defaults to "tcp". ProxyNetwork string - // ProxyAuth is the auth for the proxy. An empty auth means no auth, a nil - // auth means it is looked up. + // ProxyAuth is the auth for the proxy. Since Tor's SOCKS5 proxy is + // unauthenticated, this is rarely needed. It can be used when + // IsolateSOCKSAuth is set to ensure separate circuits. + // + // This should not be confused with downstream SOCKS proxy authentication + // which is set via Tor values for Socks5ProxyUsername and + // Socks5ProxyPassword when Socks5Proxy is set. ProxyAuth *proxy.Auth // SkipEnableNetwork, if true, will skip the enable network step in Dialer. @@ -72,23 +77,8 @@ func (t *Tor) Dialer(ctx context.Context, conf *DialConf) (*Dialer, error) { } else if proxyNetwork == "" { proxyNetwork = "tcp" } - // Lookup proxy auth as needed - proxyAuth := conf.ProxyAuth - if proxyAuth == nil { - info, err := t.Control.GetConf("Socks5ProxyUsername", "Socks5ProxyPassword") - if err != nil { - return nil, err - } - if len(info) != 2 || info[0].Key != "Socks5ProxyUsername" || info[1].Key != "Socks5ProxyPassword" { - return nil, fmt.Errorf("Unable to get proxy auth") - } - proxyAuth = &proxy.Auth{User: info[0].Val, Password: info[1].Val} - } - if proxyAuth.User == "" && proxyAuth.Password == "" { - proxyAuth = nil - } - dialer, err := proxy.SOCKS5(proxyNetwork, proxyAddress, proxyAuth, conf.Forward) + dialer, err := proxy.SOCKS5(proxyNetwork, proxyAddress, conf.ProxyAuth, conf.Forward) if err != nil { return nil, err }