diff --git a/event/common.go b/event/common.go index dd3d286..ba445f9 100644 --- a/event/common.go +++ b/event/common.go @@ -297,6 +297,6 @@ const ( // Bool strings const ( - True = "true" + True = "true" False = "false" -) \ No newline at end of file +) diff --git a/go.sum b/go.sum index 9afaa15..01d833a 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,7 @@ cwtch.im/tapir v0.2.0 h1:7MkoR5+uEuPW34/O0GZRidnIjq/01Cfm8nl5IRuqpGc= cwtch.im/tapir v0.2.0/go.mod h1:xzzZ28adyUXNkYL1YodcHsAiTt3IJ8Loc29YVn9mIEQ= +cwtch.im/tapir v0.2.1 h1:t1YJB9q5sV1A9xwiiwL6WVfw3dwQWLoecunuzT1PQtw= +cwtch.im/tapir v0.2.1/go.mod h1:xzzZ28adyUXNkYL1YodcHsAiTt3IJ8Loc29YVn9mIEQ= git.openprivacy.ca/openprivacy/bine v0.0.3 h1:PSHUmNqaW7BZUX8n2eTDeNbjsuRe+t5Ae0Og+P+jDM0= git.openprivacy.ca/openprivacy/bine v0.0.3/go.mod h1:13ZqhKyqakDsN/ZkQkIGNULsmLyqtXc46XBcnuXm/mU= git.openprivacy.ca/openprivacy/connectivity v1.2.0 h1:dbZ5CRl11vg3BNHdzRKSlDP8OUtDB+mf6FkxMVf73qw= diff --git a/protocol/connections/engine.go b/protocol/connections/engine.go index 386f3fd..11d84b6 100644 --- a/protocol/connections/engine.go +++ b/protocol/connections/engine.go @@ -262,9 +262,20 @@ func (e *engine) peerWithOnion(onion string) { // peerWithTokenServer is the entry point for cwtchPeer - server relationships // needs to be run in a goroutine as will block on Open. func (e *engine) peerWithTokenServer(onion string, tokenServerOnion string, tokenServerY string) { + + service, exists := e.ephemeralServices.Load(onion) + if exists { + connection := service.(*tor.BaseOnionService) + if conn, err := connection.GetConnection(onion); err == nil { + if conn.IsClosed() == false { + return + } + } + // Otherwise...let's reconnect + } + log.Debugf("Peering with Token Server %v %v", onion, tokenServerOnion) e.ignoreOnShutdown(e.serverConnecting)(onion) - // Create a new ephemeral service for this connection ephemeralService := new(tor.BaseOnionService) eid, epk := primitives.InitializeEphemeralIdentity() diff --git a/server/app/main.go b/server/app/main.go index b9e72a6..d43b887 100644 --- a/server/app/main.go +++ b/server/app/main.go @@ -1,6 +1,7 @@ package main import ( + "crypto/rand" "cwtch.im/cwtch/model" cwtchserver "cwtch.im/cwtch/server" "cwtch.im/tapir/primitives" @@ -9,7 +10,6 @@ import ( "git.openprivacy.ca/openprivacy/connectivity/tor" "git.openprivacy.ca/openprivacy/log" mrand "math/rand" - "crypto/rand" "os" "time" ) @@ -45,7 +45,7 @@ func main() { // we don't need real randomness for the port, just to avoid a possible conflict... mrand.Seed(int64(time.Now().Nanosecond())) - controlPort := mrand.Intn(1000)+9052 + controlPort := mrand.Intn(1000) + 9052 // generate a random password key := make([]byte, 64) @@ -54,7 +54,7 @@ func main() { panic(err) } - os.MkdirAll("tordir/tor",0700) + os.MkdirAll("tordir/tor", 0700) tor.NewTorrc().WithHashedPassword(base64.StdEncoding.EncodeToString(key)).WithControlPort(controlPort).Build("./tordir/tor/torrc") acn, err := tor.NewTorACNWithAuth("tordir", "", controlPort, tor.HashedPasswordAuthenticator{Password: base64.StdEncoding.EncodeToString(key)}) diff --git a/server/metrics/metrics.go b/server/metrics/metrics.go index 805515a..9d89190 100644 --- a/server/metrics/metrics.go +++ b/server/metrics/metrics.go @@ -12,6 +12,7 @@ import ( type counter struct { startTime time.Time count uint64 + total uint64 } // Counter providers a threadsafe counter to use for storing long running counts @@ -25,7 +26,7 @@ type Counter interface { // NewCounter initializes a counter starting at time.Now() and a count of 0 and returns it func NewCounter() Counter { - c := &counter{startTime: time.Now(), count: 0} + c := &counter{startTime: time.Now(), count: 0, total: 0} return c } diff --git a/server/metrics/monitors.go b/server/metrics/monitors.go index 5ba4c85..52d6a78 100644 --- a/server/metrics/monitors.go +++ b/server/metrics/monitors.go @@ -19,6 +19,7 @@ const ( // Monitors is a package of metrics for a Cwtch Server including message count, CPU, Mem, and conns type Monitors struct { MessageCounter Counter + TotalMessageCounter Counter Messages MonitorHistory CPU MonitorHistory Memory MonitorHistory @@ -36,7 +37,11 @@ func (mp *Monitors) Start(ts tapir.Service, configDir string, log bool) { mp.starttime = time.Now() mp.breakChannel = make(chan bool) mp.MessageCounter = NewCounter() - mp.Messages = NewMonitorHistory(Count, Cumulative, func() (c float64) { c = float64(mp.MessageCounter.Count()); mp.MessageCounter.Reset(); return }) + + // Maintain a count of total messages + mp.TotalMessageCounter = NewCounter() + mp.Messages = NewMonitorHistory(Count, Cumulative, func() (c float64) { c = float64(mp.MessageCounter.Count()); mp.TotalMessageCounter.Add(int(c)); mp.MessageCounter.Reset(); return }) + var pidUsageLock sync.Mutex mp.CPU = NewMonitorHistory(Percent, Average, func() float64 { pidUsageLock.Lock() diff --git a/server/server.go b/server/server.go index d78a6ce..78d60f2 100644 --- a/server/server.go +++ b/server/server.go @@ -11,45 +11,46 @@ import ( "cwtch.im/tapir/persistence" "cwtch.im/tapir/primitives" "cwtch.im/tapir/primitives/privacypass" + "fmt" "git.openprivacy.ca/openprivacy/connectivity" "git.openprivacy.ca/openprivacy/connectivity/tor" "git.openprivacy.ca/openprivacy/log" - "os" - "time" + "path" ) // Server encapsulates a complete, compliant Cwtch server. type Server struct { - service tapir.Service - config Config - metricsPack metrics.Monitors - closed bool - tokenTapirService tapir.Service - tokenServer *privacypass.TokenServer - tokenService primitives.Identity - tokenServicePrivKey ed25519.PrivateKey + service tapir.Service + config Config + metricsPack metrics.Monitors + tokenTapirService tapir.Service + tokenServer *privacypass.TokenServer + tokenService primitives.Identity + tokenServicePrivKey ed25519.PrivateKey + tokenServiceStopped bool + onionServiceStopped bool + running bool + existingMessageCount int } // Setup initialized a server from a given configuration func (s *Server) Setup(serverConfig Config) { s.config = serverConfig bs := new(persistence.BoltPersistence) - bs.Open("./tokens.db") + bs.Open(path.Join(serverConfig.ConfigDir, "tokens.db")) s.tokenServer = privacypass.NewTokenServerFromStore(bs) s.tokenService = s.config.TokenServiceIdentity() s.tokenServicePrivKey = s.config.TokenServerPrivateKey } +// Identity returns the main onion identity of the server +func (s *Server) Identity() primitives.Identity { + return s.config.Identity() +} + // Run starts a server with the given privateKey -// TODO: surface errors -// TODO: handle HUP/KILL signals to exit and close Tor gracefully -// TODO: handle user input to exit -func (s *Server) Run(acn connectivity.ACN) { - s.closed = false - +func (s *Server) Run(acn connectivity.ACN) error { addressIdentity := tor.GetTorV3Hostname(s.config.PublicKey) - - //tokenService := privacypass.NewTokenServer() identity := primitives.InitializeIdentity("", &s.config.PrivateKey, &s.config.PublicKey) var service tapir.Service service = new(tor2.BaseOnionService) @@ -61,29 +62,30 @@ func (s *Server) Run(acn connectivity.ACN) { ms := new(storage.MessageStore) err := ms.Init(s.config.ConfigDir, s.config.MaxBufferLines, s.metricsPack.MessageCounter) if err != nil { - log.Errorln(err) - acn.Close() - os.Exit(1) + return err } + // Needed because we only collect metrics on a per-session basis + // TODO fix metrics so they persist across sessions? + s.existingMessageCount = len(ms.FetchMessages()) + + s.tokenTapirService = new(tor2.BaseOnionService) + s.tokenTapirService.Init(acn, s.tokenServicePrivKey, &s.tokenService) + tokenApplication := new(applications.TokenApplication) + tokenApplication.TokenService = s.tokenServer + powTokenApp := new(applications.ApplicationChain). + ChainApplication(new(applications.ProofOfWorkApplication), applications.SuccessfulProofOfWorkCapability). + ChainApplication(tokenApplication, applications.HasTokensCapability) go func() { - s.tokenTapirService = new(tor2.BaseOnionService) - s.tokenTapirService.Init(acn, s.tokenServicePrivKey, &s.tokenService) - tokenApplication := new(applications.TokenApplication) - tokenApplication.TokenService = s.tokenServer - powTokenApp := new(applications.ApplicationChain). - ChainApplication(new(applications.ProofOfWorkApplication), applications.SuccessfulProofOfWorkCapability). - ChainApplication(tokenApplication, applications.HasTokensCapability) s.tokenTapirService.Listen(powTokenApp) + s.tokenServiceStopped = true }() - - for true { + go func() { s.service.Listen(NewTokenBoardServer(ms, s.tokenServer)) - if s.closed { - return - } - time.Sleep(5 * time.Second) - } + s.onionServiceStopped = true + }() + s.running = true + return nil } // KeyBundle provides the signed keybundle of the server @@ -97,10 +99,44 @@ func (s *Server) KeyBundle() *model.KeyBundle { return kb } +// CheckStatus returns true if the server is running and/or an error if any part of the server needs to be restarted. +func (s *Server) CheckStatus() (bool, error) { + + if s.onionServiceStopped == true || s.tokenServiceStopped == true { + return s.running, fmt.Errorf("one of more server components are down: onion:%v token service: %v", s.onionServiceStopped, s.tokenServiceStopped) + } + return s.running, nil +} + // Shutdown kills the app closing all connections and freeing all goroutines func (s *Server) Shutdown() { - s.closed = true s.service.Shutdown() s.tokenTapirService.Shutdown() s.metricsPack.Stop() + s.running = false +} + +// Statistics is an encapsulation of information about the server that an operator might want to know at a glance. +type Statistics struct { + TotalMessages int +} + +// GetStatistics is a stub method for providing some high level information about +// the server operation to bundling applications (e.g. the UI) +func (s *Server) GetStatistics() Statistics { + // TODO Statistics from Metrics is very awkward. Metrics needs an overhaul to make safe + total := s.existingMessageCount + if s.metricsPack.TotalMessageCounter != nil { + total += s.metricsPack.TotalMessageCounter.Count() + } + + return Statistics{ + TotalMessages: total, + } +} + +// ConfigureAutostart sets whether this server should autostart (in the Cwtch UI/bundling application) +func (s *Server) ConfigureAutostart(autostart bool) { + s.config.AutoStart = autostart + s.config.Save(s.config.ConfigDir, s.config.FilePath) } diff --git a/server/serverConfig.go b/server/serverConfig.go index 4bed576..3b35ecd 100644 --- a/server/serverConfig.go +++ b/server/serverConfig.go @@ -19,12 +19,14 @@ type Reporting struct { // Config is a struct for storing basic server configuration type Config struct { ConfigDir string `json:"-"` + FilePath string `json:"-"` MaxBufferLines int `json:"maxBufferLines"` PublicKey ed25519.PublicKey `json:"publicKey"` PrivateKey ed25519.PrivateKey `json:"privateKey"` TokenServerPublicKey ed25519.PublicKey `json:"tokenServerPublicKey"` TokenServerPrivateKey ed25519.PrivateKey `json:"tokenServerPrivateKey"` ServerReporting Reporting `json:"serverReporting"` + AutoStart bool `json:"autostart"` } // Identity returns an encapsulation of the servers keys @@ -61,6 +63,9 @@ func LoadConfig(configDir, filename string) Config { ReportingGroupID: "", ReportingServerAddr: "", } + config.AutoStart = false + config.ConfigDir = configDir + config.FilePath = filename raw, err := ioutil.ReadFile(path.Join(configDir, filename)) if err == nil { diff --git a/testing/cwtch_peer_server_integration_test.go b/testing/cwtch_peer_server_integration_test.go index b1e7b50..acd01ca 100644 --- a/testing/cwtch_peer_server_integration_test.go +++ b/testing/cwtch_peer_server_integration_test.go @@ -139,7 +139,7 @@ func TestCwtchPeerIntegration(t *testing.T) { if err != nil { t.Fatalf("Could not start Tor: %v", err) } - pid, err := acn.GetPID() + pid, _ := acn.GetPID() t.Logf("Tor pid: %v", pid) // ***** Cwtch Server management *****