From 3ae36460ef8af4755698c8c70565be298442329a Mon Sep 17 00:00:00 2001 From: Chen Yufei Date: Sun, 23 Dec 2012 13:53:07 +0800 Subject: [PATCH 1/6] Add cache_enctable option. --- shadowsocks/config.go | 13 +++++++------ shadowsocks/config_test.go | 16 +++++++++++----- shadowsocks/testdata/config-one-passwd.json | 2 +- 3 files changed, 19 insertions(+), 12 deletions(-) diff --git a/shadowsocks/config.go b/shadowsocks/config.go index 3457903..2112501 100644 --- a/shadowsocks/config.go +++ b/shadowsocks/config.go @@ -16,12 +16,13 @@ import ( ) type Config struct { - Server string `json:"server"` - ServerPort int `json:"server_port"` - LocalPort int `json:"local_port"` - Password string `json:"password"` - PortPassword map[string]string `json:"port_password"` - Timeout int `json:"timeout"` + Server string `json:"server"` + ServerPort int `json:"server_port"` + LocalPort int `json:"local_port"` + Password string `json:"password"` + PortPassword map[string]string `json:"port_password"` + Timeout int `json:"timeout"` + CacheEncTable bool `json:"cache_enctable"` } var readTimeout time.Duration diff --git a/shadowsocks/config_test.go b/shadowsocks/config_test.go index 7e48e3e..2cb8749 100644 --- a/shadowsocks/config_test.go +++ b/shadowsocks/config_test.go @@ -5,21 +5,27 @@ import ( ) func TestParseConfig1Password(t *testing.T) { - config := ParseConfig("testdata/config-one-passwd.json") + config, err := ParseConfig("testdata/config-one-passwd.json") + if err != nil { + t.Error("error parsing single password config:", err) + } if config.Password != "barfoo!" { t.Error("wrong password from config") } - if config.Debug != true { - t.Error("debug option wrong") - } if config.Timeout != 60 { t.Error("tiemout wrong") } + if !config.CacheEncTable { + t.Error("cache_enctable should be true") + } } func TestParseConfigMultiPassword(t *testing.T) { - config := ParseConfig("testdata/config-multi-passwd.json") + config, err := ParseConfig("testdata/config-multi-passwd.json") + if err != nil { + t.Error("error parsing multi password config:", err) + } if config.Password != "barfoo!" { t.Error("wrong password from config") diff --git a/shadowsocks/testdata/config-one-passwd.json b/shadowsocks/testdata/config-one-passwd.json index 4888e49..f93817f 100644 --- a/shadowsocks/testdata/config-one-passwd.json +++ b/shadowsocks/testdata/config-one-passwd.json @@ -4,5 +4,5 @@ "local_port":1081, "password":"barfoo!", "timeout":60, - "debug": true + "cache_enctable": true } From 50bbdc3ae80b9ae651de5cde082eca8bd05c1875 Mon Sep 17 00:00:00 2001 From: Chen Yufei Date: Sun, 23 Dec 2012 14:21:14 +0800 Subject: [PATCH 2/6] Move IsFileExists into shadowsocks package as utility. --- cmd/shadowsocks-local/local.go | 16 +--------------- shadowsocks/util.go | 20 ++++++++++++++++++++ 2 files changed, 21 insertions(+), 15 deletions(-) create mode 100644 shadowsocks/util.go diff --git a/cmd/shadowsocks-local/local.go b/cmd/shadowsocks-local/local.go index dc8fcde..09453f4 100644 --- a/cmd/shadowsocks-local/local.go +++ b/cmd/shadowsocks-local/local.go @@ -196,20 +196,6 @@ func enoughOptions(config *ss.Config) bool { config.LocalPort != 0 && config.Password != "" } -func isFileExists(path string) (bool, error) { - stat, err := os.Stat(path) - if err == nil { - if stat.Mode()&os.ModeType == 0 { - return true, nil - } - return false, errors.New(path + " exists but is not regular file") - } - if os.IsNotExist(err) { - return false, nil - } - return false, err -} - func main() { var configFile string var cmdConfig ss.Config @@ -223,7 +209,7 @@ func main() { flag.Parse() - exists, err := isFileExists(configFile) + exists, err := ss.IsFileExists(configFile) // If no config file in current directory, try search it in the binary directory // Note there's no portable way to detect the binary directory. binDir := path.Dir(os.Args[0]) diff --git a/shadowsocks/util.go b/shadowsocks/util.go new file mode 100644 index 0000000..f1a0405 --- /dev/null +++ b/shadowsocks/util.go @@ -0,0 +1,20 @@ +package shadowsocks + +import ( + "errors" + "os" +) + +func IsFileExists(path string) (bool, error) { + stat, err := os.Stat(path) + if err == nil { + if stat.Mode()&os.ModeType == 0 { + return true, nil + } + return false, errors.New(path + " exists but is not regular file") + } + if os.IsNotExist(err) { + return false, nil + } + return false, err +} From b537a97325dbe53e0ba0cd86a487fc2e977e7d29 Mon Sep 17 00:00:00 2001 From: Chen Yufei Date: Sun, 23 Dec 2012 15:14:23 +0800 Subject: [PATCH 3/6] Export table in EncTable for gob package. --- shadowsocks/conn.go | 4 ++-- shadowsocks/encrypt.go | 8 ++++---- shadowsocks/encrypt_test.go | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/shadowsocks/conn.go b/shadowsocks/conn.go index 8f94391..13b67cd 100644 --- a/shadowsocks/conn.go +++ b/shadowsocks/conn.go @@ -70,13 +70,13 @@ func (c Conn) Read(b []byte) (n int, err error) { buf := make([]byte, len(b), len(b)) n, err = c.Conn.Read(buf) if n > 0 { - encrypt2(c.decTbl, buf[0:n], b[0:n]) + encrypt2(c.DecTbl, buf[0:n], b[0:n]) } return } func (c Conn) Write(b []byte) (n int, err error) { - buf := encrypt(c.encTbl, b) + buf := encrypt(c.EncTbl, b) n, err = c.Conn.Write(buf) return } diff --git a/shadowsocks/encrypt.go b/shadowsocks/encrypt.go index c7dcda8..32b1250 100644 --- a/shadowsocks/encrypt.go +++ b/shadowsocks/encrypt.go @@ -8,8 +8,8 @@ import ( ) type EncryptTable struct { - encTbl []byte - decTbl []byte + EncTbl []byte + DecTbl []byte } func GetTable(key string) (tbl *EncryptTable) { @@ -38,10 +38,10 @@ func GetTable(key string) (tbl *EncryptTable) { }) } for i = 0; i < tbl_size; i++ { - tbl.encTbl[i] = byte(table[i]) + tbl.EncTbl[i] = byte(table[i]) } for i = 0; i < tbl_size; i++ { - tbl.decTbl[tbl.encTbl[i]] = byte(i) + tbl.DecTbl[tbl.EncTbl[i]] = byte(i) } return } diff --git a/shadowsocks/encrypt_test.go b/shadowsocks/encrypt_test.go index 7758493..d039adb 100644 --- a/shadowsocks/encrypt_test.go +++ b/shadowsocks/encrypt_test.go @@ -8,10 +8,10 @@ const tbl_size = 256 func checkTable(t *testing.T, tbl *EncryptTable, encTarget, decTarget []byte, msg string) { for i := 0; i < tbl_size; i++ { - if encTarget[i] != tbl.encTbl[i] { + if encTarget[i] != tbl.EncTbl[i] { t.Fatalf("%s: encrypt table error at index %d\n", msg, i) } - if decTarget[i] != tbl.decTbl[i] { + if decTarget[i] != tbl.DecTbl[i] { t.Fatalf("%s: decrypt table error at index %d\n", msg, i) } } From 0bfd627a4480cb27f900709632f74f28420bf572 Mon Sep 17 00:00:00 2001 From: Chen Yufei Date: Sun, 23 Dec 2012 16:05:11 +0800 Subject: [PATCH 4/6] Add encrypt table on disk cache. --- cmd/shadowsocks-server/server.go | 96 ++++++++++++++++++++++++++++---- config.json | 3 +- 2 files changed, 86 insertions(+), 13 deletions(-) diff --git a/cmd/shadowsocks-server/server.go b/cmd/shadowsocks-server/server.go index e6cac64..23b76c5 100644 --- a/cmd/shadowsocks-server/server.go +++ b/cmd/shadowsocks-server/server.go @@ -1,8 +1,10 @@ package main import ( + "bufio" "bytes" "encoding/binary" + "encoding/gob" "errors" "flag" ss "github.com/shadowsocks/shadowsocks-go/shadowsocks" @@ -125,22 +127,88 @@ func handleConnection(conn *ss.Conn) { return } -// Add a encrypt table cache to save memory and startup time in case of many -// same password. -// If startup time becomes an issue, save the encrypt table on disk. -var tableCache = map[string]*ss.EncryptTable{} -var tableGetCnt int32 +const tableCacheFile = "table.cache" + +var table struct { + cache map[string]*ss.EncryptTable + getCnt int32 + hitCnt int32 +} + +func initTableCache(config *ss.Config) { + var exists bool + var err error + if !config.CacheEncTable { + goto emptyCache + } + exists, err = ss.IsFileExists(tableCacheFile) + if exists { + // load table cache from file + f, err := os.Open(tableCacheFile) + if err != nil { + log.Println("error opening table cache:", err) + goto emptyCache + } + defer f.Close() + + dec := gob.NewDecoder(bufio.NewReader(f)) + if err = dec.Decode(&table.cache); err != nil { + log.Println("error loading table cache:", err) + goto emptyCache + } + debug.Println("table cache loaded from disk") + return + } + if err != nil { + log.Println("table cache:", err) + } + +emptyCache: + debug.Println("creating empty table cache") + table.cache = map[string]*ss.EncryptTable{} +} + +func storeTableCache(config *ss.Config) { + if !config.CacheEncTable || table.getCnt == table.hitCnt { + return + } + + const tmpPath = "tmp.cache" + f, err := os.Create(tmpPath) + if err != nil { + log.Println("can't create tmp table cache") + return + } + defer f.Close() + + w := bufio.NewWriter(f) + enc := gob.NewEncoder(w) + if err = enc.Encode(table.cache); err != nil { + log.Println("error saving tmp table cache:", err) + return + } + if err = w.Flush(); err != nil { + log.Println("error flushing table cache:", err) + return + } + if err = os.Rename(tmpPath, tableCacheFile); err != nil { + log.Printf("error renaming %s to %s: %v\n", tmpPath, tableCacheFile, err) + return + } + debug.Println("table cache saved") +} func getTable(password string) (tbl *ss.EncryptTable) { - if tableCache != nil { + if table.cache != nil { var ok bool - tbl, ok = tableCache[password] + tbl, ok = table.cache[password] if ok { + atomic.AddInt32(&table.hitCnt, 1) debug.Println("table cache hit for password:", password) return } tbl = ss.GetTable(password) - tableCache[password] = tbl + table.cache[password] = tbl } else { tbl = ss.GetTable(password) } @@ -248,7 +316,7 @@ func run(port, password string) { } passwdManager.add(port, password, ln) encTbl := getTable(password) - atomic.AddInt32(&tableGetCnt, 1) + atomic.AddInt32(&table.getCnt, 1) log.Printf("server listening port %v ...\n", port) for { conn, err := ln.Accept() @@ -294,6 +362,7 @@ func main() { flag.BoolVar((*bool)(&debug), "d", false, "print debug message") flag.Parse() + ss.SetDebug(debug) var err error config, err = ss.ParseConfig(configFile) @@ -310,19 +379,22 @@ func main() { } else { ss.UpdateConfig(config, &cmdConfig) } - ss.SetDebug(debug) if err = unifyPortPassword(config); err != nil { os.Exit(1) } + + initTableCache(config) for port, password := range config.PortPassword { go run(port, password) } // Wait all ports have get it's encryption table - for int(tableGetCnt) != len(config.PortPassword) { + for int(table.getCnt) != len(config.PortPassword) { time.Sleep(1 * time.Second) } + storeTableCache(config) log.Println("all ports ready") - tableCache = nil // release memory + + table.cache = nil // release memory waitSignal() } diff --git a/config.json b/config.json index 0685796..afacf8d 100644 --- a/config.json +++ b/config.json @@ -7,5 +7,6 @@ "8388": "barfoo!", "8387": "foobar!" }, - "timeout":60 + "timeout":60, + "cache_enctable":false } From 848f3c0e1c5c7fc3e69a704e0b13c4658fafbf8e Mon Sep 17 00:00:00 2001 From: Chen Yufei Date: Sun, 23 Dec 2012 16:45:41 +0800 Subject: [PATCH 5/6] Simplify command line option checking. --- cmd/shadowsocks-local/local.go | 16 +++++++++------- cmd/shadowsocks-server/server.go | 10 ++++------ 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/cmd/shadowsocks-local/local.go b/cmd/shadowsocks-local/local.go index 09453f4..1dae00e 100644 --- a/cmd/shadowsocks-local/local.go +++ b/cmd/shadowsocks-local/local.go @@ -221,15 +221,17 @@ func main() { config, err := ss.ParseConfig(configFile) if err != nil { - enough := enoughOptions(&cmdConfig) - if !(enough && os.IsNotExist(err)) { + config = &cmdConfig + if os.IsNotExist(err) { + if !enoughOptions(config) { + log.Println("must specify server address, password and both server/local port") + os.Exit(1) + } + log.Println("using all options from command line") + } else { log.Printf("error reading config file: %v\n", err) + os.Exit(1) } - if !enough { - return - } - log.Println("using all options from command line") - config = &cmdConfig } else { ss.UpdateConfig(config, &cmdConfig) } diff --git a/cmd/shadowsocks-server/server.go b/cmd/shadowsocks-server/server.go index 23b76c5..c74d255 100644 --- a/cmd/shadowsocks-server/server.go +++ b/cmd/shadowsocks-server/server.go @@ -367,14 +367,12 @@ func main() { var err error config, err = ss.ParseConfig(configFile) if err != nil { - enough := enoughOptions(&cmdConfig) - if !(enough && os.IsNotExist(err)) { + if os.IsNotExist(err) { + log.Println("using all options from command line") + } else { log.Printf("error reading %s: %v\n", configFile, err) + os.Exit(1) } - if !enough { - return - } - log.Println("using all options from command line") config = &cmdConfig } else { ss.UpdateConfig(config, &cmdConfig) From 2236e57dcd8a1ef637cb4f63bcbc432c9f10bb78 Mon Sep 17 00:00:00 2001 From: Chen Yufei Date: Sun, 23 Dec 2012 17:49:02 +0800 Subject: [PATCH 6/6] Update README. Add description about updating password for running server and cache encryption table. --- README.md | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index de463b8..59b6d17 100644 --- a/README.md +++ b/README.md @@ -31,13 +31,15 @@ server your server ip or hostname server_port server port local_port local socks5 proxy port password a password used to encrypt transfer -timeout in seconds, used by server -port_password specify multiple ports and passwords to support multiple users, used by server + +timeout server option, in seconds +port_password server option, specify multiple ports and passwords to support multiple users +cache_enctable server option, store computed encryption table on disk to speedup server startup ``` Given `port_password` option, server program will ignore `server_port` and `password` options. -Run `shadowsocks-server` on your server. To run it in the background, run `nohup shadowsocks-server > log &`. +Run `shadowsocks-server` on your server. To run it in the background, run `shadowsocks-server > log &`. On client, run `shadowsocks-local`. Change proxy settings of your browser to @@ -45,7 +47,7 @@ On client, run `shadowsocks-local`. Change proxy settings of your browser to SOCKS5 127.0.0.1:local_port ``` -# Command line options # +## Command line options ## Command line options can override settings from configuration files. @@ -55,3 +57,13 @@ shadowsocks-server -p server_port -k password -t timeout ``` Use `-d` option to enable debug message. + +## Encryption table cache ## + +If the server has many different passwords, startup would be slow because it takes much time to calculate encryption tables. It's recommended to enable the `cache_enctable` option if you have more than 20 different passwords. This will save the computed encryption table in the file `table.cache`. + +Note: unused password will not be deleted, so you may need to delete the table cache file if it grows too big. + +## Updating port password for a running server ## + +Edit the config file used to start the server, then send `SIGHUP` to the server process.