diff --git a/README.md b/README.md index d2396ba..5c89988 100644 --- a/README.md +++ b/README.md @@ -43,19 +43,43 @@ Usage: cashshuffle [flags] Flags: - -a, --auto-cert string register hostname with LetsEncrypt - -b, --bind-ip string IP address to bind to - -c, --cert string path to server.crt for TLS - -d, --debug debug mode - -h, --help help for cashshuffle - -k, --key string path to server.key for TLS - -s, --pool-size int pool size (default 5) - -p, --port int server port (default 1337) - -z, --stats-port int stats server port (default 8080) - -v, --version display version - -w, --websocket-port int websocket port (default 1338) + -a, --auto-cert string register hostname with LetsEncrypt + -b, --bind-ip string IP address to bind to + -c, --cert string path to server.crt for TLS + -d, --debug debug mode + -h, --help help for cashshuffle + -k, --key string path to server.key for TLS + -s, --pool-size int pool size (default 5) + -p, --port int server port (default 1337) + -z, --stats-port int stats server port (default 8080) + -t, --tor enable secondary listener for tor connections + --tor-bind-ip string IP address to bind to for tor (default "127.0.0.1") + --tor-port int tor server port (default 1339) + --tor-stats-port int tor stats server port (default 8081) + --tor-websocket-port int tor websocket port (default 1340) + -v, --version display version + -w, --websocket-port int websocket port (default 1338) ``` +## Tor + +To run a server on the public internet with SSL and also support Tor just use the `--tor` flag. + +``` +cashshuffle -s 5 -c -k --tor +``` + +Now edit your `torrc` and add the following. Then restart Tor for the configuration to take effect. + +``` +HiddenServiceDir /var/lib/tor/cashshuffle +HiddenServicePort 1339 127.0.0.1:1339 +HiddenServicePort 1340 127.0.0.1:1340 +HiddenServicePort 8081 127.0.0.1:8081 +``` + +For more docs on setting up onion services you can check out https://www.torproject.org/docs/tor-onion-service.html.en. + ## License cashshuffle is released under the MIT license. diff --git a/cmd/config.go b/cmd/config.go index 02c05c7..3380766 100644 --- a/cmd/config.go +++ b/cmd/config.go @@ -11,16 +11,21 @@ import ( // Config stores all the application configuration. type Config struct { - DisplayVersion bool `json:"-"` - Port int `json:"port,string"` - StatsPort int `json:"stats_port,string"` - WebSocketPort int `json:"websocket_port,string"` - Cert string `json:"cert"` - Key string `json:"key"` - PoolSize int `json:"pool_size,string"` - Debug bool `json:"debug,string"` - AutoCert string `json:"auto_cert"` - BindIP string `json:"bind_ip"` + DisplayVersion bool `json:"-"` + Port int `json:"port,string"` + StatsPort int `json:"stats_port,string"` + WebSocketPort int `json:"websocket_port,string"` + Cert string `json:"cert"` + Key string `json:"key"` + PoolSize int `json:"pool_size,string"` + Debug bool `json:"debug,string"` + AutoCert string `json:"auto_cert"` + BindIP string `json:"bind_ip"` + Tor bool `json:"tor,string"` + TorBindIP string `json:"tor_bind_ip"` + TorPort int `json:"tor_port,string"` + TorStatsPort int `json:"tor_stats_port,string"` + TorWebSocketPort int `json:"tor_websocket_port,string"` } // Load reads the configuration from ~/.cashshuffle/config and loads it into the Config struct. diff --git a/cmd/main.go b/cmd/main.go index 077feb3..3fa02e2 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -14,12 +14,16 @@ import ( ) const ( - appName = "cashshuffle" - version = "0.6.0" - defaultPort = 1337 - defaultWebSocketPort = 1338 - defaultStatsPort = 8080 - defaultPoolSize = 5 + appName = "cashshuffle" + version = "0.6.1" + defaultPort = 1337 + defaultWebSocketPort = 1338 + defaultTorPort = 1339 + defaultTorWebSocketPort = 1340 + defaultStatsPort = 8080 + defaultTorStatsPort = 8081 + defaultPoolSize = 5 + defaultTorBindIP = "127.0.0.1" ) // Stores configuration data. @@ -65,6 +69,22 @@ func prepareFlags() { config.StatsPort = defaultStatsPort } + if config.TorBindIP == "" { + config.TorBindIP = defaultTorBindIP + } + + if config.TorPort == 0 { + config.TorPort = defaultTorPort + } + + if config.TorWebSocketPort == 0 { + config.TorWebSocketPort = defaultTorWebSocketPort + } + + if config.TorStatsPort == 0 { + config.TorStatsPort = defaultTorStatsPort + } + if config.PoolSize == 0 { config.PoolSize = defaultPoolSize } @@ -89,6 +109,16 @@ func prepareFlags() { &config.AutoCert, "auto-cert", "a", config.AutoCert, "register hostname with LetsEncrypt") MainCmd.PersistentFlags().StringVarP( &config.BindIP, "bind-ip", "b", config.BindIP, "IP address to bind to") + MainCmd.PersistentFlags().BoolVarP( + &config.Tor, "tor", "t", config.Tor, "enable secondary listener for tor connections") + MainCmd.PersistentFlags().StringVarP( + &config.TorBindIP, "tor-bind-ip", "", config.TorBindIP, "IP address to bind to for tor") + MainCmd.PersistentFlags().IntVarP( + &config.TorPort, "tor-port", "", config.TorPort, "tor server port") + MainCmd.PersistentFlags().IntVarP( + &config.TorWebSocketPort, "tor-websocket-port", "", config.TorWebSocketPort, "tor websocket port") + MainCmd.PersistentFlags().IntVarP( + &config.TorStatsPort, "tor-stats-port", "", config.TorStatsPort, "tor stats server port") } // Where all the work happens. @@ -102,7 +132,7 @@ func performCommand(cmd *cobra.Command, args []string) error { return errors.New("can't specify auto-cert and key/cert") } - t := server.NewTracker(config.PoolSize, config.Port, config.WebSocketPort) + t := server.NewTracker(config.PoolSize, config.Port, config.WebSocketPort, config.TorPort, config.TorWebSocketPort) m, err := getLetsEncryptManager() if err != nil { @@ -111,15 +141,28 @@ func performCommand(cmd *cobra.Command, args []string) error { // enable stats if port specified if config.StatsPort > 0 { - go server.StartStatsServer(config.BindIP, config.StatsPort, config.Cert, config.Key, t, m) + go server.StartStatsServer(config.BindIP, config.StatsPort, config.Cert, config.Key, t, m, false) + } + + if config.Tor && config.TorStatsPort > 0 { + go server.StartStatsServer(config.TorBindIP, config.TorStatsPort, "", "", t, nil, true) } // enable websocket port if specified. if config.WebSocketPort > 0 { - go server.StartWebsocket(config.BindIP, config.WebSocketPort, config.Cert, config.Key, config.Debug, t, m) + go server.StartWebsocket(config.BindIP, config.WebSocketPort, config.Cert, config.Key, config.Debug, t, m, false) + } + + if config.Tor && config.TorWebSocketPort > 0 { + go server.StartWebsocket(config.TorBindIP, config.TorWebSocketPort, "", "", config.Debug, t, nil, true) + } + + // enable tor server if specified. + if config.Tor { + go server.Start(config.TorBindIP, config.TorPort, "", "", config.Debug, t, nil, true) } - return server.Start(config.BindIP, config.Port, config.Cert, config.Key, config.Debug, t, m) + return server.Start(config.BindIP, config.Port, config.Cert, config.Key, config.Debug, t, m, false) } func getLetsEncryptManager() (*autocert.Manager, error) { diff --git a/server/server.go b/server/server.go index aa4d789..2e8c8db 100644 --- a/server/server.go +++ b/server/server.go @@ -14,7 +14,7 @@ import ( var debugMode bool // Start brings up the TCP server. -func Start(ip string, port int, cert string, key string, debug bool, t *Tracker, m *autocert.Manager) (err error) { +func Start(ip string, port int, cert string, key string, debug bool, t *Tracker, m *autocert.Manager, tor bool) (err error) { var listener net.Listener debugMode = debug @@ -36,7 +36,12 @@ func Start(ip string, port int, cert string, key string, debug bool, t *Tracker, packetInfoChan := make(chan *packetInfo) go startPacketInfoChan(packetInfoChan) - fmt.Printf("Shuffle Listening on TCP %s:%d (pool size: %d)\n", ip, port, t.poolSize) + torStr := "" + if tor { + torStr = "Tor" + } + + fmt.Printf("%sShuffle Listening on TCP %s:%d (pool size: %d)\n", torStr, ip, port, t.poolSize) for { conn, err := listener.Accept() if err != nil { @@ -48,7 +53,7 @@ func Start(ip string, port int, cert string, key string, debug bool, t *Tracker, } // StartWebsocket brings up the websocket server. -func StartWebsocket(ip string, port int, cert string, key string, debug bool, t *Tracker, m *autocert.Manager) (err error) { +func StartWebsocket(ip string, port int, cert string, key string, debug bool, t *Tracker, m *autocert.Manager, tor bool) (err error) { packetInfoChan := make(chan *packetInfo) go startPacketInfoChan(packetInfoChan) @@ -78,7 +83,12 @@ func StartWebsocket(ip string, port int, cert string, key string, debug bool, t srv.TLSConfig.GetCertificate = m.GetCertificate } - fmt.Printf("Shuffle Listening via Websockets on %s:%d\n", ip, port) + torStr := "" + if tor { + torStr = "Tor" + } + + fmt.Printf("%sShuffle Listening via Websockets on %s:%d\n", torStr, ip, port) if tlsEnabled(cert, key, m) { err = srv.ListenAndServeTLS(cert, key) diff --git a/server/stats.go b/server/stats.go index 2d83562..9641753 100644 --- a/server/stats.go +++ b/server/stats.go @@ -2,7 +2,7 @@ package server // StatsInformer defines an interface that exposes tracker stats type StatsInformer interface { - Stats(string) *TrackerStats + Stats(string, bool) *TrackerStats } // TrackerStats represents a snapshot of the trackers statistics @@ -26,7 +26,7 @@ type PoolStats struct { } // Stats returns the tracker stats. -func (t *Tracker) Stats(ip string) *TrackerStats { +func (t *Tracker) Stats(ip string, tor bool) *TrackerStats { t.mutex.RLock() defer t.mutex.RUnlock() @@ -41,14 +41,24 @@ func (t *Tracker) Stats(ip string) *TrackerStats { } } + sp := t.shufflePort + if tor { + sp = t.torShufflePort + } + + wssp := t.shuffleWebSocketPort + if tor { + wssp = t.torShuffleWebSocketPort + } + ts := &TrackerStats{ BanScore: banScore, Banned: banned, Connections: len(t.connections), PoolSize: t.poolSize, Pools: make([]PoolStats, 0), - ShufflePort: t.shufflePort, - ShuffleWebSocketPort: t.shuffleWebSocketPort, + ShufflePort: sp, + ShuffleWebSocketPort: wssp, } for k, p := range t.pools { diff --git a/server/stats_server.go b/server/stats_server.go index bd9addc..f38b28c 100644 --- a/server/stats_server.go +++ b/server/stats_server.go @@ -12,22 +12,28 @@ import ( ) // StartStatsServer creates a new server to serve stats -func StartStatsServer(ip string, port int, cert string, key string, si StatsInformer, m *autocert.Manager) error { +func StartStatsServer(ip string, port int, cert string, key string, si StatsInformer, m *autocert.Manager, tor bool) error { mux := http.NewServeMux() - mux.HandleFunc("/stats", statsJSON(si)) + mux.HandleFunc("/stats", statsJSON(si, tor)) s := newStatsServer(fmt.Sprintf("%s:%d", ip, port), mux, m) tls := tlsEnabled(cert, key, m) - fmt.Printf("Stats Listening on TCP %s:%d (tls: %v)\n", ip, port, tls) + + torStr := "" + if tor { + torStr = "Tor" + } + + fmt.Printf("%sStats Listening on TCP %s:%d (tls: %v)\n", torStr, ip, port, tls) if tls { return s.ListenAndServeTLS(cert, key) } return s.ListenAndServe() } -func statsJSON(si StatsInformer) func(http.ResponseWriter, *http.Request) { +func statsJSON(si StatsInformer, tor bool) func(http.ResponseWriter, *http.Request) { return func(w http.ResponseWriter, r *http.Request) { ip, _, _ := net.SplitHostPort(r.RemoteAddr) - b, _ := json.Marshal(si.Stats(ip)) + b, _ := json.Marshal(si.Stats(ip, tor)) w.Header().Set("Content-Type", "application/json") w.Write(b) } diff --git a/server/stats_test.go b/server/stats_test.go index f5fd271..a5c088a 100644 --- a/server/stats_test.go +++ b/server/stats_test.go @@ -50,8 +50,11 @@ func TestTrackStats(t *testing.T) { fullPools: map[int]interface{}{ 1: nil, }, - poolSize: 5, - shufflePort: 3000, + poolSize: 5, + shufflePort: 3000, + shuffleWebSocketPort: 3001, + torShufflePort: 3002, + torShuffleWebSocketPort: 3003, bannedIPs: map[string]*banData{ "8.8.8.8": { score: maxBanScore, @@ -63,7 +66,7 @@ func TestTrackStats(t *testing.T) { } // Test with ban. - stats := tracker.Stats("8.8.8.8") + stats := tracker.Stats("8.8.8.8", false) assert.Equal(t, uint32(3), stats.BanScore) assert.Equal(t, true, stats.Banned) @@ -71,6 +74,7 @@ func TestTrackStats(t *testing.T) { assert.Equal(t, 5, stats.PoolSize) assert.Equal(t, 2, len(stats.Pools)) assert.Equal(t, 3000, stats.ShufflePort) + assert.Equal(t, 3001, stats.ShuffleWebSocketPort) assert.Contains(t, stats.Pools, PoolStats{ Members: 5, @@ -89,14 +93,15 @@ func TestTrackStats(t *testing.T) { ) // Test without ban. - stats2 := tracker.Stats("8.8.4.4") + stats2 := tracker.Stats("8.8.4.4", true) assert.Equal(t, uint32(2), stats2.BanScore) assert.Equal(t, false, stats2.Banned) assert.Equal(t, 8, stats2.Connections) assert.Equal(t, 5, stats2.PoolSize) assert.Equal(t, 2, len(stats2.Pools)) - assert.Equal(t, 3000, stats2.ShufflePort) + assert.Equal(t, 3002, stats2.ShufflePort) + assert.Equal(t, 3003, stats2.ShuffleWebSocketPort) assert.Contains(t, stats2.Pools, PoolStats{ Members: 5, diff --git a/server/tracker.go b/server/tracker.go index 637a9d0..3d451ed 100644 --- a/server/tracker.go +++ b/server/tracker.go @@ -23,18 +23,20 @@ const ( // Tracker is used to track connections to the server. type Tracker struct { - bannedIPs map[string]*banData - connections map[net.Conn]*trackerData - verificationKeys map[string]net.Conn - mutex sync.RWMutex - pools map[int]map[uint32]interface{} - poolAmounts map[int]uint64 - poolVersions map[int]uint64 - poolTypes map[int]message.ShuffleType - poolSize int - fullPools map[int]interface{} - shufflePort int - shuffleWebSocketPort int + bannedIPs map[string]*banData + connections map[net.Conn]*trackerData + verificationKeys map[string]net.Conn + mutex sync.RWMutex + pools map[int]map[uint32]interface{} + poolAmounts map[int]uint64 + poolVersions map[int]uint64 + poolTypes map[int]message.ShuffleType + poolSize int + fullPools map[int]interface{} + shufflePort int + shuffleWebSocketPort int + torShufflePort int + torShuffleWebSocketPort int } // banData is the data required to track IP bans. @@ -57,19 +59,21 @@ type trackerData struct { } // NewTracker instantiates a new tracker -func NewTracker(poolSize int, shufflePort int, shuffleWebSocketPort int) *Tracker { +func NewTracker(poolSize int, shufflePort int, shuffleWebSocketPort int, torShufflePort int, torShuffleWebSocketPort int) *Tracker { return &Tracker{ - poolSize: poolSize, - bannedIPs: make(map[string]*banData), - connections: make(map[net.Conn]*trackerData), - verificationKeys: make(map[string]net.Conn), - pools: make(map[int]map[uint32]interface{}), - poolAmounts: make(map[int]uint64), - poolVersions: make(map[int]uint64), - poolTypes: make(map[int]message.ShuffleType), - fullPools: make(map[int]interface{}), - shufflePort: shufflePort, - shuffleWebSocketPort: shuffleWebSocketPort, + poolSize: poolSize, + bannedIPs: make(map[string]*banData), + connections: make(map[net.Conn]*trackerData), + verificationKeys: make(map[string]net.Conn), + pools: make(map[int]map[uint32]interface{}), + poolAmounts: make(map[int]uint64), + poolVersions: make(map[int]uint64), + poolTypes: make(map[int]message.ShuffleType), + fullPools: make(map[int]interface{}), + shufflePort: shufflePort, + shuffleWebSocketPort: shuffleWebSocketPort, + torShufflePort: torShufflePort, + torShuffleWebSocketPort: torShuffleWebSocketPort, } }