diff --git a/.gitignore b/.gitignore index a53134e0..7eca66c3 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ script/http bin .idea +.vscode/ diff --git a/.travis.yml b/.travis.yml index 2860be22..88b9695c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,8 +9,9 @@ install: - go get golang.org/x/crypto/cast5 - go get golang.org/x/crypto/salsa20 - go get github.com/aead/chacha20 - - go install ./cmd/shadowsocks-local - - go install ./cmd/shadowsocks-server + - go get github.com/bonafideyan/shadowsocks-go/cmd/shadowsocks-local + - go get github.com/bonafideyan/shadowsocks-go/cmd/shadowsocks-server + - go get github.com/bonafideyan/shadowsocks-go/cmd/shadowsocks-httpget script: - PATH=$PATH:$HOME/gopath/bin bash -x ./script/test.sh sudo: false diff --git a/cmd/shadowsocks-httpget/httpget.go b/cmd/shadowsocks-httpget/httpget.go index 2e54833d..f1078496 100644 --- a/cmd/shadowsocks-httpget/httpget.go +++ b/cmd/shadowsocks-httpget/httpget.go @@ -3,7 +3,6 @@ package main import ( "flag" "fmt" - ss "github.com/shadowsocks/shadowsocks-go/shadowsocks" "io" "math" "net" @@ -14,6 +13,8 @@ import ( "strconv" "strings" "time" + + ss "github.com/bonafideyan/shadowsocks-go/shadowsocks" ) var config struct { @@ -57,7 +58,11 @@ func get(connid int, uri, serverAddr string, rawAddr []byte, cipher *ss.Cipher, }() tr := &http.Transport{ Dial: func(_, _ string) (net.Conn, error) { - return ss.DialWithRawAddr(rawAddr, serverAddr, cipher.Copy()) + if cipher != nil { + return ss.DialWithRawAddr(rawAddr, serverAddr, cipher.Copy()) + } + + return ss.DialAsClient(string(rawAddr), serverAddr) }, } @@ -77,8 +82,9 @@ func get(connid int, uri, serverAddr string, rawAddr []byte, cipher *ss.Cipher, } func main() { - flag.StringVar(&config.server, "s", "127.0.0.1", "server:port") - flag.IntVar(&config.port, "p", 0, "server:port") + server := flag.String("s", "127.0.0.1", "server:port") + proxy := flag.String("ss", "127.0.0.1", "proxy:port") + flag.IntVar(&config.port, "p", 0, "port") flag.IntVar(&config.core, "core", 1, "number of CPU cores to use") flag.StringVar(&config.passwd, "k", "", "password") flag.StringVar(&config.method, "m", "", "encryption method, use empty string or rc4") @@ -89,8 +95,17 @@ func main() { flag.Parse() - if config.server == "" || config.port == 0 || config.passwd == "" || len(flag.Args()) != 1 { - fmt.Printf("Usage: %s -s -p -k \n", os.Args[0]) + config.server = "127.0.0.1" + var connectProxy bool + if *server != config.server { + config.server = *server + } else { + config.server = *proxy + connectProxy = true + } + + if config.port == 0 || !connectProxy && config.passwd == "" || len(flag.Args()) != 1 { + fmt.Printf("Usage: %s -s[s] -p -k \n", os.Args[0]) os.Exit(1) } @@ -104,11 +119,6 @@ func main() { uri = "http://" + uri } - cipher, err := ss.NewCipher(config.method, config.passwd) - if err != nil { - fmt.Println("Error creating cipher:", err) - os.Exit(1) - } serverAddr := net.JoinHostPort(config.server, strconv.Itoa(config.port)) parsedURL, err := url.Parse(uri) @@ -122,10 +132,23 @@ func main() { } else { host = parsedURL.Host } - // fmt.Println(host) - rawAddr, err := ss.RawAddr(host) - if err != nil { - panic("Error getting raw address.") + + rawAddr := []byte(host) + var cipher *ss.Cipher + if !connectProxy { + if config.method == "" { + config.method = "aes-256-cfb" + } + + cipher, err = ss.NewCipher(config.method, config.passwd) + if err != nil { + fmt.Println("Error creating cipher:", err) + os.Exit(1) + } + rawAddr, err = ss.RawAddr(host) + if err != nil { + panic("Error getting raw address.") + } } done := make(chan []time.Duration) diff --git a/cmd/shadowsocks-local/local.go b/cmd/shadowsocks-local/local.go index 604bf9f7..f6da08cf 100644 --- a/cmd/shadowsocks-local/local.go +++ b/cmd/shadowsocks-local/local.go @@ -17,12 +17,12 @@ import ( "strings" "time" - ss "github.com/shadowsocks/shadowsocks-go/shadowsocks" + ss "github.com/bonafideyan/shadowsocks-go/shadowsocks" ) -var debug ss.DebugLog - var ( + debug ss.DebugLog + errAddrType = errors.New("socks addr type not supported") errVer = errors.New("socks version not supported") errMethod = errors.New("socks only support 1 method now") @@ -246,6 +246,7 @@ func connectToServer(serverId int, rawaddr []byte, addr string) (remote *ss.Conn } return nil, err } + debug.Printf("connected to %s via %s\n", addr, se.server) servers.failCnt[serverId] = 0 return @@ -280,7 +281,7 @@ func createServerConn(rawaddr []byte, addr string) (remote *ss.Conn, err error) return nil, err } -func handleConnection(conn net.Conn) { +func handleConnection(conn net.Conn, redir bool) { if debug { debug.Printf("socks connect from %s\n", conn.RemoteAddr().String()) } @@ -291,23 +292,37 @@ func handleConnection(conn net.Conn) { } }() - var err error = nil - if err = handShake(conn); err != nil { - log.Println("socks handshake:", err) - return - } - rawaddr, addr, err := getRequest(conn) - if err != nil { - log.Println("error getting request:", err) - return - } - // 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 + var rawaddr, buf []byte + var addr string + var n int + if !redir { + var err error = nil + if err = handShake(conn); err != nil { + log.Println("socks handshake:", err) + return + } + rawaddr, addr, err = getRequest(conn) + if err != nil { + log.Println("error getting request:", err) + return + } + // 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 + } + } else { + var err error + buf = ss.GetLeakyBuf() + n, err = conn.Read(buf) + if err != nil { + println(err.Error()) + } + + rawaddr, addr = ss.GetOriginalDst(&conn, string(buf[:n])) } remote, err := createServerConn(rawaddr, addr) @@ -323,13 +338,13 @@ func handleConnection(conn net.Conn) { } }() - go ss.PipeThenClose(conn, remote, nil) - ss.PipeThenClose(remote, conn, nil) + go ss.PipeThenClose(conn, remote, nil, buf, n) + ss.PipeThenClose(remote, conn, nil, nil, 0) closed = true debug.Println("closed connection to", addr) } -func run(listenAddr string) { +func run(listenAddr string, redir bool) { ln, err := net.Listen("tcp", listenAddr) if err != nil { log.Fatal(err) @@ -341,7 +356,7 @@ func run(listenAddr string) { log.Println("accept:", err) continue } - go handleConnection(conn) + go handleConnection(conn, redir) } } @@ -407,8 +422,10 @@ func main() { var configFile, cmdServer, cmdURI string var cmdConfig ss.Config var printVer bool + var redirect bool flag.BoolVar(&printVer, "version", false, "print version") + flag.BoolVar(&redirect, "redirect", false, "Redirect normal request to socks server.") flag.StringVar(&configFile, "c", "config.json", "specify config file") flag.StringVar(&cmdServer, "s", "", "server address") flag.StringVar(&cmdConfig.LocalAddress, "b", "", "local address, listen only to this address if specified") @@ -477,5 +494,6 @@ func main() { } parseServerConfig(config) - run(config.LocalAddress + ":" + strconv.Itoa(config.LocalPort)) + run(config.LocalAddress+":"+strconv.Itoa(config.LocalPort), redirect) } + diff --git a/cmd/shadowsocks-server/server.go b/cmd/shadowsocks-server/server.go index 28099981..b5d0fb6a 100644 --- a/cmd/shadowsocks-server/server.go +++ b/cmd/shadowsocks-server/server.go @@ -19,7 +19,7 @@ import ( "syscall" "time" - ss "github.com/shadowsocks/shadowsocks-go/shadowsocks" + ss "github.com/bonafideyan/shadowsocks-go/shadowsocks" ) const ( @@ -169,12 +169,12 @@ func handleConnection(conn *ss.Conn, port string) { go func() { ss.PipeThenClose(conn, remote, func(Traffic int) { passwdManager.addTraffic(port, Traffic) - }) + }, nil, 0) }() ss.PipeThenClose(remote, conn, func(Traffic int) { passwdManager.addTraffic(port, Traffic) - }) + }, nil, 0) closed = true return @@ -542,12 +542,12 @@ func managerDaemon(conn *net.UDPConn) { res = handleAddPort(bytes.Trim(data[4:], "\x00\r\n ")) case strings.HasPrefix(command, "remove:"): res = handleRemovePort(bytes.Trim(data[7:], "\x00\r\n ")) - case strings.HasPrefix(command, "ping"): - conn.WriteToUDP(handlePing(), remote) - reportconnSet[remote.String()] = remote // append the host into the report list case strings.HasPrefix(command, "ping-stop"): // add the stop ping command conn.WriteToUDP(handlePing(), remote) delete(reportconnSet, remote.String()) + case strings.HasPrefix(command, "ping"): + conn.WriteToUDP(handlePing(), remote) + reportconnSet[remote.String()] = remote // append the host into the report list } if len(res) == 0 { continue diff --git a/go.mod b/go.mod new file mode 100644 index 00000000..1da1d8a2 --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module github.com/bonafideyan/shadowsocks-go + +go 1.13 diff --git a/shadowsocks/conn.go b/shadowsocks/conn.go index 84b461a3..059e424f 100644 --- a/shadowsocks/conn.go +++ b/shadowsocks/conn.go @@ -2,14 +2,16 @@ package shadowsocks import ( "encoding/binary" + "errors" "fmt" "io" "net" "strconv" + "time" ) const ( - AddrMask byte = 0xf + AddrMask byte = 0xf ) type Conn struct { @@ -78,6 +80,91 @@ func Dial(addr, server string, cipher *Cipher) (c *Conn, err error) { return DialWithRawAddr(ra, server, cipher) } +// DialAsClient dials in client's position. +func DialAsClient(server, proxy string) (conn net.Conn, err error) { + readAll := func(conn net.Conn) (resp []byte, err error) { + resp = make([]byte, 1024) + Timeout := 5 * time.Second + if err := conn.SetReadDeadline(time.Now().Add(Timeout)); err != nil { + return nil, err + } + n, err := conn.Read(resp) + resp = resp[:n] + return + } + sendReceive := func(conn net.Conn, req []byte) (resp []byte, err error) { + Timeout := 5 * time.Second + if err := conn.SetWriteDeadline(time.Now().Add(Timeout)); err != nil { + return nil, err + } + _, err = conn.Write(req) + if err != nil { + return + } + resp, err = readAll(conn) + return + } + + conn, err = net.Dial("tcp", proxy) + if err != nil { + return + } + + // version identifier/method selection request + req := []byte{ + 5, // version number + 1, // number of methods + 0, // method 0: no authentication (only anonymous access supported for now) + } + resp, err := sendReceive(conn, req) + if err != nil { + return + } else if len(resp) != 2 { + err = errors.New("server does not respond properly") + return + } else if resp[0] != 5 { + err = errors.New("server does not support Socks 5") + return + } else if resp[1] != 0 { // no auth + err = errors.New("socks method negotiation failed") + return + } + + // detail request + host, portStr, err := net.SplitHostPort(server) + if err != nil { + return nil, err + } + portInt, err := strconv.ParseUint(portStr, 10, 16) + if err != nil { + return nil, err + } + port := uint16(portInt) + + req = []byte{ + 5, // version number + 1, // connect command + 0, // reserved, must be zero + 3, // address type, 3 means domain name + byte(len(host)), // address length + } + req = append(req, []byte(host)...) + req = append(req, []byte{ + byte(port >> 8), // higher byte of destination port + byte(port), // lower byte of destination port (big endian) + }...) + resp, err = sendReceive(conn, req) + if err != nil { + return + } else if len(resp) != 10 { + err = errors.New("server does not respond properly") + } else if resp[1] != 0 { + err = errors.New("can't complete SOCKS5 connection") + } + + return +} + func (c *Conn) GetIv() (iv []byte) { iv = make([]byte, len(c.iv)) copy(iv, c.iv) diff --git a/shadowsocks/desthost.go b/shadowsocks/desthost.go new file mode 100644 index 00000000..d3a5f9f0 --- /dev/null +++ b/shadowsocks/desthost.go @@ -0,0 +1,204 @@ +package shadowsocks + +import ( + "fmt" + "net" + "strings" + "syscall" +) + +const ( + SoOriginalDst = 80 + + DefaultPortForHttp = 80 + DefaultPortForTls = 443 + + ServerNameLen = 256 + TLSHeaderLen = 5 + TLSHandshakeContentType = 0x16 + TLSHandshakeTypeClientHello = 0x01 +) + +// ParseHTTPHeader tries to find 'Host' header. +func ParseHTTPHeader(buf string) string { + for _, l := range strings.Split(buf, "\r\n") { + if strings.HasPrefix(l, "Host:") { + return strings.TrimSpace(l[5:]) + } + } + + return "" +} + +// ParseTLSHeader tries to find 'Host' extension. +func ParseTLSHeader(buf string) string { + slen := len(buf) + if slen < TLSHeaderLen { + return "" + } + + if buf[0] != TLSHandshakeContentType { + return "" + } + + tlsVersionMajor, tlsVersionMinor := buf[1], buf[2] + if tlsVersionMajor < 3 { + return "" + } + + l := int(uint(buf[3])<<8 + uint(buf[4]) + TLSHeaderLen) + if slen < l { + return "" + } + + buf = buf[:l] + slen = len(buf) + pos := TLSHeaderLen + if slen < pos+1 { + return "" + } + + if buf[pos] != TLSHandshakeTypeClientHello { + return "" + } + + /* Skip past fixed length records: + * 1 Handshake Type + * 3 Length + * 2 Version (again) + * 32 Random + * to Session ID Length + */ + pos += 38 + + if pos+1 > slen { + return "" + } + pos += int(1 + uint(buf[pos])) + + if pos+2 > slen { + return "" + } + pos += int(2 + uint(buf[pos])<<8 + uint(buf[pos+1])) + + if pos+1 > slen { + return "" + } + pos += int(1 + uint(buf[pos])) + + if pos == slen && tlsVersionMajor == 3 && tlsVersionMinor == 0 { + return "" + } + + if pos+2 > slen { + return "" + } + l = int(uint(buf[pos])<<8 + uint(buf[pos+1])) + pos += 2 + if pos+l > slen { + return "" + } + + return parseExtensions(buf[pos : pos+l]) +} + +func parseExtensions(buf string) string { + var pos, l int + slen := len(buf) + + for pos+4 <= slen { + l = int(uint(buf[pos+2])<<8 + uint(buf[pos+3])) + if buf[pos] == 0x00 && buf[pos+1] == 0x00 { + if pos+4+l > slen { + return "" + } + + return parseServerNameExtension(buf[pos+4 : pos+4+l]) + } + pos += 4 + l + } + + return "" +} + +func parseServerNameExtension(buf string) string { + var l int + slen := len(buf) + pos := 2 + + for pos+3 < slen { + l = int(uint(buf[pos+1])<<8 + uint(buf[pos+2])) + if pos+3+l > slen { + return "" + } + + switch buf[pos] { + case 0x00: + return buf[pos+3 : pos+3+l] + default: + } + pos += 3 + l + } + + return "" +} + +// GetOriginalDst gets the raw address. +func GetOriginalDst(conn *net.Conn, buf string) (rawaddr []byte, addr string) { + tcpConn := (*conn).(*net.TCPConn) + // connection => file, will make a copy + tcpConnFile, err := tcpConn.File() + if err != nil { + panic(err) + } else { + tcpConn.Close() + } + + defer func() { + // file => connection + (*conn), err = net.FileConn(tcpConnFile) + if err != nil { + panic(err) + } + tcpConnFile.Close() + }() + + fd := int(tcpConnFile.Fd()) + req, err := syscall.GetsockoptIPv6Mreq(fd, syscall.IPPROTO_IP, SoOriginalDst) + if err != nil { + _, err := syscall.GetsockoptIPMreq(fd, syscall.SOL_IP, SoOriginalDst) + if err != nil { + println(err.Error()) + } + // TODO(me): I don't where the port is saved. + return nil, "" + } + + ip := net.IPv4(req.Multiaddr[4], req.Multiaddr[5], req.Multiaddr[6], req.Multiaddr[7]) + port := uint16(req.Multiaddr[2])<<8 + uint16(req.Multiaddr[3]) + dstaddr, err := net.ResolveTCPAddr("tcp4", fmt.Sprintf("%s:%d", ip.String(), port)) + if err != nil { + return nil, "" + } + addr = dstaddr.String() + var s string + if dstaddr.Port == DefaultPortForHttp { + s = ParseHTTPHeader(buf) + } else if dstaddr.Port == DefaultPortForTls { + s = ParseTLSHeader(buf) + } + + if s != "" { + addr = s + rawaddr = append(rawaddr, byte(3)) + rawaddr = append(rawaddr, byte(len(s))) + rawaddr = append(rawaddr, []byte(s)...) + rawaddr = append(rawaddr, req.Multiaddr[2:4]...) + return + } + + rawaddr = append(rawaddr, byte(1)) + rawaddr = append(rawaddr, req.Multiaddr[4:8]...) + rawaddr = append(rawaddr, req.Multiaddr[2:4]...) + return +} diff --git a/shadowsocks/leakybuf.go b/shadowsocks/leakybuf.go index b6922eb9..7600a3ea 100644 --- a/shadowsocks/leakybuf.go +++ b/shadowsocks/leakybuf.go @@ -20,6 +20,11 @@ func NewLeakyBuf(n, bufSize int) *LeakyBuf { } } +// GetLeakyBuf is invoked outside of the package. +func GetLeakyBuf() []byte { + return leakyBuf.Get() +} + // Get returns a buffer from the leaky buffer or create a new buffer. func (lb *LeakyBuf) Get() (b []byte) { select { diff --git a/shadowsocks/pipe.go b/shadowsocks/pipe.go index 51f52006..c0a0b733 100644 --- a/shadowsocks/pipe.go +++ b/shadowsocks/pipe.go @@ -1,6 +1,7 @@ package shadowsocks import ( + "io" "net" "time" ) @@ -12,36 +13,19 @@ func SetReadTimeout(c net.Conn) { } // PipeThenClose copies data from src to dst, closes dst when done. -func PipeThenClose(src, dst net.Conn, addTraffic func(int)) { +func PipeThenClose(src, dst net.Conn, addTraffic func(int), buf []byte, n int) { defer dst.Close() - buf := leakyBuf.Get() - defer leakyBuf.Put(buf) - for { - SetReadTimeout(src) - n, err := src.Read(buf) - if addTraffic != nil { - addTraffic(n) - } - // read may return EOF with n > 0 - // should always process n > 0 bytes before handling error + if buf != nil { if n > 0 { - // Note: avoid overwrite err returned by Read. if _, err := dst.Write(buf[0:n]); err != nil { Debug.Println("write:", err) - break + leakyBuf.Put(buf) + return } } - if err != nil { - // Always "use of closed network connection", but no easy way to - // identify this specific error. So just leave the error along for now. - // More info here: https://code.google.com/p/go/issues/detail?id=4373 - /* - if bool(Debug) && err != io.EOF { - Debug.Println("read:", err) - } - */ - break - } + leakyBuf.Put(buf) } + + io.Copy(src, dst) return }