Fixing up Server APIs #330
|
@ -297,6 +297,6 @@ const (
|
||||||
|
|
||||||
// Bool strings
|
// Bool strings
|
||||||
const (
|
const (
|
||||||
True = "true"
|
True = "true"
|
||||||
False = "false"
|
False = "false"
|
||||||
)
|
)
|
||||||
|
|
2
go.sum
2
go.sum
|
@ -1,5 +1,7 @@
|
||||||
cwtch.im/tapir v0.2.0 h1:7MkoR5+uEuPW34/O0GZRidnIjq/01Cfm8nl5IRuqpGc=
|
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.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 h1:PSHUmNqaW7BZUX8n2eTDeNbjsuRe+t5Ae0Og+P+jDM0=
|
||||||
git.openprivacy.ca/openprivacy/bine v0.0.3/go.mod h1:13ZqhKyqakDsN/ZkQkIGNULsmLyqtXc46XBcnuXm/mU=
|
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=
|
git.openprivacy.ca/openprivacy/connectivity v1.2.0 h1:dbZ5CRl11vg3BNHdzRKSlDP8OUtDB+mf6FkxMVf73qw=
|
||||||
|
|
|
@ -262,9 +262,20 @@ func (e *engine) peerWithOnion(onion string) {
|
||||||
// peerWithTokenServer is the entry point for cwtchPeer - server relationships
|
// peerWithTokenServer is the entry point for cwtchPeer - server relationships
|
||||||
// needs to be run in a goroutine as will block on Open.
|
// needs to be run in a goroutine as will block on Open.
|
||||||
func (e *engine) peerWithTokenServer(onion string, tokenServerOnion string, tokenServerY string) {
|
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)
|
log.Debugf("Peering with Token Server %v %v", onion, tokenServerOnion)
|
||||||
e.ignoreOnShutdown(e.serverConnecting)(onion)
|
e.ignoreOnShutdown(e.serverConnecting)(onion)
|
||||||
|
|
||||||
// Create a new ephemeral service for this connection
|
// Create a new ephemeral service for this connection
|
||||||
ephemeralService := new(tor.BaseOnionService)
|
ephemeralService := new(tor.BaseOnionService)
|
||||||
eid, epk := primitives.InitializeEphemeralIdentity()
|
eid, epk := primitives.InitializeEphemeralIdentity()
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/rand"
|
||||||
"cwtch.im/cwtch/model"
|
"cwtch.im/cwtch/model"
|
||||||
cwtchserver "cwtch.im/cwtch/server"
|
cwtchserver "cwtch.im/cwtch/server"
|
||||||
"cwtch.im/tapir/primitives"
|
"cwtch.im/tapir/primitives"
|
||||||
|
@ -9,7 +10,6 @@ import (
|
||||||
"git.openprivacy.ca/openprivacy/connectivity/tor"
|
"git.openprivacy.ca/openprivacy/connectivity/tor"
|
||||||
"git.openprivacy.ca/openprivacy/log"
|
"git.openprivacy.ca/openprivacy/log"
|
||||||
mrand "math/rand"
|
mrand "math/rand"
|
||||||
"crypto/rand"
|
|
||||||
"os"
|
"os"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
@ -45,7 +45,7 @@ func main() {
|
||||||
|
|
||||||
// we don't need real randomness for the port, just to avoid a possible conflict...
|
// we don't need real randomness for the port, just to avoid a possible conflict...
|
||||||
mrand.Seed(int64(time.Now().Nanosecond()))
|
mrand.Seed(int64(time.Now().Nanosecond()))
|
||||||
controlPort := mrand.Intn(1000)+9052
|
controlPort := mrand.Intn(1000) + 9052
|
||||||
|
|
||||||
// generate a random password
|
// generate a random password
|
||||||
key := make([]byte, 64)
|
key := make([]byte, 64)
|
||||||
|
@ -54,7 +54,7 @@ func main() {
|
||||||
panic(err)
|
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")
|
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)})
|
acn, err := tor.NewTorACNWithAuth("tordir", "", controlPort, tor.HashedPasswordAuthenticator{Password: base64.StdEncoding.EncodeToString(key)})
|
||||||
|
|
||||||
|
|
|
@ -12,6 +12,7 @@ import (
|
||||||
type counter struct {
|
type counter struct {
|
||||||
startTime time.Time
|
startTime time.Time
|
||||||
count uint64
|
count uint64
|
||||||
|
total uint64
|
||||||
}
|
}
|
||||||
|
|
||||||
// Counter providers a threadsafe counter to use for storing long running counts
|
// 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
|
// NewCounter initializes a counter starting at time.Now() and a count of 0 and returns it
|
||||||
func NewCounter() Counter {
|
func NewCounter() Counter {
|
||||||
c := &counter{startTime: time.Now(), count: 0}
|
c := &counter{startTime: time.Now(), count: 0, total: 0}
|
||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -19,6 +19,7 @@ const (
|
||||||
// Monitors is a package of metrics for a Cwtch Server including message count, CPU, Mem, and conns
|
// Monitors is a package of metrics for a Cwtch Server including message count, CPU, Mem, and conns
|
||||||
type Monitors struct {
|
type Monitors struct {
|
||||||
MessageCounter Counter
|
MessageCounter Counter
|
||||||
|
TotalMessageCounter Counter
|
||||||
Messages MonitorHistory
|
Messages MonitorHistory
|
||||||
CPU MonitorHistory
|
CPU MonitorHistory
|
||||||
Memory MonitorHistory
|
Memory MonitorHistory
|
||||||
|
@ -36,7 +37,11 @@ func (mp *Monitors) Start(ts tapir.Service, configDir string, log bool) {
|
||||||
mp.starttime = time.Now()
|
mp.starttime = time.Now()
|
||||||
mp.breakChannel = make(chan bool)
|
mp.breakChannel = make(chan bool)
|
||||||
mp.MessageCounter = NewCounter()
|
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
|
var pidUsageLock sync.Mutex
|
||||||
mp.CPU = NewMonitorHistory(Percent, Average, func() float64 {
|
mp.CPU = NewMonitorHistory(Percent, Average, func() float64 {
|
||||||
pidUsageLock.Lock()
|
pidUsageLock.Lock()
|
||||||
|
|
110
server/server.go
110
server/server.go
|
@ -11,45 +11,46 @@ import (
|
||||||
"cwtch.im/tapir/persistence"
|
"cwtch.im/tapir/persistence"
|
||||||
"cwtch.im/tapir/primitives"
|
"cwtch.im/tapir/primitives"
|
||||||
"cwtch.im/tapir/primitives/privacypass"
|
"cwtch.im/tapir/primitives/privacypass"
|
||||||
|
"fmt"
|
||||||
"git.openprivacy.ca/openprivacy/connectivity"
|
"git.openprivacy.ca/openprivacy/connectivity"
|
||||||
"git.openprivacy.ca/openprivacy/connectivity/tor"
|
"git.openprivacy.ca/openprivacy/connectivity/tor"
|
||||||
"git.openprivacy.ca/openprivacy/log"
|
"git.openprivacy.ca/openprivacy/log"
|
||||||
"os"
|
"path"
|
||||||
"time"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Server encapsulates a complete, compliant Cwtch server.
|
// Server encapsulates a complete, compliant Cwtch server.
|
||||||
type Server struct {
|
type Server struct {
|
||||||
service tapir.Service
|
service tapir.Service
|
||||||
config Config
|
config Config
|
||||||
metricsPack metrics.Monitors
|
metricsPack metrics.Monitors
|
||||||
closed bool
|
tokenTapirService tapir.Service
|
||||||
tokenTapirService tapir.Service
|
tokenServer *privacypass.TokenServer
|
||||||
tokenServer *privacypass.TokenServer
|
tokenService primitives.Identity
|
||||||
tokenService primitives.Identity
|
tokenServicePrivKey ed25519.PrivateKey
|
||||||
tokenServicePrivKey ed25519.PrivateKey
|
tokenServiceStopped bool
|
||||||
|
onionServiceStopped bool
|
||||||
|
running bool
|
||||||
|
existingMessageCount int
|
||||||
}
|
}
|
||||||
|
|
||||||
// Setup initialized a server from a given configuration
|
// Setup initialized a server from a given configuration
|
||||||
func (s *Server) Setup(serverConfig Config) {
|
func (s *Server) Setup(serverConfig Config) {
|
||||||
s.config = serverConfig
|
s.config = serverConfig
|
||||||
bs := new(persistence.BoltPersistence)
|
bs := new(persistence.BoltPersistence)
|
||||||
bs.Open("./tokens.db")
|
bs.Open(path.Join(serverConfig.ConfigDir, "tokens.db"))
|
||||||
s.tokenServer = privacypass.NewTokenServerFromStore(bs)
|
s.tokenServer = privacypass.NewTokenServerFromStore(bs)
|
||||||
s.tokenService = s.config.TokenServiceIdentity()
|
s.tokenService = s.config.TokenServiceIdentity()
|
||||||
s.tokenServicePrivKey = s.config.TokenServerPrivateKey
|
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
|
// Run starts a server with the given privateKey
|
||||||
// TODO: surface errors
|
func (s *Server) Run(acn connectivity.ACN) error {
|
||||||
// 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
|
|
||||||
|
|
||||||
addressIdentity := tor.GetTorV3Hostname(s.config.PublicKey)
|
addressIdentity := tor.GetTorV3Hostname(s.config.PublicKey)
|
||||||
|
|
||||||
//tokenService := privacypass.NewTokenServer()
|
|
||||||
identity := primitives.InitializeIdentity("", &s.config.PrivateKey, &s.config.PublicKey)
|
identity := primitives.InitializeIdentity("", &s.config.PrivateKey, &s.config.PublicKey)
|
||||||
var service tapir.Service
|
var service tapir.Service
|
||||||
service = new(tor2.BaseOnionService)
|
service = new(tor2.BaseOnionService)
|
||||||
|
@ -61,29 +62,30 @@ func (s *Server) Run(acn connectivity.ACN) {
|
||||||
ms := new(storage.MessageStore)
|
ms := new(storage.MessageStore)
|
||||||
err := ms.Init(s.config.ConfigDir, s.config.MaxBufferLines, s.metricsPack.MessageCounter)
|
err := ms.Init(s.config.ConfigDir, s.config.MaxBufferLines, s.metricsPack.MessageCounter)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorln(err)
|
return err
|
||||||
acn.Close()
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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() {
|
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.tokenTapirService.Listen(powTokenApp)
|
||||||
|
s.tokenServiceStopped = true
|
||||||
}()
|
}()
|
||||||
|
go func() {
|
||||||
for true {
|
|
||||||
s.service.Listen(NewTokenBoardServer(ms, s.tokenServer))
|
s.service.Listen(NewTokenBoardServer(ms, s.tokenServer))
|
||||||
if s.closed {
|
s.onionServiceStopped = true
|
||||||
return
|
}()
|
||||||
}
|
s.running = true
|
||||||
time.Sleep(5 * time.Second)
|
return nil
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// KeyBundle provides the signed keybundle of the server
|
// KeyBundle provides the signed keybundle of the server
|
||||||
|
@ -97,10 +99,44 @@ func (s *Server) KeyBundle() *model.KeyBundle {
|
||||||
return kb
|
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
|
// Shutdown kills the app closing all connections and freeing all goroutines
|
||||||
func (s *Server) Shutdown() {
|
func (s *Server) Shutdown() {
|
||||||
s.closed = true
|
|
||||||
s.service.Shutdown()
|
s.service.Shutdown()
|
||||||
s.tokenTapirService.Shutdown()
|
s.tokenTapirService.Shutdown()
|
||||||
s.metricsPack.Stop()
|
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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,12 +19,14 @@ type Reporting struct {
|
||||||
// Config is a struct for storing basic server configuration
|
// Config is a struct for storing basic server configuration
|
||||||
type Config struct {
|
type Config struct {
|
||||||
ConfigDir string `json:"-"`
|
ConfigDir string `json:"-"`
|
||||||
|
FilePath string `json:"-"`
|
||||||
MaxBufferLines int `json:"maxBufferLines"`
|
MaxBufferLines int `json:"maxBufferLines"`
|
||||||
PublicKey ed25519.PublicKey `json:"publicKey"`
|
PublicKey ed25519.PublicKey `json:"publicKey"`
|
||||||
PrivateKey ed25519.PrivateKey `json:"privateKey"`
|
PrivateKey ed25519.PrivateKey `json:"privateKey"`
|
||||||
TokenServerPublicKey ed25519.PublicKey `json:"tokenServerPublicKey"`
|
TokenServerPublicKey ed25519.PublicKey `json:"tokenServerPublicKey"`
|
||||||
TokenServerPrivateKey ed25519.PrivateKey `json:"tokenServerPrivateKey"`
|
TokenServerPrivateKey ed25519.PrivateKey `json:"tokenServerPrivateKey"`
|
||||||
ServerReporting Reporting `json:"serverReporting"`
|
ServerReporting Reporting `json:"serverReporting"`
|
||||||
|
AutoStart bool `json:"autostart"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Identity returns an encapsulation of the servers keys
|
// Identity returns an encapsulation of the servers keys
|
||||||
|
@ -61,6 +63,9 @@ func LoadConfig(configDir, filename string) Config {
|
||||||
ReportingGroupID: "",
|
ReportingGroupID: "",
|
||||||
ReportingServerAddr: "",
|
ReportingServerAddr: "",
|
||||||
}
|
}
|
||||||
|
config.AutoStart = false
|
||||||
|
config.ConfigDir = configDir
|
||||||
|
config.FilePath = filename
|
||||||
|
|
||||||
raw, err := ioutil.ReadFile(path.Join(configDir, filename))
|
raw, err := ioutil.ReadFile(path.Join(configDir, filename))
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
|
|
@ -139,7 +139,7 @@ func TestCwtchPeerIntegration(t *testing.T) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Could not start Tor: %v", err)
|
t.Fatalf("Could not start Tor: %v", err)
|
||||||
}
|
}
|
||||||
pid, err := acn.GetPID()
|
pid, _ := acn.GetPID()
|
||||||
t.Logf("Tor pid: %v", pid)
|
t.Logf("Tor pid: %v", pid)
|
||||||
|
|
||||||
// ***** Cwtch Server management *****
|
// ***** Cwtch Server management *****
|
||||||
|
|
Loading…
Reference in New Issue