From 7251c463ca6e59f826c532a94adcb2006f09952d Mon Sep 17 00:00:00 2001 From: kazhuravlev Date: Thu, 3 May 2018 19:56:34 +0300 Subject: [PATCH 01/51] Change shadowsocks2 -> go-shadowsocks2 After `go get` program will have name `go-shadowsocks2` --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index f164a4e3..ab753553 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,7 @@ go get -u -v github.com/shadowsocks/go-shadowsocks2 Start a server listening on port 8488 using `AEAD_CHACHA20_POLY1305` AEAD cipher with password `your-password`. ```sh -shadowsocks2 -s 'ss://AEAD_CHACHA20_POLY1305:your-password@:8488' -verbose +go-shadowsocks2 -s 'ss://AEAD_CHACHA20_POLY1305:your-password@:8488' -verbose ``` @@ -44,7 +44,7 @@ connections, and tunnels both UDP and TCP on port 8053 and port 8054 to 8.8.8.8: respectively. ```sh -shadowsocks2 -c 'ss://AEAD_CHACHA20_POLY1305:your-password@[server_address]:8488' \ +go-shadowsocks2 -c 'ss://AEAD_CHACHA20_POLY1305:your-password@[server_address]:8488' \ -verbose -socks :1080 -u -udptun :8053=8.8.8.8:53,:8054=8.8.4.4:53 \ -tcptun :8053=8.8.8.8:53,:8054=8.8.4.4:53 ``` @@ -65,7 +65,7 @@ Start a client listening on port 1082 for redirected TCP connections and port 10 TCP IPv6 connections. ```sh -shadowsocks2 -c 'ss://AEAD_CHACHA20_POLY1305:your-password@[server_address]:8488' -redir :1082 -redir6 :1083 +go-shadowsocks2 -c 'ss://AEAD_CHACHA20_POLY1305:your-password@[server_address]:8488' -redir :1082 -redir6 :1083 ``` @@ -86,7 +86,7 @@ Start a client on the same machine with the server. The client listens on port 1 and tunnels to localhost:5201 where iperf3 is listening. ```sh -shadowsocks2 -c 'ss://AEAD_CHACHA20_POLY1305:your-password@[server_address]:8488' -tcptun :1090=localhost:5201 +go-shadowsocks2 -c 'ss://AEAD_CHACHA20_POLY1305:your-password@[server_address]:8488' -tcptun :1090=localhost:5201 ``` Start iperf3 client to connect to the tunneld port instead From 149003339a6b23143a6871edab0bb232c54a2ec1 Mon Sep 17 00:00:00 2001 From: David xu Date: Wed, 15 Aug 2018 18:37:34 +0800 Subject: [PATCH 02/51] Better chacha20 implementation Replace by lib github.com/aead/chacha20 --- shadowstream/cipher.go | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/shadowstream/cipher.go b/shadowstream/cipher.go index dea233e1..fa916c9c 100644 --- a/shadowstream/cipher.go +++ b/shadowstream/cipher.go @@ -5,7 +5,8 @@ import ( "crypto/cipher" "strconv" - "github.com/Yawning/chacha20" + "github.com/aead/chacha20" + "github.com/aead/chacha20/chacha" ) // Cipher generates a pair of stream ciphers for encryption and decryption. @@ -54,10 +55,10 @@ func AESCFB(key []byte) (Cipher, error) { // IETF-variant of chacha20 type chacha20ietfkey []byte -func (k chacha20ietfkey) IVSize() int { return chacha20.INonceSize } +func (k chacha20ietfkey) IVSize() int { return chacha.INonceSize } func (k chacha20ietfkey) Decrypter(iv []byte) cipher.Stream { return k.Encrypter(iv) } func (k chacha20ietfkey) Encrypter(iv []byte) cipher.Stream { - ciph, err := chacha20.NewCipher(k, iv) + ciph, err := chacha20.NewCipher(iv, k) if err != nil { panic(err) // should never happen } @@ -65,18 +66,18 @@ func (k chacha20ietfkey) Encrypter(iv []byte) cipher.Stream { } func Chacha20IETF(key []byte) (Cipher, error) { - if len(key) != chacha20.KeySize { - return nil, KeySizeError(chacha20.KeySize) + if len(key) != chacha.KeySize { + return nil, KeySizeError(chacha.KeySize) } return chacha20ietfkey(key), nil } type xchacha20key []byte -func (k xchacha20key) IVSize() int { return chacha20.XNonceSize } +func (k xchacha20key) IVSize() int { return chacha.XNonceSize } func (k xchacha20key) Decrypter(iv []byte) cipher.Stream { return k.Encrypter(iv) } func (k xchacha20key) Encrypter(iv []byte) cipher.Stream { - ciph, err := chacha20.NewCipher(k, iv) + ciph, err := chacha20.NewCipher(iv, k) if err != nil { panic(err) // should never happen } @@ -84,8 +85,8 @@ func (k xchacha20key) Encrypter(iv []byte) cipher.Stream { } func Xchacha20(key []byte) (Cipher, error) { - if len(key) != chacha20.KeySize { - return nil, KeySizeError(chacha20.KeySize) + if len(key) != chacha.KeySize { + return nil, KeySizeError(chacha.KeySize) } return xchacha20key(key), nil } From 89e875bf3228dce886a7a3333b960f1ce885a0ed Mon Sep 17 00:00:00 2001 From: lucus Date: Wed, 29 Aug 2018 15:48:51 +0900 Subject: [PATCH 03/51] Fix the overlap problem of udp decryption. Now return the correct length of data read by ReadFrom. --- shadowaead/packet.go | 8 ++++++-- shadowstream/packet.go | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/shadowaead/packet.go b/shadowaead/packet.go index d8f20a40..ae5f84d4 100644 --- a/shadowaead/packet.go +++ b/shadowaead/packet.go @@ -88,6 +88,10 @@ func (c *packetConn) ReadFrom(b []byte) (int, net.Addr, error) { if err != nil { return n, addr, err } - b, err = Unpack(b, b[:n], c) - return len(b), addr, err + bb, err := Unpack(b[c.Cipher.SaltSize():], b[:n], c) + if err != nil { + return n, addr, err + } + copy(b, bb) + return len(bb), addr, err } diff --git a/shadowstream/packet.go b/shadowstream/packet.go index b266f581..0defa110 100644 --- a/shadowstream/packet.go +++ b/shadowstream/packet.go @@ -76,5 +76,5 @@ func (c *packetConn) ReadFrom(b []byte) (int, net.Addr, error) { return n, addr, err } copy(b, bb) - return len(b), addr, err + return len(bb), addr, err } From a9973cc1aaafe9eda51226a07b7794c80c9410b1 Mon Sep 17 00:00:00 2001 From: Yu Zhang Date: Fri, 23 Nov 2018 13:32:35 -0800 Subject: [PATCH 04/51] Fix incorrect AEAD cipher name Fix "cipher not supported" issue for AES-196-GCM and constantized AEAD cipher names --- core/cipher.go | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/core/cipher.go b/core/cipher.go index c6725103..f2899622 100644 --- a/core/cipher.go +++ b/core/cipher.go @@ -27,15 +27,22 @@ type PacketConnCipher interface { // ErrCipherNotSupported occurs when a cipher is not supported (likely because of security concerns). var ErrCipherNotSupported = errors.New("cipher not supported") +const ( + aeadAes128Gcm = "AEAD_AES_128_GCM" + aeadAes192Gcm = "AEAD_AES_192_GCM" + aeadAes256Gcm = "AEAD_AES_256_GCM" + aeadChacha20Poly1305 = "AEAD_CHACHA20_POLY1305" +) + // List of AEAD ciphers: key size in bytes and constructor var aeadList = map[string]struct { KeySize int New func([]byte) (shadowaead.Cipher, error) }{ - "AEAD_AES_128_GCM": {16, shadowaead.AESGCM}, - "AEAD_AES_192_GCM": {24, shadowaead.AESGCM}, - "AEAD_AES_256_GCM": {32, shadowaead.AESGCM}, - "AEAD_CHACHA20_POLY1305": {32, shadowaead.Chacha20Poly1305}, + aeadAes128Gcm: {16, shadowaead.AESGCM}, + aeadAes192Gcm: {24, shadowaead.AESGCM}, + aeadAes256Gcm: {32, shadowaead.AESGCM}, + aeadChacha20Poly1305: {32, shadowaead.Chacha20Poly1305}, } // List of stream ciphers: key size in bytes and constructor @@ -74,13 +81,13 @@ func PickCipher(name string, key []byte, password string) (Cipher, error) { case "DUMMY": return &dummy{}, nil case "CHACHA20-IETF-POLY1305": - name = "AEAD_CHACHA20_POLY1305" + name = aeadChacha20Poly1305 case "AES-128-GCM": - name = "AEAD_AES_128_GCM" - case "AES-196-GCM": - name = "AEAD_AES_196_GCM" + name = aeadAes128Gcm + case "AES-192-GCM": + name = aeadAes192Gcm case "AES-256-GCM": - name = "AEAD_AES_256_GCM" + name = aeadAes256Gcm } if choice, ok := aeadList[name]; ok { From f271e2a47e170ff6b7a1cbd9f4f83c86361c3184 Mon Sep 17 00:00:00 2001 From: mritd Date: Mon, 17 Dec 2018 11:41:57 +0800 Subject: [PATCH 05/51] chore(docker): add dockerfile add dockerfile refs riobard/go-shadowsocks2#25 Signed-off-by: mritd --- Dockerfile | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 Dockerfile diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..f41d4ce1 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,18 @@ +FROM golang:1.11.3-alpine3.8 AS builder + +RUN apk upgrade \ + && apk add git \ + && go get -ldflags '-w -s' \ + github.com/shadowsocks/go-shadowsocks2 + +FROM alpine:3.8 + +LABEL maintainer="mritd " + +RUN apk upgrade \ + && apk add bash tzdata \ + && rm -rf /var/cache/apk/* + +COPY --from=builder /go/bin/go-shadowsocks2 /usr/bin/shadowsocks + +ENTRYPOINT ["shadowsocks"] From a69e6244961996f084099555277c64acec020d37 Mon Sep 17 00:00:00 2001 From: mritd Date: Tue, 30 Apr 2019 10:55:16 +0800 Subject: [PATCH 06/51] chore(docker): update images, remove bash update images, remove bash Signed-off-by: mritd --- Dockerfile | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Dockerfile b/Dockerfile index f41d4ce1..e35a6440 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,16 +1,16 @@ -FROM golang:1.11.3-alpine3.8 AS builder +FROM golang:1.12.4-alpine3.9 AS builder RUN apk upgrade \ && apk add git \ && go get -ldflags '-w -s' \ github.com/shadowsocks/go-shadowsocks2 -FROM alpine:3.8 +FROM alpine:3.9 AS dist -LABEL maintainer="mritd " +LABEL maintainer="mritd " RUN apk upgrade \ - && apk add bash tzdata \ + && apk add tzdata \ && rm -rf /var/cache/apk/* COPY --from=builder /go/bin/go-shadowsocks2 /usr/bin/shadowsocks From 4441dc654b22ce57ddf5d0f8e34873745cd53e0d Mon Sep 17 00:00:00 2001 From: Kevin Bai Date: Thu, 2 May 2019 17:38:46 +0800 Subject: [PATCH 07/51] support Go modules --- go.mod | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 go.mod diff --git a/go.mod b/go.mod new file mode 100644 index 00000000..ff2aa333 --- /dev/null +++ b/go.mod @@ -0,0 +1,8 @@ +module github.com/shadowsocks/go-shadowsocks2 + +go 1.12 + +require ( + github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da + golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734 +) From d0e237ceed2bad75135990095ea89dfe2f6de00b Mon Sep 17 00:00:00 2001 From: Riobard Date: Fri, 3 May 2019 19:45:46 +0800 Subject: [PATCH 08/51] Added go.sum --- go.sum | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 go.sum diff --git a/go.sum b/go.sum new file mode 100644 index 00000000..6edcc782 --- /dev/null +++ b/go.sum @@ -0,0 +1,10 @@ +github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da h1:KjTM2ks9d14ZYCvmHS9iAKVt9AyzRSqNU1qabPih5BY= +github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da/go.mod h1:eHEWzANqSiWQsof+nXEI9bUVUyV6F53Fp89EuCh2EAA= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734 h1:p/H982KKEjUnLJkM3tt/LemDnOc1GiZL5FCVlORJ5zo= +golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= From 36204b7539e2278b369ad6370629899814642ca0 Mon Sep 17 00:00:00 2001 From: Kevin Bai Date: Sun, 5 May 2019 11:35:43 +0800 Subject: [PATCH 09/51] Update go.mod add 'replace' directive at go.mod file for China. --- go.mod | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/go.mod b/go.mod index ff2aa333..7c066773 100644 --- a/go.mod +++ b/go.mod @@ -6,3 +6,12 @@ require ( github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734 ) + +replace ( + golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 => github.com/golang/crypto v0.0.0-20190308221718-c2843e01d9a2 + golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734 => github.com/golang/crypto v0.0.0-20190426145343-a29dc8fdc734 + golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 => github.com/golang/net v0.0.0-20190404232315-eb5bcb51f2a3 + golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a => github.com/golang/sys v0.0.0-20190215142949-d0b11bdaac8a + golang.org/x/sys v0.0.0-20190412213103-97732733099d => github.com/golang/sys v0.0.0-20190412213103-97732733099d + golang.org/x/text v0.3.0 => github.com/golang/text v0.3.0 +) From e05f51f0ef75869bab4ba09d1a8c07f34e1c3967 Mon Sep 17 00:00:00 2001 From: mritd Date: Wed, 12 Jun 2019 16:24:33 +0800 Subject: [PATCH 10/51] chore(docker): support go mod support go mod, remove ldflags Signed-off-by: mritd --- Dockerfile | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Dockerfile b/Dockerfile index e35a6440..65fa56dd 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,9 +1,11 @@ -FROM golang:1.12.4-alpine3.9 AS builder +FROM golang:1.12.5-alpine3.9 AS builder + +ENV GO111MODULE on +ENV GOPROXY https://goproxy.io RUN apk upgrade \ && apk add git \ - && go get -ldflags '-w -s' \ - github.com/shadowsocks/go-shadowsocks2 + && go get github.com/shadowsocks/go-shadowsocks2 FROM alpine:3.9 AS dist From 50f32764d38fc27b388ca5392e3df2b00b6c5c66 Mon Sep 17 00:00:00 2001 From: Riobard Date: Sat, 15 Jun 2019 23:26:18 +0800 Subject: [PATCH 11/51] Removed .gitignore file --- .gitignore | 1 - 1 file changed, 1 deletion(-) delete mode 100644 .gitignore diff --git a/.gitignore b/.gitignore deleted file mode 100644 index e660fd93..00000000 --- a/.gitignore +++ /dev/null @@ -1 +0,0 @@ -bin/ From c7fd071f2223634d351d0ed3b0520af162457db1 Mon Sep 17 00:00:00 2001 From: eric Date: Sat, 6 Jul 2019 14:32:30 +0800 Subject: [PATCH 12/51] fix UDP associate Read() simply returns if the buffer is empty. --- tcp.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tcp.go b/tcp.go index fe0701d5..243b2704 100644 --- a/tcp.go +++ b/tcp.go @@ -48,7 +48,7 @@ func tcpLocal(addr, server string, shadow func(net.Conn) net.Conn, getAddr func( // UDP: keep the connection until disconnect then free the UDP socket if err == socks.InfoUDPAssociate { - buf := []byte{} + buf := make([]byte, 1) // block here for { _, err := c.Read(buf) From 77aef2a656cf7daecc4bfdffb3b1a25ceea835c3 Mon Sep 17 00:00:00 2001 From: mritd Date: Fri, 12 Jul 2019 16:16:35 +0800 Subject: [PATCH 13/51] chore(docker): update build image to go 1.12.7 update build image to golang 1.12.7 update base image to alpine 3.10 Signed-off-by: mritd --- Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 65fa56dd..f3212dcb 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM golang:1.12.5-alpine3.9 AS builder +FROM golang:1.12.7-alpine3.10 AS builder ENV GO111MODULE on ENV GOPROXY https://goproxy.io @@ -7,7 +7,7 @@ RUN apk upgrade \ && apk add git \ && go get github.com/shadowsocks/go-shadowsocks2 -FROM alpine:3.9 AS dist +FROM alpine:3.10 AS dist LABEL maintainer="mritd " From a030dd96d640c93f1179a23ce7daca52bb7d8629 Mon Sep 17 00:00:00 2001 From: Rio Date: Thu, 18 Jul 2019 16:01:09 +0800 Subject: [PATCH 14/51] Update README.md Fixed Travis CI build image --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f164a4e3..ac50c862 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ A fresh implementation of Shadowsocks in Go. GoDoc at https://godoc.org/github.com/shadowsocks/go-shadowsocks2/ -[![Build Status](https://travis-ci.org/shadowsocks/go-shadowsocks2.svg?branch=master)](https://travis-ci.org/shadowsocks/go-shadowsocks2) +[![Build Status](https://travis-ci.com/shadowsocks/go-shadowsocks2.svg?branch=master)](https://travis-ci.com/shadowsocks/go-shadowsocks2) ## Features From 48c3647411f2cdded6d250eea891687e2e5ae3cc Mon Sep 17 00:00:00 2001 From: Lucus Lee Date: Wed, 18 Sep 2019 10:25:44 +0900 Subject: [PATCH 15/51] Support SIP003 --- README.md | 23 +++++++++++++++ log.go | 31 ++++++++++++++++++++ main.go | 60 ++++++++++++++++++++++--------------- plugin.go | 88 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 179 insertions(+), 23 deletions(-) create mode 100644 log.go create mode 100644 plugin.go diff --git a/README.md b/README.md index ac50c862..5b63e8f6 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,7 @@ GoDoc at https://godoc.org/github.com/shadowsocks/go-shadowsocks2/ - [x] Support for Netfilter TCP redirect (IPv6 should work but not tested) - [x] UDP tunneling (e.g. relay DNS packets) - [x] TCP tunneling (e.g. benchmark with iperf3) +- [x] SIP003 plugins ## Install @@ -95,6 +96,28 @@ Start iperf3 client to connect to the tunneld port instead iperf3 -c localhost -p 1090 ``` +### SIP003 Plugins (Experimental) + +Both client and server support SIP003 plugins. +Use `-plugin` and `-plugin-opts` parameters to enable. + +Client: + +```sh +shadowsocks2 -c 'ss://AEAD_CHACHA20_POLY1305:your-password@[server_address]:8488' \ + -verbose -socks :1080 -u -plugin v2ray +``` +Server: + +```sh +shadowsocks2 -s 'ss://AEAD_CHACHA20_POLY1305:your-password@:8488' -verbose \ + -plugin v2ray -plugin-opts "server" +``` +Note: + +It will look for the plugin in the current directory first, then `$PATH`. + +UDP connections will not be affected by SIP003. ## Design Principles diff --git a/log.go b/log.go new file mode 100644 index 00000000..7648db61 --- /dev/null +++ b/log.go @@ -0,0 +1,31 @@ +package main + +import ( + "fmt" + "log" + "os" +) + +var logger = log.New(os.Stderr, "", log.Lshortfile|log.LstdFlags) + +func logf(f string, v ...interface{}) { + if config.Verbose { + logger.Output(2, fmt.Sprintf(f, v...)) + } +} + +type logHelper struct { + prefix string +} + +func (l *logHelper) Write(p []byte) (n int, err error) { + if config.Verbose { + logger.Printf("%s%s\n", l.prefix, p) + return len(p), nil + } + return +} + +func newLogHelper(prefix string) *logHelper { + return &logHelper{prefix} +} diff --git a/main.go b/main.go index a2894b5e..e1a7a428 100644 --- a/main.go +++ b/main.go @@ -23,29 +23,23 @@ var config struct { UDPTimeout time.Duration } -var logger = log.New(os.Stderr, "", log.Lshortfile|log.LstdFlags) - -func logf(f string, v ...interface{}) { - if config.Verbose { - logger.Output(2, fmt.Sprintf(f, v...)) - } -} - func main() { var flags struct { - Client string - Server string - Cipher string - Key string - Password string - Keygen int - Socks string - RedirTCP string - RedirTCP6 string - TCPTun string - UDPTun string - UDPSocks bool + Client string + Server string + Cipher string + Key string + Password string + Keygen int + Socks string + RedirTCP string + RedirTCP6 string + TCPTun string + UDPTun string + UDPSocks bool + Plugin string + PluginOpts string } flag.BoolVar(&config.Verbose, "verbose", false, "verbose mode") @@ -61,6 +55,8 @@ func main() { flag.StringVar(&flags.RedirTCP6, "redir6", "", "(client-only) redirect TCP IPv6 from this address") flag.StringVar(&flags.TCPTun, "tcptun", "", "(client-only) TCP tunnel (laddr1=raddr1,laddr2=raddr2,...)") flag.StringVar(&flags.UDPTun, "udptun", "", "(client-only) UDP tunnel (laddr1=raddr1,laddr2=raddr2,...)") + flag.StringVar(&flags.Plugin, "plugin", "", "Enable SIP003 plugin. (e.g., v2ray-plugin)") + flag.StringVar(&flags.PluginOpts, "plugin-opts", "", "Set SIP003 plugin options. (e.g., \"server;tls;host=mydomain.me\")") flag.DurationVar(&config.UDPTimeout, "udptimeout", 5*time.Minute, "UDP tunnel timeout") flag.Parse() @@ -98,15 +94,24 @@ func main() { } } + udpAddr := addr + ciph, err := core.PickCipher(cipher, key, password) if err != nil { log.Fatal(err) } + if flags.Plugin != "" { + addr, err = startPlugin(flags.Plugin, flags.PluginOpts, addr, false) + if err != nil { + log.Fatal(err) + } + } + if flags.UDPTun != "" { for _, tun := range strings.Split(flags.UDPTun, ",") { p := strings.Split(tun, "=") - go udpLocal(p[0], addr, p[1], ciph.PacketConn) + go udpLocal(p[0], udpAddr, p[1], ciph.PacketConn) } } @@ -121,7 +126,7 @@ func main() { socks.UDPEnabled = flags.UDPSocks go socksLocal(flags.Socks, addr, ciph.StreamConn) if flags.UDPSocks { - go udpSocksLocal(flags.Socks, addr, ciph.PacketConn) + go udpSocksLocal(flags.Socks, udpAddr, ciph.PacketConn) } } @@ -147,12 +152,21 @@ func main() { } } + udpAddr := addr + + if flags.Plugin != "" { + addr, err = startPlugin(flags.Plugin, flags.PluginOpts, addr, true) + if err != nil { + log.Fatal(err) + } + } + ciph, err := core.PickCipher(cipher, key, password) if err != nil { log.Fatal(err) } - go udpRemote(addr, ciph.PacketConn) + go udpRemote(udpAddr, ciph.PacketConn) go tcpRemote(addr, ciph.StreamConn) } diff --git a/plugin.go b/plugin.go new file mode 100644 index 00000000..0d2ac420 --- /dev/null +++ b/plugin.go @@ -0,0 +1,88 @@ +package main + +import ( + "fmt" + "net" + "os" + "os/exec" +) + +func startPlugin(plugin, pluginOpts, ssAddr string, isServer bool) (newAddr string, err error) { + logf("starting plugin (%s) with option (%s)....", plugin, pluginOpts) + freePort, err := getFreePort() + if err != nil { + return "", fmt.Errorf("failed to fetch an unused port for plugin (%v)", err) + } + localHost := "127.0.0.1" + ssHost, ssPort, err := net.SplitHostPort(ssAddr) + if err != nil { + return "", err + } + newAddr = localHost + ":" + freePort + if isServer { + if ssHost == "" { + ssHost = "0.0.0.0" + } + logf("plugin (%s) will listen on %s:%s", plugin, ssHost, ssPort) + } else { + logf("plugin (%s) will listen on %s:%s", plugin, localHost, freePort) + } + err = execPlugin(plugin, pluginOpts, ssHost, ssPort, localHost, freePort) + return +} + +func execPlugin(plugin, pluginOpts, remoteHost, remotePort, localHost, localPort string) error { + if fileExists(plugin) { + plugin = "./" + plugin + } + logH := newLogHelper("[" + plugin + "]: ") + env := append(os.Environ(), + "SS_REMOTE_HOST="+remoteHost, + "SS_REMOTE_PORT="+remotePort, + "SS_LOCAL_HOST="+localHost, + "SS_LOCAL_PORT="+localPort, + "SS_PLUGIN_OPTIONS="+pluginOpts, + ) + cmd := &exec.Cmd{ + Path: plugin, + Args: []string{plugin}, + Env: env, + Stdout: logH, + Stderr: logH, + } + if err := cmd.Start(); err != nil { + return err + } + go func() { + if err := cmd.Wait(); err != nil { + logf("plugin exited (%v)\n", err) + os.Exit(2) + } + logf("plugin exited\n") + os.Exit(0) + }() + return nil +} + +func fileExists(filename string) bool { + info, err := os.Stat(filename) + if os.IsNotExist(err) { + return false + } + return !info.IsDir() +} + +func getFreePort() (string, error) { + addr, err := net.ResolveTCPAddr("tcp", "localhost:0") + if err != nil { + return "", err + } + + l, err := net.ListenTCP("tcp", addr) + if err != nil { + return "", err + } + port := fmt.Sprintf("%d", l.Addr().(*net.TCPAddr).Port) + l.Close() + return port, nil +} From b423f77e4717e1b4eb355a15bd6b1b8597e86e5a Mon Sep 17 00:00:00 2001 From: lucus lee Date: Sat, 21 Sep 2019 14:25:08 +0900 Subject: [PATCH 16/51] Fix inconsistency --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 8a324923..71fbf69b 100644 --- a/README.md +++ b/README.md @@ -104,13 +104,13 @@ Use `-plugin` and `-plugin-opts` parameters to enable. Client: ```sh -shadowsocks2 -c 'ss://AEAD_CHACHA20_POLY1305:your-password@[server_address]:8488' \ +go-shadowsocks2 -c 'ss://AEAD_CHACHA20_POLY1305:your-password@[server_address]:8488' \ -verbose -socks :1080 -u -plugin v2ray ``` Server: ```sh -shadowsocks2 -s 'ss://AEAD_CHACHA20_POLY1305:your-password@:8488' -verbose \ +go-shadowsocks2 -s 'ss://AEAD_CHACHA20_POLY1305:your-password@:8488' -verbose \ -plugin v2ray -plugin-opts "server" ``` Note: From 550ec5fe735900e3f65d350ad4f7e564f8dff795 Mon Sep 17 00:00:00 2001 From: Lucus Lee Date: Mon, 23 Sep 2019 02:35:38 +0900 Subject: [PATCH 17/51] Better plugin life-cycle management --- main.go | 1 + plugin.go | 27 ++++++++++++++++++++++++++- 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/main.go b/main.go index e1a7a428..042a10de 100644 --- a/main.go +++ b/main.go @@ -173,6 +173,7 @@ func main() { sigCh := make(chan os.Signal, 1) signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM) <-sigCh + killPlugin() } func parseURL(s string) (addr, cipher, password string, err error) { diff --git a/plugin.go b/plugin.go index 0d2ac420..237296e5 100644 --- a/plugin.go +++ b/plugin.go @@ -5,8 +5,13 @@ import ( "net" "os" "os/exec" + "path/filepath" + "syscall" + "time" ) +var pluginCmd *exec.Cmd + func startPlugin(plugin, pluginOpts, ssAddr string, isServer bool) (newAddr string, err error) { logf("starting plugin (%s) with option (%s)....", plugin, pluginOpts) freePort, err := getFreePort() @@ -31,9 +36,28 @@ func startPlugin(plugin, pluginOpts, ssAddr string, isServer bool) (newAddr stri return } +func killPlugin() { + if pluginCmd != nil { + pluginCmd.Process.Signal(syscall.SIGTERM) + waitCh := make(chan struct{}) + go func() { + pluginCmd.Wait() + close(waitCh) + }() + timeout := time.After(3 * time.Second) + select { + case <-waitCh: + case <-timeout: + pluginCmd.Process.Kill() + } + } +} + func execPlugin(plugin, pluginOpts, remoteHost, remotePort, localHost, localPort string) error { if fileExists(plugin) { - plugin = "./" + plugin + if !filepath.IsAbs(plugin) { + plugin = "./" + plugin + } } logH := newLogHelper("[" + plugin + "]: ") env := append(os.Environ(), @@ -53,6 +77,7 @@ func execPlugin(plugin, pluginOpts, remoteHost, remotePort, localHost, localPort if err := cmd.Start(); err != nil { return err } + pluginCmd = cmd go func() { if err := cmd.Wait(); err != nil { logf("plugin exited (%v)\n", err) From 5f7ec9f6686dccc7bd5d2aa14de06cf63d21efd8 Mon Sep 17 00:00:00 2001 From: Lucus Lee Date: Mon, 23 Sep 2019 02:58:25 +0900 Subject: [PATCH 18/51] Better finding the plugin --- plugin.go | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/plugin.go b/plugin.go index 237296e5..01f7d3ae 100644 --- a/plugin.go +++ b/plugin.go @@ -53,10 +53,16 @@ func killPlugin() { } } -func execPlugin(plugin, pluginOpts, remoteHost, remotePort, localHost, localPort string) error { +func execPlugin(plugin, pluginOpts, remoteHost, remotePort, localHost, localPort string) (err error) { + pluginFile := plugin if fileExists(plugin) { if !filepath.IsAbs(plugin) { - plugin = "./" + plugin + pluginFile = "./" + plugin + } + } else { + pluginFile, err = exec.LookPath(plugin) + if err != nil { + return err } } logH := newLogHelper("[" + plugin + "]: ") @@ -68,13 +74,12 @@ func execPlugin(plugin, pluginOpts, remoteHost, remotePort, localHost, localPort "SS_PLUGIN_OPTIONS="+pluginOpts, ) cmd := &exec.Cmd{ - Path: plugin, - Args: []string{plugin}, + Path: pluginFile, Env: env, Stdout: logH, Stderr: logH, } - if err := cmd.Start(); err != nil { + if err = cmd.Start(); err != nil { return err } pluginCmd = cmd From a57bc393e43ae203c928cf72a32dbe33340f0fcd Mon Sep 17 00:00:00 2001 From: lucus lee Date: Wed, 11 Dec 2019 11:02:44 +0900 Subject: [PATCH 19/51] Fix plugin crash without -verbose flag --- log.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/log.go b/log.go index 7648db61..a9e5bc5e 100644 --- a/log.go +++ b/log.go @@ -23,7 +23,7 @@ func (l *logHelper) Write(p []byte) (n int, err error) { logger.Printf("%s%s\n", l.prefix, p) return len(p), nil } - return + return len(p), nil } func newLogHelper(prefix string) *logHelper { From b388c9cba6c652fd9ee97ef8f45722c507425d68 Mon Sep 17 00:00:00 2001 From: Riobard Date: Sat, 15 Feb 2020 12:04:29 +0800 Subject: [PATCH 20/51] Build binary for win32 --- Makefile | 8 ++++++-- go.sum | 16 ++++++++-------- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/Makefile b/Makefile index 4b23652f..409cf7a3 100644 --- a/Makefile +++ b/Makefile @@ -3,7 +3,7 @@ BINDIR=bin GOBUILD=CGO_ENABLED=0 go build -ldflags '-w -s' # The -w and -s flags reduce binary sizes by excluding unnecessary symbols and debug info -all: linux macos win64 +all: linux macos win64 win32 linux: GOARCH=amd64 GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@ @@ -14,11 +14,15 @@ macos: win64: GOARCH=amd64 GOOS=windows $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe -releases: linux macos win64 +win32: + GOARCH=386 GOOS=windows $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe + +releases: linux macos win64 win32 chmod +x $(BINDIR)/$(NAME)-* gzip $(BINDIR)/$(NAME)-linux gzip $(BINDIR)/$(NAME)-macos zip -m -j $(BINDIR)/$(NAME)-win64.zip $(BINDIR)/$(NAME)-win64.exe + zip -m -j $(BINDIR)/$(NAME)-win64.zip $(BINDIR)/$(NAME)-win32.exe clean: rm $(BINDIR)/* \ No newline at end of file diff --git a/go.sum b/go.sum index 6edcc782..9bf41e35 100644 --- a/go.sum +++ b/go.sum @@ -1,10 +1,10 @@ github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da h1:KjTM2ks9d14ZYCvmHS9iAKVt9AyzRSqNU1qabPih5BY= github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da/go.mod h1:eHEWzANqSiWQsof+nXEI9bUVUyV6F53Fp89EuCh2EAA= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734 h1:p/H982KKEjUnLJkM3tt/LemDnOc1GiZL5FCVlORJ5zo= -golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +github.com/golang/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +github.com/golang/crypto v0.0.0-20190426145343-a29dc8fdc734 h1:qNA2pFZuLItM7tUdO3b9JA2ibgtrUMUznl87BaPSgIg= +github.com/golang/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +github.com/golang/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +github.com/golang/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:5JyrLPvD/ZdaYkT7IqKhsP5xt7aLjA99KXRtk4EIYDk= +github.com/golang/sys v0.0.0-20190412213103-97732733099d h1:blRtD+FQOxZ6P7jigy+HS0R8zyGOMOv8TET4wCpzVwM= +github.com/golang/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +github.com/golang/text v0.3.0/go.mod h1:GUiq9pdJKRKKAZXiVgWFEvocYuREvC14NhI4OPgEjeE= From 510e5b6e6ee589b641e8ea80b2bd08d2ec2c6228 Mon Sep 17 00:00:00 2001 From: Rio Date: Sat, 15 Feb 2020 12:25:05 +0800 Subject: [PATCH 21/51] Create release.yml --- .github/workflows/release.yml | 47 +++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 .github/workflows/release.yml diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 00000000..c7f49d86 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,47 @@ +name: Release +on: release +jobs: + release-linux-amd64: + name: release linux/amd64 + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@master + - name: compile and release + uses: ngs/go-release.action@v1.0.1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GOARCH: amd64 + GOOS: linux + release-darwin-amd64: + name: release darwin/amd64 + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@master + - name: compile and release + uses: ngs/go-release.action@v1.0.1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GOARCH: amd64 + GOOS: darwin + release-windows-386: + name: release windows/386 + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@master + - name: compile and release + uses: ngs/go-release.action@v1.0.1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GOARCH: "386" + GOOS: windows + release-windows-amd64: + name: release windows/amd64 + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@master + - name: compile and release + uses: ngs/go-release.action@v1.0.1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GOARCH: amd64 + GOOS: windows From a34369e55fb4cb2db6ae8cf5a529653adfe14f58 Mon Sep 17 00:00:00 2001 From: Rio Date: Sat, 15 Feb 2020 12:25:29 +0800 Subject: [PATCH 22/51] Delete .travis.yml Switch from Travis CI to GitHub Action --- .travis.yml | 19 ------------------- 1 file changed, 19 deletions(-) delete mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index a79b00e9..00000000 --- a/.travis.yml +++ /dev/null @@ -1,19 +0,0 @@ -language: go -sudo: false -go: -- 1.x -install: go get -u -t -v ./... -script: go test -before_deploy: make -j releases -deploy: - provider: releases - prerelease: true - skip_cleanup: true - file_glob: true - file: bin/* - on: - repo: shadowsocks/go-shadowsocks2 - branch: master - tags: true - api_key: - secure: hXN6BYAim9TYIf/NpI8CuaBQmIJ+q6asypw37DP+cBkdkXbmAX6oSPbQMKUVGQg3U7YNbebaXmCdu87ORiEmTzhAPf2rG86Q80Q1KcU8fpGgxQ+Ie2XmaRlkBnkT1QVym9e/4jCtnBg5B6iDu5//K94ASgKL8z3GLgzJ/+k4lOjxV2iRTrZwdvs30yy3QqSTpp2d8+JMkg3XFv5LiUqTo5xCLgavgRJ23UEAk1JsxgBVFnggEVywtdWKXdFX23CyQ/0rDTdegYXHOGMRSo/PXql8VMFwljc9fSOCjoKtKsmO/W61T2O/2OY/aOcyIdztEDoboZ6hfFt7hYsakLWhx6UoTH6k6PTwzTqsOjW+RofZVxGXG8sNmRi1bQBX3pDQaVTnzFZ7cuf9qclT3ATgISVTErF6fakXBTBja53IfdKOMgThPJNF7vThq1SFyhXnysmHKyHJq/4BM6OEb/Ac25ZhILU1FfwTcRjwi8w6iOGLU6vwcypuqctO9d5NbmNdkJ32YsbcDZoUaJxEUA+LiyaZYkjVyKWkpEA7Vjj7kcUdxMLXGyRKqT0ufinVpx2pAqMeXvM7BaIneoqJk6JRidsxc2HxI9qRcslrs6gcyI6x8qtJgVwlPJgmyodt/oSQC4astqmSoa6CIIP3lYgq/78jy/LxIDJt4d2bJGBMkEg= From 674c9bd5600c77efda9cc9f96f6a040108a7534c Mon Sep 17 00:00:00 2001 From: Rio Date: Sat, 15 Feb 2020 12:29:12 +0800 Subject: [PATCH 23/51] Create build.yml --- .github/workflows/build.yml | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 .github/workflows/build.yml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 00000000..fe18be92 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,28 @@ +name: Build +on: [push] +jobs: + + build: + name: Build + runs-on: ubuntu-latest + steps: + + - name: Set up Go 1.13 + uses: actions/setup-go@v1 + with: + go-version: 1.13 + id: go + + - name: Check out code into the Go module directory + uses: actions/checkout@v2 + + - name: Get dependencies + run: | + go get -v -t -d ./... + if [ -f Gopkg.toml ]; then + curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh + dep ensure + fi + + - name: Build + run: go build -v . From 4094d5e519bebbb39afc7538500c131b447e1b7e Mon Sep 17 00:00:00 2001 From: Rio Date: Sat, 15 Feb 2020 12:31:27 +0800 Subject: [PATCH 24/51] Delete release.yml --- .github/workflows/release.yml | 47 ----------------------------------- 1 file changed, 47 deletions(-) delete mode 100644 .github/workflows/release.yml diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml deleted file mode 100644 index c7f49d86..00000000 --- a/.github/workflows/release.yml +++ /dev/null @@ -1,47 +0,0 @@ -name: Release -on: release -jobs: - release-linux-amd64: - name: release linux/amd64 - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@master - - name: compile and release - uses: ngs/go-release.action@v1.0.1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - GOARCH: amd64 - GOOS: linux - release-darwin-amd64: - name: release darwin/amd64 - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@master - - name: compile and release - uses: ngs/go-release.action@v1.0.1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - GOARCH: amd64 - GOOS: darwin - release-windows-386: - name: release windows/386 - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@master - - name: compile and release - uses: ngs/go-release.action@v1.0.1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - GOARCH: "386" - GOOS: windows - release-windows-amd64: - name: release windows/amd64 - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@master - - name: compile and release - uses: ngs/go-release.action@v1.0.1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - GOARCH: amd64 - GOOS: windows From 8a9bda6195b7d932b27ff6f1968c74a24df6ed33 Mon Sep 17 00:00:00 2001 From: Rio Date: Sat, 15 Feb 2020 12:31:45 +0800 Subject: [PATCH 25/51] Create release.yml --- .github/workflows/release.yml | 47 +++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 .github/workflows/release.yml diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 00000000..c7f49d86 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,47 @@ +name: Release +on: release +jobs: + release-linux-amd64: + name: release linux/amd64 + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@master + - name: compile and release + uses: ngs/go-release.action@v1.0.1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GOARCH: amd64 + GOOS: linux + release-darwin-amd64: + name: release darwin/amd64 + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@master + - name: compile and release + uses: ngs/go-release.action@v1.0.1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GOARCH: amd64 + GOOS: darwin + release-windows-386: + name: release windows/386 + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@master + - name: compile and release + uses: ngs/go-release.action@v1.0.1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GOARCH: "386" + GOOS: windows + release-windows-amd64: + name: release windows/amd64 + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@master + - name: compile and release + uses: ngs/go-release.action@v1.0.1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GOARCH: amd64 + GOOS: windows From 8c89620df12a3239aceeceef7cef3e458e11b424 Mon Sep 17 00:00:00 2001 From: Neo Zhuo Date: Sat, 15 Feb 2020 14:41:49 +0800 Subject: [PATCH 26/51] implements BloomRing and saltfilter --- go.mod | 1 + go.sum | 4 ++ internal/bloomring.go | 67 +++++++++++++++++++++++++++++++ internal/bloomring_test.go | 68 ++++++++++++++++++++++++++++++++ internal/saltfilter.go | 80 ++++++++++++++++++++++++++++++++++++++ 5 files changed, 220 insertions(+) create mode 100644 internal/bloomring.go create mode 100644 internal/bloomring_test.go create mode 100644 internal/saltfilter.go diff --git a/go.mod b/go.mod index 7c066773..ec09b99e 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,7 @@ go 1.12 require ( github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da + github.com/riobard/go-bloom v0.0.0-20200213042214-218e1707c495 golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734 ) diff --git a/go.sum b/go.sum index 9bf41e35..5fea12d9 100644 --- a/go.sum +++ b/go.sum @@ -8,3 +8,7 @@ github.com/golang/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:5JyrLPvD/ZdaY github.com/golang/sys v0.0.0-20190412213103-97732733099d h1:blRtD+FQOxZ6P7jigy+HS0R8zyGOMOv8TET4wCpzVwM= github.com/golang/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= github.com/golang/text v0.3.0/go.mod h1:GUiq9pdJKRKKAZXiVgWFEvocYuREvC14NhI4OPgEjeE= +github.com/riobard/go-bloom v0.0.0-20170218180955-2b113c64a69b h1:H9yjH/g5w8MOPjQR2zMSP/Md1kKtj/33fIht9ChC2OU= +github.com/riobard/go-bloom v0.0.0-20170218180955-2b113c64a69b/go.mod h1:HgjTstvQsPGkxUsCd2KWxErBblirPizecHcpD3ffK+s= +github.com/riobard/go-bloom v0.0.0-20200213042214-218e1707c495 h1:p7xbxYTzzfXghR1kpsJDeoVVRRWAotKc8u7FP/N48rU= +github.com/riobard/go-bloom v0.0.0-20200213042214-218e1707c495/go.mod h1:HgjTstvQsPGkxUsCd2KWxErBblirPizecHcpD3ffK+s= diff --git a/internal/bloomring.go b/internal/bloomring.go new file mode 100644 index 00000000..7a603a40 --- /dev/null +++ b/internal/bloomring.go @@ -0,0 +1,67 @@ +package internal + +import ( + "hash/fnv" + "sync" + + "github.com/riobard/go-bloom" +) + +// simply use Double FNV here as our Bloom Filter hash +func doubleFNV(b []byte) (uint64, uint64) { + hx := fnv.New64() + hx.Write(b) + x := hx.Sum64() + hy := fnv.New64a() + hy.Write(b) + y := hy.Sum64() + return x, y +} + +type BloomRing struct { + slotCapacity int + slotPosition int + slotCount int + entryCounter int + slots []bloom.Filter + mutex sync.RWMutex +} + +func NewBloomRing(slot, capacity int, falsePositiveRate float64) *BloomRing { + // Calculate entries for each slot + r := &BloomRing{ + slotCapacity: capacity / slot, + slotCount: slot, + slots: make([]bloom.Filter, slot), + } + for i := 0; i < slot; i++ { + r.slots[i] = bloom.New(r.slotCapacity, falsePositiveRate, doubleFNV) + } + return r +} + +func (r *BloomRing) Add(b []byte) { + r.mutex.Lock() + defer r.mutex.Unlock() + slot := r.slots[r.slotPosition] + if r.entryCounter > r.slotCapacity { + // Move to next slot and reset + r.slotPosition = (r.slotPosition + 1) % r.slotCount + slot = r.slots[r.slotPosition] + slot.Reset() + r.entryCounter = 0 + } + r.entryCounter++ + slot.Add(b) +} + +func (r *BloomRing) Test(b []byte) bool { + r.mutex.RLock() + defer r.mutex.RUnlock() + for _, s := range r.slots { + if s.Test(b) { + return true + } + } + return false +} diff --git a/internal/bloomring_test.go b/internal/bloomring_test.go new file mode 100644 index 00000000..7f230215 --- /dev/null +++ b/internal/bloomring_test.go @@ -0,0 +1,68 @@ +package internal_test + +import ( + "fmt" + "os" + "testing" + + "github.com/shadowsocks/go-shadowsocks2/internal" +) + +var ( + bloomRingInstance *internal.BloomRing +) + +func TestMain(m *testing.M) { + bloomRingInstance = internal.NewBloomRing(internal.DefaultSFSlot, int(internal.DefaultSFCapacity), + internal.DefaultSFFPR) + os.Exit(m.Run()) +} + +func TestBloomRing_Add(t *testing.T) { + defer func() { + if any := recover(); any != nil { + t.Fatalf("Should not got panic while adding item: %v", any) + } + }() + bloomRingInstance.Add(make([]byte, 16)) +} + +func TestBloomRing_Test(t *testing.T) { + buf := []byte("shadowsocks") + bloomRingInstance.Add(buf) + if !bloomRingInstance.Test(buf) { + t.Fatal("Test on filter missing") + } +} + +func BenchmarkBloomRing(b *testing.B) { + // Generate test samples with different length + samples := make([][]byte, internal.DefaultSFCapacity-internal.DefaultSFSlot) + var checkPoints [][]byte + for i := 0; i < len(samples); i++ { + samples[i] = []byte(fmt.Sprint(i)) + if i%1000 == 0 { + checkPoints = append(checkPoints, samples[i]) + } + } + b.Logf("Generated %d samples and %d check points", len(samples), len(checkPoints)) + for i := 1; i < 16; i++ { + b.Run(fmt.Sprintf("Slot%d", i), benchmarkBloomRing(samples, checkPoints, i)) + } +} + +func benchmarkBloomRing(samples, checkPoints [][]byte, slot int) func(*testing.B) { + filter := internal.NewBloomRing(slot, int(internal.DefaultSFCapacity), internal.DefaultSFFPR) + for _, sample := range samples { + filter.Add(sample) + } + return func(b *testing.B) { + b.ResetTimer() + b.ReportAllocs() + for i := 0; i < b.N; i++ { + for _, cp := range checkPoints { + filter.Test(cp) + } + } + } +} diff --git a/internal/saltfilter.go b/internal/saltfilter.go new file mode 100644 index 00000000..aed9b6e0 --- /dev/null +++ b/internal/saltfilter.go @@ -0,0 +1,80 @@ +package internal + +import ( + "fmt" + "os" + "strconv" +) + +// Those suggest value are all set according to +// https://github.com/shadowsocks/shadowsocks-org/issues/44#issuecomment-281021054 +// Due to this package contains various internal implementation so const named with DefaultBR prefix +const ( + DefaultSFCapacity = 1e6 + // FalsePositiveRate + DefaultSFFPR = 1e-6 + DefaultSFSlot = 10 +) + +const EnvironmentPrefix = "SHADOWSOCKS_" + +// A shared instance used for checking salt repeat +var saltfilter *BloomRing + +func init() { + var ( + finalCapacity = DefaultSFCapacity + finalFPR = DefaultSFFPR + finalSlot = float64(DefaultSFSlot) + ) + for _, opt := range []struct { + ENVName string + Target *float64 + }{ + { + ENVName: "CAPACITY", + Target: &finalCapacity, + }, + { + ENVName: "FPR", + Target: &finalFPR, + }, + { + ENVName: "SLOT", + Target: &finalSlot, + }, + } { + envKey := EnvironmentPrefix + "SF_" + opt.ENVName + env := os.Getenv(envKey) + if env != "" { + p, err := strconv.ParseFloat(env, 64) + if err != nil { + panic(fmt.Sprintf("Invalid envrionment `%s` setting in saltfilter: %s", envKey, env)) + } + *opt.Target = p + } + } + // Support disable saltfilter by given a negative capacity + if finalCapacity <= 0 { + return + } + saltfilter = NewBloomRing(int(finalSlot), int(finalCapacity), finalFPR) +} + +// TestSalt returns true if salt is repeated +func TestSalt(b []byte) bool { + // If nil means feature disabled, return false to bypass salt repeat detection + if saltfilter == nil { + return false + } + return saltfilter.Test(b) +} + +// AddSalt salt to filter +func AddSalt(b []byte) { + // If nil means feature disabled + if saltfilter == nil { + return + } + saltfilter.Add(b) +} From 5d517aa8542471ccc64def6647d108855d501382 Mon Sep 17 00:00:00 2001 From: Neo Zhuo Date: Sat, 15 Feb 2020 14:42:04 +0800 Subject: [PATCH 27/51] integrate cipher with saltfilter --- shadowaead/cipher.go | 4 ++++ shadowaead/packet.go | 7 +++++++ shadowaead/stream.go | 8 +++++++- shadowstream/cipher.go | 4 ++++ shadowstream/packet.go | 8 +++++++- shadowstream/stream.go | 7 +++++++ 6 files changed, 36 insertions(+), 2 deletions(-) diff --git a/shadowaead/cipher.go b/shadowaead/cipher.go index 19410df9..e60fc54d 100644 --- a/shadowaead/cipher.go +++ b/shadowaead/cipher.go @@ -4,6 +4,7 @@ import ( "crypto/aes" "crypto/cipher" "crypto/sha1" + "errors" "io" "strconv" @@ -11,6 +12,9 @@ import ( "golang.org/x/crypto/hkdf" ) +// ErrRepeatedSalt means detected a reused salt +var ErrRepeatedSalt = errors.New("repeated salt detected") + type Cipher interface { KeySize() int SaltSize() int diff --git a/shadowaead/packet.go b/shadowaead/packet.go index ae5f84d4..6f48f14c 100644 --- a/shadowaead/packet.go +++ b/shadowaead/packet.go @@ -6,6 +6,8 @@ import ( "io" "net" "sync" + + "github.com/shadowsocks/go-shadowsocks2/internal" ) // ErrShortPacket means that the packet is too short for a valid encrypted packet. @@ -27,6 +29,7 @@ func Pack(dst, plaintext []byte, ciph Cipher) ([]byte, error) { if err != nil { return nil, err } + internal.AddSalt(salt) if len(dst) < saltSize+len(plaintext)+aead.Overhead() { return nil, io.ErrShortBuffer @@ -43,10 +46,14 @@ func Unpack(dst, pkt []byte, ciph Cipher) ([]byte, error) { return nil, ErrShortPacket } salt := pkt[:saltSize] + if internal.TestSalt(salt) { + return nil, ErrRepeatedSalt + } aead, err := ciph.Decrypter(salt) if err != nil { return nil, err } + internal.AddSalt(salt) if len(pkt) < saltSize+aead.Overhead() { return nil, ErrShortPacket } diff --git a/shadowaead/stream.go b/shadowaead/stream.go index 5f499a21..a41e14ea 100644 --- a/shadowaead/stream.go +++ b/shadowaead/stream.go @@ -6,6 +6,8 @@ import ( "crypto/rand" "io" "net" + + "github.com/shadowsocks/go-shadowsocks2/internal" ) // payloadSizeMask is the maximum size of payload in bytes. @@ -203,11 +205,14 @@ func (c *streamConn) initReader() error { if _, err := io.ReadFull(c.Conn, salt); err != nil { return err } - + if internal.TestSalt(salt) { + return ErrRepeatedSalt + } aead, err := c.Decrypter(salt) if err != nil { return err } + internal.AddSalt(salt) c.r = newReader(c.Conn, aead) return nil @@ -244,6 +249,7 @@ func (c *streamConn) initWriter() error { if err != nil { return err } + internal.AddSalt(salt) c.w = newWriter(c.Conn, aead) return nil } diff --git a/shadowstream/cipher.go b/shadowstream/cipher.go index fa916c9c..a0aedba7 100644 --- a/shadowstream/cipher.go +++ b/shadowstream/cipher.go @@ -3,12 +3,16 @@ package shadowstream import ( "crypto/aes" "crypto/cipher" + "errors" "strconv" "github.com/aead/chacha20" "github.com/aead/chacha20/chacha" ) +// ErrRepeatedSalt means detected a reused salt +var ErrRepeatedSalt = errors.New("repeated salt detected") + // Cipher generates a pair of stream ciphers for encryption and decryption. type Cipher interface { IVSize() int diff --git a/shadowstream/packet.go b/shadowstream/packet.go index 0defa110..4ae7ee43 100644 --- a/shadowstream/packet.go +++ b/shadowstream/packet.go @@ -6,6 +6,8 @@ import ( "io" "net" "sync" + + "github.com/shadowsocks/go-shadowsocks2/internal" ) // ErrShortPacket means the packet is too short to be a valid encrypted packet. @@ -23,7 +25,7 @@ func Pack(dst, plaintext []byte, s Cipher) ([]byte, error) { if err != nil { return nil, err } - + internal.AddSalt(iv) s.Encrypter(iv).XORKeyStream(dst[len(iv):], plaintext) return dst[:len(iv)+len(plaintext)], nil } @@ -39,6 +41,10 @@ func Unpack(dst, pkt []byte, s Cipher) ([]byte, error) { return nil, io.ErrShortBuffer } iv := pkt[:s.IVSize()] + if internal.TestSalt(iv) { + return nil, ErrRepeatedSalt + } + internal.AddSalt(iv) s.Decrypter(iv).XORKeyStream(dst, pkt[len(iv):]) return dst[:len(pkt)-len(iv)], nil } diff --git a/shadowstream/stream.go b/shadowstream/stream.go index eb4d9679..0cbf8fb2 100644 --- a/shadowstream/stream.go +++ b/shadowstream/stream.go @@ -6,6 +6,8 @@ import ( "crypto/rand" "io" "net" + + "github.com/shadowsocks/go-shadowsocks2/internal" ) const bufSize = 32 * 1024 @@ -114,6 +116,10 @@ func (c *conn) initReader() error { if _, err := io.ReadFull(c.Conn, iv); err != nil { return err } + if internal.TestSalt(iv) { + return ErrRepeatedSalt + } + internal.AddSalt(iv) c.r = &reader{Reader: c.Conn, Stream: c.Decrypter(iv), buf: buf} } return nil @@ -147,6 +153,7 @@ func (c *conn) initWriter() error { if _, err := c.Conn.Write(iv); err != nil { return err } + internal.AddSalt(iv) c.w = &writer{Writer: c.Conn, Stream: c.Encrypter(iv), buf: buf} } return nil From 85d570e940a6701e65292ed4a2799afbc4ddb718 Mon Sep 17 00:00:00 2001 From: Neo Zhuo Date: Sat, 15 Feb 2020 14:52:10 +0800 Subject: [PATCH 28/51] tidy go mods --- go.sum | 2 -- 1 file changed, 2 deletions(-) diff --git a/go.sum b/go.sum index 5fea12d9..23592e19 100644 --- a/go.sum +++ b/go.sum @@ -8,7 +8,5 @@ github.com/golang/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:5JyrLPvD/ZdaY github.com/golang/sys v0.0.0-20190412213103-97732733099d h1:blRtD+FQOxZ6P7jigy+HS0R8zyGOMOv8TET4wCpzVwM= github.com/golang/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= github.com/golang/text v0.3.0/go.mod h1:GUiq9pdJKRKKAZXiVgWFEvocYuREvC14NhI4OPgEjeE= -github.com/riobard/go-bloom v0.0.0-20170218180955-2b113c64a69b h1:H9yjH/g5w8MOPjQR2zMSP/Md1kKtj/33fIht9ChC2OU= -github.com/riobard/go-bloom v0.0.0-20170218180955-2b113c64a69b/go.mod h1:HgjTstvQsPGkxUsCd2KWxErBblirPizecHcpD3ffK+s= github.com/riobard/go-bloom v0.0.0-20200213042214-218e1707c495 h1:p7xbxYTzzfXghR1kpsJDeoVVRRWAotKc8u7FP/N48rU= github.com/riobard/go-bloom v0.0.0-20200213042214-218e1707c495/go.mod h1:HgjTstvQsPGkxUsCd2KWxErBblirPizecHcpD3ffK+s= From 9e4f39dcf46b7a3f233c6ba18148d09b5a0a41ca Mon Sep 17 00:00:00 2001 From: Neo Zhuo Date: Sat, 15 Feb 2020 15:20:25 +0800 Subject: [PATCH 29/51] update README with saltfilter decription --- README.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/README.md b/README.md index 71fbf69b..17f98180 100644 --- a/README.md +++ b/README.md @@ -119,6 +119,22 @@ It will look for the plugin in the current directory first, then `$PATH`. UDP connections will not be affected by SIP003. +### Reuse Detection + +This feature used for resistance with reuse attack by checking cipher salt/iv is repeated. + +Expose some environment variables below to control this feature: +- `SHADOWSOCKS_SF_CAPACITY`(an integer): The most recently salt items to keep for checking duplication. Default 1e6, +on gave a non-positive integer this feature will be disabled; +- `SHADOWSOCKS_SF_FPR`(decimal): False positive rate of the filter, 0.0003 means 0.03% FPR. Default 1e-6; +- `SHADOWSOCKS_SF_SLOT`(a positive integer): All the salt items will be added into lots(how many this variable defines) +filter items for the check. Default 10. + + +```sh +SHADOWSOCKS_SF_CAPACITY=1e6 SHADOWSOCKS_SF_FPR=1e-6 SHADOWSOCKS_SF_SLOT=10 go-shadowsocks2 ... +``` + ## Design Principles The code base strives to From c03a025f8f9a64500dc1708464ba924e2759e48f Mon Sep 17 00:00:00 2001 From: Riobard Date: Thu, 20 Feb 2020 17:41:54 +0800 Subject: [PATCH 30/51] Deprecate all stream ciphers And AES-192-GCM which no one ever uses anyway. --- core/cipher.go | 42 ---------- go.mod | 1 - go.sum | 2 - shadowstream/cipher.go | 96 ---------------------- shadowstream/doc.go | 2 - shadowstream/packet.go | 86 -------------------- shadowstream/stream.go | 178 ----------------------------------------- 7 files changed, 407 deletions(-) delete mode 100644 shadowstream/cipher.go delete mode 100644 shadowstream/doc.go delete mode 100644 shadowstream/packet.go delete mode 100644 shadowstream/stream.go diff --git a/core/cipher.go b/core/cipher.go index f2899622..71a67d8a 100644 --- a/core/cipher.go +++ b/core/cipher.go @@ -8,7 +8,6 @@ import ( "strings" "github.com/shadowsocks/go-shadowsocks2/shadowaead" - "github.com/shadowsocks/go-shadowsocks2/shadowstream" ) type Cipher interface { @@ -29,7 +28,6 @@ var ErrCipherNotSupported = errors.New("cipher not supported") const ( aeadAes128Gcm = "AEAD_AES_128_GCM" - aeadAes192Gcm = "AEAD_AES_192_GCM" aeadAes256Gcm = "AEAD_AES_256_GCM" aeadChacha20Poly1305 = "AEAD_CHACHA20_POLY1305" ) @@ -40,35 +38,16 @@ var aeadList = map[string]struct { New func([]byte) (shadowaead.Cipher, error) }{ aeadAes128Gcm: {16, shadowaead.AESGCM}, - aeadAes192Gcm: {24, shadowaead.AESGCM}, aeadAes256Gcm: {32, shadowaead.AESGCM}, aeadChacha20Poly1305: {32, shadowaead.Chacha20Poly1305}, } -// List of stream ciphers: key size in bytes and constructor -var streamList = map[string]struct { - KeySize int - New func(key []byte) (shadowstream.Cipher, error) -}{ - "AES-128-CTR": {16, shadowstream.AESCTR}, - "AES-192-CTR": {24, shadowstream.AESCTR}, - "AES-256-CTR": {32, shadowstream.AESCTR}, - "AES-128-CFB": {16, shadowstream.AESCFB}, - "AES-192-CFB": {24, shadowstream.AESCFB}, - "AES-256-CFB": {32, shadowstream.AESCFB}, - "CHACHA20-IETF": {32, shadowstream.Chacha20IETF}, - "XCHACHA20": {32, shadowstream.Xchacha20}, -} - // ListCipher returns a list of available cipher names sorted alphabetically. func ListCipher() []string { var l []string for k := range aeadList { l = append(l, k) } - for k := range streamList { - l = append(l, k) - } sort.Strings(l) return l } @@ -84,8 +63,6 @@ func PickCipher(name string, key []byte, password string) (Cipher, error) { name = aeadChacha20Poly1305 case "AES-128-GCM": name = aeadAes128Gcm - case "AES-192-GCM": - name = aeadAes192Gcm case "AES-256-GCM": name = aeadAes256Gcm } @@ -101,17 +78,6 @@ func PickCipher(name string, key []byte, password string) (Cipher, error) { return &aeadCipher{aead}, err } - if choice, ok := streamList[name]; ok { - if len(key) == 0 { - key = kdf(password, choice.KeySize) - } - if len(key) != choice.KeySize { - return nil, shadowstream.KeySizeError(choice.KeySize) - } - ciph, err := choice.New(key) - return &streamCipher{ciph}, err - } - return nil, ErrCipherNotSupported } @@ -122,15 +88,7 @@ func (aead *aeadCipher) PacketConn(c net.PacketConn) net.PacketConn { return shadowaead.NewPacketConn(c, aead) } -type streamCipher struct{ shadowstream.Cipher } - -func (ciph *streamCipher) StreamConn(c net.Conn) net.Conn { return shadowstream.NewConn(c, ciph) } -func (ciph *streamCipher) PacketConn(c net.PacketConn) net.PacketConn { - return shadowstream.NewPacketConn(c, ciph) -} - // dummy cipher does not encrypt - type dummy struct{} func (dummy) StreamConn(c net.Conn) net.Conn { return c } diff --git a/go.mod b/go.mod index ec09b99e..453b627d 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,6 @@ module github.com/shadowsocks/go-shadowsocks2 go 1.12 require ( - github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da github.com/riobard/go-bloom v0.0.0-20200213042214-218e1707c495 golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734 ) diff --git a/go.sum b/go.sum index 23592e19..0e6b7bf8 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,3 @@ -github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da h1:KjTM2ks9d14ZYCvmHS9iAKVt9AyzRSqNU1qabPih5BY= -github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da/go.mod h1:eHEWzANqSiWQsof+nXEI9bUVUyV6F53Fp89EuCh2EAA= github.com/golang/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= github.com/golang/crypto v0.0.0-20190426145343-a29dc8fdc734 h1:qNA2pFZuLItM7tUdO3b9JA2ibgtrUMUznl87BaPSgIg= github.com/golang/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= diff --git a/shadowstream/cipher.go b/shadowstream/cipher.go deleted file mode 100644 index a0aedba7..00000000 --- a/shadowstream/cipher.go +++ /dev/null @@ -1,96 +0,0 @@ -package shadowstream - -import ( - "crypto/aes" - "crypto/cipher" - "errors" - "strconv" - - "github.com/aead/chacha20" - "github.com/aead/chacha20/chacha" -) - -// ErrRepeatedSalt means detected a reused salt -var ErrRepeatedSalt = errors.New("repeated salt detected") - -// Cipher generates a pair of stream ciphers for encryption and decryption. -type Cipher interface { - IVSize() int - Encrypter(iv []byte) cipher.Stream - Decrypter(iv []byte) cipher.Stream -} - -type KeySizeError int - -func (e KeySizeError) Error() string { - return "key size error: need " + strconv.Itoa(int(e)) + " bytes" -} - -// CTR mode -type ctrStream struct{ cipher.Block } - -func (b *ctrStream) IVSize() int { return b.BlockSize() } -func (b *ctrStream) Decrypter(iv []byte) cipher.Stream { return b.Encrypter(iv) } -func (b *ctrStream) Encrypter(iv []byte) cipher.Stream { return cipher.NewCTR(b, iv) } - -func AESCTR(key []byte) (Cipher, error) { - blk, err := aes.NewCipher(key) - if err != nil { - return nil, err - } - return &ctrStream{blk}, nil -} - -// CFB mode -type cfbStream struct{ cipher.Block } - -func (b *cfbStream) IVSize() int { return b.BlockSize() } -func (b *cfbStream) Decrypter(iv []byte) cipher.Stream { return cipher.NewCFBDecrypter(b, iv) } -func (b *cfbStream) Encrypter(iv []byte) cipher.Stream { return cipher.NewCFBEncrypter(b, iv) } - -func AESCFB(key []byte) (Cipher, error) { - blk, err := aes.NewCipher(key) - if err != nil { - return nil, err - } - return &cfbStream{blk}, nil -} - -// IETF-variant of chacha20 -type chacha20ietfkey []byte - -func (k chacha20ietfkey) IVSize() int { return chacha.INonceSize } -func (k chacha20ietfkey) Decrypter(iv []byte) cipher.Stream { return k.Encrypter(iv) } -func (k chacha20ietfkey) Encrypter(iv []byte) cipher.Stream { - ciph, err := chacha20.NewCipher(iv, k) - if err != nil { - panic(err) // should never happen - } - return ciph -} - -func Chacha20IETF(key []byte) (Cipher, error) { - if len(key) != chacha.KeySize { - return nil, KeySizeError(chacha.KeySize) - } - return chacha20ietfkey(key), nil -} - -type xchacha20key []byte - -func (k xchacha20key) IVSize() int { return chacha.XNonceSize } -func (k xchacha20key) Decrypter(iv []byte) cipher.Stream { return k.Encrypter(iv) } -func (k xchacha20key) Encrypter(iv []byte) cipher.Stream { - ciph, err := chacha20.NewCipher(iv, k) - if err != nil { - panic(err) // should never happen - } - return ciph -} - -func Xchacha20(key []byte) (Cipher, error) { - if len(key) != chacha.KeySize { - return nil, KeySizeError(chacha.KeySize) - } - return xchacha20key(key), nil -} diff --git a/shadowstream/doc.go b/shadowstream/doc.go deleted file mode 100644 index 4c0897ab..00000000 --- a/shadowstream/doc.go +++ /dev/null @@ -1,2 +0,0 @@ -// Package shadowstream implements the original Shadowsocks protocol protected by stream cipher. -package shadowstream diff --git a/shadowstream/packet.go b/shadowstream/packet.go deleted file mode 100644 index 4ae7ee43..00000000 --- a/shadowstream/packet.go +++ /dev/null @@ -1,86 +0,0 @@ -package shadowstream - -import ( - "crypto/rand" - "errors" - "io" - "net" - "sync" - - "github.com/shadowsocks/go-shadowsocks2/internal" -) - -// ErrShortPacket means the packet is too short to be a valid encrypted packet. -var ErrShortPacket = errors.New("short packet") - -// Pack encrypts plaintext using stream cipher s and a random IV. -// Returns a slice of dst containing random IV and ciphertext. -// Ensure len(dst) >= s.IVSize() + len(plaintext). -func Pack(dst, plaintext []byte, s Cipher) ([]byte, error) { - if len(dst) < s.IVSize()+len(plaintext) { - return nil, io.ErrShortBuffer - } - iv := dst[:s.IVSize()] - _, err := io.ReadFull(rand.Reader, iv) - if err != nil { - return nil, err - } - internal.AddSalt(iv) - s.Encrypter(iv).XORKeyStream(dst[len(iv):], plaintext) - return dst[:len(iv)+len(plaintext)], nil -} - -// Unpack decrypts pkt using stream cipher s. -// Returns a slice of dst containing decrypted plaintext. -func Unpack(dst, pkt []byte, s Cipher) ([]byte, error) { - if len(pkt) < s.IVSize() { - return nil, ErrShortPacket - } - - if len(dst) < len(pkt)-s.IVSize() { - return nil, io.ErrShortBuffer - } - iv := pkt[:s.IVSize()] - if internal.TestSalt(iv) { - return nil, ErrRepeatedSalt - } - internal.AddSalt(iv) - s.Decrypter(iv).XORKeyStream(dst, pkt[len(iv):]) - return dst[:len(pkt)-len(iv)], nil -} - -type packetConn struct { - net.PacketConn - Cipher - buf []byte - sync.Mutex // write lock -} - -// NewPacketConn wraps a net.PacketConn with stream cipher encryption/decryption. -func NewPacketConn(c net.PacketConn, ciph Cipher) net.PacketConn { - return &packetConn{PacketConn: c, Cipher: ciph, buf: make([]byte, 64*1024)} -} - -func (c *packetConn) WriteTo(b []byte, addr net.Addr) (int, error) { - c.Lock() - defer c.Unlock() - buf, err := Pack(c.buf, b, c.Cipher) - if err != nil { - return 0, err - } - _, err = c.PacketConn.WriteTo(buf, addr) - return len(b), err -} - -func (c *packetConn) ReadFrom(b []byte) (int, net.Addr, error) { - n, addr, err := c.PacketConn.ReadFrom(b) - if err != nil { - return n, addr, err - } - bb, err := Unpack(b[c.IVSize():], b[:n], c.Cipher) - if err != nil { - return n, addr, err - } - copy(b, bb) - return len(bb), addr, err -} diff --git a/shadowstream/stream.go b/shadowstream/stream.go deleted file mode 100644 index 0cbf8fb2..00000000 --- a/shadowstream/stream.go +++ /dev/null @@ -1,178 +0,0 @@ -package shadowstream - -import ( - "bytes" - "crypto/cipher" - "crypto/rand" - "io" - "net" - - "github.com/shadowsocks/go-shadowsocks2/internal" -) - -const bufSize = 32 * 1024 - -type writer struct { - io.Writer - cipher.Stream - buf []byte -} - -// NewWriter wraps an io.Writer with stream cipher encryption. -func NewWriter(w io.Writer, s cipher.Stream) io.Writer { - return &writer{Writer: w, Stream: s, buf: make([]byte, bufSize)} -} - -func (w *writer) ReadFrom(r io.Reader) (n int64, err error) { - for { - buf := w.buf - nr, er := r.Read(buf) - if nr > 0 { - n += int64(nr) - buf = buf[:nr] - w.XORKeyStream(buf, buf) - _, ew := w.Writer.Write(buf) - if ew != nil { - err = ew - return - } - } - - if er != nil { - if er != io.EOF { // ignore EOF as per io.ReaderFrom contract - err = er - } - return - } - } -} - -func (w *writer) Write(b []byte) (int, error) { - n, err := w.ReadFrom(bytes.NewBuffer(b)) - return int(n), err -} - -type reader struct { - io.Reader - cipher.Stream - buf []byte -} - -// NewReader wraps an io.Reader with stream cipher decryption. -func NewReader(r io.Reader, s cipher.Stream) io.Reader { - return &reader{Reader: r, Stream: s, buf: make([]byte, bufSize)} -} - -func (r *reader) Read(b []byte) (int, error) { - - n, err := r.Reader.Read(b) - if err != nil { - return 0, err - } - b = b[:n] - r.XORKeyStream(b, b) - return n, nil -} - -func (r *reader) WriteTo(w io.Writer) (n int64, err error) { - for { - buf := r.buf - nr, er := r.Read(buf) - if nr > 0 { - nw, ew := w.Write(buf[:nr]) - n += int64(nw) - - if ew != nil { - err = ew - return - } - } - - if er != nil { - if er != io.EOF { // ignore EOF as per io.Copy contract (using src.WriteTo shortcut) - err = er - } - return - } - } -} - -type conn struct { - net.Conn - Cipher - r *reader - w *writer -} - -// NewConn wraps a stream-oriented net.Conn with stream cipher encryption/decryption. -func NewConn(c net.Conn, ciph Cipher) net.Conn { - return &conn{Conn: c, Cipher: ciph} -} - -func (c *conn) initReader() error { - if c.r == nil { - buf := make([]byte, bufSize) - iv := buf[:c.IVSize()] - if _, err := io.ReadFull(c.Conn, iv); err != nil { - return err - } - if internal.TestSalt(iv) { - return ErrRepeatedSalt - } - internal.AddSalt(iv) - c.r = &reader{Reader: c.Conn, Stream: c.Decrypter(iv), buf: buf} - } - return nil -} - -func (c *conn) Read(b []byte) (int, error) { - if c.r == nil { - if err := c.initReader(); err != nil { - return 0, err - } - } - return c.r.Read(b) -} - -func (c *conn) WriteTo(w io.Writer) (int64, error) { - if c.r == nil { - if err := c.initReader(); err != nil { - return 0, err - } - } - return c.r.WriteTo(w) -} - -func (c *conn) initWriter() error { - if c.w == nil { - buf := make([]byte, bufSize) - iv := buf[:c.IVSize()] - if _, err := io.ReadFull(rand.Reader, iv); err != nil { - return err - } - if _, err := c.Conn.Write(iv); err != nil { - return err - } - internal.AddSalt(iv) - c.w = &writer{Writer: c.Conn, Stream: c.Encrypter(iv), buf: buf} - } - return nil -} - -func (c *conn) Write(b []byte) (int, error) { - if c.w == nil { - if err := c.initWriter(); err != nil { - return 0, err - } - } - return c.w.Write(b) -} - -func (c *conn) ReadFrom(r io.Reader) (int64, error) { - if c.w == nil { - if err := c.initWriter(); err != nil { - return 0, err - } - } - return c.w.ReadFrom(r) -} From 8b8484abcfaf35f7a37e904e83824b4664f25193 Mon Sep 17 00:00:00 2001 From: Riobard Date: Fri, 31 Jan 2020 18:41:10 +0800 Subject: [PATCH 31/51] Split Netfilter and pf code into subpackages --- go.mod | 2 +- go.sum | 4 +- nfutil/nf_linux.go | 55 ++++++++++++++ .../socketcall_linux_386.go | 2 +- .../socketcall_linux_other.go | 2 +- pfutil/pf_darwin.go | 51 +++++++++++++ tcp_darwin.go | 24 ++++++ tcp_linux.go | 76 ++----------------- tcp_other.go | 2 +- 9 files changed, 144 insertions(+), 74 deletions(-) create mode 100644 nfutil/nf_linux.go rename tcp_linux_386.go => nfutil/socketcall_linux_386.go (96%) rename tcp_linux_other.go => nfutil/socketcall_linux_other.go (94%) create mode 100644 pfutil/pf_darwin.go create mode 100644 tcp_darwin.go diff --git a/go.mod b/go.mod index 453b627d..7aa4b934 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.12 require ( github.com/riobard/go-bloom v0.0.0-20200213042214-218e1707c495 - golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734 + golang.org/x/crypto v0.0.0-20200128174031-69ecbb4d6d5d ) replace ( diff --git a/go.sum b/go.sum index 0e6b7bf8..685f40ff 100644 --- a/go.sum +++ b/go.sum @@ -1,6 +1,4 @@ github.com/golang/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -github.com/golang/crypto v0.0.0-20190426145343-a29dc8fdc734 h1:qNA2pFZuLItM7tUdO3b9JA2ibgtrUMUznl87BaPSgIg= -github.com/golang/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= github.com/golang/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= github.com/golang/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:5JyrLPvD/ZdaYkT7IqKhsP5xt7aLjA99KXRtk4EIYDk= github.com/golang/sys v0.0.0-20190412213103-97732733099d h1:blRtD+FQOxZ6P7jigy+HS0R8zyGOMOv8TET4wCpzVwM= @@ -8,3 +6,5 @@ github.com/golang/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQ github.com/golang/text v0.3.0/go.mod h1:GUiq9pdJKRKKAZXiVgWFEvocYuREvC14NhI4OPgEjeE= github.com/riobard/go-bloom v0.0.0-20200213042214-218e1707c495 h1:p7xbxYTzzfXghR1kpsJDeoVVRRWAotKc8u7FP/N48rU= github.com/riobard/go-bloom v0.0.0-20200213042214-218e1707c495/go.mod h1:HgjTstvQsPGkxUsCd2KWxErBblirPizecHcpD3ffK+s= +golang.org/x/crypto v0.0.0-20200128174031-69ecbb4d6d5d h1:9FCpayM9Egr1baVnV1SX0H87m+XB0B8S0hAMi99X/3U= +golang.org/x/crypto v0.0.0-20200128174031-69ecbb4d6d5d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= diff --git a/nfutil/nf_linux.go b/nfutil/nf_linux.go new file mode 100644 index 00000000..dc165363 --- /dev/null +++ b/nfutil/nf_linux.go @@ -0,0 +1,55 @@ +package nfutil + +import ( + "net" + "syscall" + "unsafe" +) + +// Get the original destination of a TCP connection redirected by Netfilter. +func GetOrigDst(c *net.TCPConn, ipv6 bool) (*net.TCPAddr, error) { + rc, err := c.SyscallConn() + if err != nil { + return nil, err + } + var addr *net.TCPAddr + rc.Control(func(fd uintptr) { + if ipv6 { + addr, err = ipv6_getorigdst(fd) + } else { + addr, err = getorigdst(fd) + } + }) + return addr, err +} + +// Call getorigdst() from linux/net/ipv4/netfilter/nf_conntrack_l3proto_ipv4.c +func getorigdst(fd uintptr) (*net.TCPAddr, error) { + const _SO_ORIGINAL_DST = 80 // from linux/include/uapi/linux/netfilter_ipv4.h + var raw syscall.RawSockaddrInet4 + siz := unsafe.Sizeof(raw) + if err := socketcall(GETSOCKOPT, fd, syscall.IPPROTO_IP, _SO_ORIGINAL_DST, uintptr(unsafe.Pointer(&raw)), uintptr(unsafe.Pointer(&siz)), 0); err != nil { + return nil, err + } + var addr net.TCPAddr + addr.IP = raw.Addr[:] + port := (*[2]byte)(unsafe.Pointer(&raw.Port)) // raw.Port is big-endian + addr.Port = int(port[0])<<8 | int(port[1]) + return &addr, nil +} + +// Call ipv6_getorigdst() from linux/net/ipv6/netfilter/nf_conntrack_l3proto_ipv6.c +// NOTE: I haven't tried yet but it should work since Linux 3.8. +func ipv6_getorigdst(fd uintptr) (*net.TCPAddr, error) { + const _IP6T_SO_ORIGINAL_DST = 80 // from linux/include/uapi/linux/netfilter_ipv6/ip6_tables.h + var raw syscall.RawSockaddrInet6 + siz := unsafe.Sizeof(raw) + if err := socketcall(GETSOCKOPT, fd, syscall.IPPROTO_IPV6, _IP6T_SO_ORIGINAL_DST, uintptr(unsafe.Pointer(&raw)), uintptr(unsafe.Pointer(&siz)), 0); err != nil { + return nil, err + } + var addr net.TCPAddr + addr.IP = raw.Addr[:] + port := (*[2]byte)(unsafe.Pointer(&raw.Port)) // raw.Port is big-endian + addr.Port = int(port[0])<<8 | int(port[1]) + return &addr, nil +} diff --git a/tcp_linux_386.go b/nfutil/socketcall_linux_386.go similarity index 96% rename from tcp_linux_386.go rename to nfutil/socketcall_linux_386.go index 8b8db6fb..e4c91e48 100644 --- a/tcp_linux_386.go +++ b/nfutil/socketcall_linux_386.go @@ -1,4 +1,4 @@ -package main +package nfutil import ( "syscall" diff --git a/tcp_linux_other.go b/nfutil/socketcall_linux_other.go similarity index 94% rename from tcp_linux_other.go rename to nfutil/socketcall_linux_other.go index 96f9561a..6f7ca324 100644 --- a/tcp_linux_other.go +++ b/nfutil/socketcall_linux_other.go @@ -1,6 +1,6 @@ // +build linux,!386 -package main +package nfutil import "syscall" diff --git a/pfutil/pf_darwin.go b/pfutil/pf_darwin.go new file mode 100644 index 00000000..ad2127fb --- /dev/null +++ b/pfutil/pf_darwin.go @@ -0,0 +1,51 @@ +package pfutil + +import ( + "net" + "syscall" + "unsafe" +) + +func NatLookup(c *net.TCPConn) (*net.TCPAddr, error) { + const ( + PF_INOUT = 0 + PF_IN = 1 + PF_OUT = 2 + IOC_OUT = 0x40000000 + IOC_IN = 0x80000000 + IOC_INOUT = IOC_IN | IOC_OUT + IOCPARM_MASK = 0x1FFF + LEN = 4*16 + 4*4 + 4*1 + // #define _IOC(inout,group,num,len) (inout | ((len & IOCPARM_MASK) << 16) | ((group) << 8) | (num)) + // #define _IOWR(g,n,t) _IOC(IOC_INOUT, (g), (n), sizeof(t)) + // #define DIOCNATLOOK _IOWR('D', 23, struct pfioc_natlook) + DIOCNATLOOK = IOC_INOUT | ((LEN & IOCPARM_MASK) << 16) | ('D' << 8) | 23 + ) + fd, err := syscall.Open("/dev/pf", 0, syscall.O_RDONLY) + if err != nil { + return nil, err + } + defer syscall.Close(fd) + nl := struct { // struct pfioc_natlook + saddr, daddr, rsaddr, rdaddr [16]byte + sxport, dxport, rsxport, rdxport [4]byte + af, proto, protoVariant, direction uint8 + }{ + af: syscall.AF_INET, + proto: syscall.IPPROTO_TCP, + direction: PF_OUT, + } + saddr := c.RemoteAddr().(*net.TCPAddr) + daddr := c.LocalAddr().(*net.TCPAddr) + copy(nl.saddr[:], saddr.IP) + copy(nl.daddr[:], daddr.IP) + nl.sxport[0], nl.sxport[1] = byte(saddr.Port>>8), byte(saddr.Port) + nl.dxport[0], nl.dxport[1] = byte(daddr.Port>>8), byte(daddr.Port) + if _, _, errno := syscall.Syscall(syscall.SYS_IOCTL, uintptr(fd), DIOCNATLOOK, uintptr(unsafe.Pointer(&nl))); errno != 0 { + return nil, errno + } + var addr net.TCPAddr + addr.IP = nl.rdaddr[:4] + addr.Port = int(nl.rdxport[0])<<8 | int(nl.rdxport[1]) + return &addr, nil +} diff --git a/tcp_darwin.go b/tcp_darwin.go new file mode 100644 index 00000000..1497d5d8 --- /dev/null +++ b/tcp_darwin.go @@ -0,0 +1,24 @@ +package main + +import ( + "net" + + "github.com/shadowsocks/go-shadowsocks2/pfutil" + "github.com/shadowsocks/go-shadowsocks2/socks" +) + +func redirLocal(addr, server string, shadow func(net.Conn) net.Conn) { + tcpLocal(addr, server, shadow, natLookup) +} + +func redir6Local(addr, server string, shadow func(net.Conn) net.Conn) { + panic("TCP6 redirect not supported") +} + +func natLookup(c net.Conn) (socks.Addr, error) { + if tc, ok := c.(*net.TCPConn); ok { + addr, err := pfutil.NatLookup(tc) + return socks.ParseAddr(addr.String()), err + } + panic("not TCP connection") +} diff --git a/tcp_linux.go b/tcp_linux.go index 84d10e78..6f4d11e7 100644 --- a/tcp_linux.go +++ b/tcp_linux.go @@ -1,18 +1,19 @@ package main import ( - "errors" "net" - "syscall" - "unsafe" + "github.com/shadowsocks/go-shadowsocks2/nfutil" "github.com/shadowsocks/go-shadowsocks2/socks" ) -const ( - SO_ORIGINAL_DST = 80 // from linux/include/uapi/linux/netfilter_ipv4.h - IP6T_SO_ORIGINAL_DST = 80 // from linux/include/uapi/linux/netfilter_ipv6/ip6_tables.h -) +func getOrigDst(c net.Conn, ipv6 bool) (socks.Addr, error) { + if tc, ok := c.(*net.TCPConn); ok { + addr, err := nfutil.GetOrigDst(tc, ipv6) + return socks.ParseAddr(addr.String()), err + } + panic("not a TCP connection") +} // Listen on addr for netfilter redirected TCP connections func redirLocal(addr, server string, shadow func(net.Conn) net.Conn) { @@ -25,64 +26,3 @@ func redir6Local(addr, server string, shadow func(net.Conn) net.Conn) { logf("TCP6 redirect %s <-> %s", addr, server) tcpLocal(addr, server, shadow, func(c net.Conn) (socks.Addr, error) { return getOrigDst(c, true) }) } - -// Get the original destination of a TCP connection. -func getOrigDst(conn net.Conn, ipv6 bool) (socks.Addr, error) { - c, ok := conn.(*net.TCPConn) - if !ok { - return nil, errors.New("only work with TCP connection") - } - f, err := c.File() - if err != nil { - return nil, err - } - defer f.Close() - - fd := f.Fd() - - // The File() call above puts both the original socket fd and the file fd in blocking mode. - // Set the file fd back to non-blocking mode and the original socket fd will become non-blocking as well. - // Otherwise blocking I/O will waste OS threads. - if err := syscall.SetNonblock(int(fd), true); err != nil { - return nil, err - } - - if ipv6 { - return ipv6_getorigdst(fd) - } - - return getorigdst(fd) -} - -// Call getorigdst() from linux/net/ipv4/netfilter/nf_conntrack_l3proto_ipv4.c -func getorigdst(fd uintptr) (socks.Addr, error) { - raw := syscall.RawSockaddrInet4{} - siz := unsafe.Sizeof(raw) - if err := socketcall(GETSOCKOPT, fd, syscall.IPPROTO_IP, SO_ORIGINAL_DST, uintptr(unsafe.Pointer(&raw)), uintptr(unsafe.Pointer(&siz)), 0); err != nil { - return nil, err - } - - addr := make([]byte, 1+net.IPv4len+2) - addr[0] = socks.AtypIPv4 - copy(addr[1:1+net.IPv4len], raw.Addr[:]) - port := (*[2]byte)(unsafe.Pointer(&raw.Port)) // big-endian - addr[1+net.IPv4len], addr[1+net.IPv4len+1] = port[0], port[1] - return addr, nil -} - -// Call ipv6_getorigdst() from linux/net/ipv6/netfilter/nf_conntrack_l3proto_ipv6.c -// NOTE: I haven't tried yet but it should work since Linux 3.8. -func ipv6_getorigdst(fd uintptr) (socks.Addr, error) { - raw := syscall.RawSockaddrInet6{} - siz := unsafe.Sizeof(raw) - if err := socketcall(GETSOCKOPT, fd, syscall.IPPROTO_IPV6, IP6T_SO_ORIGINAL_DST, uintptr(unsafe.Pointer(&raw)), uintptr(unsafe.Pointer(&siz)), 0); err != nil { - return nil, err - } - - addr := make([]byte, 1+net.IPv6len+2) - addr[0] = socks.AtypIPv6 - copy(addr[1:1+net.IPv6len], raw.Addr[:]) - port := (*[2]byte)(unsafe.Pointer(&raw.Port)) // big-endian - addr[1+net.IPv6len], addr[1+net.IPv6len+1] = port[0], port[1] - return addr, nil -} diff --git a/tcp_other.go b/tcp_other.go index a7c41d63..aa4549a7 100644 --- a/tcp_other.go +++ b/tcp_other.go @@ -1,4 +1,4 @@ -// +build !linux +// +build !linux,!darwin package main From 99671dd6aa2730cdfb2da265fb32b47daac169de Mon Sep 17 00:00:00 2001 From: Riobard Date: Thu, 20 Feb 2020 19:10:45 +0800 Subject: [PATCH 32/51] Fix win32 release build --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 409cf7a3..666ebba7 100644 --- a/Makefile +++ b/Makefile @@ -22,7 +22,7 @@ releases: linux macos win64 win32 gzip $(BINDIR)/$(NAME)-linux gzip $(BINDIR)/$(NAME)-macos zip -m -j $(BINDIR)/$(NAME)-win64.zip $(BINDIR)/$(NAME)-win64.exe - zip -m -j $(BINDIR)/$(NAME)-win64.zip $(BINDIR)/$(NAME)-win32.exe + zip -m -j $(BINDIR)/$(NAME)-win32.zip $(BINDIR)/$(NAME)-win32.exe clean: rm $(BINDIR)/* \ No newline at end of file From b0fcb11abbfa3916ba57db3db18485a210f1a04a Mon Sep 17 00:00:00 2001 From: Riobard Date: Thu, 20 Feb 2020 19:20:25 +0800 Subject: [PATCH 33/51] Updated workflow --- .github/workflows/build.yml | 26 +++------------- .github/workflows/release.yml | 57 ++++++++--------------------------- Makefile | 28 +++++++++++++++-- 3 files changed, 44 insertions(+), 67 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index fe18be92..2b1b40c3 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,28 +1,12 @@ -name: Build +name: Build and test on: [push] jobs: - build: - name: Build runs-on: ubuntu-latest steps: - - - name: Set up Go 1.13 - uses: actions/setup-go@v1 + - uses: actions/setup-go@v1 with: go-version: 1.13 - id: go - - - name: Check out code into the Go module directory - uses: actions/checkout@v2 - - - name: Get dependencies - run: | - go get -v -t -d ./... - if [ -f Gopkg.toml ]; then - curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh - dep ensure - fi - - - name: Build - run: go build -v . + - uses: actions/checkout@v2 + - run: make -j all + - run: make -j test \ No newline at end of file diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c7f49d86..5d2a09fa 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,47 +1,16 @@ -name: Release -on: release +name: Build and release +on: + release: + types: [published] jobs: - release-linux-amd64: - name: release linux/amd64 + build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@master - - name: compile and release - uses: ngs/go-release.action@v1.0.1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - GOARCH: amd64 - GOOS: linux - release-darwin-amd64: - name: release darwin/amd64 - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@master - - name: compile and release - uses: ngs/go-release.action@v1.0.1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - GOARCH: amd64 - GOOS: darwin - release-windows-386: - name: release windows/386 - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@master - - name: compile and release - uses: ngs/go-release.action@v1.0.1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - GOARCH: "386" - GOOS: windows - release-windows-amd64: - name: release windows/amd64 - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@master - - name: compile and release - uses: ngs/go-release.action@v1.0.1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - GOARCH: amd64 - GOOS: windows + - uses: actions/checkout@v2 + - uses: actions/setup-go@v1 + with: + go-version: 1.13 + - run: make -j upload + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITHUB_RELEASE_UPLOAD_URL: ${{ github.event.release.upload_url }} \ No newline at end of file diff --git a/Makefile b/Makefile index 666ebba7..c585cea6 100644 --- a/Makefile +++ b/Makefile @@ -17,12 +17,36 @@ win64: win32: GOARCH=386 GOOS=windows $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe + +test: test-linux test-macos test-win64 test-win32 + +test-linux: + GOARCH=amd64 GOOS=linux go test + +test-macos: + GOARCH=amd64 GOOS=darwin go test + +test-win64: + GOARCH=amd64 GOOS=windows go test + +test-win32: + GOARCH=386 GOOS=windows go test + releases: linux macos win64 win32 chmod +x $(BINDIR)/$(NAME)-* gzip $(BINDIR)/$(NAME)-linux gzip $(BINDIR)/$(NAME)-macos - zip -m -j $(BINDIR)/$(NAME)-win64.zip $(BINDIR)/$(NAME)-win64.exe zip -m -j $(BINDIR)/$(NAME)-win32.zip $(BINDIR)/$(NAME)-win32.exe + zip -m -j $(BINDIR)/$(NAME)-win64.zip $(BINDIR)/$(NAME)-win64.exe clean: - rm $(BINDIR)/* \ No newline at end of file + rm $(BINDIR)/* + +# Remove trailing {} from the release upload url +GITHUB_UPLOAD_URL=$(shell echo $${GITHUB_RELEASE_UPLOAD_URL%\{*}) + +upload: releases + curl -H "Authorization: token $(GITHUB_TOKEN)" -H "Content-Type: application/gzip" --data-binary @$(BINDIR)/$(NAME)-linux.gz "$(GITHUB_UPLOAD_URL)?name=$(NAME)-linux.gz" + curl -H "Authorization: token $(GITHUB_TOKEN)" -H "Content-Type: application/gzip" --data-binary @$(BINDIR)/$(NAME)-macos.gz "$(GITHUB_UPLOAD_URL)?name=$(NAME)-macos.gz" + curl -H "Authorization: token $(GITHUB_TOKEN)" -H "Content-Type: application/zip" --data-binary @$(BINDIR)/$(NAME)-win64.zip "$(GITHUB_UPLOAD_URL)?name=$(NAME)-win64.zip" + curl -H "Authorization: token $(GITHUB_TOKEN)" -H "Content-Type: application/zip" --data-binary @$(BINDIR)/$(NAME)-win32.zip "$(GITHUB_UPLOAD_URL)?name=$(NAME)-win32.zip" \ No newline at end of file From 07b789296fe637222aa91652f5830b4ec55af0dd Mon Sep 17 00:00:00 2001 From: Riobard Date: Thu, 20 Feb 2020 22:19:18 +0800 Subject: [PATCH 34/51] Update README --- README.md | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 17f98180..1d93bd78 100644 --- a/README.md +++ b/README.md @@ -4,16 +4,18 @@ A fresh implementation of Shadowsocks in Go. GoDoc at https://godoc.org/github.com/shadowsocks/go-shadowsocks2/ -[![Build Status](https://travis-ci.com/shadowsocks/go-shadowsocks2.svg?branch=master)](https://travis-ci.com/shadowsocks/go-shadowsocks2) +![Build and test](https://github.com/shadowsocks/go-shadowsocks2/workflows/Build%20and%20test/badge.svg) ## Features - [x] SOCKS5 proxy with UDP Associate -- [x] Support for Netfilter TCP redirect (IPv6 should work but not tested) +- [x] Support for Netfilter TCP redirect on Linux (IPv6 should work but not tested) +- [x] Support for Packet Filter TCP redirect on MacOS/Darwin (IPv4 only) - [x] UDP tunneling (e.g. relay DNS packets) - [x] TCP tunneling (e.g. benchmark with iperf3) - [x] SIP003 plugins +- [x] Replay attack mitigation ## Install @@ -56,7 +58,7 @@ Replace `[server_address]` with the server's public address. ## Advanced Usage -### Netfilter TCP redirect (Linux only) +### Netfilter TCP redirect on Linux The client offers `-redir` and `-redir6` (for IPv6) options to handle TCP connections redirected by Netfilter on Linux. The feature works similar to `ss-redir` from `shadowsocks-libev`. @@ -119,17 +121,15 @@ It will look for the plugin in the current directory first, then `$PATH`. UDP connections will not be affected by SIP003. -### Reuse Detection +### Replay Attack Mitigation -This feature used for resistance with reuse attack by checking cipher salt/iv is repeated. - -Expose some environment variables below to control this feature: -- `SHADOWSOCKS_SF_CAPACITY`(an integer): The most recently salt items to keep for checking duplication. Default 1e6, -on gave a non-positive integer this feature will be disabled; -- `SHADOWSOCKS_SF_FPR`(decimal): False positive rate of the filter, 0.0003 means 0.03% FPR. Default 1e-6; -- `SHADOWSOCKS_SF_SLOT`(a positive integer): All the salt items will be added into lots(how many this variable defines) -filter items for the check. Default 10. +By default a [Bloom filter](https://en.wikipedia.org/wiki/Bloom_filter) is deployed to defend against [replay attacks](https://en.wikipedia.org/wiki/Replay_attack). +Use the following environment variables to fine-tune the mechanism: +- `SHADOWSOCKS_SF_CAPACITY`: Number of recent connections to track. Default `1e6` (one million). Setting it to 0 disables the feature. +- `SHADOWSOCKS_SF_FPR`: False positive rate of the Bloom filter. Default `1e-6` (0.0001%). This should be enough for most cases. +- `SHADOWSOCKS_SF_SLOT`: The Bloom filter is divided into a number (default `10`) of slots. When the Bloom filter is full, the + oldest slot will be cleared for recycling. In general you should not change this number unless you understand what you are doing. ```sh SHADOWSOCKS_SF_CAPACITY=1e6 SHADOWSOCKS_SF_FPR=1e-6 SHADOWSOCKS_SF_SLOT=10 go-shadowsocks2 ... From 1b56d9d3efc0abd62a282717ba84dbab4ebcaa3f Mon Sep 17 00:00:00 2001 From: Rio Date: Sun, 7 Jun 2020 17:54:28 +0800 Subject: [PATCH 35/51] Update issue templates --- .github/ISSUE_TEMPLATE/bug_report.md | 38 +++++++++++++++++++++++ .github/ISSUE_TEMPLATE/feature_request.md | 20 ++++++++++++ 2 files changed, 58 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/bug_report.md create mode 100644 .github/ISSUE_TEMPLATE/feature_request.md diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 00000000..dd84ea78 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,38 @@ +--- +name: Bug report +about: Create a report to help us improve +title: '' +labels: '' +assignees: '' + +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Steps to reproduce the behavior: +1. Go to '...' +2. Click on '....' +3. Scroll down to '....' +4. See error + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Screenshots** +If applicable, add screenshots to help explain your problem. + +**Desktop (please complete the following information):** + - OS: [e.g. iOS] + - Browser [e.g. chrome, safari] + - Version [e.g. 22] + +**Smartphone (please complete the following information):** + - Device: [e.g. iPhone6] + - OS: [e.g. iOS8.1] + - Browser [e.g. stock browser, safari] + - Version [e.g. 22] + +**Additional context** +Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 00000000..bbcbbe7d --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,20 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: '' +labels: '' +assignees: '' + +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. From 172f9e06370e8c8ab79b1012dea6c1325e878834 Mon Sep 17 00:00:00 2001 From: Riobard Date: Wed, 24 Jun 2020 01:15:34 +0800 Subject: [PATCH 36/51] Server-side UDP disabled by default Use -udp flag to turn on UDP support on server --- main.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/main.go b/main.go index 042a10de..02f2b90e 100644 --- a/main.go +++ b/main.go @@ -38,6 +38,7 @@ func main() { TCPTun string UDPTun string UDPSocks bool + UDP bool Plugin string PluginOpts string } @@ -57,6 +58,7 @@ func main() { flag.StringVar(&flags.UDPTun, "udptun", "", "(client-only) UDP tunnel (laddr1=raddr1,laddr2=raddr2,...)") flag.StringVar(&flags.Plugin, "plugin", "", "Enable SIP003 plugin. (e.g., v2ray-plugin)") flag.StringVar(&flags.PluginOpts, "plugin-opts", "", "Set SIP003 plugin options. (e.g., \"server;tls;host=mydomain.me\")") + flag.BoolVar(&flags.UDP, "udp", false, "(server-only) enable UDP support") flag.DurationVar(&config.UDPTimeout, "udptimeout", 5*time.Minute, "UDP tunnel timeout") flag.Parse() @@ -166,7 +168,9 @@ func main() { log.Fatal(err) } - go udpRemote(udpAddr, ciph.PacketConn) + if flags.UDP { + go udpRemote(udpAddr, ciph.PacketConn) + } go tcpRemote(addr, ciph.StreamConn) } From 6bd5a4ab4db0fce30fde694992fe5a62182ed519 Mon Sep 17 00:00:00 2001 From: mritd Date: Sun, 12 Jul 2020 13:29:25 +0800 Subject: [PATCH 37/51] chore(docker): update base image update base image Signed-off-by: mritd --- Dockerfile | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Dockerfile b/Dockerfile index f3212dcb..3a49ebfe 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,13 +1,13 @@ -FROM golang:1.12.7-alpine3.10 AS builder +FROM golang:1.14.4-alpine3.12 AS builder ENV GO111MODULE on -ENV GOPROXY https://goproxy.io +#ENV GOPROXY https://goproxy.io RUN apk upgrade \ && apk add git \ && go get github.com/shadowsocks/go-shadowsocks2 -FROM alpine:3.10 AS dist +FROM alpine:3.12 AS dist LABEL maintainer="mritd " From 6e80ea880a6653d2bedc7f17b12589d47f056f48 Mon Sep 17 00:00:00 2001 From: mritd Date: Sun, 12 Jul 2020 13:32:01 +0800 Subject: [PATCH 38/51] chore(docker): add goproxy add goproxy Signed-off-by: mritd --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 3a49ebfe..2439a077 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,7 @@ FROM golang:1.14.4-alpine3.12 AS builder ENV GO111MODULE on -#ENV GOPROXY https://goproxy.io +ENV GOPROXY https://goproxy.cn RUN apk upgrade \ && apk add git \ From c4371a6ab856a78c209804347b4170266c5f5bf3 Mon Sep 17 00:00:00 2001 From: Riobard Date: Sun, 9 Aug 2020 13:03:39 +0800 Subject: [PATCH 39/51] Add a toggle to disable TCP mode --- main.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/main.go b/main.go index 02f2b90e..281b6a8a 100644 --- a/main.go +++ b/main.go @@ -39,6 +39,7 @@ func main() { UDPTun string UDPSocks bool UDP bool + TCP bool Plugin string PluginOpts string } @@ -59,6 +60,7 @@ func main() { flag.StringVar(&flags.Plugin, "plugin", "", "Enable SIP003 plugin. (e.g., v2ray-plugin)") flag.StringVar(&flags.PluginOpts, "plugin-opts", "", "Set SIP003 plugin options. (e.g., \"server;tls;host=mydomain.me\")") flag.BoolVar(&flags.UDP, "udp", false, "(server-only) enable UDP support") + flag.BoolVar(&flags.UDP, "tcp", true, "(server-only) enable TCP support") flag.DurationVar(&config.UDPTimeout, "udptimeout", 5*time.Minute, "UDP tunnel timeout") flag.Parse() @@ -171,7 +173,9 @@ func main() { if flags.UDP { go udpRemote(udpAddr, ciph.PacketConn) } - go tcpRemote(addr, ciph.StreamConn) + if flags.TCP { + go tcpRemote(addr, ciph.StreamConn) + } } sigCh := make(chan os.Signal, 1) From 50676a73f6643df35b3d15ea74850b142c46df94 Mon Sep 17 00:00:00 2001 From: Riobard Date: Sun, 9 Aug 2020 16:55:43 +0800 Subject: [PATCH 40/51] Optionally use TCP_CORK on client-side TCP_CORK/TCP_NOPUSH coalesces salt+address+initial data in one packet --- main.go | 2 ++ tcp.go | 6 +++++- tcp_darwin.go | 17 +++++++++++++++++ tcp_linux.go | 17 +++++++++++++++++ tcp_other.go | 7 ++++++- 5 files changed, 47 insertions(+), 2 deletions(-) diff --git a/main.go b/main.go index 281b6a8a..196eb018 100644 --- a/main.go +++ b/main.go @@ -21,6 +21,7 @@ import ( var config struct { Verbose bool UDPTimeout time.Duration + TCPCork bool } func main() { @@ -61,6 +62,7 @@ func main() { flag.StringVar(&flags.PluginOpts, "plugin-opts", "", "Set SIP003 plugin options. (e.g., \"server;tls;host=mydomain.me\")") flag.BoolVar(&flags.UDP, "udp", false, "(server-only) enable UDP support") flag.BoolVar(&flags.UDP, "tcp", true, "(server-only) enable TCP support") + flag.BoolVar(&config.TCPCork, "tcpcork", false, "(client-only) enable TCP_CORK (Linux) or TCP_NOPUSH (BSD) for the first few packets") flag.DurationVar(&config.UDPTimeout, "udptimeout", 5*time.Minute, "UDP tunnel timeout") flag.Parse() diff --git a/tcp.go b/tcp.go index 243b2704..b47e3c3b 100644 --- a/tcp.go +++ b/tcp.go @@ -70,7 +70,11 @@ func tcpLocal(addr, server string, shadow func(net.Conn) net.Conn, getAddr func( return } defer rc.Close() - rc.(*net.TCPConn).SetKeepAlive(true) + tc := rc.(*net.TCPConn) + tc.SetKeepAlive(true) + if config.TCPCork { + timedCork(tc, 10*time.Millisecond) + } rc = shadow(rc) if _, err = rc.Write(tgt); err != nil { diff --git a/tcp_darwin.go b/tcp_darwin.go index 1497d5d8..29e5eb5b 100644 --- a/tcp_darwin.go +++ b/tcp_darwin.go @@ -2,6 +2,8 @@ package main import ( "net" + "syscall" + "time" "github.com/shadowsocks/go-shadowsocks2/pfutil" "github.com/shadowsocks/go-shadowsocks2/socks" @@ -22,3 +24,18 @@ func natLookup(c net.Conn) (socks.Addr, error) { } panic("not TCP connection") } + +func timedCork(c *net.TCPConn, d time.Duration) error { + rc, err := c.SyscallConn() + if err != nil { + return err + } + rc.Control(func(fd uintptr) { err = syscall.SetsockoptInt(int(fd), syscall.IPPROTO_TCP, syscall.TCP_NOPUSH, 1) }) + if err != nil { + return err + } + time.AfterFunc(d, func() { + rc.Control(func(fd uintptr) { syscall.SetsockoptInt(int(fd), syscall.IPPROTO_TCP, syscall.TCP_NOPUSH, 0) }) + }) + return nil +} diff --git a/tcp_linux.go b/tcp_linux.go index 6f4d11e7..60612d66 100644 --- a/tcp_linux.go +++ b/tcp_linux.go @@ -2,6 +2,8 @@ package main import ( "net" + "syscall" + "time" "github.com/shadowsocks/go-shadowsocks2/nfutil" "github.com/shadowsocks/go-shadowsocks2/socks" @@ -26,3 +28,18 @@ func redir6Local(addr, server string, shadow func(net.Conn) net.Conn) { logf("TCP6 redirect %s <-> %s", addr, server) tcpLocal(addr, server, shadow, func(c net.Conn) (socks.Addr, error) { return getOrigDst(c, true) }) } + +func timedCork(c *net.TCPConn, d time.Duration) error { + rc, err := c.SyscallConn() + if err != nil { + return err + } + rc.Control(func(fd uintptr) { err = syscall.SetsockoptInt(int(fd), syscall.IPPROTO_TCP, syscall.TCP_CORK, 1) }) + if err != nil { + return err + } + time.AfterFunc(d, func() { + rc.Control(func(fd uintptr) { syscall.SetsockoptInt(int(fd), syscall.IPPROTO_TCP, syscall.TCP_CORK, 0) }) + }) + return nil +} diff --git a/tcp_other.go b/tcp_other.go index aa4549a7..e5cde9ad 100644 --- a/tcp_other.go +++ b/tcp_other.go @@ -2,7 +2,10 @@ package main -import "net" +import ( + "net" + "time" +) func redirLocal(addr, server string, shadow func(net.Conn) net.Conn) { logf("TCP redirect not supported") @@ -11,3 +14,5 @@ func redirLocal(addr, server string, shadow func(net.Conn) net.Conn) { func redir6Local(addr, server string, shadow func(net.Conn) net.Conn) { logf("TCP6 redirect not supported") } + +func timedCork(c *net.TCPConn, d time.Duration) error { return nil } From a44e366bf64ecc755692aad6f989da970e936307 Mon Sep 17 00:00:00 2001 From: Riobard Date: Sun, 9 Aug 2020 19:58:06 +0800 Subject: [PATCH 41/51] Fixed typo --- main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.go b/main.go index 196eb018..4a4be4b6 100644 --- a/main.go +++ b/main.go @@ -61,7 +61,7 @@ func main() { flag.StringVar(&flags.Plugin, "plugin", "", "Enable SIP003 plugin. (e.g., v2ray-plugin)") flag.StringVar(&flags.PluginOpts, "plugin-opts", "", "Set SIP003 plugin options. (e.g., \"server;tls;host=mydomain.me\")") flag.BoolVar(&flags.UDP, "udp", false, "(server-only) enable UDP support") - flag.BoolVar(&flags.UDP, "tcp", true, "(server-only) enable TCP support") + flag.BoolVar(&flags.TCP, "tcp", true, "(server-only) enable TCP support") flag.BoolVar(&config.TCPCork, "tcpcork", false, "(client-only) enable TCP_CORK (Linux) or TCP_NOPUSH (BSD) for the first few packets") flag.DurationVar(&config.UDPTimeout, "udptimeout", 5*time.Minute, "UDP tunnel timeout") flag.Parse() From 24ded0cfbc9e659c651bc8d305e10a5785b3f8b3 Mon Sep 17 00:00:00 2001 From: Riobard Date: Thu, 13 Aug 2020 01:40:40 +0800 Subject: [PATCH 42/51] Remove TCPConn.SetKeepAlive as it's on by default --- tcp.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/tcp.go b/tcp.go index b47e3c3b..22971a8a 100644 --- a/tcp.go +++ b/tcp.go @@ -42,7 +42,6 @@ func tcpLocal(addr, server string, shadow func(net.Conn) net.Conn, getAddr func( go func() { defer c.Close() - c.(*net.TCPConn).SetKeepAlive(true) tgt, err := getAddr(c) if err != nil { @@ -71,7 +70,6 @@ func tcpLocal(addr, server string, shadow func(net.Conn) net.Conn, getAddr func( } defer rc.Close() tc := rc.(*net.TCPConn) - tc.SetKeepAlive(true) if config.TCPCork { timedCork(tc, 10*time.Millisecond) } @@ -112,7 +110,6 @@ func tcpRemote(addr string, shadow func(net.Conn) net.Conn) { go func() { defer c.Close() - c.(*net.TCPConn).SetKeepAlive(true) c = shadow(c) tgt, err := socks.ReadAddr(c) @@ -127,7 +124,6 @@ func tcpRemote(addr string, shadow func(net.Conn) net.Conn) { return } defer rc.Close() - rc.(*net.TCPConn).SetKeepAlive(true) logf("proxy %s <-> %s", c.RemoteAddr(), tgt) _, _, err = relay(c, rc) From 65bd7fbd1d46e4e1b79b73169c7cfd99e1385926 Mon Sep 17 00:00:00 2001 From: Riobard Date: Thu, 13 Aug 2020 01:47:43 +0800 Subject: [PATCH 43/51] Simplified relay --- tcp.go | 38 +++++++++++++++++--------------------- 1 file changed, 17 insertions(+), 21 deletions(-) diff --git a/tcp.go b/tcp.go index 22971a8a..c4779369 100644 --- a/tcp.go +++ b/tcp.go @@ -3,6 +3,7 @@ package main import ( "io" "net" + "sync" "time" "github.com/shadowsocks/go-shadowsocks2/socks" @@ -81,7 +82,7 @@ func tcpLocal(addr, server string, shadow func(net.Conn) net.Conn, getAddr func( } logf("proxy %s <-> %s <-> %s", c.RemoteAddr(), server, tgt) - _, _, err = relay(rc, c) + err = relay(rc, c) if err != nil { if err, ok := err.(net.Error); ok && err.Timeout() { return // ignore i/o timeout @@ -126,7 +127,7 @@ func tcpRemote(addr string, shadow func(net.Conn) net.Conn) { defer rc.Close() logf("proxy %s <-> %s", c.RemoteAddr(), tgt) - _, _, err = relay(c, rc) + err = relay(c, rc) if err != nil { if err, ok := err.(net.Error); ok && err.Timeout() { return // ignore i/o timeout @@ -137,29 +138,24 @@ func tcpRemote(addr string, shadow func(net.Conn) net.Conn) { } } -// relay copies between left and right bidirectionally. Returns number of -// bytes copied from right to left, from left to right, and any error occurred. -func relay(left, right net.Conn) (int64, int64, error) { - type res struct { - N int64 - Err error - } - ch := make(chan res) +// relay copies between left and right bidirectionally. Returns any error occurred. +func relay(left, right net.Conn) error { + var err, err1 error + var wg sync.WaitGroup + wg.Add(1) go func() { - n, err := io.Copy(right, left) - right.SetDeadline(time.Now()) // wake up the other goroutine blocking on right - left.SetDeadline(time.Now()) // wake up the other goroutine blocking on left - ch <- res{n, err} + defer wg.Done() + _, err1 = io.Copy(right, left) + right.SetReadDeadline(time.Now()) // unblock read on right }() - n, err := io.Copy(left, right) - right.SetDeadline(time.Now()) // wake up the other goroutine blocking on right - left.SetDeadline(time.Now()) // wake up the other goroutine blocking on left - rs := <-ch + _, err = io.Copy(left, right) + left.SetReadDeadline(time.Now()) // unblock read on left + wg.Wait() - if err == nil { - err = rs.Err + if err1 != nil { + err = err1 } - return n, rs.N, err + return err } From 09d8cb9b65a548e0ea3adf88c6009d5e62a112f4 Mon Sep 17 00:00:00 2001 From: Riobard Date: Thu, 13 Aug 2020 02:22:52 +0800 Subject: [PATCH 44/51] Drain net.Conn to avoid leaking behavioral feature --- tcp.go | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/tcp.go b/tcp.go index c4779369..1fb48b78 100644 --- a/tcp.go +++ b/tcp.go @@ -2,6 +2,7 @@ package main import ( "io" + "io/ioutil" "net" "sync" "time" @@ -111,11 +112,17 @@ func tcpRemote(addr string, shadow func(net.Conn) net.Conn) { go func() { defer c.Close() - c = shadow(c) + sc := shadow(c) - tgt, err := socks.ReadAddr(c) + tgt, err := socks.ReadAddr(sc) if err != nil { logf("failed to get target address: %v", err) + // drain c to avoid leaking server behavioral features + // see https://www.ndss-symposium.org/ndss-paper/detecting-probe-resistant-proxies/ + _, err = io.Copy(ioutil.Discard, c) + if err != nil { + logf("discard error: %v", err) + } return } @@ -127,7 +134,7 @@ func tcpRemote(addr string, shadow func(net.Conn) net.Conn) { defer rc.Close() logf("proxy %s <-> %s", c.RemoteAddr(), tgt) - err = relay(c, rc) + err = relay(sc, rc) if err != nil { if err, ok := err.(net.Error); ok && err.Timeout() { return // ignore i/o timeout From d551e24634f08d52132f08b3c68beda874e05f56 Mon Sep 17 00:00:00 2001 From: Riobard Date: Mon, 17 Aug 2020 05:49:16 +0800 Subject: [PATCH 45/51] Generic TCP Corking --- tcp.go | 43 +++++++++++++++++++++++++++++++++++++++++-- tcp_darwin.go | 17 ----------------- tcp_linux.go | 17 ----------------- tcp_other.go | 3 --- 4 files changed, 41 insertions(+), 39 deletions(-) diff --git a/tcp.go b/tcp.go index 1fb48b78..63ecedca 100644 --- a/tcp.go +++ b/tcp.go @@ -1,6 +1,7 @@ package main import ( + "bufio" "io" "io/ioutil" "net" @@ -71,9 +72,8 @@ func tcpLocal(addr, server string, shadow func(net.Conn) net.Conn, getAddr func( return } defer rc.Close() - tc := rc.(*net.TCPConn) if config.TCPCork { - timedCork(tc, 10*time.Millisecond) + rc = timedCork(rc, 10*time.Millisecond, 1280) } rc = shadow(rc) @@ -166,3 +166,42 @@ func relay(left, right net.Conn) error { } return err } + +type corkedConn struct { + net.Conn + bufw *bufio.Writer + corked bool + delay time.Duration + err error + lock sync.Mutex + once sync.Once +} + +func timedCork(c net.Conn, d time.Duration, bufSize int) net.Conn { + return &corkedConn{ + Conn: c, + bufw: bufio.NewWriterSize(c, bufSize), + corked: true, + delay: d, + } +} + +func (w *corkedConn) Write(p []byte) (int, error) { + w.lock.Lock() + defer w.lock.Unlock() + if w.err != nil { + return 0, w.err + } + if w.corked { + w.once.Do(func() { + time.AfterFunc(w.delay, func() { + w.lock.Lock() + defer w.lock.Unlock() + w.corked = false + w.err = w.bufw.Flush() + }) + }) + return w.bufw.Write(p) + } + return w.Conn.Write(p) +} diff --git a/tcp_darwin.go b/tcp_darwin.go index 29e5eb5b..1497d5d8 100644 --- a/tcp_darwin.go +++ b/tcp_darwin.go @@ -2,8 +2,6 @@ package main import ( "net" - "syscall" - "time" "github.com/shadowsocks/go-shadowsocks2/pfutil" "github.com/shadowsocks/go-shadowsocks2/socks" @@ -24,18 +22,3 @@ func natLookup(c net.Conn) (socks.Addr, error) { } panic("not TCP connection") } - -func timedCork(c *net.TCPConn, d time.Duration) error { - rc, err := c.SyscallConn() - if err != nil { - return err - } - rc.Control(func(fd uintptr) { err = syscall.SetsockoptInt(int(fd), syscall.IPPROTO_TCP, syscall.TCP_NOPUSH, 1) }) - if err != nil { - return err - } - time.AfterFunc(d, func() { - rc.Control(func(fd uintptr) { syscall.SetsockoptInt(int(fd), syscall.IPPROTO_TCP, syscall.TCP_NOPUSH, 0) }) - }) - return nil -} diff --git a/tcp_linux.go b/tcp_linux.go index 60612d66..6f4d11e7 100644 --- a/tcp_linux.go +++ b/tcp_linux.go @@ -2,8 +2,6 @@ package main import ( "net" - "syscall" - "time" "github.com/shadowsocks/go-shadowsocks2/nfutil" "github.com/shadowsocks/go-shadowsocks2/socks" @@ -28,18 +26,3 @@ func redir6Local(addr, server string, shadow func(net.Conn) net.Conn) { logf("TCP6 redirect %s <-> %s", addr, server) tcpLocal(addr, server, shadow, func(c net.Conn) (socks.Addr, error) { return getOrigDst(c, true) }) } - -func timedCork(c *net.TCPConn, d time.Duration) error { - rc, err := c.SyscallConn() - if err != nil { - return err - } - rc.Control(func(fd uintptr) { err = syscall.SetsockoptInt(int(fd), syscall.IPPROTO_TCP, syscall.TCP_CORK, 1) }) - if err != nil { - return err - } - time.AfterFunc(d, func() { - rc.Control(func(fd uintptr) { syscall.SetsockoptInt(int(fd), syscall.IPPROTO_TCP, syscall.TCP_CORK, 0) }) - }) - return nil -} diff --git a/tcp_other.go b/tcp_other.go index e5cde9ad..8687dac4 100644 --- a/tcp_other.go +++ b/tcp_other.go @@ -4,7 +4,6 @@ package main import ( "net" - "time" ) func redirLocal(addr, server string, shadow func(net.Conn) net.Conn) { @@ -14,5 +13,3 @@ func redirLocal(addr, server string, shadow func(net.Conn) net.Conn) { func redir6Local(addr, server string, shadow func(net.Conn) net.Conn) { logf("TCP6 redirect not supported") } - -func timedCork(c *net.TCPConn, d time.Duration) error { return nil } From 67ba6d4de20c4bc60361c5cb424922cf0d738054 Mon Sep 17 00:00:00 2001 From: Riobard Date: Wed, 19 Aug 2020 12:30:18 +0800 Subject: [PATCH 46/51] Option tcpcork works on server-side --- main.go | 2 +- tcp.go | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/main.go b/main.go index 4a4be4b6..ee90b773 100644 --- a/main.go +++ b/main.go @@ -62,7 +62,7 @@ func main() { flag.StringVar(&flags.PluginOpts, "plugin-opts", "", "Set SIP003 plugin options. (e.g., \"server;tls;host=mydomain.me\")") flag.BoolVar(&flags.UDP, "udp", false, "(server-only) enable UDP support") flag.BoolVar(&flags.TCP, "tcp", true, "(server-only) enable TCP support") - flag.BoolVar(&config.TCPCork, "tcpcork", false, "(client-only) enable TCP_CORK (Linux) or TCP_NOPUSH (BSD) for the first few packets") + flag.BoolVar(&config.TCPCork, "tcpcork", false, "coalesce writing first few packets") flag.DurationVar(&config.UDPTimeout, "udptimeout", 5*time.Minute, "UDP tunnel timeout") flag.Parse() diff --git a/tcp.go b/tcp.go index 63ecedca..4919ecb0 100644 --- a/tcp.go +++ b/tcp.go @@ -112,6 +112,9 @@ func tcpRemote(addr string, shadow func(net.Conn) net.Conn) { go func() { defer c.Close() + if config.TCPCork { + c = timedCork(c, 10*time.Millisecond, 1280) + } sc := shadow(c) tgt, err := socks.ReadAddr(sc) From 60c7f85e29f242483c5bb394588fe62f57efb014 Mon Sep 17 00:00:00 2001 From: Riobard Date: Wed, 19 Aug 2020 12:31:14 +0800 Subject: [PATCH 47/51] Log remote address of failed conn --- tcp.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tcp.go b/tcp.go index 4919ecb0..1512acb1 100644 --- a/tcp.go +++ b/tcp.go @@ -119,7 +119,7 @@ func tcpRemote(addr string, shadow func(net.Conn) net.Conn) { tgt, err := socks.ReadAddr(sc) if err != nil { - logf("failed to get target address: %v", err) + logf("failed to get target address from %v: %v", c.RemoteAddr(), err) // drain c to avoid leaking server behavioral features // see https://www.ndss-symposium.org/ndss-paper/detecting-probe-resistant-proxies/ _, err = io.Copy(ioutil.Discard, c) From 83d899e53ed86d1ce5741ccc14252cd60401f183 Mon Sep 17 00:00:00 2001 From: Riobard Date: Wed, 19 Aug 2020 13:01:48 +0800 Subject: [PATCH 48/51] Wait a short while before disrupting copy in relay The short wait period allows sending remaining bytes in the other direction. --- tcp.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tcp.go b/tcp.go index 1512acb1..ff239ae7 100644 --- a/tcp.go +++ b/tcp.go @@ -152,16 +152,16 @@ func tcpRemote(addr string, shadow func(net.Conn) net.Conn) { func relay(left, right net.Conn) error { var err, err1 error var wg sync.WaitGroup - + var wait = 5 * time.Second wg.Add(1) go func() { defer wg.Done() _, err1 = io.Copy(right, left) - right.SetReadDeadline(time.Now()) // unblock read on right + right.SetReadDeadline(time.Now().Add(wait)) // unblock read on right }() _, err = io.Copy(left, right) - left.SetReadDeadline(time.Now()) // unblock read on left + left.SetReadDeadline(time.Now().Add(wait)) // unblock read on left wg.Wait() if err1 != nil { From 4a0b15dde73c24cd98db69ef050241f1aec55752 Mon Sep 17 00:00:00 2001 From: Riobard Date: Tue, 25 Aug 2020 18:54:07 +0800 Subject: [PATCH 49/51] Upgrade to Go 1.15 --- .github/workflows/build.yml | 2 +- .github/workflows/release.yml | 2 +- go.mod | 7 ++++--- go.sum | 6 ++++++ 4 files changed, 12 insertions(+), 5 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 2b1b40c3..42486929 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -6,7 +6,7 @@ jobs: steps: - uses: actions/setup-go@v1 with: - go-version: 1.13 + go-version: 1.15 - uses: actions/checkout@v2 - run: make -j all - run: make -j test \ No newline at end of file diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 5d2a09fa..e54f9f5b 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -9,7 +9,7 @@ jobs: - uses: actions/checkout@v2 - uses: actions/setup-go@v1 with: - go-version: 1.13 + go-version: 1.15 - run: make -j upload env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/go.mod b/go.mod index 7aa4b934..921113c0 100644 --- a/go.mod +++ b/go.mod @@ -1,10 +1,11 @@ module github.com/shadowsocks/go-shadowsocks2 -go 1.12 +go 1.15 require ( - github.com/riobard/go-bloom v0.0.0-20200213042214-218e1707c495 - golang.org/x/crypto v0.0.0-20200128174031-69ecbb4d6d5d + github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 + golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a + golang.org/x/sys v0.0.0-20200824131525-c12d262b63d8 // indirect ) replace ( diff --git a/go.sum b/go.sum index 685f40ff..a1733d08 100644 --- a/go.sum +++ b/go.sum @@ -6,5 +6,11 @@ github.com/golang/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQ github.com/golang/text v0.3.0/go.mod h1:GUiq9pdJKRKKAZXiVgWFEvocYuREvC14NhI4OPgEjeE= github.com/riobard/go-bloom v0.0.0-20200213042214-218e1707c495 h1:p7xbxYTzzfXghR1kpsJDeoVVRRWAotKc8u7FP/N48rU= github.com/riobard/go-bloom v0.0.0-20200213042214-218e1707c495/go.mod h1:HgjTstvQsPGkxUsCd2KWxErBblirPizecHcpD3ffK+s= +github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 h1:f/FNXud6gA3MNr8meMVVGxhp+QBTqY91tM8HjEuMjGg= +github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3/go.mod h1:HgjTstvQsPGkxUsCd2KWxErBblirPizecHcpD3ffK+s= golang.org/x/crypto v0.0.0-20200128174031-69ecbb4d6d5d h1:9FCpayM9Egr1baVnV1SX0H87m+XB0B8S0hAMi99X/3U= golang.org/x/crypto v0.0.0-20200128174031-69ecbb4d6d5d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a h1:vclmkQCjlDX5OydZ9wv8rBCcS0QyQY66Mpf/7BZbInM= +golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/sys v0.0.0-20200824131525-c12d262b63d8 h1:AvbQYmiaaaza3cW3QXRyPo5kYgpFIzOAfeAAN7m3qQ4= +golang.org/x/sys v0.0.0-20200824131525-c12d262b63d8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= From 12053b86615aa9669f42cb46bb33ca318f454ef4 Mon Sep 17 00:00:00 2001 From: Riobard Date: Tue, 25 Aug 2020 18:54:37 +0800 Subject: [PATCH 50/51] Improved relay handling --- tcp.go | 27 +++++++++++---------------- 1 file changed, 11 insertions(+), 16 deletions(-) diff --git a/tcp.go b/tcp.go index ff239ae7..09461a06 100644 --- a/tcp.go +++ b/tcp.go @@ -2,9 +2,11 @@ package main import ( "bufio" + "errors" "io" "io/ioutil" "net" + "os" "sync" "time" @@ -83,11 +85,7 @@ func tcpLocal(addr, server string, shadow func(net.Conn) net.Conn, getAddr func( } logf("proxy %s <-> %s <-> %s", c.RemoteAddr(), server, tgt) - err = relay(rc, c) - if err != nil { - if err, ok := err.(net.Error); ok && err.Timeout() { - return // ignore i/o timeout - } + if err = relay(rc, c); err != nil { logf("relay error: %v", err) } }() @@ -137,18 +135,14 @@ func tcpRemote(addr string, shadow func(net.Conn) net.Conn) { defer rc.Close() logf("proxy %s <-> %s", c.RemoteAddr(), tgt) - err = relay(sc, rc) - if err != nil { - if err, ok := err.(net.Error); ok && err.Timeout() { - return // ignore i/o timeout - } + if err = relay(sc, rc); err != nil { logf("relay error: %v", err) } }() } } -// relay copies between left and right bidirectionally. Returns any error occurred. +// relay copies between left and right bidirectionally func relay(left, right net.Conn) error { var err, err1 error var wg sync.WaitGroup @@ -159,15 +153,16 @@ func relay(left, right net.Conn) error { _, err1 = io.Copy(right, left) right.SetReadDeadline(time.Now().Add(wait)) // unblock read on right }() - _, err = io.Copy(left, right) left.SetReadDeadline(time.Now().Add(wait)) // unblock read on left wg.Wait() - - if err1 != nil { - err = err1 + if err1 != nil && !errors.Is(err1, os.ErrDeadlineExceeded) { // requires Go 1.15+ + return err1 + } + if err != nil && !errors.Is(err, os.ErrDeadlineExceeded) { + return err } - return err + return nil } type corkedConn struct { From 2b125d0cb814c36b9ec5a9d2d3932306a5f29d8e Mon Sep 17 00:00:00 2001 From: mritd Date: Tue, 1 Sep 2020 18:14:43 +0800 Subject: [PATCH 51/51] chore(docker): update build image to go 1.15 update build image to go 1.15 Signed-off-by: mritd --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 2439a077..539c3aa7 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM golang:1.14.4-alpine3.12 AS builder +FROM golang:1.15.0-alpine3.12 AS builder ENV GO111MODULE on ENV GOPROXY https://goproxy.cn