diff --git a/connectivity/torProvider.go b/connectivity/torProvider.go index d1d305a..3f2253c 100644 --- a/connectivity/torProvider.go +++ b/connectivity/torProvider.go @@ -6,6 +6,9 @@ import ( "git.openprivacy.ca/openprivacy/libricochet-go/utils" "github.com/cretz/bine/control" "github.com/cretz/bine/tor" + bineed255192 "github.com/cretz/bine/torutil/ed25519" + "golang.org/x/crypto/ed25519" + "golang.org/x/crypto/sha3" "net" "net/textproto" "os" @@ -94,16 +97,48 @@ func (tp *torProvider) WaitTillBootstrapped() { } func (tp *torProvider) Listen(identity PrivateKey, port int) (ListenService, error) { + var onion = "" + var privkey ed25519.PrivateKey + + switch pk := identity.(type) { + case ed25519.PrivateKey: + privkey = pk + gpubk := pk.Public() + switch pubk := gpubk.(type) { + case ed25519.PublicKey: + onion = utils.GetTorV3Hostname(pubk) + } + } + + // Hack around tor detached onions not having a more obvious resume mechanism + // So we use deterministic ports + seedbytes := sha3.New224().Sum([]byte(onion)) + localport := int(seedbytes[0]) + (int(seedbytes[1]) << 8) + if localport < 1024 { // this is not uniformly random, but we don't need it to be + localport += 1024 + } + if tp.t == nil { return nil, errors.New("Tor is offline") } + + localListener, err := net.Listen("tcp", "127.0.0.1:"+strconv.Itoa(localport)) + tp.lock.Lock() defer tp.lock.Unlock() - conf := &tor.ListenConf{NoWait: true, Version3: true, Key: identity, RemotePorts: []int{port}, Detach: true, DiscardKey: true} + conf := &tor.ListenConf{NoWait: true, Version3: true, Key: identity, RemotePorts: []int{port}, Detach: true, DiscardKey: true, LocalListener: localListener} os, err := tp.t.Listen(nil, conf) + if err != nil && strings.Contains(err.Error(), "550 Unspecified Tor error: Onion address collision") { + os = &tor.OnionService{Tor: tp.t, LocalListener: localListener, ID: onion, Version3: true, Key: bineed255192.FromCryptoPrivateKey(privkey), ClientAuths: make(map[string]string, 0), RemotePorts: []int{port}} + err = nil + } + // Not set in t.Listen if supplied, we want it to handle this however + os.CloseLocalListenerOnClose = true + if err != nil { return nil, err } + ols := &onionListenService{os: os, tp: tp} tp.childListeners[ols.AddressIdentity()] = ols return ols, nil @@ -198,7 +233,7 @@ func StartTor(appDirectory string, bundledTorPath string) (ACN, error) { } return tp, err } - return nil, errors.New("Could not connect to or start Tor that met requirments") + return nil, errors.New("Could not connect to or start Tor that met requirments (min Tor version 0.3.5.x)") } func (tp *torProvider) unregisterListener(id string) { diff --git a/testing/integration_test.go b/testing/integration_test.go index 9e8830d..45aba1f 100644 --- a/testing/integration_test.go +++ b/testing/integration_test.go @@ -107,6 +107,9 @@ func TestApplicationIntegration(t *testing.T) { t.Fatalf("Could not start tor: %v", err) } + time.Sleep(1 * time.Second) + acnStartGoRoutines := runtime.NumGoroutine() + messageStack := &Messages{} messageStack.Init() @@ -212,13 +215,16 @@ func TestApplicationIntegration(t *testing.T) { fmt.Println("Shutting alice down...") alice.Shutdown() time.Sleep(15 * time.Second) + aliceShutdownGoRoutines := runtime.NumGoroutine() fmt.Println("Shutting down bine/tor") acn.Close() + time.Sleep(5 * time.Second) + finalGoRoutines := runtime.NumGoroutine() - fmt.Printf("startGoRoutines: %v\nrunningGoRoutines: %v\nconnectedGoRoutines: %v\nBobShutdownGoRoutines: %v\nfinalGoRoutines: %v\n", startGoRoutines, runningGoRoutines, connectedGoRoutines, bobShutdownGoRoutines, finalGoRoutines) + fmt.Printf("startGoRoutines: %v\nacnStartedGoRoutines: %v\nrunningGoRoutines: %v\nconnectedGoRoutines: %v\nBobShutdownGoRoutines: %v\naliceShutdownGoRoutines: %v\nfinalGoRoutines: %v\n", startGoRoutines, acnStartGoRoutines, runningGoRoutines, connectedGoRoutines, bobShutdownGoRoutines, aliceShutdownGoRoutines, finalGoRoutines) if finalGoRoutines != startGoRoutines { t.Errorf("After shutting alice and bob down, go routines were not at start value. Expected: %v Actual: %v", startGoRoutines, finalGoRoutines)