Skip to content

Commit

Permalink
Merge branch 'release/0.5'
Browse files Browse the repository at this point in the history
  • Loading branch information
cyfdecyf committed Dec 25, 2012
2 parents c3f87b1 + 9f26f09 commit 3cec162
Show file tree
Hide file tree
Showing 17 changed files with 274 additions and 94 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
table.cache
33 changes: 23 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,21 +26,16 @@ go get github.com/shadowsocks/shadowsocks-go/cmd/shadowsocks-local

Both the server and client program will look for `config.json` in the current directory. You can use `-c` option to specify another configuration file.

The configuration syntax is the same with [shadowsocks-nodejs](https://github.com/clowwindy/shadowsocks-nodejs/). You can download the sample [`config.json`](https://github.com/shadowsocks/shadowsocks-go/blob/master/config.json), change the following values:
Configuration file is in json format and has the same syntax with [shadowsocks-nodejs](https://github.com/clowwindy/shadowsocks-nodejs/). You can download the sample [`config.json`](https://github.com/shadowsocks/shadowsocks-go/blob/master/config.json), change the following values:

```
server your server ip or hostname
server_port server port
local_port local socks5 proxy port
password a password used to encrypt transfer
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 `shadowsocks-server > log &`.

On client, run `shadowsocks-local`. Change proxy settings of your browser to
Expand All @@ -60,12 +55,30 @@ shadowsocks-server -p server_port -k password -t timeout -c config.json

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`.
## Use multiple servers on client

```
server_password specify multiple server and password, server should be in the form of host:port
```

Here's a sample configuration [`client-multi-server.json`](https://github.com/shadowsocks/shadowsocks-go/blob/master/sample-config/client-multi-server.json). Given `server_password`, client program will ignore `server_port`, `server` and `password` options.

Servers are chosen in round robin fasion. If a server can't be connected, the client will try the next one. The client does not try to detect connection problems caused by incorrect password, this is intended for the user to notice the error.

## Multiple users with different passwords on server

The server can support users with different passwords. Each user will be served by a unique port. Use the following options on the server for such setup:

```
port_password specify multiple ports and passwords to support multiple users
cache_enctable store computed encryption table on disk to speedup server startup
```

Here's a sample configuration [`server-multi-port.json`](https://github.com/shadowsocks/shadowsocks-go/blob/master/sample-config/server-multi-port.json). Given `port_password`, server program will ignore `server_port` and `password` options.

Note: unused password will not be deleted, so you may need to delete the table cache file if it grows too big.
Enabling `cache_enctable` is recommended if you have more than 20 different passwords. Unused password will not be deleted, so you may need to delete the file `table.cache` if it grows too big.

## Updating port password for a running server ##
### Update port password for a running server ###

Edit the config file used to start the server, then send `SIGHUP` to the server process.
138 changes: 111 additions & 27 deletions cmd/shadowsocks-local/local.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import (
ss "github.com/shadowsocks/shadowsocks-go/shadowsocks"
"io"
"log"
"math/rand"
"net"
"os"
"path"
Expand Down Expand Up @@ -137,7 +136,83 @@ func getRequest(conn net.Conn) (rawaddr []byte, host string, err error) {
return
}

func handleConnection(conn net.Conn, server string, encTbl *ss.EncryptTable) {
type ServerEnctbl struct {
server string
enctbl *ss.EncryptTable
}

var servers struct {
srvenc []*ServerEnctbl
idx uint8
}

func initServers(config *ss.Config) {
if len(config.ServerPassword) == 0 {
// only one encryption table
enctbl := ss.GetTable(config.Password)
srvPort := strconv.Itoa(config.ServerPort)
srvArr := config.GetServerArray()
n := len(srvArr)
servers.srvenc = make([]*ServerEnctbl, n, n)

for i, s := range srvArr {
if ss.HasPort(s) {
log.Println("ignore server_port option for server", s)
servers.srvenc[i] = &ServerEnctbl{s, enctbl}
} else {
servers.srvenc[i] = &ServerEnctbl{s + ":" + srvPort, enctbl}
}
}
} else {
n := len(config.ServerPassword)
servers.srvenc = make([]*ServerEnctbl, n, n)

tblCache := make(map[string]*ss.EncryptTable)
i := 0
for s, passwd := range config.ServerPassword {
if !ss.HasPort(s) {
log.Fatal("no port for server %s, please specify port in the form of %s:port", s, s)
}
tbl, ok := tblCache[passwd]
if !ok {
tbl = ss.GetTable(passwd)
tblCache[passwd] = tbl
}
servers.srvenc[i] = &ServerEnctbl{s, tbl}
i++
}
}
for _, se := range servers.srvenc {
log.Println("available remote server", se.server)
}
return
}

// select one server to connect in round robin order
func createServerConn(rawaddr []byte, addr string) (remote *ss.Conn, err error) {
n := len(servers.srvenc)
if n == 1 {
se := servers.srvenc[0]
debug.Printf("connecting to %s via %s\n", addr, se.server)
return ss.DialWithRawAddr(rawaddr, se.server, se.enctbl)
}

id := servers.idx
servers.idx++ // it's ok for concurrent update
for i := 0; i < n; i++ {
se := servers.srvenc[(int(id)+i)%n]
remote, err = ss.DialWithRawAddr(rawaddr, se.server, se.enctbl)
if err == nil {
debug.Printf("connected to %s via %s\n", addr, se.server)
return
} else {
log.Println("error connecting to shadowsocks server:", err)
}
}
return
}

func handleConnection(conn net.Conn) {
if debug {
debug.Printf("socks connect from %s\n", conn.RemoteAddr().String())
}
Expand All @@ -153,17 +228,20 @@ func handleConnection(conn net.Conn, server string, encTbl *ss.EncryptTable) {
log.Println("error getting request:", err)
return
}
// TODO should send error code to client if connect to server failed
// Sending connection established message immediately to client.
// This some round trip time for creating socks connection with the client.
// But if connection failed, the client will get connection reset error.
_, err = conn.Write([]byte{0x05, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x08, 0x43})
if err != nil {
debug.Println("send connection confirmation:", err)
return
}

debug.Printf("connecting to %s via %s\n", addr, server)
remote, err := ss.DialWithRawAddr(rawaddr, server, encTbl)
remote, err := createServerConn(rawaddr, addr)
if err != nil {
log.Println("error connect to shadowsocks server:", err)
if len(servers.srvenc) > 1 {
log.Println("Failed connect to all avaiable shadowsocks server")
}
return
}
defer remote.Close()
Expand All @@ -175,27 +253,19 @@ func handleConnection(conn net.Conn, server string, encTbl *ss.EncryptTable) {
debug.Println("closing")
}

func getServer(server []string) string {
if len(server) == 0 {
return server[0]
}
return server[rand.Intn(len(server))]
}

func run(port, password string, server []string) {
func run(port string) {
ln, err := net.Listen("tcp", ":"+port)
if err != nil {
log.Fatal(err)
}
encTbl := ss.GetTable(password)
log.Printf("starting local socks5 server at port %v, remote shadowsocks server %s ...\n", port, server)
log.Printf("starting local socks5 server at port %v ...\n", port)
for {
conn, err := ln.Accept()
if err != nil {
log.Println("accept:", err)
continue
}
go handleConnection(conn, getServer(server), encTbl)
go handleConnection(conn)
}
}

Expand All @@ -207,7 +277,9 @@ func enoughOptions(config *ss.Config) bool {
func main() {
var configFile, cmdServer string
var cmdConfig ss.Config
var printVer bool

flag.BoolVar(&printVer, "version", false, "print version")
flag.StringVar(&configFile, "c", "config.json", "specify config file")
flag.StringVar(&cmdServer, "s", "", "server address")
flag.StringVar(&cmdConfig.Password, "k", "", "password")
Expand All @@ -216,7 +288,14 @@ func main() {
flag.BoolVar((*bool)(&debug), "d", false, "print debug message")

flag.Parse()

if printVer {
ss.PrintVersion()
os.Exit(0)
}

cmdConfig.Server = cmdServer
ss.SetDebug(debug)

exists, err := ss.IsFileExists(configFile)
// If no config file in current directory, try search it in the binary directory
Expand All @@ -240,17 +319,22 @@ func main() {
} else {
ss.UpdateConfig(config, &cmdConfig)
}
if !enoughOptions(config) {
log.Println("must specify server address, password and both server/local port")
os.Exit(1)
}
ss.SetDebug(debug)

srvArr := config.GetServerArray()
srvPort := strconv.Itoa(config.ServerPort)
for i, _ := range srvArr {
srvArr[i] += ":" + srvPort
if len(config.ServerPassword) == 0 {
if !enoughOptions(config) {
log.Println("must specify server address, password and both server/local port")
os.Exit(1)
}
} else {
if config.Password != "" || config.ServerPort != 0 || config.GetServerArray() != nil {
log.Println("given server_password, ignore server, server_port and password option:", config)
}
if config.LocalPort == 0 {
log.Fatal("must specify local port")
}
}

run(strconv.Itoa(config.LocalPort), config.Password, srvArr)
initServers(config)

run(strconv.Itoa(config.LocalPort))
}
8 changes: 8 additions & 0 deletions cmd/shadowsocks-server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -354,14 +354,22 @@ var config *ss.Config

func main() {
var cmdConfig ss.Config
var printVer bool

flag.BoolVar(&printVer, "version", false, "print version")
flag.StringVar(&configFile, "c", "config.json", "specify config file")
flag.StringVar(&cmdConfig.Password, "k", "", "password")
flag.IntVar(&cmdConfig.ServerPort, "p", 0, "server port")
flag.IntVar(&cmdConfig.Timeout, "t", 60, "connection timeout (in seconds)")
flag.BoolVar((*bool)(&debug), "d", false, "print debug message")

flag.Parse()

if printVer {
ss.PrintVersion()
os.Exit(0)
}

ss.SetDebug(debug)

var err error
Expand Down
19 changes: 7 additions & 12 deletions config.json
Original file line number Diff line number Diff line change
@@ -1,12 +1,7 @@
{
"server":["127.0.0.1"],
"server_port":8388,
"local_port":1080,
"password":"barfoo!",
"port_password": {
"8388": "barfoo!",
"8387": "foobar!"
},
"timeout":60,
"cache_enctable":false
}
{
"server":"127.0.0.1",
"server_port":8388,
"local_port":1080,
"password":"barfoo!",
"timeout":60
}
7 changes: 7 additions & 0 deletions sample-config/client-multi-server.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"local_port":1081,
"server_password": {
"127.0.0.1:8387": "foobar",
"127.0.0.1:8388": "barfoo"
}
}
8 changes: 8 additions & 0 deletions sample-config/server-multi-port.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"port_password": {
"8387": "foobar",
"8388": "barfoo"
},
"timeout": 60,
"cache_enctable": true
}
20 changes: 16 additions & 4 deletions shadowsocks/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,24 +11,32 @@ import (
"encoding/json"
"fmt"
"io/ioutil"
"log"
"os"
"reflect"
"time"
)

type Config struct {
Server interface{} `json:"server"`
ServerPort int `json:"server_port"`
LocalPort int `json:"local_port"`
Password string `json:"password"`
Server interface{} `json:"server"`
ServerPort int `json:"server_port"`
LocalPort int `json:"local_port"`
Password string `json:"password"`

// following options are only used by server
PortPassword map[string]string `json:"port_password"`
Timeout int `json:"timeout"`
CacheEncTable bool `json:"cache_enctable"`

// following options are only used by client
ServerPassword map[string]string `json:"server_password"`
}

var readTimeout time.Duration

func (config *Config) GetServerArray() []string {
// Specifying multiple servers in the "server" options is deprecated.
// But for backward compatiblity, keep this.
if config.Server == nil {
return nil
}
Expand All @@ -38,6 +46,10 @@ func (config *Config) GetServerArray() []string {
}
arr, ok := config.Server.([]interface{})
if ok {
if len(arr) > 1 {
log.Println("Multiple servers in \"server\" option is deprecated. " +
"Please use \"server_password\" instead.")
}
serverArr := make([]string, len(arr), len(arr))
for i, s := range arr {
serverArr[i], ok = s.(string)
Expand Down
Loading

0 comments on commit 3cec162

Please sign in to comment.