Skip to content

Commit

Permalink
Merge branch 'develop'. Add feature: encryption table cache.
Browse files Browse the repository at this point in the history
  • Loading branch information
cyfdecyf committed Dec 23, 2012
2 parents 80a212f + 2236e57 commit b8ef47c
Show file tree
Hide file tree
Showing 11 changed files with 163 additions and 65 deletions.
20 changes: 16 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,21 +31,23 @@ 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

```
SOCKS5 127.0.0.1:local_port
```

# Command line options #
## Command line options ##

Command line options can override settings from configuration files.

Expand All @@ -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.
32 changes: 10 additions & 22 deletions cmd/shadowsocks-local/local.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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])
Expand All @@ -235,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)
}
Expand Down
106 changes: 88 additions & 18 deletions cmd/shadowsocks-server/server.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package main

import (
"bufio"
"bytes"
"encoding/binary"
"encoding/gob"
"errors"
"flag"
ss "github.com/shadowsocks/shadowsocks-go/shadowsocks"
Expand Down Expand Up @@ -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)
}
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -294,35 +362,37 @@ func main() {
flag.BoolVar((*bool)(&debug), "d", false, "print debug message")

flag.Parse()
ss.SetDebug(debug)

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)
}
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()
}
3 changes: 2 additions & 1 deletion config.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,6 @@
"8388": "barfoo!",
"8387": "foobar!"
},
"timeout":60
"timeout":60,
"cache_enctable":false
}
13 changes: 7 additions & 6 deletions shadowsocks/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
16 changes: 11 additions & 5 deletions shadowsocks/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
4 changes: 2 additions & 2 deletions shadowsocks/conn.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
8 changes: 4 additions & 4 deletions shadowsocks/encrypt.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ import (
)

type EncryptTable struct {
encTbl []byte
decTbl []byte
EncTbl []byte
DecTbl []byte
}

func GetTable(key string) (tbl *EncryptTable) {
Expand Down Expand Up @@ -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
}
Expand Down
4 changes: 2 additions & 2 deletions shadowsocks/encrypt_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
Expand Down
2 changes: 1 addition & 1 deletion shadowsocks/testdata/config-one-passwd.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@
"local_port":1081,
"password":"barfoo!",
"timeout":60,
"debug": true
"cache_enctable": true
}
Loading

0 comments on commit b8ef47c

Please sign in to comment.