diff --git a/app/app.go b/app/app.go index 6a33042..2a0ced1 100644 --- a/app/app.go +++ b/app/app.go @@ -2,12 +2,12 @@ package app import ( "crypto/rand" - "cwtch.im/cwtch/connectivity/tor" "cwtch.im/cwtch/peer" "cwtch.im/cwtch/storage" "encoding/hex" "fmt" + "git.openprivacy.ca/openprivacy/libricochet-go/connectivity" "io/ioutil" "log" "os" @@ -18,7 +18,7 @@ import ( type application struct { peers map[string]peer.CwtchPeer - torManager *tor.Manager + mn connectivity.Mixnet directory string mutex sync.Mutex primaryonion string @@ -35,23 +35,17 @@ type Application interface { GetPeer(onion string) peer.CwtchPeer ListPeers() map[string]string - GetTorStatus() (map[string]string, error) + //GetTorStatus() (map[string]string, error) Shutdown() } // NewApp creates a new app with some environment awareness and initializes a Tor Manager -func NewApp(appDirectory string, torPath string) (Application, error) { - log.Printf("NewApp(%v, %v)\n", appDirectory, torPath) - app := &application{peers: make(map[string]peer.CwtchPeer), storage: make(map[string]storage.ProfileStore), directory: appDirectory} - os.MkdirAll(path.Join(appDirectory, "tor"), 0700) +func NewApp(mn connectivity.Mixnet, appDirectory string) Application { + log.Printf("NewApp(%v)\n", appDirectory) + app := &application{peers: make(map[string]peer.CwtchPeer), storage: make(map[string]storage.ProfileStore), directory: appDirectory, mn: mn} os.Mkdir(path.Join(app.directory, "profiles"), 0700) - - err := app.startTor(torPath) - if err != nil { - return nil, err - } - return app, nil + return app } func generateRandomFilename() string { @@ -78,7 +72,8 @@ func (app *application) CreatePeer(name string, password string) (peer.CwtchPeer if err != nil { return nil, err } - app.startPeer(p) + p.Init(app.mn) + p.Listen() _, exists := app.peers[p.GetProfile().Onion] if exists { p.Shutdown() @@ -104,7 +99,6 @@ func (app *application) LoadProfiles(password string) error { p, err := fileStore.Load() if err != nil { - continue } @@ -114,7 +108,8 @@ func (app *application) LoadProfiles(password string) error { log.Printf("Error: profile for onion %v already exists", p.GetProfile().Onion) continue } - app.startPeer(p) + p.Init(app.mn) + p.Listen() app.mutex.Lock() app.peers[p.GetProfile().Onion] = p app.storage[p.GetProfile().Onion] = fileStore @@ -126,40 +121,6 @@ func (app *application) LoadProfiles(password string) error { return nil } -// startTor will create a local torrc if needed -func (app *application) startTor(torPath string) error { - // Creating a local cwtch tor server config for the user - // creating $app.directory/torrc file - // SOCKSPort socksPort - // ControlPort controlPort - torrc := path.Join(app.directory, "tor", "torrc") - if _, err := os.Stat(torrc); os.IsNotExist(err) { - log.Printf("writing torrc to: %v\n", torrc) - file, err := os.Create(torrc) - if err != nil { - return err - } - fmt.Fprintf(file, "SOCKSPort %d\nControlPort %d\nCookieAuthentication 0\nSafeSocks 1\n", 9050, 9051) - file.Close() - } - - tm, err := tor.NewTorManager(9050, 9051, torPath, torrc) - if err != nil { - return err - } - app.torManager = tm - return nil -} - -func (app *application) startPeer(peer peer.CwtchPeer) { - go func() { - e := peer.Listen() - if e != nil { - log.Fatalf("ERROR: peer %v has crashed with: %v\n", peer.GetProfile().Onion, e) - } - }() -} - // ListPeers returns a map of onions to their profile's Name func (app *application) ListPeers() map[string]string { keys := map[string]string{} @@ -182,15 +143,15 @@ func (app *application) GetPeer(onion string) peer.CwtchPeer { return nil } +/* // GetTorStatus returns tor control port bootstrap-phase status info in a map func (app *application) GetTorStatus() (map[string]string, error) { return app.torManager.GetStatus() -} +}*/ // Shutdown shutsdown all peers of an app and then the tormanager func (app *application) Shutdown() { for _, peer := range app.peers { peer.Shutdown() } - app.torManager.Shutdown() } diff --git a/app/cli/main.go b/app/cli/main.go index f8ffd07..8b693c0 100644 --- a/app/cli/main.go +++ b/app/cli/main.go @@ -8,11 +8,11 @@ import ( "cwtch.im/cwtch/model" "cwtch.im/cwtch/peer/connections" "fmt" + "git.openprivacy.ca/openprivacy/libricochet-go/connectivity" "github.com/c-bata/go-prompt" "golang.org/x/crypto/ssh/terminal" "log" "os" - "os/exec" "os/user" "path" "strings" @@ -250,17 +250,16 @@ func main() { quit := false - torPath, err := exec.LookPath("tor") - if err != nil { - log.Fatal("tor could not be found on this system. Please install it in the system $PATH") - } - usr, err := user.Current() if err != nil { log.Fatalf("\nError: could not load current user: %v\n", err) } - app, err = app2.NewApp(path.Join(usr.HomeDir, ".cwtch"), torPath) + mn, err := connectivity.StartTor(path.Join(usr.HomeDir, ".cwtch"), "") + if err != nil { + log.Fatalf("\nError connecting to Tor: %v\n", err) + } + app = app2.NewApp(mn, path.Join(usr.HomeDir, ".cwtch")) if err != nil { log.Fatalf("Error initializing application: %v", err) } @@ -307,6 +306,12 @@ func main() { quit = true case "/new-profile": if len(commands) == 2 { + name := strings.Trim(commands[1], " ") + if name == "" { + fmt.Printf("Error creating profile, usage: %v\n", usages[commands[0]]) + break + } + fmt.Print("** WARNING: PASSWORDS CANNOT BE RECOVERED! **\n") password := "" @@ -329,17 +334,17 @@ func main() { } if failcount >= 3 { - fmt.Printf("Error creating profile for %v: Your password entries must match!\n", commands[1]) + fmt.Printf("Error creating profile for %v: Your password entries must match!\n", name) } else { - p, err := app.CreatePeer(commands[1], password) + p, err := app.CreatePeer(name, password) if err == nil { stopGroupFollow() - fmt.Printf("\nNew profile created for %v\n", commands[1]) + fmt.Printf("\nNew profile created for %v\n", name) peer = p suggestions = append(suggestionsBase, suggestionsSelectedProfile...) } else { - fmt.Printf("\nError creating profile for %v: %v\n", commands[1], err) + fmt.Printf("\nError creating profile for %v: %v\n", name, err) } } } else { @@ -597,6 +602,6 @@ func main() { } app.Shutdown() + mn.Close() os.Exit(0) - } diff --git a/connectivity/tor/tormanager.go b/connectivity/tor/tormanager.go deleted file mode 100644 index ee27fa9..0000000 --- a/connectivity/tor/tormanager.go +++ /dev/null @@ -1,168 +0,0 @@ -package tor - -import ( - "errors" - "fmt" - "github.com/yawning/bulb" - "log" - "net" - "net/http" - "net/url" - "os" - "os/exec" - "path" - "strings" - "time" -) - -// Manager checks connectivity of the Tor process used to support Cwtch -type Manager struct { - socksPort int - controlPort int - process *exec.Cmd -} - -// NewTorManager Instantiates a new connection manager, returns non-nil error if it fails to connect to a tor daemon on the given ports. -func NewTorManager(socksPort int, controlPort int, torPath string, torrc string) (*Manager, error) { - torManager := new(Manager) - torManager.socksPort = socksPort - torManager.controlPort = controlPort - - err := torManager.TestConnection() - - if err == nil { - log.Printf("using existing tor proxy") - return torManager, nil - } - - // try to start tor - - cmd := exec.Command(torPath, "-f", torrc) - - // on Android, home can be set to '/' which is not writeable - if os.Getenv("HOME") == "" { - cmd.Env = append(os.Environ(), fmt.Sprintf("HOME=%s", path.Dir(torrc))) - } - - log.Printf("starting local tor proxy") - err = cmd.Start() - if err != nil { - log.Printf("starting tor failed %v", err) - return nil, err - } - torManager.process = cmd - - // for 30 seconds check every 5 if tor is up and working - for i := 0; i < 6; i++ { - time.Sleep(time.Second * 5) - err = torManager.TestConnection() - if err == nil { - break - } - } - return torManager, err -} - -type proxyStatus int - -const ( - proxyStatusOK proxyStatus = iota - proxyStatusWrongType - proxyStatusCannotConnect - proxyStatusTimeout -) - -// Shutdown kills the managed Tor Process -func (tm *Manager) Shutdown() { - if tm.process != nil { - if err := tm.process.Process.Kill(); err != nil { - log.Fatal("failed to kill process: ", err) - } - } -} - -// Detect whether a proxy is connectable and is a Tor proxy -func checkTorProxy(proxyAddress string) proxyStatus { - // A trick to do this without making an outward connection is, - // paradoxically, to try to open it as http. - // This is documented in section 4 here: https://github.com/torproject/torspec/blob/master/socks-extensions.txt - client := &http.Client{Timeout: 2 * time.Second} - response, err := client.Get("http://" + proxyAddress + "/") - if err != nil { - switch t := err.(type) { - case *url.Error: - switch t.Err.(type) { - case *net.OpError: // Network-level error. Will in turn contain a os.SyscallError - return proxyStatusCannotConnect - default: - // http.error unfortunately not exported, need to match on string - // net/http: request canceled - if strings.Index(t.Err.Error(), "request canceled") != -1 { - return proxyStatusTimeout - } - } - } - // Protocol-level errors mean that http failed, so it's not Tor - return proxyStatusWrongType - } - defer response.Body.Close() - if response.Status != "501 Tor is not an HTTP Proxy" { - return proxyStatusWrongType - } - return proxyStatusOK -} - -func proxyStatusMessage(status proxyStatus) string { - switch status { - case proxyStatusWrongType: - return "Proxy specified is not a Tor proxy" - case proxyStatusCannotConnect: - return "Cannot connect to Tor proxy" - case proxyStatusTimeout: - return "Proxy timeout" - default: - return "Unknown proxy error" - } -} - -// TestConnection returns nil if both the socks and control ports of the Tor connection are active, otherwise it returns an error. -func (tm *Manager) TestConnection() error { - proxyStatus := checkTorProxy(fmt.Sprintf("127.0.0.1:%d", tm.socksPort)) - controlAddress := fmt.Sprintf("127.0.0.1:%d", tm.controlPort) - if proxyStatus == proxyStatusOK { - c, err := bulb.Dial("tcp4", controlAddress) - if c != nil { - c.Close() - } - if err == nil { - return nil - } - return fmt.Errorf("could not connect to Tor Control Port %v %v", tm.controlPort, err) - } - return errors.New(proxyStatusMessage(proxyStatus)) -} - -// GetStatus returns tor control port bootstrap-phase status info in a map -func (tm *Manager) GetStatus() (map[string]string, error) { - controlAddress := fmt.Sprintf("127.0.0.1:%d", tm.controlPort) - c, err := bulb.Dial("tcp4", controlAddress) - if err != nil { - return nil, err - } - defer c.Close() - c.Request("AUTHENTICATE \"\"") - resp, err := c.Request("GETINFO status/bootstrap-phase") - if err != nil { - return nil, err - } - - lines := strings.SplitN(resp.RawLines[0], " ", 5) - var resps = make(map[string]string) - for _, l := range lines { - kv := strings.Split(l, "=") - if len(kv) == 2 { - resps[kv[0]] = kv[1] - } - } - return resps, nil -} diff --git a/connectivity/tor/tormanager_test.go b/connectivity/tor/tormanager_test.go deleted file mode 100644 index 6081ae8..0000000 --- a/connectivity/tor/tormanager_test.go +++ /dev/null @@ -1,31 +0,0 @@ -package tor - -import ( - "fmt" - "os" - "os/exec" - "testing" -) - -func TestTorManager(t *testing.T) { - tor, err := exec.LookPath("tor") - if err != nil { - t.Errorf("tor not found in PATH") - } - os.Remove("/tmp/torrc") - file, _ := os.Create("/tmp/torrc") - fmt.Fprintf(file, "SOCKSPort %d\nControlPort %d\nDataDirectory /tmp/tor\n", 10050, 10051) - file.Close() - tm, err := NewTorManager(10050, 10051, tor, "/tmp/torrc") - if err != nil { - t.Errorf("creating a new tor manager failed: %v", err) - } else { - - tm2, err := NewTorManager(10050, 10051, tor, "/tmp/torrc") - if err != nil { - t.Errorf("creating a new tor manager failed: %v", err) - } - tm2.Shutdown() // should not noop - } - tm.Shutdown() -} diff --git a/peer/connections/connectionsmanager.go b/peer/connections/connectionsmanager.go index d9e2e15..d1a8de6 100644 --- a/peer/connections/connectionsmanager.go +++ b/peer/connections/connectionsmanager.go @@ -4,6 +4,7 @@ import ( "cwtch.im/cwtch/model" "cwtch.im/cwtch/protocol" "git.openprivacy.ca/openprivacy/libricochet-go/application" + "git.openprivacy.ca/openprivacy/libricochet-go/connectivity" "sync" "time" ) @@ -14,11 +15,13 @@ type Manager struct { serverConnections map[string]*PeerServerConnection lock sync.Mutex breakChannel chan bool + mn connectivity.Mixnet } // NewConnectionsManager creates a new instance of Manager. -func NewConnectionsManager() *Manager { +func NewConnectionsManager(mn connectivity.Mixnet) *Manager { m := new(Manager) + m.mn = mn m.peerConnections = make(map[string]*PeerPeerConnection) m.serverConnections = make(map[string]*PeerServerConnection) m.breakChannel = make(chan bool) @@ -32,7 +35,7 @@ func (m *Manager) ManagePeerConnection(host string, profile *model.Profile, data _, exists := m.peerConnections[host] if !exists { - ppc := NewPeerPeerConnection(host, profile, dataHandler, aif) + ppc := NewPeerPeerConnection(m.mn, host, profile, dataHandler, aif) go ppc.Run() m.peerConnections[host] = ppc return ppc @@ -46,7 +49,7 @@ func (m *Manager) ManageServerConnection(host string, handler func(string, *prot _, exists := m.serverConnections[host] if !exists { - psc := NewPeerServerConnection(host) + psc := NewPeerServerConnection(m.mn, host) go psc.Run() psc.GroupMessageHandler = handler m.serverConnections[host] = psc diff --git a/peer/connections/connectsmanager_test.go b/peer/connections/connectsmanager_test.go index 87a1c79..dcb9312 100644 --- a/peer/connections/connectsmanager_test.go +++ b/peer/connections/connectsmanager_test.go @@ -1,10 +1,11 @@ package connections import ( + "git.openprivacy.ca/openprivacy/libricochet-go/connectivity" "testing" ) func TestConnectionsManager(t *testing.T) { // TODO We need to encapsulate connections behind a well defined interface for tesintg - NewConnectionsManager() + NewConnectionsManager(connectivity.LocalProvider()) } diff --git a/peer/connections/peerpeerconnection.go b/peer/connections/peerpeerconnection.go index e54609f..384fb22 100644 --- a/peer/connections/peerpeerconnection.go +++ b/peer/connections/peerpeerconnection.go @@ -8,6 +8,7 @@ import ( "git.openprivacy.ca/openprivacy/libricochet-go/application" "git.openprivacy.ca/openprivacy/libricochet-go/channels" "git.openprivacy.ca/openprivacy/libricochet-go/connection" + "git.openprivacy.ca/openprivacy/libricochet-go/connectivity" "git.openprivacy.ca/openprivacy/libricochet-go/identity" "log" "time" @@ -22,11 +23,13 @@ type PeerPeerConnection struct { profile *model.Profile dataHandler func(string, []byte) []byte aif application.ApplicationInstanceFactory + mn connectivity.Mixnet } // NewPeerPeerConnection creates a new peer connection for the given hostname and profile. -func NewPeerPeerConnection(peerhostname string, profile *model.Profile, dataHandler func(string, []byte) []byte, aif application.ApplicationInstanceFactory) *PeerPeerConnection { +func NewPeerPeerConnection(mn connectivity.Mixnet, peerhostname string, profile *model.Profile, dataHandler func(string, []byte) []byte, aif application.ApplicationInstanceFactory) *PeerPeerConnection { ppc := new(PeerPeerConnection) + ppc.mn = mn ppc.PeerHostname = peerhostname ppc.profile = profile ppc.dataHandler = dataHandler @@ -107,7 +110,7 @@ func (ppc *PeerPeerConnection) WaitTilAuthenticated() { // Run manages the setup and teardown of a peer->peer connection func (ppc *PeerPeerConnection) Run() error { ppc.state = CONNECTING - rc, err := goricochet.Open(ppc.PeerHostname) + rc, err := goricochet.Open(ppc.mn, ppc.PeerHostname) if err == nil { rc.TraceLog(false) ppc.connection = rc diff --git a/peer/connections/peerpeerconnection_test.go b/peer/connections/peerpeerconnection_test.go index 6bf9104..a9d5827 100644 --- a/peer/connections/peerpeerconnection_test.go +++ b/peer/connections/peerpeerconnection_test.go @@ -5,10 +5,12 @@ import ( "cwtch.im/cwtch/model" "cwtch.im/cwtch/peer/peer" "cwtch.im/cwtch/protocol" + "fmt" "git.openprivacy.ca/openprivacy/libricochet-go" "git.openprivacy.ca/openprivacy/libricochet-go/application" "git.openprivacy.ca/openprivacy/libricochet-go/channels" "git.openprivacy.ca/openprivacy/libricochet-go/connection" + "git.openprivacy.ca/openprivacy/libricochet-go/connectivity" "git.openprivacy.ca/openprivacy/libricochet-go/identity" "golang.org/x/crypto/ed25519" "net" @@ -59,13 +61,14 @@ func TestPeerPeerConnection(t *testing.T) { profile := model.GenerateNewProfile("alice") hostname := identity.Hostname() - ppc := NewPeerPeerConnection("127.0.0.1:5452|"+hostname, profile, nil, application.ApplicationInstanceFactory{}) + ppc := NewPeerPeerConnection(connectivity.LocalProvider(), "127.0.0.1:5452|"+hostname, profile, nil, application.ApplicationInstanceFactory{}) tp := new(TestPeer) tp.Init() go runtestpeer(t, tp, identity) state := ppc.GetState() if state != DISCONNECTED { + fmt.Println("ERROR state should be disconnected") t.Errorf("new connections should start in disconnected state") } go ppc.Run() @@ -80,5 +83,4 @@ func TestPeerPeerConnection(t *testing.T) { if tp.ReceivedGroupInvite == false { t.Errorf("should have received an group invite packet") } - } diff --git a/peer/connections/peerserverconnection.go b/peer/connections/peerserverconnection.go index ba75e7d..0af48d3 100644 --- a/peer/connections/peerserverconnection.go +++ b/peer/connections/peerserverconnection.go @@ -10,6 +10,7 @@ import ( "git.openprivacy.ca/openprivacy/libricochet-go" "git.openprivacy.ca/openprivacy/libricochet-go/channels" "git.openprivacy.ca/openprivacy/libricochet-go/connection" + "git.openprivacy.ca/openprivacy/libricochet-go/connectivity" "git.openprivacy.ca/openprivacy/libricochet-go/identity" "golang.org/x/crypto/ed25519" "log" @@ -22,13 +23,15 @@ type PeerServerConnection struct { Server string state ConnectionState connection *connection.Connection + mn connectivity.Mixnet GroupMessageHandler func(string, *protocol.GroupMessage) } // NewPeerServerConnection creates a new Peer->Server outbound connection -func NewPeerServerConnection(serverhostname string) *PeerServerConnection { +func NewPeerServerConnection(mn connectivity.Mixnet, serverhostname string) *PeerServerConnection { psc := new(PeerServerConnection) + psc.mn = mn psc.Server = serverhostname psc.Init() return psc @@ -52,7 +55,7 @@ func (psc *PeerServerConnection) WaitTilAuthenticated() { // Run manages the setup and teardown of a peer server connection func (psc *PeerServerConnection) Run() error { log.Printf("Connecting to %v", psc.Server) - rc, err := goricochet.Open(psc.Server) + rc, err := goricochet.Open(psc.mn, psc.Server) if err == nil { rc.TraceLog(true) psc.connection = rc diff --git a/peer/connections/peerserverconnection_test.go b/peer/connections/peerserverconnection_test.go index adcf553..5015025 100644 --- a/peer/connections/peerserverconnection_test.go +++ b/peer/connections/peerserverconnection_test.go @@ -8,6 +8,7 @@ import ( "git.openprivacy.ca/openprivacy/libricochet-go" "git.openprivacy.ca/openprivacy/libricochet-go/channels" "git.openprivacy.ca/openprivacy/libricochet-go/connection" + "git.openprivacy.ca/openprivacy/libricochet-go/connectivity" "git.openprivacy.ca/openprivacy/libricochet-go/identity" "golang.org/x/crypto/ed25519" "net" @@ -72,7 +73,7 @@ func TestPeerServerConnection(t *testing.T) { go runtestserver(t, ts, identity) onionAddr := identity.Hostname() - psc := NewPeerServerConnection("127.0.0.1:5451|" + onionAddr) + psc := NewPeerServerConnection(connectivity.LocalProvider(), "127.0.0.1:5451|"+onionAddr) numcalls := 0 psc.GroupMessageHandler = func(s string, gm *protocol.GroupMessage) { numcalls++ diff --git a/peer/cwtch_peer.go b/peer/cwtch_peer.go index 06db97f..59ac9a1 100644 --- a/peer/cwtch_peer.go +++ b/peer/cwtch_peer.go @@ -12,6 +12,7 @@ import ( "git.openprivacy.ca/openprivacy/libricochet-go/application" "git.openprivacy.ca/openprivacy/libricochet-go/channels" "git.openprivacy.ca/openprivacy/libricochet-go/connection" + "git.openprivacy.ca/openprivacy/libricochet-go/connectivity" "git.openprivacy.ca/openprivacy/libricochet-go/identity" "git.openprivacy.ca/openprivacy/libricochet-go/utils" "github.com/golang/protobuf/proto" @@ -19,6 +20,7 @@ import ( "log" "strings" "sync" + "time" ) // cwtchPeer manages incoming and outgoing connections and all processing for a Cwtch cwtchPeer @@ -26,17 +28,19 @@ type cwtchPeer struct { connection.AutoConnectionHandler Profile *model.Profile app *application.RicochetApplication + mn connectivity.Mixnet mutex sync.Mutex connectionsManager *connections.Manager dataHandler func(string, []byte) []byte //handlers map[string]func(*application.ApplicationInstance) func() channels.Handler - aif application.ApplicationInstanceFactory + aif application.ApplicationInstanceFactory + shutdown bool } // CwtchPeer provides us with a way of testing systems built on top of cwtch without having to // directly implement a cwtchPeer. type CwtchPeer interface { - Init() + Init(connectivity.Mixnet) PeerWithOnion(string) *connections.PeerPeerConnection InviteOnionToGroup(string, string) error @@ -66,7 +70,7 @@ type CwtchPeer interface { SetApplicationInstanceFactory(factory application.ApplicationInstanceFactory) SetPeerDataHandler(func(string, []byte) []byte) - Listen() error + Listen() Shutdown() } @@ -74,7 +78,7 @@ type CwtchPeer interface { func NewCwtchPeer(name string) CwtchPeer { cp := new(cwtchPeer) cp.Profile = model.GenerateNewProfile(name) - cp.Init() + cp.shutdown = false return cp } @@ -82,13 +86,13 @@ func NewCwtchPeer(name string) CwtchPeer { func FromProfile(profile *model.Profile) CwtchPeer { cp := new(cwtchPeer) cp.Profile = profile - cp.Init() return cp } // Init instantiates a cwtchPeer -func (cp *cwtchPeer) Init() { - cp.connectionsManager = connections.NewConnectionsManager() +func (cp *cwtchPeer) Init(mn connectivity.Mixnet) { + cp.mn = mn + cp.connectionsManager = connections.NewConnectionsManager(cp.mn) go cp.connectionsManager.AttemptReconnections() } @@ -295,11 +299,25 @@ func (cp *cwtchPeer) ContactRequest(name string, message string) string { return "Accepted" } +func (cp *cwtchPeer) Listen() { + go func() { + for !cp.shutdown { + e := cp.listenFn() + if e != nil { + // TODO: was panic, then fatal + fmt.Printf("ERROR: peer %v has crashed with: %v\n", cp.GetProfile().Onion, e) + } + // listenFn failed, wait 5 seconds and try again + time.Sleep(5 * time.Second) + } + }() +} + // Listen sets up an onion listener to process incoming cwtch messages -func (cp *cwtchPeer) Listen() error { - cwtchpeer := new(application.RicochetApplication) - l, err := application.SetupOnionV3("127.0.0.1:9051", "tcp4", "", cp.Profile.Ed25519PrivateKey, cp.GetProfile().Onion, 9878) - if err != nil && fmt.Sprintf("%v", err) != "550 Unspecified Tor error: Onion address collision" { +func (cp *cwtchPeer) listenFn() error { + ra := new(application.RicochetApplication) + onionService, err := cp.mn.Listen(cp.Profile.Ed25519PrivateKey, application.RicochetPort) + if err != nil /*&& fmt.Sprintf("%v", err) != "550 Unspecified Tor error: Onion address collision"*/ { return err } @@ -307,7 +325,7 @@ func (cp *cwtchPeer) Listen() error { af.Init() af.AddHandler("im.cwtch.peer", func(rai *application.ApplicationInstance) func() channels.Handler { cpi := new(CwtchPeerInstance) - cpi.Init(rai, cwtchpeer) + cpi.Init(rai, ra) return func() channels.Handler { cpc := new(peer.CwtchPeerChannel) cpc.Handler = &CwtchPeerHandler{Onion: rai.RemoteHostname, Peer: cp} @@ -318,7 +336,7 @@ func (cp *cwtchPeer) Listen() error { if cp.dataHandler != nil { af.AddHandler("im.cwtch.peer.data", func(rai *application.ApplicationInstance) func() channels.Handler { cpi := new(CwtchPeerInstance) - cpi.Init(rai, cwtchpeer) + cpi.Init(rai, ra) return func() channels.Handler { cpc := new(peer.CwtchPeerDataChannel) cpc.Handler = &CwtchPeerHandler{Onion: rai.RemoteHostname, Peer: cp, DataHandler: cp.dataHandler} @@ -332,15 +350,16 @@ func (cp *cwtchPeer) Listen() error { af.AddHandler(handlers[i], cp.aif.GetHandler(handlers[i])) } - cwtchpeer.InitV3(cp.Profile.Name, identity.InitializeV3(cp.Profile.Name, &cp.Profile.Ed25519PrivateKey, &cp.Profile.Ed25519PublicKey), af, cp) - log.Printf("Running cwtch peer on %v", l.Addr().String()) - cp.app = cwtchpeer - cwtchpeer.Run(l) + ra.Init(cp.mn, cp.Profile.Name, identity.InitializeV3(cp.Profile.Name, &cp.Profile.Ed25519PrivateKey, &cp.Profile.Ed25519PublicKey), af, cp) + log.Printf("Running cwtch peer on %v", onionService.AddressFull()) + cp.app = ra + ra.Run(onionService) return nil } // Shutdown kills all connections and cleans up all goroutines for the peer func (cp *cwtchPeer) Shutdown() { + cp.shutdown = true cp.connectionsManager.Shutdown() if cp.app != nil { cp.app.Shutdown() diff --git a/peer/cwtch_peer_test.go b/peer/cwtch_peer_test.go index 780309b..84325fb 100644 --- a/peer/cwtch_peer_test.go +++ b/peer/cwtch_peer_test.go @@ -1,6 +1,7 @@ package peer import ( + "git.openprivacy.ca/openprivacy/libricochet-go/connectivity" "testing" ) @@ -21,7 +22,9 @@ func TestCwtchPeerGenerate(t *testing.T) { func TestTrustPeer(t *testing.T) { groupName := "test.server" alice := NewCwtchPeer("alice") + alice.Init(connectivity.LocalProvider()) bob := NewCwtchPeer("bob") + bob.Init(connectivity.LocalProvider()) bobOnion := bob.GetProfile().Onion aliceOnion := alice.GetProfile().Onion diff --git a/server/app/main.go b/server/app/main.go index b0deef7..71c1861 100644 --- a/server/app/main.go +++ b/server/app/main.go @@ -2,8 +2,10 @@ package main import ( cwtchserver "cwtch.im/cwtch/server" + "git.openprivacy.ca/openprivacy/libricochet-go/connectivity" "log" "os" + "path" ) const ( @@ -15,9 +17,16 @@ func main() { serverConfig := cwtchserver.LoadConfig(configDir, serverConfigFile) + mn, err := connectivity.StartTor(path.Join(configDir, "tor"), "") + if err != nil { + log.Fatalf("\nError connecting to Tor: %v\n", err) + } + defer mn.Close() + server := new(cwtchserver.Server) log.Printf("starting cwtch server...") // TODO load params from .cwtch/server.conf or command line flag - server.Run(serverConfig) + // TODO: respond to HUP so t.Close is gracefully called + server.Run(mn, serverConfig) } diff --git a/server/server.go b/server/server.go index f81d70c..9f476c4 100644 --- a/server/server.go +++ b/server/server.go @@ -8,7 +8,7 @@ import ( "cwtch.im/cwtch/storage" "git.openprivacy.ca/openprivacy/libricochet-go/application" "git.openprivacy.ca/openprivacy/libricochet-go/channels" - "git.openprivacy.ca/openprivacy/libricochet-go/utils" + "git.openprivacy.ca/openprivacy/libricochet-go/connectivity" "log" ) @@ -21,12 +21,14 @@ type Server struct { // Run starts a server with the given privateKey // TODO: surface errors -func (s *Server) Run(serverConfig Config) { +// TODO: handle HUP/KILL signals to exit and close Tor gracefully +// TODO: handle user input to exit +func (s *Server) Run(mn connectivity.Mixnet, serverConfig Config) { s.config = serverConfig cwtchserver := new(application.RicochetApplication) s.metricsPack.Start(cwtchserver, serverConfig.ConfigDir, s.config.ServerReporting.LogMetricsToFile) - l, err := application.SetupOnionV3("127.0.0.1:9051", "tcp4", "", s.config.PrivateKey, utils.GetTorV3Hostname(s.config.PublicKey), 9878) + listenService, err := mn.Listen(s.config.PrivateKey, application.RicochetPort) if err != nil { log.Fatalf("error setting up onion service: %v", err) @@ -66,10 +68,10 @@ func (s *Server) Run(serverConfig Config) { } }) - cwtchserver.InitV3("cwtch server for "+l.Addr().String(), s.config.Identity(), af, new(application.AcceptAllContactManager)) - log.Printf("cwtch server running on cwtch:%s", l.Addr().String()) + cwtchserver.Init(mn, "cwtch server for "+listenService.AddressIdentity(), s.config.Identity(), af, new(application.AcceptAllContactManager)) + log.Printf("cwtch server running on cwtch:%s", listenService.AddressFull()) s.app = cwtchserver - s.app.Run(l) + s.app.Run(listenService) } // Shutdown kills the app closing all connections and freeing all goroutines diff --git a/testing/cwtch_peer_server_intergration_test.go b/testing/cwtch_peer_server_intergration_test.go index aeaadfa..0e8bde2 100644 --- a/testing/cwtch_peer_server_intergration_test.go +++ b/testing/cwtch_peer_server_intergration_test.go @@ -6,9 +6,11 @@ import ( "cwtch.im/cwtch/peer/connections" cwtchserver "cwtch.im/cwtch/server" "fmt" + "git.openprivacy.ca/openprivacy/libricochet-go/connectivity" "golang.org/x/net/proxy" "io/ioutil" "log" + "os" "runtime" "testing" "time" @@ -52,7 +54,7 @@ func serverCheck(t *testing.T, serverAddr string) bool { return true } -func waitForPeerConnection(t *testing.T, peer peer.CwtchPeer, server string) { +func waitForPeerServerConnection(t *testing.T, peer peer.CwtchPeer, server string) { for { servers := peer.GetServers() state, ok := servers[server] @@ -61,6 +63,7 @@ func waitForPeerConnection(t *testing.T, peer peer.CwtchPeer, server string) { t.Fatalf("%v could not connect to %v", peer.GetProfile().Onion, server) } if state != connections.AUTHENTICATED { + fmt.Printf("peer %v waiting connect to server %v, currently: %v\n", peer.GetProfile().Onion, server, connections.ConnectionStateName[state]) time.Sleep(time.Second * 10) continue } @@ -72,11 +75,37 @@ func waitForPeerConnection(t *testing.T, peer peer.CwtchPeer, server string) { return } +func waitForPeerPeerConnection(t *testing.T, peera peer.CwtchPeer, peerb peer.CwtchPeer) { + for { + peers := peera.GetPeers() + state, ok := peers[peerb.GetProfile().Onion] + if ok { + if state == connections.FAILED { + t.Fatalf("%v could not connect to %v", peera.GetProfile().Onion, peerb.GetProfile().Onion) + } + if state != connections.AUTHENTICATED { + fmt.Printf("peer% v waiting connect to peer %v, currently: %v\n", peera.GetProfile().Onion, peerb.GetProfile().Onion, connections.ConnectionStateName[state]) + time.Sleep(time.Second * 10) + continue + } + } else { + t.Fatalf("peer peer connectiond %v should have entry for server %v", peers, peerb.GetProfile().Onion) + } + break + } + return +} + func TestCwtchPeerIntegration(t *testing.T) { // Hide logging "noise" log.SetOutput(ioutil.Discard) numGoRoutinesStart := runtime.NumGoroutine() + mn, err := connectivity.StartTor(".", "") + if err != nil { + t.Fatalf("Could not start Tor: %v", err) + } + // ***** Cwtch Server managment ***** var server *cwtchserver.Server @@ -88,10 +117,11 @@ func TestCwtchPeerIntegration(t *testing.T) { fmt.Println("No server found!") server = new(cwtchserver.Server) fmt.Println("Starting cwtch server...") + os.Remove("server-test.json") config := cwtchserver.LoadConfig(".", "server-test.json") identity := config.Identity() serverAddr = identity.Hostname() - go server.Run(config) + go server.Run(mn, config) // let tor get established fmt.Printf("Establishing Tor hidden service: %v...\n", serverAddr) @@ -105,17 +135,20 @@ func TestCwtchPeerIntegration(t *testing.T) { fmt.Println("Creating Alice...") alice := peer.NewCwtchPeer("Alice") - go alice.Listen() + alice.Init(mn) + alice.Listen() fmt.Println("Alice created:", alice.GetProfile().Onion) fmt.Println("Creating Bob...") bob := peer.NewCwtchPeer("Bob") - go bob.Listen() + bob.Init(mn) + bob.Listen() fmt.Println("Bob created:", bob.GetProfile().Onion) fmt.Println("Creating Carol...") carol := peer.NewCwtchPeer("Carol") - go carol.Listen() + carol.Init(mn) + carol.Listen() fmt.Println("Carol created:", carol.GetProfile().Onion) fmt.Println("Waiting for Alice, Bob, and Carol to connection with onion network...") @@ -142,8 +175,17 @@ func TestCwtchPeerIntegration(t *testing.T) { fmt.Println("Bob joining server...") bob.JoinServer(serverAddr) - fmt.Println("Waiting for peerings and server joins...") - time.Sleep(time.Second * 240) + fmt.Println("Waiting for alice to join server...") + waitForPeerServerConnection(t, alice, serverAddr) + + fmt.Println("Waiting for bob to join server...") + waitForPeerServerConnection(t, bob, serverAddr) + + fmt.Println("Waiting for alice and Bob to peer...") + waitForPeerPeerConnection(t, alice, bob) + + /*fmt.Println("Waiting for peerings and server joins...") + time.Sleep(time.Second * 240)*/ fmt.Println("Alice inviting Bob to group...") err = alice.InviteOnionToGroup(bob.GetProfile().Onion, groupID) @@ -193,8 +235,8 @@ func TestCwtchPeerIntegration(t *testing.T) { time.Sleep(time.Second * 110) */ // Wait for them to join the server - waitForPeerConnection(t, alice, serverAddr) - waitForPeerConnection(t, bob, serverAddr) + waitForPeerServerConnection(t, alice, serverAddr) + waitForPeerServerConnection(t, bob, serverAddr) //numGouRoutinesPostServerConnect := runtime.NumGoroutine() // ***** Conversation ***** @@ -246,7 +288,7 @@ func TestCwtchPeerIntegration(t *testing.T) { fmt.Println("Carol joining server...") carol.JoinServer(serverAddr) - waitForPeerConnection(t, carol, serverAddr) + waitForPeerServerConnection(t, carol, serverAddr) numGoRotinesPostCarolConnect := runtime.NumGoroutine() fmt.Println("Bob> ", bobLines[2]) diff --git a/testing/tests.sh b/testing/tests.sh index ba55761..2ce493c 100755 --- a/testing/tests.sh +++ b/testing/tests.sh @@ -16,7 +16,6 @@ go test ${1} -coverprofile=server.listen.cover.out -v ./server/listen go test ${1} -coverprofile=server.send.cover.out -v ./server/send go test ${1} -coverprofile=server.metrics.cover.out -v ./server/metrics go test ${1} -coverprofile=server.cover.out -v ./server -go test ${1} -coverprofile=tor.cover.out -v ./connectivity/tor echo "mode: set" > coverage.out && cat *.cover.out | grep -v mode: | sort -r | \ awk '{if($1 != last) {print $0;last=$1}}' >> coverage.out rm -rf *.cover.out