From 31243563379b3cc1b2c3d97504129ade00468a42 Mon Sep 17 00:00:00 2001 From: Chen Yufei Date: Wed, 19 Dec 2012 12:54:46 +0800 Subject: [PATCH 1/5] shadowsocks-local: try config.json in binary dir if no config file is found. This make it easy to run the client program by double click. --- cmd/shadowsocks-local/local.go | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/cmd/shadowsocks-local/local.go b/cmd/shadowsocks-local/local.go index 0a588bd..5486ac6 100644 --- a/cmd/shadowsocks-local/local.go +++ b/cmd/shadowsocks-local/local.go @@ -10,6 +10,7 @@ import ( "log" "net" "os" + "path" "strconv" ) @@ -143,7 +144,7 @@ func handleConnection(conn net.Conn, server string, encTbl *ss.EncryptTable) { var err error = nil if err = handShake(conn); err != nil { - log.Println("socks handshack:", err) + log.Println("socks handshake:", err) return } rawaddr, addr, err := getRequest(conn) @@ -195,6 +196,20 @@ 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 @@ -208,11 +223,20 @@ func main() { flag.Parse() + exists, err := isFileExists(configFile) + // if no config file in current directory, search it in the binary directory + if !exists || err != nil { + baseDir := path.Dir(os.Args[0]) + oldConfig := configFile + configFile = path.Join(baseDir, "config.json") + log.Printf("%s not found, try config file %s\n", oldConfig, configFile) + } + config, err := ss.ParseConfig(configFile) if err != nil { enough := enoughOptions(&cmdConfig) if !(enough && os.IsNotExist(err)) { - log.Printf("error reading %s: %v\n", configFile, err) + log.Printf("error reading config file: %v\n", err) } if !enough { return From a8c185f56ce68446a314b16fbd148f8e34100be6 Mon Sep 17 00:00:00 2001 From: Chen Yufei Date: Wed, 19 Dec 2012 13:12:55 +0800 Subject: [PATCH 2/5] Do not try another config file if dir of args[0] is current dir. --- cmd/shadowsocks-local/local.go | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/cmd/shadowsocks-local/local.go b/cmd/shadowsocks-local/local.go index 5486ac6..dc8fcde 100644 --- a/cmd/shadowsocks-local/local.go +++ b/cmd/shadowsocks-local/local.go @@ -224,11 +224,12 @@ func main() { flag.Parse() exists, err := isFileExists(configFile) - // if no config file in current directory, search it in the binary directory - if !exists || err != nil { - baseDir := path.Dir(os.Args[0]) + // 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]) + if (!exists || err != nil) && binDir != "" && binDir != "." { oldConfig := configFile - configFile = path.Join(baseDir, "config.json") + configFile = path.Join(binDir, "config.json") log.Printf("%s not found, try config file %s\n", oldConfig, configFile) } From 94171a361d90d4f4e12f94994788fc8cb6978ae1 Mon Sep 17 00:00:00 2001 From: Chen Yufei Date: Wed, 19 Dec 2012 21:59:14 +0800 Subject: [PATCH 3/5] Update password upon SIGHUP. --- cmd/shadowsocks-server/server.go | 150 +++++++++++++++++++++++++++---- 1 file changed, 134 insertions(+), 16 deletions(-) diff --git a/cmd/shadowsocks-server/server.go b/cmd/shadowsocks-server/server.go index a41972e..21a9806 100644 --- a/cmd/shadowsocks-server/server.go +++ b/cmd/shadowsocks-server/server.go @@ -10,8 +10,11 @@ import ( "log" "net" "os" + "os/signal" "strconv" + "sync" "sync/atomic" + "syscall" "time" ) @@ -123,29 +126,138 @@ var tableCache = map[string]*ss.EncryptTable{} var tableGetCnt int32 func getTable(password string) (tbl *ss.EncryptTable) { - tbl, ok := tableCache[password] - if ok { - debug.Println("table cache hit for password:", password) - return + if tableCache != nil { + var ok bool + tbl, ok = tableCache[password] + if ok { + debug.Println("table cache hit for password:", password) + return + } + tbl = ss.GetTable(password) + tableCache[password] = tbl + } else { + tbl = ss.GetTable(password) } - tbl = ss.GetTable(password) - tableCache[password] = tbl return } +type PortListener struct { + password string + listener net.Listener +} + +type PasswdManager struct { + sync.Mutex + portListener map[string]*PortListener +} + +func (pm *PasswdManager) add(port, password string, listener net.Listener) { + pm.Lock() + pm.portListener[port] = &PortListener{password, listener} + pm.Unlock() +} + +func (pm *PasswdManager) get(port string) (pl *PortListener, ok bool) { + pm.Lock() + pl, ok = pm.portListener[port] + pm.Unlock() + return +} + +func (pm *PasswdManager) del(port string) { + pl, ok := pm.get(port) + if !ok { + return + } + pl.listener.Close() + pm.Lock() + delete(pm.portListener, port) + pm.Unlock() +} + +func (pm *PasswdManager) updatePortPasswd(port, password string, oldconfig *ss.Config) { + if oldconfig.PortPassword != nil { + delete(oldconfig.PortPassword, port) + } + pl, ok := pm.get(port) + if !ok { + log.Printf("new port %s added\n", port) + } else { + if pl.password == password { + return + } + log.Printf("closing port %s to update password\n", port) + pl.listener.Close() + } + // run will add the new port listener to passwdManager. + // So there maybe concurrent access to passwdManager and we need lock to protect it. + go run(port, password) +} + +var passwdManager = PasswdManager{portListener: map[string]*PortListener{}} + +func updatePasswd() { + log.Println("updating password") + newconfig, err := ss.ParseConfig(configFile) + if err != nil { + log.Printf("error parsing config file %s to update password: %v\n", configFile, err) + return + } + oldconfig := config + config = newconfig + if len(config.PortPassword) == 0 { + if !enoughOptions(config) { + log.Println("must specify both password and server port in config") + return + } + port := strconv.Itoa(config.ServerPort) + passwdManager.updatePortPasswd(port, config.Password, oldconfig) + } else { + if config.ServerPort != 0 || config.Password != "" { + log.Println("ignoring server_port and password option, only uses port_password") + } + for port, passwd := range config.PortPassword { + passwdManager.updatePortPasswd(port, passwd, oldconfig) + } + } + // port password left in the old config should be deleted + for port, _ := range oldconfig.PortPassword { + log.Printf("closing port %s as it's deleted\n", port) + passwdManager.del(port) + } + log.Println("password updated") +} + +func waitSignal() { + var sigChan = make(chan os.Signal, 1) + signal.Notify(sigChan, syscall.SIGHUP) + for sig := range sigChan { + if sig == syscall.SIGHUP { + updatePasswd() + } else { + // is this going to happen? + log.Printf("caught signal %v, exit", sig) + os.Exit(0) + } + } +} + func run(port, password string) { ln, err := net.Listen("tcp", ":"+port) if err != nil { - log.Fatal(err) + log.Printf("try listening port %v: %v\n", port, err) + return } + passwdManager.add(port, password, ln) encTbl := getTable(password) atomic.AddInt32(&tableGetCnt, 1) - log.Printf("starting server at port %v ...\n", port) + log.Printf("server listening port %v ...\n", port) for { conn, err := ln.Accept() if err != nil { - log.Println("accept:", err) - continue + // listener maybe closed to update password + debug.Printf("accept error: %v\n", err) + return } go handleConnection(ss.NewConn(conn, encTbl)) } @@ -155,8 +267,10 @@ func enoughOptions(config *ss.Config) bool { return config.ServerPort != 0 && config.Password != "" } +var configFile string +var config *ss.Config + func main() { - var configFile string var cmdConfig ss.Config flag.StringVar(&configFile, "c", "config.json", "specify config file") @@ -167,7 +281,8 @@ func main() { flag.Parse() - config, err := ss.ParseConfig(configFile) + var err error + config, err = ss.ParseConfig(configFile) if err != nil { enough := enoughOptions(&cmdConfig) if !(enough && os.IsNotExist(err)) { @@ -184,9 +299,13 @@ func main() { ss.SetDebug(debug) if len(config.PortPassword) == 0 { - run(strconv.Itoa(config.ServerPort), config.Password) + if !enoughOptions(config) { + log.Println("must specify both port and password") + os.Exit(1) + } + go run(strconv.Itoa(config.ServerPort), config.Password) } else { - if config.ServerPort != 0 { + if config.Password != "" || config.ServerPort != 0 { log.Println("ignoring server_port and password option, only uses port_password") } for port, password := range config.PortPassword { @@ -198,7 +317,6 @@ func main() { } log.Println("all ports ready") tableCache = nil // release memory - c := make(chan byte) - <-c // block forever } + waitSignal() } From 7749ae3202a54baee4ff0645882a360d50bedc42 Mon Sep 17 00:00:00 2001 From: Chen Yufei Date: Wed, 19 Dec 2012 22:29:33 +0800 Subject: [PATCH 4/5] Simplify port password handling. --- cmd/shadowsocks-server/server.go | 74 ++++++++++++++++---------------- 1 file changed, 37 insertions(+), 37 deletions(-) diff --git a/cmd/shadowsocks-server/server.go b/cmd/shadowsocks-server/server.go index 21a9806..cec06be 100644 --- a/cmd/shadowsocks-server/server.go +++ b/cmd/shadowsocks-server/server.go @@ -175,10 +175,7 @@ func (pm *PasswdManager) del(port string) { pm.Unlock() } -func (pm *PasswdManager) updatePortPasswd(port, password string, oldconfig *ss.Config) { - if oldconfig.PortPassword != nil { - delete(oldconfig.PortPassword, port) - } +func (pm *PasswdManager) updatePortPasswd(port, password string) { pl, ok := pm.get(port) if !ok { log.Printf("new port %s added\n", port) @@ -205,22 +202,17 @@ func updatePasswd() { } oldconfig := config config = newconfig - if len(config.PortPassword) == 0 { - if !enoughOptions(config) { - log.Println("must specify both password and server port in config") - return - } - port := strconv.Itoa(config.ServerPort) - passwdManager.updatePortPasswd(port, config.Password, oldconfig) - } else { - if config.ServerPort != 0 || config.Password != "" { - log.Println("ignoring server_port and password option, only uses port_password") - } - for port, passwd := range config.PortPassword { - passwdManager.updatePortPasswd(port, passwd, oldconfig) + + if err = unifyPortPassword(config); err != nil { + return + } + for port, passwd := range config.PortPassword { + passwdManager.updatePortPasswd(port, passwd) + if oldconfig.PortPassword != nil { + delete(oldconfig.PortPassword, port) } } - // port password left in the old config should be deleted + // port password still left in the old config should be closed for port, _ := range oldconfig.PortPassword { log.Printf("closing port %s as it's deleted\n", port) passwdManager.del(port) @@ -267,6 +259,22 @@ func enoughOptions(config *ss.Config) bool { return config.ServerPort != 0 && config.Password != "" } +func unifyPortPassword(config *ss.Config) (err error) { + if len(config.PortPassword) == 0 { // this handles both nil PortPassword and empty one + if !enoughOptions(config) { + log.Println("must specify both port and password") + return errors.New("not enough options") + } + port := strconv.Itoa(config.ServerPort) + config.PortPassword = map[string]string{port: config.Password} + } else { + if config.Password != "" || config.ServerPort != 0 { + log.Println("given port_password, ignore server_port and password option") + } + } + return +} + var configFile string var config *ss.Config @@ -298,25 +306,17 @@ func main() { } ss.SetDebug(debug) - if len(config.PortPassword) == 0 { - if !enoughOptions(config) { - log.Println("must specify both port and password") - os.Exit(1) - } - go run(strconv.Itoa(config.ServerPort), config.Password) - } else { - if config.Password != "" || config.ServerPort != 0 { - log.Println("ignoring server_port and password option, only uses port_password") - } - 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) { - time.Sleep(1 * time.Second) - } - log.Println("all ports ready") - tableCache = nil // release memory + if err = unifyPortPassword(config); err != nil { + os.Exit(1) + } + 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) { + time.Sleep(1 * time.Second) } + log.Println("all ports ready") + tableCache = nil // release memory waitSignal() } From d6dc84f8de171c149611d269a4570858d0c2d6e0 Mon Sep 17 00:00:00 2001 From: Chen Yufei Date: Fri, 21 Dec 2012 13:54:03 +0800 Subject: [PATCH 5/5] Log too many open file error. --- cmd/shadowsocks-server/server.go | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/cmd/shadowsocks-server/server.go b/cmd/shadowsocks-server/server.go index cec06be..e6cac64 100644 --- a/cmd/shadowsocks-server/server.go +++ b/cmd/shadowsocks-server/server.go @@ -98,7 +98,13 @@ func handleConnection(conn *ss.Conn) { debug.Println("connecting", host) remote, err := net.Dial("tcp", host) if err != nil { - debug.Println("error connecting to:", host, err) + if ne, ok := err.(*net.OpError); ok && (ne.Err == syscall.EMFILE || ne.Err == syscall.ENFILE) { + // log too many open file error + // EMFILE is process reaches open file limits, ENFILE is system limit + log.Println("dial error:", err) + } else { + debug.Println("error connecting to:", host, err) + } return } defer remote.Close()