From f5d0f2fb2c68d58e4bd70a0807f7b4ec4e798052 Mon Sep 17 00:00:00 2001 From: Vinicius Fortuna Date: Thu, 14 Mar 2024 18:49:13 -0400 Subject: [PATCH 1/6] Clean up old code --- .gitignore | 3 + README.md | 4 +- Taskfile.yml | 40 ++ client/client.go | 127 ----- client/salt.go | 24 - go.mod | 28 +- go.sum | 59 ++- internal/integration_test/integration_test.go | 36 +- service/cipher_list_test.go | 4 +- service/server_salt.go | 2 +- shadowsocks/cipher.go | 160 ------- shadowsocks/cipher_test.go | 60 --- shadowsocks/cipher_testing.go | 40 -- shadowsocks/compatibility_test.go | 65 --- shadowsocks/packet.go | 66 --- shadowsocks/packet_test.go | 42 -- shadowsocks/salt.go | 37 -- shadowsocks/salt_test.go | 44 -- shadowsocks/stream.go | 428 ----------------- shadowsocks/stream_test.go | 441 ------------------ tools.go | 1 + 21 files changed, 139 insertions(+), 1572 deletions(-) create mode 100644 Taskfile.yml delete mode 100644 client/client.go delete mode 100644 client/salt.go delete mode 100644 shadowsocks/cipher.go delete mode 100644 shadowsocks/cipher_test.go delete mode 100644 shadowsocks/cipher_testing.go delete mode 100644 shadowsocks/compatibility_test.go delete mode 100644 shadowsocks/packet.go delete mode 100644 shadowsocks/packet_test.go delete mode 100644 shadowsocks/salt.go delete mode 100644 shadowsocks/salt_test.go delete mode 100644 shadowsocks/stream.go delete mode 100644 shadowsocks/stream_test.go diff --git a/.gitignore b/.gitignore index 0182c89b..cf216544 100644 --- a/.gitignore +++ b/.gitignore @@ -24,3 +24,6 @@ # Prometheus /data/ + +# Go task +/.task/ \ No newline at end of file diff --git a/README.md b/README.md index 0ebe36bb..1da3014d 100644 --- a/README.md +++ b/README.md @@ -165,7 +165,7 @@ We use [GoReleaser](https://goreleaser.com/) to build and upload binaries to our Summary: - Test the build locally: ``` - make release-local + go run github.com/go-task/task/v3/cmd/task release-local ``` - Export an environment variable named `GITHUB_TOKEN` with a temporary repo-scoped GitHub token ([create one here](https://github.com/settings/tokens/new)): ```bash @@ -178,7 +178,7 @@ Summary: ``` - Build and upload: ```bash - make release + go run github.com/go-task/task/v3/cmd/task release ``` - Go to https://github.com/Jigsaw-Code/outline-ss-server/releases, review and publish the release. diff --git a/Taskfile.yml b/Taskfile.yml new file mode 100644 index 00000000..60f7fc5b --- /dev/null +++ b/Taskfile.yml @@ -0,0 +1,40 @@ +version: '3' + +run: when_changed + +vars: + OUT_DIR: "{{.USER_WORKING_DIR}}/out" + +tasks: + release: + desc: "Release" + deps: [clean] + cmds: + - go run github.com/goreleaser/goreleaser --clean --snapshot + + + release-local: + desc: "Build a release binary" + cmds: + - go run github.com/goreleaser/goreleaser --clean --snapshot + + test: + desc: "Runs tests" + deps: [mmdb] + cmds: + - go test -v -race -benchmem -bench=. ./... -benchtime=100ms + + mmdb: + cmds: + - git submodule update --init --depth=1 + sources: + - "{{.ROOT_DIR}}/gitmodules" + generates: + - "{{.ROOT_DIR}}/third_party/maxmind/test-data/GeoIP2-Country-Test.mmdb" + + clean: + desc: "Cleans output directory" + cmds: + - rm -rf {{.OUT_DIR}} + - git submodule deinit --all + - go clean \ No newline at end of file diff --git a/client/client.go b/client/client.go deleted file mode 100644 index b623c7da..00000000 --- a/client/client.go +++ /dev/null @@ -1,127 +0,0 @@ -// Copyright 2023 Jigsaw Operations LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// https://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// Deprecated: Use the StreamDialer and PacketListener types under github.com/Jigsaw-Code/outline-ss-server/net instead. -package client - -import ( - "context" - "fmt" - "net" - - "github.com/Jigsaw-Code/outline-sdk/transport" - "github.com/Jigsaw-Code/outline-sdk/transport/shadowsocks" - onet "github.com/Jigsaw-Code/outline-ss-server/net" -) - -// Client is a client for Shadowsocks TCP and UDP connections. -// -// Deprecated: Use shadowsocks.StreamDialer and shadowsocks.PacketListener instead. -type Client interface { - // DialTCP connects to `raddr` over TCP though a Shadowsocks proxy. - // `laddr` is a local bind address, a local address is automatically chosen if nil. - // `raddr` has the form `host:port`, where `host` can be a domain name or IP address. - // - // Deprecated: use StreamDialer.Dial instead. - DialTCP(laddr *net.TCPAddr, raddr string) (onet.DuplexConn, error) - - // ListenUDP relays UDP packets though a Shadowsocks proxy. - // `laddr` is a local bind address, a local address is automatically chosen if nil. - // - // Deprecated: use PacketDialer.ListenPacket instead. - ListenUDP(laddr *net.UDPAddr) (net.PacketConn, error) - - // SetTCPSaltGenerator controls the SaltGenerator used for TCP upstream. - // `salter` may be `nil`. - // This method is not thread-safe. - SetTCPSaltGenerator(shadowsocks.SaltGenerator) -} - -// NewClient creates a client that routes connections to a Shadowsocks proxy listening at -// `host:port`, with authentication parameters `cipher` (AEAD) and `password`. -// -// Deprecated: Use ssclient.StreamDialer and ssclient.PacketListener instead. -func NewClient(host string, port int, password, cipherName string) (Client, error) { - // TODO: consider using net.LookupIP to get a list of IPs, and add logic for optimal selection. - proxyIP, err := net.ResolveIPAddr("ip", host) - if err != nil { - return nil, fmt.Errorf("failed to resolve proxy address: %w", err) - } - key, err := shadowsocks.NewEncryptionKey(cipherName, password) - if err != nil { - return nil, fmt.Errorf("failed to create encryption key: %w", err) - } - return &ssClient{ - key: key, - proxyAddress: net.JoinHostPort(proxyIP.String(), fmt.Sprint(port)), - }, nil -} - -type ssClient struct { - key *shadowsocks.EncryptionKey - proxyAddress string - salter shadowsocks.SaltGenerator -} - -// ListenUDP implements the Client.ListenUDP API. -func (c *ssClient) ListenUDP(laddr *net.UDPAddr) (net.PacketConn, error) { - endpoint := transport.UDPEndpoint{Address: c.proxyAddress} - if laddr != nil { - endpoint.Dialer.LocalAddr = laddr - } - packetListener, err := shadowsocks.NewPacketListener(endpoint, c.key) - if err != nil { - return nil, fmt.Errorf("failed to create PacketListener: %w", err) - } - return packetListener.ListenPacket(context.Background()) -} - -func (c *ssClient) SetTCPSaltGenerator(salter shadowsocks.SaltGenerator) { - c.salter = salter -} - -// DialTCP implements the Client.DialTCP API. -func (c *ssClient) DialTCP(laddr *net.TCPAddr, raddr string) (onet.DuplexConn, error) { - endpoint := transport.TCPEndpoint{Address: c.proxyAddress} - if laddr != nil { - endpoint.Dialer.LocalAddr = laddr - } - streamDialer, err := shadowsocks.NewStreamDialer(&endpoint, c.key) - if err != nil { - return nil, fmt.Errorf("failed to create StreamDialer: %w", err) - } - streamDialer.SaltGenerator = c.salter - return streamDialer.Dial(context.Background(), raddr) -} - -type addr struct { - address string - network string -} - -func (a *addr) String() string { - return a.address -} - -func (a *addr) Network() string { - return a.network -} - -// newAddr returns a net.Addr that holds an address of the form `host:port` with a domain name or IP as host. -// Used for SOCKS addressing. -// -// Deprecated: use [net.UDPAddr] or [net.TCPAddr] instead. -func NewAddr(address, network string) net.Addr { - return &addr{address: address, network: network} -} diff --git a/client/salt.go b/client/salt.go deleted file mode 100644 index 527362ce..00000000 --- a/client/salt.go +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright 2022 Jigsaw Operations LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// https://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package client - -import ( - "github.com/Jigsaw-Code/outline-sdk/transport/shadowsocks" -) - -// Deprecated: Prefer github.com/Jigsaw-Code/outline-ss-server/shadowsocks/client.NewPrefixSaltGenerator -func NewPrefixSaltGenerator(prefix []byte) shadowsocks.SaltGenerator { - return shadowsocks.NewPrefixSaltGenerator(prefix) -} diff --git a/go.mod b/go.mod index c139b696..039ece82 100644 --- a/go.mod +++ b/go.mod @@ -1,7 +1,8 @@ module github.com/Jigsaw-Code/outline-ss-server require ( - github.com/Jigsaw-Code/outline-sdk v0.0.2 + github.com/Jigsaw-Code/outline-sdk v0.0.13 + github.com/go-task/task/v3 v3.34.1 github.com/goreleaser/goreleaser v1.18.2 github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 github.com/oschwald/geoip2-golang v1.8.0 @@ -9,7 +10,7 @@ require ( github.com/shadowsocks/go-shadowsocks2 v0.1.5 github.com/stretchr/testify v1.8.4 golang.org/x/crypto v0.17.0 - golang.org/x/term v0.15.0 + golang.org/x/term v0.16.0 gopkg.in/yaml.v2 v2.4.0 ) @@ -110,6 +111,7 @@ require ( github.com/elliotchance/orderedmap/v2 v2.2.0 // indirect github.com/emirpasic/gods v1.18.1 // indirect github.com/evanphx/json-patch/v5 v5.6.0 // indirect + github.com/fatih/color v1.16.0 // indirect github.com/fsnotify/fsnotify v1.6.0 // indirect github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect github.com/go-git/go-billy/v5 v5.5.0 // indirect @@ -124,6 +126,7 @@ require ( github.com/go-openapi/strfmt v0.21.7 // indirect github.com/go-openapi/swag v0.22.3 // indirect github.com/go-openapi/validate v0.22.1 // indirect + github.com/go-task/slim-sprig/v3 v3.0.0 // indirect github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible // indirect github.com/gobwas/glob v0.2.3 // indirect github.com/gogo/protobuf v1.3.2 // indirect @@ -158,21 +161,26 @@ require ( github.com/invopop/jsonschema v0.7.0 // indirect github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect + github.com/joho/godotenv v1.5.1 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/kevinburke/ssh_config v1.2.0 // indirect github.com/klauspost/compress v1.16.3 // indirect + github.com/klauspost/cpuid/v2 v2.0.9 // indirect github.com/klauspost/pgzip v1.2.5 // indirect github.com/kylelemons/godebug v1.1.0 // indirect github.com/letsencrypt/boulder v0.0.0-20221109233200-85aa52084eaf // indirect github.com/lucasb-eyer/go-colorful v1.2.0 // indirect github.com/magiconair/properties v1.8.7 // indirect github.com/mailru/easyjson v0.7.7 // indirect - github.com/mattn/go-isatty v0.0.17 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-mastodon v0.0.6 // indirect github.com/mattn/go-runewidth v0.0.14 // indirect + github.com/mattn/go-zglob v0.0.4 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect github.com/mitchellh/copystructure v1.2.0 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect + github.com/mitchellh/hashstructure/v2 v2.0.2 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/mitchellh/reflectwalk v1.0.2 // indirect github.com/muesli/mango v0.1.0 // indirect @@ -194,9 +202,11 @@ require ( github.com/prometheus/client_model v0.3.0 // indirect github.com/prometheus/common v0.42.0 // indirect github.com/prometheus/procfs v0.9.0 // indirect + github.com/radovskyb/watcher v1.0.7 // indirect github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 // indirect github.com/rivo/uniseg v0.4.2 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect + github.com/sajari/fuzzy v1.0.0 // indirect github.com/sasha-s/go-csync v0.0.0-20210812194225-61421b77c44b // indirect github.com/sergi/go-diff v1.2.0 // indirect github.com/sigstore/cosign/v2 v2.0.0 // indirect @@ -221,20 +231,21 @@ require ( github.com/withfig/autocomplete-tools/integrations/cobra v1.2.1 // indirect github.com/xanzy/go-gitlab v0.83.0 // indirect github.com/xanzy/ssh-agent v0.3.3 // indirect + github.com/zeebo/xxh3 v1.0.2 // indirect gitlab.com/digitalxero/go-conventional-commit v1.0.7 // indirect go.mongodb.org/mongo-driver v1.11.3 // indirect go.opencensus.io v0.24.0 // indirect go.uber.org/automaxprocs v1.5.2 // indirect gocloud.dev v0.29.0 // indirect - golang.org/x/exp v0.0.0-20230124195608-d38c7dcee874 // indirect - golang.org/x/mod v0.12.0 // indirect + golang.org/x/exp v0.0.0-20240110193028-0dcbfd608b1e // indirect + golang.org/x/mod v0.14.0 // indirect golang.org/x/net v0.19.0 // indirect golang.org/x/oauth2 v0.7.0 // indirect - golang.org/x/sync v0.3.0 // indirect - golang.org/x/sys v0.15.0 // indirect + golang.org/x/sync v0.6.0 // indirect + golang.org/x/sys v0.16.0 // indirect golang.org/x/text v0.14.0 // indirect golang.org/x/time v0.3.0 // indirect - golang.org/x/tools v0.13.0 // indirect + golang.org/x/tools v0.16.0 // indirect golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect google.golang.org/api v0.119.0 // indirect google.golang.org/appengine v1.6.7 // indirect @@ -247,6 +258,7 @@ require ( gopkg.in/square/go-jose.v2 v2.6.0 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect + mvdan.cc/sh/v3 v3.7.0 // indirect sigs.k8s.io/kind v0.17.0 // indirect sigs.k8s.io/yaml v1.3.0 // indirect ) diff --git a/go.sum b/go.sum index 74f4b4c6..485bac1d 100644 --- a/go.sum +++ b/go.sum @@ -512,6 +512,8 @@ github.com/HdrHistogram/hdrhistogram-go v1.1.0/go.mod h1:yDgFjdqOqDEKOvasDdhWNXY github.com/HdrHistogram/hdrhistogram-go v1.1.2/go.mod h1:yDgFjdqOqDEKOvasDdhWNXYg9BVp4O+o5f6V/ehm6Oo= github.com/Jigsaw-Code/outline-sdk v0.0.2 h1:uCuyJMaWj57IYEG/Hdml8YMdk9chU60ZkSxJXBhyGHU= github.com/Jigsaw-Code/outline-sdk v0.0.2/go.mod h1:hhlKz0+r9wSDFT8usvN8Zv/BFToCIFAUn1P2Qk8G2CM= +github.com/Jigsaw-Code/outline-sdk v0.0.13 h1:My3klfEmtakgTgv+Xmg/T5xnFxWvAccMtBY0DIaeMhc= +github.com/Jigsaw-Code/outline-sdk v0.0.13/go.mod h1:9cEaF6sWWMzY8orcUI9pV5D0oFp2FZArTSyJiYtMQQs= github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= @@ -915,6 +917,7 @@ github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46t github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= github.com/cyphar/filepath-securejoin v0.2.2/go.mod h1:FpkQEhXnPnOthhzymB7CGsFk2G9VLXONKD9G7QGMM+4= github.com/cyphar/filepath-securejoin v0.2.3/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg= @@ -1028,8 +1031,9 @@ github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5Kwzbycv github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM= github.com/fatih/color v1.12.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM= -github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= +github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= +github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/felixge/httpsnoop v1.0.2/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= @@ -1040,8 +1044,8 @@ github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHqu github.com/franela/goblin v0.0.0-20210519012713-85d372ac71e2/go.mod h1:VzmDKDJVZI3aJmnRI9VjAn9nJ8qPPsN1fqzr9dqInIo= github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20= github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k= -github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= github.com/frankban/quicktest v1.14.3/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUorkibMOrVTHZps= +github.com/frankban/quicktest v1.14.5 h1:dfYrrRyLtiqT9GyKXgdh+k4inNeTvmGbuSgZ3lx3GhA= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU= @@ -1150,6 +1154,10 @@ github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9 github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-stack/stack v1.8.1/go.mod h1:dcoOX6HbPZSZptuspn9bctJ+N/CnF5gGygcUP3XYfe4= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= +github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= +github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= +github.com/go-task/task/v3 v3.34.1 h1:yAAxUM54zoaHv+OtDnGgkWSVeiRuaOCn1lPUXPQQA0o= +github.com/go-task/task/v3 v3.34.1/go.mod h1:DqrukYghah7qNmILi0Z4OwPujsJ7crUkDJZKLTsceX0= github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible h1:2cauKuaELYAEARXRkq2LrJ0yDDv1rW7+wrTEdVL3uaU= github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible/go.mod h1:qf9acutJ8cwBUhm1bqgz6Bei9/C/c93FPDljKWwsOgM= github.com/go-test/deep v1.0.4/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= @@ -1560,6 +1568,8 @@ github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfC github.com/jmhodges/clock v0.0.0-20160418191101-880ee4c33548 h1:dYTbLf4m0a5u0KLmPfB6mgxbcV7588bOCx79hxa5Sr4= github.com/joefitzgerald/rainbow-reporter v0.1.0/go.mod h1:481CNgqmVHQZzdIbN52CupLJyoVwB10FQ/IQlF1pdL8= github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/jonboulle/clockwork v0.2.2/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= @@ -1594,6 +1604,8 @@ github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47e github.com/klauspost/compress v1.15.1/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= github.com/klauspost/compress v1.16.3 h1:XuJt9zzcnaz6a16/OU53ZjWp/v7/42WcR5t2a0PcNQY= github.com/klauspost/compress v1.16.3/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= +github.com/klauspost/cpuid/v2 v2.0.9 h1:lgaqFMSdTdQYdZ04uHyN2d/eKdOMyi2YLSvlQIBFYa4= +github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/pgzip v1.2.5 h1:qnWYvvKqedOF2ulHpMG72XQol4ILEJ8k2wwRl/Km8oE= github.com/klauspost/pgzip v1.2.5/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= github.com/kolo/xmlrpc v0.0.0-20201022064351-38db28db192b/go.mod h1:pcaDhQK0/NJZEvtCO0qQPPropqV0sJOJ6YW7X+9kRwM= @@ -1658,6 +1670,7 @@ github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= @@ -1667,8 +1680,9 @@ github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcME github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= -github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= -github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-mastodon v0.0.6 h1:lqU1sOeeIapaDsDUL6udDZIzMb2Wqapo347VZlaOzf0= github.com/mattn/go-mastodon v0.0.6/go.mod h1:cg7RFk2pcUfHZw/IvKe1FUzmlq5KnLFqs7eV2PHplV8= github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= @@ -1678,6 +1692,8 @@ github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh github.com/mattn/go-shellwords v1.0.3/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o= github.com/mattn/go-shellwords v1.0.6/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o= github.com/mattn/go-shellwords v1.0.12/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y= +github.com/mattn/go-zglob v0.0.4 h1:LQi2iOm0/fGgu80AioIJ/1j9w9Oh+9DZ39J4VAGzHQM= +github.com/mattn/go-zglob v0.0.4/go.mod h1:MxxjyoXXnMxfIpxTK2GAkw1w8glPsQILx3N5wrKakiY= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= github.com/matttproud/golang_protobuf_extensions v1.0.2/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= @@ -1707,6 +1723,8 @@ github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eI github.com/mitchellh/go-testing-interface v1.14.1/go.mod h1:gfgS7OtZj6MA4U1UrDRp04twqAjfvlZyCfX3sDjEym8= github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= +github.com/mitchellh/hashstructure/v2 v2.0.2 h1:vGKWl0YJqUNxE8d+h8f6NJLcCJrgbhC4NcD46KavDd4= +github.com/mitchellh/hashstructure/v2 v2.0.2/go.mod h1:MG3aRVU/N29oo/V/IhBX8GR/zz4kQkprJgF2EVszyDE= github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= @@ -1952,6 +1970,8 @@ github.com/prometheus/procfs v0.9.0/go.mod h1:+pB4zwohETzFnmlpe6yd2lSc+0/46IYZRB github.com/prometheus/prometheus v0.35.0/go.mod h1:7HaLx5kEPKJ0GDgbODG0fZgXbQ8K/XjZNJXQmbmgQlY= github.com/prometheus/prometheus v0.42.0/go.mod h1:Pfqb/MLnnR2KK+0vchiaH39jXxvLMBk+3lnIGP4N7Vk= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= +github.com/radovskyb/watcher v1.0.7 h1:AYePLih6dpmS32vlHfhCeli8127LzkIgwJGcwwe8tUE= +github.com/radovskyb/watcher v1.0.7/go.mod h1:78okwvY5wPdzcb1UYnip1pvrZNIVEIh/Cm+ZuvsUYIg= github.com/rakyll/embedmd v0.0.0-20171029212350-c8060a0752a2/go.mod h1:7jOTMgqac46PZcF54q6l2hkLEG8op93fZu61KmxWDV4= github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 h1:f/FNXud6gA3MNr8meMVVGxhp+QBTqY91tM8HjEuMjGg= @@ -1980,6 +2000,8 @@ github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFo github.com/safchain/ethtool v0.0.0-20190326074333-42ed695e3de8/go.mod h1:Z0q5wiBQGYcxhMZ6gUqHn6pYNLypFAvaL3UvgZLR0U4= github.com/safchain/ethtool v0.0.0-20210803160452-9aa261dae9b1/go.mod h1:Z0q5wiBQGYcxhMZ6gUqHn6pYNLypFAvaL3UvgZLR0U4= github.com/sagikazarmark/crypt v0.6.0/go.mod h1:U8+INwJo3nBv1m6A/8OBXAq7Jnpspk5AxSgDyEQcea8= +github.com/sajari/fuzzy v1.0.0 h1:+FmwVvJErsd0d0hAPlj4CxqxUtQY/fOoY0DwX4ykpRY= +github.com/sajari/fuzzy v1.0.0/go.mod h1:OjYR6KxoWOe9+dOlXeiCJd4dIbED4Oo8wpS89o0pwOo= github.com/sasha-s/go-csync v0.0.0-20210812194225-61421b77c44b h1:qYTY2tN72LhgDj2rtWG+LI6TXFl2ygFQQ4YezfVaGQE= github.com/sasha-s/go-csync v0.0.0-20210812194225-61421b77c44b/go.mod h1:/pA7k3zsXKdjjAiUhB5CjuKib9KJGCaLvZwtxGC8U0s= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= @@ -2073,6 +2095,7 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v0.0.0-20180303142811-b89eecf5ca5d/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= @@ -2169,6 +2192,9 @@ github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5t github.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43/go.mod h1:aX5oPXxHm3bOH+xeAttToC8pqch2ScQN/JoXYupl6xs= github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50/go.mod h1:NUSPSUX/bi6SeDMUh6brw0nXpxHnc96TguQh0+r/ssA= github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f/go.mod h1:GlGEuHIJweS1mbCqG+7vt2nvWLzLLnRHbXz5JKd/Qbg= +github.com/zeebo/assert v1.3.0 h1:g7C04CbJuIDKNPFHmsk4hwZDO5O+kntRxzaUoNXj+IQ= +github.com/zeebo/xxh3 v1.0.2 h1:xZmwmqxHZA8AI603jOQ0tMqmBr9lPeFwGg6d+xy9DC0= +github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA= github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= gitlab.com/digitalxero/go-conventional-commit v1.0.7 h1:8/dO6WWG+98PMhlZowt/YjuiKhqhGlOCwlIV8SqqGh8= gitlab.com/digitalxero/go-conventional-commit v1.0.7/go.mod h1:05Xc2BFsSyC5tKhK0y+P3bs0AwUtNuTp+mTpbCU/DZ0= @@ -2343,8 +2369,9 @@ golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u0 golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= golang.org/x/exp v0.0.0-20230108222341-4b8118a2686a/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= -golang.org/x/exp v0.0.0-20230124195608-d38c7dcee874 h1:kWC3b7j6Fu09SnEBr7P4PuQyM0R6sqyH9R+EjIvT1nQ= golang.org/x/exp v0.0.0-20230124195608-d38c7dcee874/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= +golang.org/x/exp v0.0.0-20240110193028-0dcbfd608b1e h1:723BNChdd0c2Wk6WOE320qGBiPtYx0F0Bbm1kriShfE= +golang.org/x/exp v0.0.0-20240110193028-0dcbfd608b1e/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI= golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= @@ -2378,8 +2405,8 @@ golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91 golang.org/x/mod v0.6.0/go.mod h1:4mET923SAdbXp2ki8ey+zGs1SLqsuM2Y0uvdZR/fUNI= golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc= -golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0= +golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -2522,8 +2549,8 @@ golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= -golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= +golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -2677,8 +2704,8 @@ golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= -golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= +golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= @@ -2690,8 +2717,8 @@ golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA= golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= -golang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4= -golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= +golang.org/x/term v0.16.0 h1:m+B6fahuftsE9qjo0VWp2FW0mB3MTJvR0BaMQrq0pmE= +golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -2813,8 +2840,8 @@ golang.org/x/tools v0.3.0/go.mod h1:/rWhSS2+zyEVwoJf8YAX6L2f0ntZ7Kn/mGgAWcipA5k= golang.org/x/tools v0.4.0/go.mod h1:UE5sM2OK9E/d67R0ANs2xJizIymRP5gJU295PvKXxjQ= golang.org/x/tools v0.5.0/go.mod h1:N+Kgy78s5I24c24dU8OfWNEotWjutIs8SnJvn5IDq+k= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ= -golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= +golang.org/x/tools v0.16.0 h1:GO788SKMRunPIBCXiQyo2AaexLstOrVhuAL5YwsckQM= +golang.org/x/tools v0.16.0/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0= golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -3231,6 +3258,8 @@ k8s.io/utils v0.0.0-20210930125809-cb0fa318a74b/go.mod h1:jPW/WVKK9YHAvNhRxK0md/ k8s.io/utils v0.0.0-20211116205334-6203023598ed/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= k8s.io/utils v0.0.0-20221107191617-1a15be271d1d/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= k8s.io/utils v0.0.0-20221128185143-99ec85e7a448/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +mvdan.cc/sh/v3 v3.7.0 h1:lSTjdP/1xsddtaKfGg7Myu7DnlHItd3/M2tomOcNNBg= +mvdan.cc/sh/v3 v3.7.0/go.mod h1:K2gwkaesF/D7av7Kxl0HbF5kGOd2ArupNTX3X44+8l8= nhooyr.io/websocket v1.8.6/go.mod h1:B70DZP8IakI65RVQ51MsWP/8jndNma26DVA/nFSCgW0= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= diff --git a/internal/integration_test/integration_test.go b/internal/integration_test/integration_test.go index ecb5ee2b..abef36eb 100644 --- a/internal/integration_test/integration_test.go +++ b/internal/integration_test/integration_test.go @@ -17,6 +17,7 @@ package integration_test import ( "bytes" "context" + "fmt" "io" "net" "sync" @@ -29,7 +30,6 @@ import ( onet "github.com/Jigsaw-Code/outline-ss-server/net" "github.com/Jigsaw-Code/outline-ss-server/service" "github.com/Jigsaw-Code/outline-ss-server/service/metrics" - sstest "github.com/Jigsaw-Code/outline-ss-server/shadowsocks" logging "github.com/op/go-logging" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -41,6 +41,24 @@ func init() { logging.SetLevel(logging.INFO, "") } +// makeTestPayload returns a slice of `size` arbitrary bytes. +func makeTestPayload(size int) []byte { + payload := make([]byte, size) + for i := 0; i < size; i++ { + payload[i] = byte(i) + } + return payload +} + +// makeTestSecrets returns a slice of `n` test passwords. Not secure! +func makeTestSecrets(n int) []string { + secrets := make([]string, n) + for i := 0; i < n; i++ { + secrets[i] = fmt.Sprintf("secret-%v", i) + } + return secrets +} + func allowAll(ip net.IP) *onet.ConnectionError { // Allow access to localhost so that we can run integration tests with // an actual destination server. @@ -125,7 +143,7 @@ func TestTCPEcho(t *testing.T) { require.NoError(t, err) client, err := shadowsocks.NewStreamDialer(&transport.TCPEndpoint{Address: proxyListener.Addr().String()}, cryptoKey) require.NoError(t, err) - conn, err := client.Dial(context.Background(), echoListener.Addr().String()) + conn, err := client.DialStream(context.Background(), echoListener.Addr().String()) require.NoError(t, err) const N = 1000 @@ -210,7 +228,7 @@ func TestRestrictedAddresses(t *testing.T) { } for _, address := range addresses { - conn, err := dialer.Dial(context.Background(), address) + conn, err := dialer.DialStream(context.Background(), address) require.NoError(t, err, "Failed to dial %v", address) n, err := conn.Read(buf) assert.Equal(t, 0, n, "Server should close without replying on rejected address") @@ -284,7 +302,7 @@ func TestUDPEcho(t *testing.T) { require.NoError(t, err) const N = 1000 - up := sstest.MakeTestPayload(N) + up := makeTestPayload(N) n, err := conn.WriteTo(up, echoConn.LocalAddr()) if err != nil { t.Fatal(err) @@ -373,11 +391,11 @@ func BenchmarkTCPThroughput(b *testing.B) { require.NoError(b, err) client, err := shadowsocks.NewStreamDialer(&transport.TCPEndpoint{Address: proxyListener.Addr().String()}, cryptoKey) require.NoError(b, err) - conn, err := client.Dial(context.Background(), echoListener.Addr().String()) + conn, err := client.DialStream(context.Background(), echoListener.Addr().String()) require.NoError(b, err) const N = 1000 - up := sstest.MakeTestPayload(N) + up := makeTestPayload(N) down := make([]byte, N) start := time.Now() @@ -416,7 +434,7 @@ func BenchmarkTCPMultiplexing(b *testing.B) { b.Fatalf("ListenTCP failed: %v", err) } const numKeys = 50 - secrets := sstest.MakeTestSecrets(numKeys) + secrets := makeTestSecrets(numKeys) cipherList, err := service.MakeTestCiphers(secrets) if err != nil { b.Fatal(err) @@ -451,7 +469,7 @@ func BenchmarkTCPMultiplexing(b *testing.B) { go func() { defer wg.Done() for i := 0; i < k; i++ { - conn, err := client.Dial(context.Background(), echoListener.Addr().String()) + conn, err := client.DialStream(context.Background(), echoListener.Addr().String()) if err != nil { b.Errorf("ShadowsocksClient.DialTCP failed: %v", err) } @@ -535,7 +553,7 @@ func BenchmarkUDPManyKeys(b *testing.B) { b.Fatalf("ListenTCP failed: %v", err) } const numKeys = 100 - secrets := sstest.MakeTestSecrets(numKeys) + secrets := makeTestSecrets(numKeys) cipherList, err := service.MakeTestCiphers(secrets) if err != nil { b.Fatal(err) diff --git a/service/cipher_list_test.go b/service/cipher_list_test.go index 72a852cf..94dc23ce 100644 --- a/service/cipher_list_test.go +++ b/service/cipher_list_test.go @@ -18,8 +18,6 @@ import ( "math/rand" "net" "testing" - - sstest "github.com/Jigsaw-Code/outline-ss-server/shadowsocks" ) func BenchmarkLocking(b *testing.B) { @@ -41,7 +39,7 @@ func BenchmarkSnapshot(b *testing.B) { // Small cipher lists (N~1e3) fit entirely in cache, and are ~10 times // faster to copy (per entry) than very large cipher lists (N~1e5). const N = 1e3 - ciphers, _ := MakeTestCiphers(sstest.MakeTestSecrets(N)) + ciphers, _ := MakeTestCiphers(makeTestSecrets(N)) // Shuffling simulates the behavior of a real server, where successive // ciphers are not expected to be nearby in memory. diff --git a/service/server_salt.go b/service/server_salt.go index b1656578..da5192b1 100644 --- a/service/server_salt.go +++ b/service/server_salt.go @@ -22,7 +22,7 @@ import ( "fmt" "io" - ss "github.com/Jigsaw-Code/outline-ss-server/shadowsocks" + ss "github.com/Jigsaw-Code/outline-sdk/transport/shadowsocks" "golang.org/x/crypto/hkdf" ) diff --git a/shadowsocks/cipher.go b/shadowsocks/cipher.go deleted file mode 100644 index 70c6ac44..00000000 --- a/shadowsocks/cipher.go +++ /dev/null @@ -1,160 +0,0 @@ -// Copyright 2020 Jigsaw Operations LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// https://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package shadowsocks - -import ( - "crypto/aes" - "crypto/cipher" - "crypto/md5" - "crypto/sha1" - "fmt" - "io" - "strings" - - "golang.org/x/crypto/chacha20poly1305" - "golang.org/x/crypto/hkdf" -) - -// SupportedCipherNames lists the names of the AEAD ciphers that are supported. -func SupportedCipherNames() []string { - names := make([]string, len(supportedAEADs)) - for i, spec := range supportedAEADs { - names[i] = spec.name - } - return names -} - -type aeadSpec struct { - name string - newInstance func(key []byte) (cipher.AEAD, error) - keySize int - saltSize int - tagSize int -} - -// List of supported AEAD ciphers, as specified at https://shadowsocks.org/en/spec/AEAD-Ciphers.html -var supportedAEADs = [...]aeadSpec{ - newAEADSpec("chacha20-ietf-poly1305", chacha20poly1305.New, chacha20poly1305.KeySize, 32), - newAEADSpec("aes-256-gcm", newAesGCM, 32, 32), - newAEADSpec("aes-192-gcm", newAesGCM, 24, 24), - newAEADSpec("aes-128-gcm", newAesGCM, 16, 16), -} - -func newAEADSpec(name string, newInstance func(key []byte) (cipher.AEAD, error), keySize, saltSize int) aeadSpec { - dummyAead, err := newInstance(make([]byte, keySize)) - if err != nil { - panic(fmt.Sprintf("Failed to initialize AEAD %v", name)) - } - return aeadSpec{name, newInstance, keySize, saltSize, dummyAead.Overhead()} -} - -func getAEADSpec(name string) (*aeadSpec, error) { - name = strings.ToLower(name) - for _, aeadSpec := range supportedAEADs { - if aeadSpec.name == name { - return &aeadSpec, nil - } - } - return nil, fmt.Errorf("unknown cipher %v", name) -} - -func newAesGCM(key []byte) (cipher.AEAD, error) { - blk, err := aes.NewCipher(key) - if err != nil { - return nil, err - } - return cipher.NewGCM(blk) -} - -func maxTagSize() int { - max := 0 - for _, spec := range supportedAEADs { - if spec.tagSize > max { - max = spec.tagSize - } - } - return max -} - -// Cipher encapsulates a Shadowsocks AEAD spec and a secret -type Cipher struct { - aead aeadSpec - secret []byte -} - -// SaltSize is the size of the salt for this Cipher -func (c *Cipher) SaltSize() int { - return c.aead.saltSize -} - -// TagSize is the size of the AEAD tag for this Cipher -func (c *Cipher) TagSize() int { - return c.aead.tagSize -} - -var subkeyInfo = []byte("ss-subkey") - -// NewAEAD creates the AEAD for this cipher -func (c *Cipher) NewAEAD(salt []byte) (cipher.AEAD, error) { - sessionKey := make([]byte, c.aead.keySize) - r := hkdf.New(sha1.New, c.secret, salt, subkeyInfo) - if _, err := io.ReadFull(r, sessionKey); err != nil { - return nil, err - } - return c.aead.newInstance(sessionKey) -} - -// Function definition at https://www.openssl.org/docs/manmaster/man3/EVP_BytesToKey.html -func simpleEVPBytesToKey(data []byte, keyLen int) []byte { - var derived, di []byte - h := md5.New() - for len(derived) < keyLen { - h.Write(di) - h.Write(data) - derived = h.Sum(derived) - di = derived[len(derived)-h.Size():] - h.Reset() - } - return derived[:keyLen] -} - -// NewCipher creates a Cipher given a cipher name and a secret -func NewCipher(cipherName string, secretText string) (*Cipher, error) { - aeadSpec, err := getAEADSpec(cipherName) - if err != nil { - return nil, err - } - // Key derivation as per https://shadowsocks.org/en/spec/AEAD-Ciphers.html - secret := simpleEVPBytesToKey([]byte(secretText), aeadSpec.keySize) - return &Cipher{*aeadSpec, secret}, nil -} - -// Assumes all ciphers have NonceSize() <= 12. -var zeroNonce [12]byte - -// DecryptOnce will decrypt the cipherText using the cipher and salt, appending the output to plainText. -func DecryptOnce(cipher *Cipher, salt []byte, plainText, cipherText []byte) ([]byte, error) { - aead, err := cipher.NewAEAD(salt) - if err != nil { - return nil, err - } - if len(cipherText) < aead.Overhead() { - return nil, io.ErrUnexpectedEOF - } - if cap(plainText)-len(plainText) < len(cipherText)-aead.Overhead() { - return nil, io.ErrShortBuffer - } - return aead.Open(plainText, zeroNonce[:aead.NonceSize()], cipherText, nil) -} diff --git a/shadowsocks/cipher_test.go b/shadowsocks/cipher_test.go deleted file mode 100644 index 8460863e..00000000 --- a/shadowsocks/cipher_test.go +++ /dev/null @@ -1,60 +0,0 @@ -// Copyright 2020 Jigsaw Operations LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// https://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package shadowsocks - -import ( - "testing" -) - -func assertCipher(t *testing.T, name string, saltSize, tagSize int) { - cipher, err := NewCipher(name, "") - if err != nil { - t.Fatal(err) - } - if cipher.SaltSize() != saltSize || cipher.TagSize() != tagSize { - t.Fatalf("Bad spec for %v", name) - } -} - -func TestSizes(t *testing.T) { - // Values from https://shadowsocks.org/en/spec/AEAD-Ciphers.html - assertCipher(t, "chacha20-ietf-poly1305", 32, 16) - assertCipher(t, "aes-256-gcm", 32, 16) - assertCipher(t, "aes-192-gcm", 24, 16) - assertCipher(t, "aes-128-gcm", 16, 16) -} - -func TestUnsupportedCipher(t *testing.T) { - _, err := NewCipher("aes-256-cfb", "") - if err == nil { - t.Errorf("Should get an error for unsupported cipher") - } -} - -func TestMaxNonceSize(t *testing.T) { - for _, aeadName := range SupportedCipherNames() { - cipher, err := NewCipher(aeadName, "") - if err != nil { - t.Errorf("Failed to create Cipher %v: %v", aeadName, err) - } - aead, err := cipher.NewAEAD(make([]byte, cipher.SaltSize())) - if err != nil { - t.Errorf("Failed to create AEAD %v: %v", aeadName, err) - } - if aead.NonceSize() > len(zeroNonce) { - t.Errorf("Cipher %v has nonce size %v > zeroNonce (%v)", aeadName, aead.NonceSize(), len(zeroNonce)) - } - } -} diff --git a/shadowsocks/cipher_testing.go b/shadowsocks/cipher_testing.go deleted file mode 100644 index b4073123..00000000 --- a/shadowsocks/cipher_testing.go +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright 2018 Jigsaw Operations LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// https://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package shadowsocks - -import ( - "fmt" -) - -// TestCipher is a preferred cipher to use in testing. -const TestCipher = "chacha20-ietf-poly1305" - -// MakeTestSecrets returns a slice of `n` test passwords. Not secure! -func MakeTestSecrets(n int) []string { - secrets := make([]string, n) - for i := 0; i < n; i++ { - secrets[i] = fmt.Sprintf("secret-%v", i) - } - return secrets -} - -// MakeTestPayload returns a slice of `size` arbitrary bytes. -func MakeTestPayload(size int) []byte { - payload := make([]byte, size) - for i := 0; i < size; i++ { - payload[i] = byte(i) - } - return payload -} diff --git a/shadowsocks/compatibility_test.go b/shadowsocks/compatibility_test.go deleted file mode 100644 index 1e3a1443..00000000 --- a/shadowsocks/compatibility_test.go +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright 2020 Jigsaw Operations LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// https://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package shadowsocks - -import ( - "io" - "net" - "sync" - "testing" - - "github.com/shadowsocks/go-shadowsocks2/core" - "github.com/shadowsocks/go-shadowsocks2/shadowaead" - "github.com/stretchr/testify/require" -) - -func TestCompatibility(t *testing.T) { - cipherName := "chacha20-ietf-poly1305" - secret := "secret" - toRight := "payload1" - fromRight := "payload2" - left, right := net.Pipe() - - var wait sync.WaitGroup - wait.Add(1) - go func() { - cipher, err := NewCipher(cipherName, secret) - require.NoError(t, err, "NewCipher failed: %v", err) - ssWriter := NewShadowsocksWriter(left, cipher) - ssWriter.Write([]byte(toRight)) - - ssReader := NewShadowsocksReader(left, cipher) - output := make([]byte, len(fromRight)) - _, err = ssReader.Read(output) - require.NoError(t, err, "Read failed: %v", err) - require.Equal(t, fromRight, string(output)) - left.Close() - wait.Done() - }() - - otherCipher, err := core.PickCipher(cipherName, []byte{}, secret) - require.NoError(t, err) - conn := shadowaead.NewConn(right, otherCipher.(shadowaead.Cipher)) - output := make([]byte, len(toRight)) - _, err = io.ReadFull(conn, output) - require.NoError(t, err) - require.Equal(t, toRight, string(output)) - - _, err = conn.Write([]byte(fromRight)) - require.NoError(t, err, "Write failed: %v", err) - - conn.Close() - wait.Wait() -} diff --git a/shadowsocks/packet.go b/shadowsocks/packet.go deleted file mode 100644 index edde01f9..00000000 --- a/shadowsocks/packet.go +++ /dev/null @@ -1,66 +0,0 @@ -// Copyright 2020 Jigsaw Operations LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// https://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package shadowsocks - -import ( - "errors" - "io" -) - -// ErrShortPacket is identical to shadowaead.ErrShortPacket -var ErrShortPacket = errors.New("short packet") - -// Pack encrypts a Shadowsocks-UDP packet and returns a slice containing the encrypted packet. -// dst must be big enough to hold the encrypted packet. -// If plaintext and dst overlap but are not aligned for in-place encryption, this -// function will panic. -func Pack(dst, plaintext []byte, cipher *Cipher) ([]byte, error) { - saltSize := cipher.SaltSize() - if len(dst) < saltSize { - return nil, io.ErrShortBuffer - } - salt := dst[:saltSize] - if err := RandomSaltGenerator.GetSalt(salt); err != nil { - return nil, err - } - - aead, err := cipher.NewAEAD(salt) - if err != nil { - return nil, err - } - - if len(dst) < saltSize+len(plaintext)+aead.Overhead() { - return nil, io.ErrShortBuffer - } - return aead.Seal(salt, zeroNonce[:aead.NonceSize()], plaintext, nil), nil -} - -// Unpack decrypts a Shadowsocks-UDP packet and returns a slice containing the decrypted payload or an error. -// If dst is present, it is used to store the plaintext, and must have enough capacity. -// If dst is nil, decryption proceeds in-place. -// This function is needed because shadowaead.Unpack() embeds its own replay detection, -// which we do not always want, especially on memory-constrained clients. -func Unpack(dst, pkt []byte, cipher *Cipher) ([]byte, error) { - saltSize := cipher.SaltSize() - if len(pkt) < saltSize { - return nil, ErrShortPacket - } - salt := pkt[:saltSize] - msg := pkt[saltSize:] - if dst == nil { - dst = msg - } - return DecryptOnce(cipher, salt, dst[:0], msg) -} diff --git a/shadowsocks/packet_test.go b/shadowsocks/packet_test.go deleted file mode 100644 index 51ab5e79..00000000 --- a/shadowsocks/packet_test.go +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright 2022 Jigsaw Operations LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// https://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package shadowsocks - -import ( - "testing" - "time" -) - -// Microbenchmark for the performance of Shadowsocks UDP encryption. -func BenchmarkPack(b *testing.B) { - b.StopTimer() - b.ResetTimer() - - cipher := newTestCipher(b) - MTU := 1500 - pkt := make([]byte, MTU) - plaintextBuf := pkt[cipher.SaltSize() : len(pkt)-cipher.TagSize()] - - start := time.Now() - b.StartTimer() - for i := 0; i < b.N; i++ { - Pack(pkt, plaintextBuf, cipher) - } - b.StopTimer() - elapsed := time.Now().Sub(start) - - megabits := float64(8*len(plaintextBuf)*b.N) * 1e-6 - b.ReportMetric(megabits/(elapsed.Seconds()), "mbps") -} diff --git a/shadowsocks/salt.go b/shadowsocks/salt.go deleted file mode 100644 index 64c60cb4..00000000 --- a/shadowsocks/salt.go +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright 2020 Jigsaw Operations LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// https://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package shadowsocks - -import ( - "crypto/rand" -) - -// SaltGenerator generates unique salts to use in Shadowsocks connections. -type SaltGenerator interface { - // Returns a new salt - GetSalt(salt []byte) error -} - -// randomSaltGenerator generates a new random salt. -type randomSaltGenerator struct{} - -// GetSalt outputs a random salt. -func (randomSaltGenerator) GetSalt(salt []byte) error { - _, err := rand.Read(salt) - return err -} - -// RandomSaltGenerator is a basic SaltGenerator. -var RandomSaltGenerator SaltGenerator = randomSaltGenerator{} diff --git a/shadowsocks/salt_test.go b/shadowsocks/salt_test.go deleted file mode 100644 index 647e8b7b..00000000 --- a/shadowsocks/salt_test.go +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright 2020 Jigsaw Operations LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// https://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package shadowsocks - -import ( - "bytes" - "testing" -) - -func TestRandomSaltGenerator(t *testing.T) { - if err := RandomSaltGenerator.GetSalt(nil); err != nil { - t.Error(err) - } - salt := make([]byte, 16) - if err := RandomSaltGenerator.GetSalt(salt); err != nil { - t.Error(err) - } - if bytes.Equal(salt, make([]byte, 16)) { - t.Error("Salt is all zeros") - } -} - -func BenchmarkRandomSaltGenerator(b *testing.B) { - b.RunParallel(func(pb *testing.PB) { - salt := make([]byte, 32) - for pb.Next() { - if err := RandomSaltGenerator.GetSalt(salt); err != nil { - b.Fatal(err) - } - } - }) -} diff --git a/shadowsocks/stream.go b/shadowsocks/stream.go deleted file mode 100644 index 3cd0e368..00000000 --- a/shadowsocks/stream.go +++ /dev/null @@ -1,428 +0,0 @@ -// Copyright 2018 Jigsaw Operations LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// https://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package shadowsocks - -import ( - "bytes" - "crypto/cipher" - "encoding/binary" - "fmt" - "io" - "sync" - - "github.com/Jigsaw-Code/outline-ss-server/internal/slicepool" -) - -// payloadSizeMask is the maximum size of payload in bytes. -const payloadSizeMask = 0x3FFF // 16*1024 - 1 - -// Buffer pool used for decrypting Shadowsocks streams. -// The largest buffer we could need is for decrypting a max-length payload. -var readBufPool = slicepool.MakePool(payloadSizeMask + maxTagSize()) - -// Writer is an io.Writer that also implements io.ReaderFrom to -// allow for piping the data without extra allocations and copies. -// The LazyWrite and Flush methods allow a header to be -// added but delayed until the first write, for concatenation. -// All methods except Flush must be called from a single thread. -type Writer struct { - // This type is single-threaded except when needFlush is true. - // mu protects needFlush, and also protects everything - // else while needFlush could be true. - mu sync.Mutex - // Indicates that a concurrent flush is currently allowed. - needFlush bool - writer io.Writer - ssCipher *Cipher - saltGenerator SaltGenerator - // Wrapper for input that arrives as a slice. - byteWrapper bytes.Reader - // Number of plaintext bytes that are currently buffered. - pending int - // These are populated by init(): - buf []byte - aead cipher.AEAD - // Index of the next encrypted chunk to write. - counter []byte -} - -// NewShadowsocksWriter creates a Writer that encrypts the given Writer using -// the shadowsocks protocol with the given shadowsocks cipher. -func NewShadowsocksWriter(writer io.Writer, ssCipher *Cipher) *Writer { - return &Writer{writer: writer, ssCipher: ssCipher, saltGenerator: RandomSaltGenerator} -} - -// SetSaltGenerator sets the salt generator to be used. Must be called before the first write. -func (sw *Writer) SetSaltGenerator(saltGenerator SaltGenerator) { - sw.saltGenerator = saltGenerator -} - -// init generates a random salt, sets up the AEAD object and writes -// the salt to the inner Writer. -func (sw *Writer) init() (err error) { - if sw.aead == nil { - salt := make([]byte, sw.ssCipher.SaltSize()) - if err := sw.saltGenerator.GetSalt(salt); err != nil { - return fmt.Errorf("failed to generate salt: %w", err) - } - sw.aead, err = sw.ssCipher.NewAEAD(salt) - if err != nil { - return fmt.Errorf("failed to create AEAD: %w", err) - } - sw.saltGenerator = nil // No longer needed, so release reference. - sw.counter = make([]byte, sw.aead.NonceSize()) - // The maximum length message is the salt (first message only), length, length tag, - // payload, and payload tag. - sizeBufSize := 2 + sw.aead.Overhead() - maxPayloadBufSize := payloadSizeMask + sw.aead.Overhead() - sw.buf = make([]byte, len(salt)+sizeBufSize+maxPayloadBufSize) - // Store the salt at the start of sw.buf. - copy(sw.buf, salt) - } - return nil -} - -// encryptBlock encrypts `plaintext` in-place. The slice must have enough capacity -// for the tag. Returns the total ciphertext length. -func (sw *Writer) encryptBlock(plaintext []byte) int { - out := sw.aead.Seal(plaintext[:0], sw.counter, plaintext, nil) - increment(sw.counter) - return len(out) -} - -func (sw *Writer) Write(p []byte) (int, error) { - sw.byteWrapper.Reset(p) - n, err := sw.ReadFrom(&sw.byteWrapper) - return int(n), err -} - -// LazyWrite queues p to be written, but doesn't send it until Flush() is -// called, a non-lazy write is made, or the buffer is filled. -func (sw *Writer) LazyWrite(p []byte) (int, error) { - if err := sw.init(); err != nil { - return 0, err - } - - // Locking is needed due to potential concurrency with the Flush() - // for a previous call to LazyWrite(). - sw.mu.Lock() - defer sw.mu.Unlock() - - queued := 0 - for { - n := sw.enqueue(p) - queued += n - p = p[n:] - if len(p) == 0 { - sw.needFlush = true - return queued, nil - } - // p didn't fit in the buffer. Flush the buffer and try - // again. - if err := sw.flush(); err != nil { - return queued, err - } - } -} - -// Flush sends the pending data, if any. This method is thread-safe. -func (sw *Writer) Flush() error { - sw.mu.Lock() - defer sw.mu.Unlock() - if !sw.needFlush { - return nil - } - return sw.flush() -} - -func isZero(b []byte) bool { - for _, v := range b { - if v != 0 { - return false - } - } - return true -} - -// Returns the slices of sw.buf in which to place plaintext for encryption. -func (sw *Writer) buffers() (sizeBuf, payloadBuf []byte) { - // sw.buf starts with the salt. - saltSize := sw.ssCipher.SaltSize() - - // Each Shadowsocks-TCP message consists of a fixed-length size block, - // followed by a variable-length payload block. - sizeBuf = sw.buf[saltSize : saltSize+2] - payloadStart := saltSize + 2 + sw.aead.Overhead() - payloadBuf = sw.buf[payloadStart : payloadStart+payloadSizeMask] - return -} - -// ReadFrom implements the io.ReaderFrom interface. -func (sw *Writer) ReadFrom(r io.Reader) (int64, error) { - if err := sw.init(); err != nil { - return 0, err - } - var written int64 - var err error - _, payloadBuf := sw.buffers() - - // Special case: one thread-safe read, if necessary - sw.mu.Lock() - if sw.needFlush { - pending := sw.pending - - sw.mu.Unlock() - saltsize := sw.ssCipher.SaltSize() - overhead := sw.aead.Overhead() - // The first pending+overhead bytes of payloadBuf are potentially - // in use, and may be modified on the flush thread. Data after - // that is safe to use on this thread. - readBuf := sw.buf[saltsize+2+overhead+pending+overhead:] - var plaintextSize int - plaintextSize, err = r.Read(readBuf) - written = int64(plaintextSize) - sw.mu.Lock() - - sw.enqueue(readBuf[:plaintextSize]) - if flushErr := sw.flush(); flushErr != nil { - err = flushErr - } - sw.needFlush = false - } - sw.mu.Unlock() - - // Main transfer loop - for err == nil { - sw.pending, err = r.Read(payloadBuf) - written += int64(sw.pending) - if flushErr := sw.flush(); flushErr != nil { - err = flushErr - } - } - - if err == io.EOF { // ignore EOF as per io.ReaderFrom contract - return written, nil - } - return written, fmt.Errorf("failed to read payload: %w", err) -} - -// Adds as much of `plaintext` into the buffer as will fit, and increases -// sw.pending accordingly. Returns the number of bytes consumed. -func (sw *Writer) enqueue(plaintext []byte) int { - _, payloadBuf := sw.buffers() - n := copy(payloadBuf[sw.pending:], plaintext) - sw.pending += n - return n -} - -// Encrypts all pending data and writes it to the output. -func (sw *Writer) flush() error { - if sw.pending == 0 { - return nil - } - // sw.buf starts with the salt. - saltSize := sw.ssCipher.SaltSize() - // Normally we ignore the salt at the beginning of sw.buf. - start := saltSize - if isZero(sw.counter) { - // For the first message, include the salt. Compared to writing the salt - // separately, this saves one packet during TCP slow-start and potentially - // avoids having a distinctive size for the first packet. - start = 0 - } - - sizeBuf, payloadBuf := sw.buffers() - binary.BigEndian.PutUint16(sizeBuf, uint16(sw.pending)) - sizeBlockSize := sw.encryptBlock(sizeBuf) - payloadSize := sw.encryptBlock(payloadBuf[:sw.pending]) - _, err := sw.writer.Write(sw.buf[start : saltSize+sizeBlockSize+payloadSize]) - sw.pending = 0 - return err -} - -// ChunkReader is similar to io.Reader, except that it controls its own -// buffer granularity. -type ChunkReader interface { - // ReadChunk reads the next chunk and returns its payload. The caller must - // complete its use of the returned buffer before the next call. - // The buffer is nil iff there is an error. io.EOF indicates a close. - ReadChunk() ([]byte, error) -} - -type chunkReader struct { - reader io.Reader - ssCipher *Cipher - // These are lazily initialized: - aead cipher.AEAD - // Index of the next encrypted chunk to read. - counter []byte - // Buffer for the uint16 size and its AEAD tag. Made in init(). - payloadSizeBuf []byte - // Holds a buffer for the payload and its AEAD tag, when needed. - payload slicepool.LazySlice -} - -// Reader is an io.Reader that also implements io.WriterTo to -// allow for piping the data without extra allocations and copies. -type Reader interface { - io.Reader - io.WriterTo -} - -// NewShadowsocksReader creates a Reader that decrypts the given Reader using -// the shadowsocks protocol with the given shadowsocks cipher. -func NewShadowsocksReader(reader io.Reader, ssCipher *Cipher) Reader { - return &readConverter{ - cr: &chunkReader{ - reader: reader, - ssCipher: ssCipher, - payload: readBufPool.LazySlice(), - }, - } -} - -// init reads the salt from the inner Reader and sets up the AEAD object -func (cr *chunkReader) init() (err error) { - if cr.aead == nil { - // For chacha20-poly1305, SaltSize is 32, NonceSize is 12 and Overhead is 16. - salt := make([]byte, cr.ssCipher.SaltSize()) - if _, err := io.ReadFull(cr.reader, salt); err != nil { - if err != io.EOF && err != io.ErrUnexpectedEOF { - err = fmt.Errorf("failed to read salt: %w", err) - } - return err - } - cr.aead, err = cr.ssCipher.NewAEAD(salt) - if err != nil { - return fmt.Errorf("failed to create AEAD: %w", err) - } - cr.counter = make([]byte, cr.aead.NonceSize()) - cr.payloadSizeBuf = make([]byte, 2+cr.aead.Overhead()) - } - return nil -} - -// readMessage reads, decrypts, and verifies a single AEAD ciphertext. -// The ciphertext and tag (i.e. "overhead") must exactly fill `buf`, -// and the decrypted message will be placed in buf[:len(buf)-overhead]. -// Returns an error only if the block could not be read. -func (cr *chunkReader) readMessage(buf []byte) error { - _, err := io.ReadFull(cr.reader, buf) - if err != nil { - return err - } - _, err = cr.aead.Open(buf[:0], cr.counter, buf, nil) - increment(cr.counter) - if err != nil { - return fmt.Errorf("failed to decrypt message: %w", err) - } - return nil -} - -// ReadChunk returns the next chunk from the stream. Callers must fully -// consume and discard the previous chunk before calling ReadChunk again. -func (cr *chunkReader) ReadChunk() ([]byte, error) { - if err := cr.init(); err != nil { - return nil, err - } - - // Release the previous payload buffer. - cr.payload.Release() - - // In Shadowsocks-AEAD, each chunk consists of two - // encrypted messages. The first message contains the payload length, - // and the second message is the payload. Idle read threads will - // block here until the next chunk. - if err := cr.readMessage(cr.payloadSizeBuf); err != nil { - if err != io.EOF && err != io.ErrUnexpectedEOF { - err = fmt.Errorf("failed to read payload size: %w", err) - } - return nil, err - } - size := int(binary.BigEndian.Uint16(cr.payloadSizeBuf) & payloadSizeMask) - sizeWithTag := size + cr.aead.Overhead() - payloadBuf := cr.payload.Acquire() - if cap(payloadBuf) < sizeWithTag { - // This code is unreachable if the constants are set correctly. - return nil, io.ErrShortBuffer - } - if err := cr.readMessage(payloadBuf[:sizeWithTag]); err != nil { - if err == io.EOF { // EOF is not expected mid-chunk. - err = io.ErrUnexpectedEOF - } - cr.payload.Release() - return nil, err - } - return payloadBuf[:size], nil -} - -// readConverter adapts from ChunkReader, with source-controlled -// chunk sizes, to Go-style IO. -type readConverter struct { - cr ChunkReader - leftover []byte -} - -func (c *readConverter) Read(b []byte) (int, error) { - if err := c.ensureLeftover(); err != nil { - return 0, err - } - n := copy(b, c.leftover) - c.leftover = c.leftover[n:] - return n, nil -} - -func (c *readConverter) WriteTo(w io.Writer) (written int64, err error) { - for { - if err = c.ensureLeftover(); err != nil { - if err == io.EOF { - err = nil - } - return written, err - } - n, err := w.Write(c.leftover) - written += int64(n) - c.leftover = c.leftover[n:] - if err != nil { - return written, err - } - } -} - -// Ensures that c.leftover is nonempty. If leftover is empty, this method -// waits for incoming data and decrypts it. -// Returns an error only if c.leftover could not be populated. -func (c *readConverter) ensureLeftover() error { - if len(c.leftover) > 0 { - return nil - } - c.leftover = nil - payload, err := c.cr.ReadChunk() - if err != nil { - return err - } - c.leftover = payload - return nil -} - -// increment little-endian encoded unsigned integer b. Wrap around on overflow. -func increment(b []byte) { - for i := range b { - b[i]++ - if b[i] != 0 { - return - } - } -} diff --git a/shadowsocks/stream_test.go b/shadowsocks/stream_test.go deleted file mode 100644 index a15aefb8..00000000 --- a/shadowsocks/stream_test.go +++ /dev/null @@ -1,441 +0,0 @@ -package shadowsocks - -import ( - "bytes" - "fmt" - "io" - "io/ioutil" - "strings" - "sync" - "testing" - "time" - - "golang.org/x/crypto/chacha20poly1305" -) - -func newTestCipher(t testing.TB) *Cipher { - cipher, err := NewCipher("chacha20-ietf-poly1305", "test secret") - if err != nil { - t.Fatal(err) - } - return cipher -} - -// Overhead for cipher chacha20poly1305 -const testCipherOverhead = 16 - -func TestCipherReaderAuthenticationFailure(t *testing.T) { - cipher := newTestCipher(t) - - clientReader := strings.NewReader("Fails Authentication") - reader := NewShadowsocksReader(clientReader, cipher) - _, err := reader.Read(make([]byte, 1)) - if err == nil { - t.Fatalf("Expected authentication failure, got %v", err) - } -} - -func TestCipherReaderUnexpectedEOF(t *testing.T) { - cipher := newTestCipher(t) - - clientReader := strings.NewReader("short") - server := NewShadowsocksReader(clientReader, cipher) - _, err := server.Read(make([]byte, 10)) - if err != io.ErrUnexpectedEOF { - t.Fatalf("Expected ErrUnexpectedEOF, got %v", err) - } -} - -func TestCipherReaderEOF(t *testing.T) { - cipher := newTestCipher(t) - - clientReader := strings.NewReader("") - server := NewShadowsocksReader(clientReader, cipher) - _, err := server.Read(make([]byte, 10)) - if err != io.EOF { - t.Fatalf("Expected EOF, got %v", err) - } - _, err = server.Read([]byte{}) - if err != io.EOF { - t.Fatalf("Expected EOF, got %v", err) - } -} - -func encryptBlocks(cipher *Cipher, salt []byte, blocks [][]byte) (io.Reader, error) { - var ssText bytes.Buffer - aead, err := cipher.NewAEAD(salt) - if err != nil { - return nil, fmt.Errorf("failed to create AEAD: %w", err) - } - ssText.Write(salt) - // buf must fit the larges block ciphertext - buf := make([]byte, 2+100+testCipherOverhead) - var expectedCipherSize int - nonce := make([]byte, chacha20poly1305.NonceSize) - for _, block := range blocks { - ssText.Write(aead.Seal(buf[:0], nonce, []byte{0, byte(len(block))}, nil)) - nonce[0]++ - expectedCipherSize += 2 + testCipherOverhead - ssText.Write(aead.Seal(buf[:0], nonce, block, nil)) - nonce[0]++ - expectedCipherSize += len(block) + testCipherOverhead - } - if ssText.Len() != cipher.SaltSize()+expectedCipherSize { - return nil, fmt.Errorf("cipherText has size %v. Expected %v", ssText.Len(), cipher.SaltSize()+expectedCipherSize) - } - return &ssText, nil -} - -func TestCipherReaderGoodReads(t *testing.T) { - cipher := newTestCipher(t) - - salt := []byte("12345678901234567890123456789012") - if len(salt) != cipher.SaltSize() { - t.Fatalf("Salt has size %v. Expected %v", len(salt), cipher.SaltSize()) - } - ssText, err := encryptBlocks(cipher, salt, [][]byte{ - []byte("[First Block]"), - []byte(""), // Corner case: empty block - []byte("[Third Block]")}) - if err != nil { - t.Fatal(err) - } - - reader := NewShadowsocksReader(ssText, cipher) - plainText := make([]byte, len("[First Block]")+len("[Third Block]")) - n, err := io.ReadFull(reader, plainText) - if err != nil { - t.Fatalf("Failed to fully read plain text. Got %v bytes: %v", n, err) - } - _, err = reader.Read([]byte{}) - if err != io.EOF { - t.Fatalf("Expected EOF, got %v", err) - } - _, err = reader.Read(make([]byte, 1)) - if err != io.EOF { - t.Fatalf("Expected EOF, got %v", err) - } -} - -func TestCipherReaderClose(t *testing.T) { - cipher := newTestCipher(t) - - pipeReader, pipeWriter := io.Pipe() - server := NewShadowsocksReader(pipeReader, cipher) - result := make(chan error) - go func() { - _, err := server.Read(make([]byte, 10)) - result <- err - }() - pipeWriter.Close() - err := <-result - if err != io.EOF { - t.Fatalf("Expected ErrUnexpectedEOF, got %v", err) - } -} - -func TestCipherReaderCloseError(t *testing.T) { - cipher := newTestCipher(t) - - pipeReader, pipeWriter := io.Pipe() - server := NewShadowsocksReader(pipeReader, cipher) - result := make(chan error) - go func() { - _, err := server.Read(make([]byte, 10)) - result <- err - }() - pipeWriter.CloseWithError(fmt.Errorf("xx!!ERROR!!xx")) - err := <-result - if err == nil || !strings.Contains(err.Error(), "xx!!ERROR!!xx") { - t.Fatalf("Unexpected error: %v", err) - } -} - -func TestEndToEnd(t *testing.T) { - cipher := newTestCipher(t) - - connReader, connWriter := io.Pipe() - writer := NewShadowsocksWriter(connWriter, cipher) - reader := NewShadowsocksReader(connReader, cipher) - expected := "Test" - go func() { - defer connWriter.Close() - _, err := writer.Write([]byte(expected)) - if err != nil { - t.Fatalf("Failed Write: %v", err) - } - }() - var output bytes.Buffer - _, err := reader.WriteTo(&output) - if err != nil { - t.Fatalf("Failed WriteTo: %v", err) - } - if output.String() != expected { - t.Fatalf("Expected output '%v'. Got '%v'", expected, output.String()) - } -} - -func TestLazyWriteFlush(t *testing.T) { - cipher := newTestCipher(t) - buf := new(bytes.Buffer) - writer := NewShadowsocksWriter(buf, cipher) - header := []byte{1, 2, 3, 4} - n, err := writer.LazyWrite(header) - if n != len(header) { - t.Errorf("Wrong write size: %d", n) - } - if err != nil { - t.Errorf("LazyWrite failed: %v", err) - } - if buf.Len() != 0 { - t.Errorf("LazyWrite isn't lazy: %v", buf.Bytes()) - } - if err = writer.Flush(); err != nil { - t.Errorf("Flush failed: %v", err) - } - len1 := buf.Len() - if len1 <= len(header) { - t.Errorf("Not enough bytes flushed: %d", len1) - } - - // Check that normal writes now work - body := []byte{5, 6, 7} - n, err = writer.Write(body) - if n != len(body) { - t.Errorf("Wrong write size: %d", n) - } - if err != nil { - t.Errorf("Write failed: %v", err) - } - if buf.Len() == len1 { - t.Errorf("No write observed") - } - - // Verify content arrives in two blocks - reader := NewShadowsocksReader(buf, cipher) - decrypted := make([]byte, len(header)+len(body)) - n, err = reader.Read(decrypted) - if n != len(header) { - t.Errorf("Wrong number of bytes out: %d", n) - } - if err != nil { - t.Errorf("Read failed: %v", err) - } - if !bytes.Equal(decrypted[:n], header) { - t.Errorf("Wrong final content: %v", decrypted) - } - n, err = reader.Read(decrypted[n:]) - if n != len(body) { - t.Errorf("Wrong number of bytes out: %d", n) - } - if err != nil { - t.Errorf("Read failed: %v", err) - } - if !bytes.Equal(decrypted[len(header):], body) { - t.Errorf("Wrong final content: %v", decrypted) - } -} - -func TestLazyWriteConcat(t *testing.T) { - cipher := newTestCipher(t) - buf := new(bytes.Buffer) - writer := NewShadowsocksWriter(buf, cipher) - header := []byte{1, 2, 3, 4} - n, err := writer.LazyWrite(header) - if n != len(header) { - t.Errorf("Wrong write size: %d", n) - } - if err != nil { - t.Errorf("LazyWrite failed: %v", err) - } - if buf.Len() != 0 { - t.Errorf("LazyWrite isn't lazy: %v", buf.Bytes()) - } - - // Write additional data and flush the header. - body := []byte{5, 6, 7} - n, err = writer.Write(body) - if n != len(body) { - t.Errorf("Wrong write size: %d", n) - } - if err != nil { - t.Errorf("Write failed: %v", err) - } - len1 := buf.Len() - if len1 <= len(body)+len(header) { - t.Errorf("Not enough bytes flushed: %d", len1) - } - - // Flush after write should have no effect - if err = writer.Flush(); err != nil { - t.Errorf("Flush failed: %v", err) - } - if buf.Len() != len1 { - t.Errorf("Flush should have no effect") - } - - // Verify content arrives in one block - reader := NewShadowsocksReader(buf, cipher) - decrypted := make([]byte, len(body)+len(header)) - n, err = reader.Read(decrypted) - if n != len(decrypted) { - t.Errorf("Wrong number of bytes out: %d", n) - } - if err != nil { - t.Errorf("Read failed: %v", err) - } - if !bytes.Equal(decrypted[:len(header)], header) || - !bytes.Equal(decrypted[len(header):], body) { - t.Errorf("Wrong final content: %v", decrypted) - } -} - -func TestLazyWriteOversize(t *testing.T) { - cipher := newTestCipher(t) - buf := new(bytes.Buffer) - writer := NewShadowsocksWriter(buf, cipher) - N := 25000 // More than one block, less than two. - data := make([]byte, N) - for i := range data { - data[i] = byte(i) - } - n, err := writer.LazyWrite(data) - if n != len(data) { - t.Errorf("Wrong write size: %d", n) - } - if err != nil { - t.Errorf("LazyWrite failed: %v", err) - } - if buf.Len() >= N { - t.Errorf("Too much data in first block: %d", buf.Len()) - } - if err = writer.Flush(); err != nil { - t.Errorf("Flush failed: %v", err) - } - if buf.Len() <= N { - t.Errorf("Not enough data written after flush: %d", buf.Len()) - } - - // Verify content - reader := NewShadowsocksReader(buf, cipher) - decrypted, err := ioutil.ReadAll(reader) - if len(decrypted) != N { - t.Errorf("Wrong number of bytes out: %d", len(decrypted)) - } - if err != nil { - t.Errorf("Read failed: %v", err) - } - if !bytes.Equal(decrypted, data) { - t.Errorf("Wrong final content: %v", decrypted) - } -} - -func TestLazyWriteConcurrentFlush(t *testing.T) { - cipher := newTestCipher(t) - buf := new(bytes.Buffer) - writer := NewShadowsocksWriter(buf, cipher) - header := []byte{1, 2, 3, 4} - n, err := writer.LazyWrite(header) - if n != len(header) { - t.Errorf("Wrong write size: %d", n) - } - if err != nil { - t.Errorf("LazyWrite failed: %v", err) - } - if buf.Len() != 0 { - t.Errorf("LazyWrite isn't lazy: %v", buf.Bytes()) - } - - body := []byte{5, 6, 7} - r, w := io.Pipe() - wg := sync.WaitGroup{} - wg.Add(1) - go func() { - n, err := writer.ReadFrom(r) - if n != int64(len(body)) { - t.Errorf("ReadFrom: Wrong read size %d", n) - } - if err != nil { - t.Errorf("ReadFrom: %v", err) - } - wg.Done() - }() - - // Wait for ReadFrom to start and get blocked. - time.Sleep(20 * time.Millisecond) - - // Flush while ReadFrom is blocked. - if err := writer.Flush(); err != nil { - t.Errorf("Flush error: %v", err) - } - len1 := buf.Len() - if len1 == 0 { - t.Errorf("No bytes flushed") - } - - // Check that normal writes now work - n, err = w.Write(body) - if n != len(body) { - t.Errorf("Wrong write size: %d", n) - } - if err != nil { - t.Errorf("Write failed: %v", err) - } - w.Close() - wg.Wait() - if buf.Len() == len1 { - t.Errorf("No write observed") - } - - // Verify content arrives in two blocks - reader := NewShadowsocksReader(buf, cipher) - decrypted := make([]byte, len(header)+len(body)) - n, err = reader.Read(decrypted) - if n != len(header) { - t.Errorf("Wrong number of bytes out: %d", n) - } - if err != nil { - t.Errorf("Read failed: %v", err) - } - if !bytes.Equal(decrypted[:len(header)], header) { - t.Errorf("Wrong final content: %v", decrypted) - } - n, err = reader.Read(decrypted[len(header):]) - if n != len(body) { - t.Errorf("Wrong number of bytes out: %d", n) - } - if err != nil { - t.Errorf("Read failed: %v", err) - } - if !bytes.Equal(decrypted[len(header):], body) { - t.Errorf("Wrong final content: %v", decrypted) - } -} - -type nullIO struct{} - -func (n *nullIO) Write(b []byte) (int, error) { - return len(b), nil -} - -func (r *nullIO) Read(b []byte) (int, error) { - return len(b), nil -} - -// Microbenchmark for the performance of Shadowsocks TCP encryption. -func BenchmarkWriter(b *testing.B) { - b.StopTimer() - b.ResetTimer() - - cipher := newTestCipher(b) - writer := NewShadowsocksWriter(new(nullIO), cipher) - - start := time.Now() - b.StartTimer() - io.CopyN(writer, new(nullIO), int64(b.N)) - b.StopTimer() - elapsed := time.Now().Sub(start) - - megabits := 8 * float64(b.N) * 1e-6 - b.ReportMetric(megabits/(elapsed.Seconds()), "mbps") -} diff --git a/tools.go b/tools.go index 551a6f86..8fb61e5c 100644 --- a/tools.go +++ b/tools.go @@ -21,5 +21,6 @@ package tools import ( + _ "github.com/go-task/task/v3/cmd/task" _ "github.com/goreleaser/goreleaser" ) From d440bf4b90db87692e0bc75579cd05bfbb10e743 Mon Sep 17 00:00:00 2001 From: Vinicius Fortuna Date: Thu, 14 Mar 2024 18:51:40 -0400 Subject: [PATCH 2/6] Updates --- .gitignore | 2 +- Makefile | 25 ------------------------- Taskfile.yml | 4 ++-- 3 files changed, 3 insertions(+), 28 deletions(-) delete mode 100644 Makefile diff --git a/.gitignore b/.gitignore index cf216544..82e9d403 100644 --- a/.gitignore +++ b/.gitignore @@ -26,4 +26,4 @@ /data/ # Go task -/.task/ \ No newline at end of file +/.task/ diff --git a/Makefile b/Makefile deleted file mode 100644 index 6e2c3e13..00000000 --- a/Makefile +++ /dev/null @@ -1,25 +0,0 @@ -BUILDDIR=$(CURDIR)/dist -GORELEASER=go run github.com/goreleaser/goreleaser - -.PHONY: release release-local test clean - -# This requires GITHUB_TOKEN to be set. -release: clean - $(GORELEASER) - -release-local: - $(GORELEASER) --clean --snapshot - -test: third_party/maxmind/test-data/GeoIP2-Country-Test.mmdb - go test -v -race -benchmem -bench=. ./... -benchtime=100ms - -third_party/maxmind/test-data/GeoIP2-Country-Test.mmdb: - git submodule update --init --depth=1 - -go.mod: tools.go - go mod tidy - touch go.mod - -clean: - rm -rf $(BUILDDIR) - go clean diff --git a/Taskfile.yml b/Taskfile.yml index 60f7fc5b..51fd7402 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -3,7 +3,7 @@ version: '3' run: when_changed vars: - OUT_DIR: "{{.USER_WORKING_DIR}}/out" + OUT_DIR: "{{.USER_WORKING_DIR}}/dist" tasks: release: @@ -37,4 +37,4 @@ tasks: cmds: - rm -rf {{.OUT_DIR}} - git submodule deinit --all - - go clean \ No newline at end of file + - go clean From dc744b8533d8c6826873d91edfa1ffd916430dfc Mon Sep 17 00:00:00 2001 From: Vinicius Fortuna Date: Fri, 15 Mar 2024 11:18:05 -0400 Subject: [PATCH 3/6] Upgrade SDK dependency --- go.mod | 3 +-- go.sum | 6 ++---- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 039ece82..8935374c 100644 --- a/go.mod +++ b/go.mod @@ -1,7 +1,7 @@ module github.com/Jigsaw-Code/outline-ss-server require ( - github.com/Jigsaw-Code/outline-sdk v0.0.13 + github.com/Jigsaw-Code/outline-sdk v0.0.14 github.com/go-task/task/v3 v3.34.1 github.com/goreleaser/goreleaser v1.18.2 github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 @@ -203,7 +203,6 @@ require ( github.com/prometheus/common v0.42.0 // indirect github.com/prometheus/procfs v0.9.0 // indirect github.com/radovskyb/watcher v1.0.7 // indirect - github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 // indirect github.com/rivo/uniseg v0.4.2 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/sajari/fuzzy v1.0.0 // indirect diff --git a/go.sum b/go.sum index 485bac1d..846e9c24 100644 --- a/go.sum +++ b/go.sum @@ -510,10 +510,8 @@ github.com/DataDog/zstd v1.4.5 h1:EndNeuB0l9syBZhut0wns3gV1hL8zX8LIu6ZiVHWLIQ= github.com/GoogleCloudPlatform/cloudsql-proxy v1.33.2/go.mod h1:uqoR4sJc63p7ugW8a/vsEspOsNuehbi7ptS2CHCyOnY= github.com/HdrHistogram/hdrhistogram-go v1.1.0/go.mod h1:yDgFjdqOqDEKOvasDdhWNXYg9BVp4O+o5f6V/ehm6Oo= github.com/HdrHistogram/hdrhistogram-go v1.1.2/go.mod h1:yDgFjdqOqDEKOvasDdhWNXYg9BVp4O+o5f6V/ehm6Oo= -github.com/Jigsaw-Code/outline-sdk v0.0.2 h1:uCuyJMaWj57IYEG/Hdml8YMdk9chU60ZkSxJXBhyGHU= -github.com/Jigsaw-Code/outline-sdk v0.0.2/go.mod h1:hhlKz0+r9wSDFT8usvN8Zv/BFToCIFAUn1P2Qk8G2CM= -github.com/Jigsaw-Code/outline-sdk v0.0.13 h1:My3klfEmtakgTgv+Xmg/T5xnFxWvAccMtBY0DIaeMhc= -github.com/Jigsaw-Code/outline-sdk v0.0.13/go.mod h1:9cEaF6sWWMzY8orcUI9pV5D0oFp2FZArTSyJiYtMQQs= +github.com/Jigsaw-Code/outline-sdk v0.0.14 h1:uJLvIne7YJNolbX7KDacd8gLidrUzRuweBO2APmQEmI= +github.com/Jigsaw-Code/outline-sdk v0.0.14/go.mod h1:9cEaF6sWWMzY8orcUI9pV5D0oFp2FZArTSyJiYtMQQs= github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= From 1aaddccd54a7fdbd0df6b99544e6d0ed047d8b3d Mon Sep 17 00:00:00 2001 From: Vinicius Fortuna Date: Fri, 15 Mar 2024 11:20:51 -0400 Subject: [PATCH 4/6] Address review --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 1da3014d..151433a3 100644 --- a/README.md +++ b/README.md @@ -148,7 +148,7 @@ You can mix and match the libev and go servers and clients. To run the tests and benchmarks, call: ``` -make test +go run github.com/go-task/task/v3/cmd/task test ``` You can benchmark the cipher finding code with From 8fad19559a3ab640326918ce7e0d7ef38326e687 Mon Sep 17 00:00:00 2001 From: Vinicius Fortuna Date: Fri, 15 Mar 2024 11:29:12 -0400 Subject: [PATCH 5/6] Tweak to not leave token in history --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 151433a3..8567ba22 100644 --- a/README.md +++ b/README.md @@ -169,7 +169,8 @@ Summary: ``` - Export an environment variable named `GITHUB_TOKEN` with a temporary repo-scoped GitHub token ([create one here](https://github.com/settings/tokens/new)): ```bash - export GITHUB_TOKEN=yournewtoken + read -s -p "Type your Github token:" GITHUB_TOKEN + export GITHUB_TOKEN ``` - Create a new tag and push it to GitHub e.g.: ```bash From 2d62d6738dbbc29905f29979aaf585811e5d8e61 Mon Sep 17 00:00:00 2001 From: Vinicius Fortuna Date: Fri, 15 Mar 2024 11:34:05 -0400 Subject: [PATCH 6/6] Fix release task --- Taskfile.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Taskfile.yml b/Taskfile.yml index 51fd7402..f40c74fe 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -10,7 +10,7 @@ tasks: desc: "Release" deps: [clean] cmds: - - go run github.com/goreleaser/goreleaser --clean --snapshot + - go run github.com/goreleaser/goreleaser release-local: