Skip to content

Commit

Permalink
Connect to server in order, change server_password to array.
Browse files Browse the repository at this point in the history
Using map to specify multiple servers for client can't preserve the order
specified by the user, so changed the configuration to use array.
  • Loading branch information
cyfdecyf committed Mar 15, 2013
1 parent bed1f45 commit f75d779
Show file tree
Hide file tree
Showing 4 changed files with 93 additions and 36 deletions.
78 changes: 55 additions & 23 deletions cmd/shadowsocks-local/local.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
ss "github.com/shadowsocks/shadowsocks-go/shadowsocks"
"io"
"log"
"math/rand"
"net"
"os"
"path"
Expand Down Expand Up @@ -146,10 +147,10 @@ type ServerCipher struct {

var servers struct {
srvCipher []*ServerCipher
idx uint8
failCnt []int // failed connection count
}

func initServers(config *ss.Config) {
func parseServerConfig(config *ss.Config) {
if len(config.ServerPassword) == 0 {
// only one encryption table
cipher, err := ss.NewCipher(config.Method, config.Password)
Expand All @@ -170,56 +171,87 @@ func initServers(config *ss.Config) {
}
}
} else {
// multiple servers
n := len(config.ServerPassword)
servers.srvCipher = make([]*ServerCipher, n)

cipherCache := make(map[string]ss.Cipher)
i := 0
for s, passwd := range config.ServerPassword {
if !ss.HasPort(s) {
log.Fatalf("no port for server %s, please specify port in the form of %s:port\n", s, s)
for _, serverInfo := range config.ServerPassword {
if len(serverInfo) < 2 || len(serverInfo) > 3 {
log.Fatalf("server %v syntax error\n", serverInfo)
}
server := serverInfo[0]
passwd := serverInfo[1]
encmethod := ""
if len(serverInfo) == 3 {
encmethod = serverInfo[2]
}
if !ss.HasPort(server) {
log.Fatalf("no port for server %s, please specify port in the form of %s:port\n", server, server)
}
cipher, ok := cipherCache[passwd]
if !ok {
var err error
cipher, err = ss.NewCipher(config.Method, passwd)
cipher, err = ss.NewCipher(encmethod, passwd)
if err != nil {
log.Fatal("Failed generating ciphers:", err)
}
cipherCache[passwd] = cipher
}
servers.srvCipher[i] = &ServerCipher{s, cipher}
servers.srvCipher[i] = &ServerCipher{server, cipher}
i++
}
}
servers.failCnt = make([]int, len(servers.srvCipher))
for _, se := range servers.srvCipher {
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.srvCipher)
if n == 1 {
se := servers.srvCipher[0]
debug.Printf("connecting to %s via %s\n", addr, se.server)
return ss.DialWithRawAddr(rawaddr, se.server, se.cipher.Copy())
func connectToServer(serverId int, rawaddr []byte, addr string) (remote *ss.Conn, err error) {
se := servers.srvCipher[serverId]
remote, err = ss.DialWithRawAddr(rawaddr, se.server, se.cipher.Copy())
if err != nil {
log.Println("error connecting to shadowsocks server:", err)
const maxFailCnt = 50
if servers.failCnt[serverId] < maxFailCnt {
servers.failCnt[serverId]++
}
return nil, err
}
debug.Printf("connected to %s via %s\n", addr, se.server)
servers.failCnt[serverId] = 0
return
}

id := servers.idx
servers.idx++ // it's ok for concurrent update
// Connection to the server in the order specified in the config. On
// connection failure, try the next server. A failed server will be tried with
// some probability according to its fail count, so we can discover recovered
// servers.
func createServerConn(rawaddr []byte, addr string) (remote *ss.Conn, err error) {
n := len(servers.srvCipher)
skipped := make([]int, 0)
for i := 0; i < n; i++ {
se := servers.srvCipher[(int(id)+i)%n]
remote, err = ss.DialWithRawAddr(rawaddr, se.server, se.cipher.Copy())
// skip failed server, but try it with some probability
if servers.failCnt[i] > 0 && rand.Intn(servers.failCnt[i]+1) != 0 {
skipped = append(skipped, i)
continue
}
remote, err = connectToServer(i, rawaddr, addr)
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
// last resort, try skipped servers, not likely to succeed
for _, i := range skipped {
remote, err = connectToServer(i, rawaddr, addr)
if err == nil {
return
}
}
return nil, err
}

func handleConnection(conn net.Conn) {
Expand Down Expand Up @@ -354,7 +386,7 @@ func main() {
}
}

initServers(config)
parseServerConfig(config)

run(strconv.Itoa(config.LocalPort))
}
8 changes: 4 additions & 4 deletions sample-config/client-multi-server.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"local_port":1081,
"server_password": {
"127.0.0.1:8387": "foobar",
"127.0.0.1:8388": "barfoo"
}
"server_password": [
["127.0.0.1:8387", "foobar"],
["127.0.0.1:8388", "barfoo", "rc4"]
]
}
17 changes: 11 additions & 6 deletions shadowsocks/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import (
"encoding/json"
"fmt"
"io/ioutil"
"log"
// "log"
"os"
"reflect"
"time"
Expand All @@ -29,7 +29,10 @@ type Config struct {
Timeout int `json:"timeout"`

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

// The order of servers in the client config is significant, so use array
// instead of map to preserve the order.
ServerPassword [][]string `json:"server_password"`
}

var readTimeout time.Duration
Expand All @@ -46,10 +49,12 @@ 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.")
}
/*
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
26 changes: 23 additions & 3 deletions shadowsocks/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,9 +64,29 @@ func TestClientMultiServerArray(t *testing.T) {
t.Fatal("error parsing client-multi-server.json:", err)
}

if config.ServerPassword["127.0.0.1:8387"] != "foobar" ||
config.ServerPassword["127.0.0.1:8388"] != "barfoo" {
t.Error("server_password parse error")
sv := config.ServerPassword[0]
if len(sv) != 2 {
t.Fatalf("server_password 1st server wrong, have %d items\n", len(sv[0]))
}
if sv[0] != "127.0.0.1:8387" {
t.Error("server_password 1st server wrong")
}
if sv[1] != "foobar" {
t.Error("server_password 1st server passwd wrong")
}

sv = config.ServerPassword[1]
if len(sv) != 3 {
t.Fatalf("server_password 2nd server wrong, have %d items\n", len(sv[0]))
}
if sv[0] != "127.0.0.1:8388" {
t.Error("server_password 2nd server wrong")
}
if sv[1] != "barfoo" {
t.Error("server_password 2nd server passwd wrong")
}
if sv[2] != "rc4" {
t.Error("server_password 2nd server enc method wrong")
}
}

Expand Down

0 comments on commit f75d779

Please sign in to comment.