From a18ed95c3a1dacf47a37e0f4497e9076165ebab9 Mon Sep 17 00:00:00 2001 From: Chen Yufei Date: Sat, 15 Dec 2012 12:02:29 +0800 Subject: [PATCH 1/5] Use io.ReadAtLeast to simplify handshake and getRequest in local.go --- cmd/shadowsocks-local/local.go | 138 ++++++++++++++++----------------- 1 file changed, 66 insertions(+), 72 deletions(-) diff --git a/cmd/shadowsocks-local/local.go b/cmd/shadowsocks-local/local.go index d09fe38..bd21ff4 100644 --- a/cmd/shadowsocks-local/local.go +++ b/cmd/shadowsocks-local/local.go @@ -15,11 +15,17 @@ import ( var debug ss.DebugLog var ( - errAddr = errors.New("socks addr type not supported") - errVer = errors.New("socks version not supported") - errMethod = errors.New("socks only support 1 method now") - errAuth = errors.New("socks authentication not required") - errCmd = errors.New("socks command not supported") + errAddrType = errors.New("socks addr type not supported") + errVer = errors.New("socks version not supported") + errMethod = errors.New("socks only support 1 method now") + errAuthExtraData = errors.New("socks authentication get extra data") + errReqExtraData = errors.New("socks request get extra data") + errCmd = errors.New("socks command not supported") +) + +const ( + socksVer5 = 5 + socksCmdConnect = 1 ) func handShake(conn net.Conn) (err error) { @@ -29,26 +35,36 @@ func handShake(conn net.Conn) (err error) { ) // version identification and method selection message in theory can have // at most 256 methods, plus version and nmethod field in total 258 bytes - // the current rfc defines only 3 authentication methods (plus 2 reserved) + // the current rfc defines only 3 authentication methods (plus 2 reserved), + // so it won't be such long in practice - buf := make([]byte, 258-2, 258-2) // reuse the buf to read nmethod field + buf := make([]byte, 258, 258) - if _, err = io.ReadFull(conn, buf[:2]); err != nil { + var n int + // make sure we get the nmethod field + if n, err = io.ReadAtLeast(conn, buf, idNmethod+1); err != nil { return } - if buf[idVer] != 5 { + if buf[idVer] != socksVer5 { return errVer } - nmethod := buf[idNmethod] - if _, err = io.ReadFull(conn, buf[:nmethod]); err != nil { - return + nmethod := int(buf[idNmethod]) + msgLen := nmethod + 2 + if n == msgLen { // handshake done, common case + // do nothing, jump directly to send confirmation + } else if n < msgLen { // has more methods to read, rare case + if _, err = io.ReadFull(conn, buf[n:msgLen]); err != nil { + return + } + } else { // error, should not get extra data + return errAuthExtraData } - // version 5, no authentication required - _, err = conn.Write([]byte{5, 0}) + // send confirmation: version 5, no authentication required + _, err = conn.Write([]byte{socksVer5, 0}) return } -func getRequest(conn net.Conn) (rawaddr []byte, extra []byte, host string, err error) { +func getRequest(conn net.Conn) (rawaddr []byte, host string, err error) { const ( idVer = 0 idCmd = 1 @@ -65,71 +81,56 @@ func getRequest(conn net.Conn) (rawaddr []byte, extra []byte, host string, err e ) // refer to getRequest in server.go for why set buffer size to 263 buf := make([]byte, 263, 263) - cur := 0 // current location in buf - reqLen := 0 + var n int + // read till we get possible domain length field + if n, err = io.ReadAtLeast(conn, buf, idDmLen+1); err != nil { + return + } + // check version and cmd + if buf[idVer] != socksVer5 { + err = errVer + return + } + if buf[idCmd] != socksCmdConnect { + err = errCmd + return + } - for { - var n int - // usually need to read only once - if n, err = conn.Read(buf[cur:]); err != nil { - // debug.Println("read request error:", err) - return - } - cur += n - if cur < idType+1 { // read till we get addr type - continue - } - // check version and cmd - if buf[idVer] != 5 { - err = errVer - return - } - if buf[idCmd] != 1 { - err = errCmd - return - } - // TODO following code is copied from server.go, fix code duplication? - if buf[idType] == typeIP { - if cur >= lenIP { - // debug.Println("ip request complete, cur:", cur) - reqLen = lenIP - break - } - } else if buf[idType] == typeDm { - if cur < idDmLen+1 { // read until we get address length byte - continue - } - if cur >= lenDmBase+int(buf[idDmLen]) { - // debug.Println("domain request complete, cur:", cur) - reqLen = lenDmBase + int(buf[idDmLen]) - break - } - } else { - err = errAddr + reqLen := lenIP + if buf[idType] == typeDm { + reqLen = int(buf[idDmLen]) + lenDmBase + } else if buf[idType] != typeIP { + err = errAddrType + return + } + + if n == reqLen { + // common case, do nothing + } else if n < reqLen { // rare case + if _, err = io.ReadFull(conn, buf[n:reqLen]); err != nil { return } - // debug.Println("request not complete, cur:", cur) + } else { + err = errReqExtraData + return } rawaddr = buf[idType:reqLen] - if cur > reqLen { - extra = buf[reqLen:cur] - // debug.Println("extra:", string(extra)) - } if debug { - if buf[idType] == typeIP { + if buf[idType] == typeDm { + host = string(buf[idDm0 : idDm0+buf[idDmLen]]) + } else if buf[idType] == typeIP { addrIp := make(net.IP, 4) copy(addrIp, buf[idIP0:idIP0+4]) host = addrIp.String() - } else if buf[idType] == typeDm { - host = string(buf[idDm0 : idDm0+buf[idDmLen]]) } var port int16 sb := bytes.NewBuffer(buf[reqLen-2 : reqLen]) binary.Read(sb, binary.BigEndian, &port) host += ":" + strconv.Itoa(int(port)) } + return } @@ -144,7 +145,7 @@ func handleConnection(conn net.Conn, server string, encTbl *ss.EncryptTable) { log.Println("socks handshack:", err) return } - rawaddr, extra, addr, err := getRequest(conn) + rawaddr, addr, err := getRequest(conn) if err != nil { log.Println("error getting request:", err) return @@ -163,13 +164,6 @@ func handleConnection(conn net.Conn, server string, encTbl *ss.EncryptTable) { return } defer remote.Close() - if extra != nil { - debug.Println("writing extra content to remote, len", len(extra)) - if _, err = remote.Write(extra); err != nil { - debug.Println("write request extra error:", err) - return - } - } c := make(chan byte, 2) go ss.Pipe(conn, remote, c) @@ -199,7 +193,7 @@ func main() { var configFile string flag.StringVar(&configFile, "c", "config.json", "specify config file") flag.Parse() - + config := ss.ParseConfig(configFile) debug = ss.Debug run(strconv.Itoa(config.LocalPort), config.Password, From d9e55b36060b7c956c892b07c8fea729a7270424 Mon Sep 17 00:00:00 2001 From: Chen Yufei Date: Sat, 15 Dec 2012 12:31:49 +0800 Subject: [PATCH 2/5] Use io.ReadAtLeast to simplify getRequest in server.go --- cmd/shadowsocks-server/server.go | 66 +++++++++++++------------------- 1 file changed, 27 insertions(+), 39 deletions(-) diff --git a/cmd/shadowsocks-server/server.go b/cmd/shadowsocks-server/server.go index b28d5a9..76f1a55 100644 --- a/cmd/shadowsocks-server/server.go +++ b/cmd/shadowsocks-server/server.go @@ -6,6 +6,7 @@ import ( "errors" "flag" ss "github.com/shadowsocks/shadowsocks-go/shadowsocks" + "io" "log" "net" "strconv" @@ -15,7 +16,7 @@ import ( var debug ss.DebugLog -var errAddr = errors.New("addr type not supported") +var errAddrType = errors.New("addr type not supported") func getRequest(conn *ss.Conn) (host string, extra []byte, err error) { const ( @@ -35,58 +36,45 @@ func getRequest(conn *ss.Conn) (host string, extra []byte, err error) { // request size (when addrType is 3, domain name has at most 256 bytes) // 1(addrType) + 1(lenByte) + 256(max length address) + 2(port) buf := make([]byte, 260, 260) - cur := 0 // current location in buf + var n int + // read till we get possible domain length field + ss.SetReadTimeout(conn) + if n, err = io.ReadAtLeast(conn, buf, idDmLen+1); err != nil { + return + } - // first read the complete request, may read extra bytes - for { - // hopefully, we should only need one read to get the complete request - // this read normally will read just the request, no extra data + reqLen := lenIP + if buf[idType] == typeDm { + reqLen = int(buf[idDmLen]) + lenDmBase + } else if buf[idType] != typeIP { + err = errAddrType + return + } + + if n < reqLen { // rare case ss.SetReadTimeout(conn) - var n int - if n, err = conn.Read(buf[cur:]); err != nil { - // debug.Println("read request error:", err) + if _, err = io.ReadFull(conn, buf[n:reqLen]); err != nil { return } - cur += n - if buf[idType] == typeIP { - if cur >= lenIP { - // debug.Println("ip request complete, cur:", cur) - break - } - } else if buf[idType] == typeDm { - if cur < idDmLen+1 { // read until we get address length byte - continue - } - if cur >= lenDmBase+int(buf[idDmLen]) { - // debug.Println("domain request complete, cur:", cur) - break - } - } else { - err = errAddr - return - } - // debug.Println("request not complete, cur:", cur) + } else if n > reqLen { + // it's possible to read more than just the request head + extra = buf[reqLen:n] } - reqLen := lenIP // default to IP request length - if buf[idType] == typeIP { + // TODO add ipv6 support + if buf[idType] == typeDm { + host = string(buf[idDm0 : idDm0+buf[idDmLen]]) + } else if buf[idType] == typeIP { addrIp := make(net.IP, 4) copy(addrIp, buf[idIP0:idIP0+4]) host = addrIp.String() - } else if buf[idType] == typeDm { - reqLen = lenDmBase + int(buf[idDmLen]) - host = string(buf[idDm0 : idDm0+buf[idDmLen]]) } + // parse port var port int16 sb := bytes.NewBuffer(buf[reqLen-2 : reqLen]) binary.Read(sb, binary.BigEndian, &port) - // debug.Println("requesting:", host, "header len", reqLen) host += ":" + strconv.Itoa(int(port)) - if cur > reqLen { - extra = buf[reqLen:cur] - // debug.Println("extra:", string(extra)) - } return } @@ -112,7 +100,7 @@ func handleConnection(conn *ss.Conn) { defer remote.Close() // write extra bytes read from if extra != nil { - debug.Println("writing extra content to remote, len", len(extra)) + debug.Println("getRequest read extra data, writing to remote, len", len(extra)) if _, err = remote.Write(extra); err != nil { debug.Println("write request extra error:", err) return From b3a5269ce95dcd55d10190936372ddaac757e7a8 Mon Sep 17 00:00:00 2001 From: Chen Yufei Date: Sun, 16 Dec 2012 13:06:37 +0800 Subject: [PATCH 3/5] Do not set read timeout if timeout config is 0. --- shadowsocks/pipe.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/shadowsocks/pipe.go b/shadowsocks/pipe.go index 963d196..50e9001 100644 --- a/shadowsocks/pipe.go +++ b/shadowsocks/pipe.go @@ -7,7 +7,9 @@ import ( ) func SetReadTimeout(c net.Conn) { - c.SetReadDeadline(time.Now().Add(readTimeout)) + if readTimeout != 0 { + c.SetReadDeadline(time.Now().Add(readTimeout)) + } } func Pipe(src, dst net.Conn, end chan byte) { From 30a888aabb20519cc7563970618eb01cf6d703cc Mon Sep 17 00:00:00 2001 From: Chen Yufei Date: Sun, 16 Dec 2012 13:10:54 +0800 Subject: [PATCH 4/5] ParseConfig return err instead of exit. --- cmd/shadowsocks-local/local.go | 5 ++++- cmd/shadowsocks-server/server.go | 5 ++++- shadowsocks/config.go | 19 ++++++++++++------- 3 files changed, 20 insertions(+), 9 deletions(-) diff --git a/cmd/shadowsocks-local/local.go b/cmd/shadowsocks-local/local.go index bd21ff4..7bfbd5e 100644 --- a/cmd/shadowsocks-local/local.go +++ b/cmd/shadowsocks-local/local.go @@ -194,7 +194,10 @@ func main() { flag.StringVar(&configFile, "c", "config.json", "specify config file") flag.Parse() - config := ss.ParseConfig(configFile) + config, err := ss.ParseConfig(configFile) + if err != nil { + return + } debug = ss.Debug run(strconv.Itoa(config.LocalPort), config.Password, config.Server+":"+strconv.Itoa(config.ServerPort)) diff --git a/cmd/shadowsocks-server/server.go b/cmd/shadowsocks-server/server.go index 76f1a55..c1f56f2 100644 --- a/cmd/shadowsocks-server/server.go +++ b/cmd/shadowsocks-server/server.go @@ -155,7 +155,10 @@ func main() { flag.StringVar(&configFile, "c", "config.json", "specify config file") flag.Parse() - config := ss.ParseConfig(configFile) + config, err := ss.ParseConfig(configFile) + if err != nil { + return + } debug = ss.Debug if len(config.PortPassword) == 0 { run(strconv.Itoa(config.ServerPort), config.Password) diff --git a/shadowsocks/config.go b/shadowsocks/config.go index 21b70be..b8a45ba 100644 --- a/shadowsocks/config.go +++ b/shadowsocks/config.go @@ -27,21 +27,26 @@ type Config struct { var readTimeout time.Duration -func ParseConfig(path string) *Config { +func ParseConfig(path string) (config *Config, err error) { file, err := os.Open(path) // For read access. if err != nil { - log.Fatal("error opening config file:", err) + log.Println("error opening config file:", err) + return } defer file.Close() + data, err := ioutil.ReadAll(file) if err != nil { - log.Fatalln("error reading config:", err) + log.Println("error reading config:", err) + return } - var config Config - if err = json.Unmarshal(data, &config); err != nil { - log.Fatalln("can not parse config:", err) + + config = &Config{} + if err = json.Unmarshal(data, config); err != nil { + log.Println("can not parse config:", err) + return nil, err } Debug = DebugLog(config.Debug) readTimeout = time.Duration(config.Timeout) * time.Second - return &config + return } From 7c1f7da830736f25b37fc4128c6c49a055bd8d43 Mon Sep 17 00:00:00 2001 From: Chen Yufei Date: Sun, 16 Dec 2012 14:29:47 +0800 Subject: [PATCH 5/5] More command line options, can override config file. --- cmd/shadowsocks-local/local.go | 14 ++++++++++-- cmd/shadowsocks-server/server.go | 11 +++++++++- config.json | 3 +-- shadowsocks/config.go | 37 ++++++++++++++++++++++++++++++-- 4 files changed, 58 insertions(+), 7 deletions(-) diff --git a/cmd/shadowsocks-local/local.go b/cmd/shadowsocks-local/local.go index 7bfbd5e..81481f6 100644 --- a/cmd/shadowsocks-local/local.go +++ b/cmd/shadowsocks-local/local.go @@ -178,7 +178,7 @@ func run(port, password, server string) { log.Fatal(err) } encTbl := ss.GetTable(password) - log.Printf("starting server at port %v ...\n", port) + log.Printf("starting local socks5 server at port %v, remote shadowsocks server %s...\n", port, server) for { conn, err := ln.Accept() if err != nil { @@ -191,14 +191,24 @@ func run(port, password, server string) { func main() { var configFile string + var cmdConfig ss.Config + flag.StringVar(&configFile, "c", "config.json", "specify config file") + flag.StringVar(&cmdConfig.Server, "s", "", "server address") + flag.StringVar(&cmdConfig.Password, "k", "", "password") + flag.IntVar(&cmdConfig.ServerPort, "p", 0, "server port") + flag.IntVar(&cmdConfig.LocalPort, "l", 0, "local socks5 proxy port") + flag.BoolVar((*bool)(&debug), "d", false, "print debug message") + flag.Parse() config, err := ss.ParseConfig(configFile) if err != nil { return } - debug = ss.Debug + ss.UpdateConfig(config, &cmdConfig) + ss.SetDebug(debug) + run(strconv.Itoa(config.LocalPort), config.Password, config.Server+":"+strconv.Itoa(config.ServerPort)) } diff --git a/cmd/shadowsocks-server/server.go b/cmd/shadowsocks-server/server.go index c1f56f2..8f7b321 100644 --- a/cmd/shadowsocks-server/server.go +++ b/cmd/shadowsocks-server/server.go @@ -152,14 +152,23 @@ func run(port, password string) { func main() { var configFile string + var cmdConfig ss.Config + 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() config, err := ss.ParseConfig(configFile) if err != nil { return } - debug = ss.Debug + ss.UpdateConfig(config, &cmdConfig) + ss.SetDebug(debug) + if len(config.PortPassword) == 0 { run(strconv.Itoa(config.ServerPort), config.Password) } else { diff --git a/config.json b/config.json index 02a39cc..0685796 100644 --- a/config.json +++ b/config.json @@ -7,6 +7,5 @@ "8388": "barfoo!", "8387": "foobar!" }, - "timeout":60, - "debug":true + "timeout":60 } diff --git a/shadowsocks/config.go b/shadowsocks/config.go index b8a45ba..91086c1 100644 --- a/shadowsocks/config.go +++ b/shadowsocks/config.go @@ -12,6 +12,7 @@ import ( "io/ioutil" "log" "os" + "reflect" "time" ) @@ -22,7 +23,6 @@ type Config struct { Password string `json:"password"` PortPassword map[string]string `json:"port_password"` Timeout int `json:"timeout"` - Debug bool `json:"debug"` } var readTimeout time.Duration @@ -46,7 +46,40 @@ func ParseConfig(path string) (config *Config, err error) { log.Println("can not parse config:", err) return nil, err } - Debug = DebugLog(config.Debug) readTimeout = time.Duration(config.Timeout) * time.Second return } + +func SetDebug(d DebugLog) { + Debug = d +} + +// Useful for command line to override options specified in config file +// Debug is not updated. +func UpdateConfig(old, new *Config) { + // Using reflection here is not necessary, but it's a good exercise. + // For more information on reflections in Go, read "The Laws of Reflection" + // http://golang.org/doc/articles/laws_of_reflection.html + newVal := reflect.ValueOf(new).Elem() + oldVal := reflect.ValueOf(old).Elem() + + // typeOfT := newVal.Type() + for i := 0; i < newVal.NumField(); i++ { + newField := newVal.Field(i) + oldField := oldVal.Field(i) + // log.Printf("%d: %s %s = %v\n", i, + // typeOfT.Field(i).Name, newField.Type(), newField.Interface()) + switch newField.Kind() { + case reflect.String: + s := newField.String() + if s != "" { + oldField.SetString(s) + } + case reflect.Int: + i := newField.Int() + if i != 0 { + oldField.SetInt(i) + } + } + } +}