├── .github ├── dependabot.yml └── workflows │ ├── build.yml │ └── release.yml ├── .gitignore ├── .gitmodules ├── .goreleaser.yml ├── .travis.yml ├── LICENSE ├── README.md ├── client ├── client.go ├── client_test.go ├── logger.go ├── nat.go ├── service.go ├── tcp.go └── udp.go ├── cmd ├── outline-ss-client │ └── main.go └── outline-ss-server │ └── main.go ├── config_example.yml ├── go.mod ├── go.sum ├── integration_test └── integration_test.go ├── logging └── zap.go ├── net ├── net.go ├── net_bsd.go ├── net_default.go ├── net_linux.go ├── net_linuxwindows.go ├── net_notlinuxwindows.go ├── net_notunix.go ├── net_unix.go ├── net_windows.go ├── private_net.go └── private_net_test.go ├── prometheus_example.yml ├── service ├── PROBES.md ├── cipher_list.go ├── cipher_list_test.go ├── cipher_list_testing.go ├── logger.go ├── metrics │ ├── metrics.go │ └── metrics_test.go ├── nat.go ├── replay.go ├── replay_test.go ├── server_salt.go ├── server_salt_test.go ├── tcp.go ├── tcp_test.go ├── udp.go └── udp_test.go ├── shadowsocks ├── cipher.go ├── cipher_test.go ├── cipher_testing.go ├── header.go ├── packet.go ├── salt.go ├── salt_test.go ├── stream.go └── stream_test.go ├── slicepool ├── slicepool.go └── slicepool_test.go └── socks └── socks.go /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: gomod 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | 8 | - package-ecosystem: github-actions 9 | directory: "/" 10 | schedule: 11 | interval: daily 12 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | push: 5 | branches: 6 | - '**' 7 | paths-ignore: 8 | - 'README.md' 9 | - 'LICENSE' 10 | pull_request: 11 | paths-ignore: 12 | - 'README.md' 13 | - 'LICENSE' 14 | 15 | jobs: 16 | build: 17 | name: Build 18 | strategy: 19 | matrix: 20 | os: [ubuntu-latest, windows-latest, macos-latest] 21 | fail-fast: false 22 | runs-on: ${{ matrix.os }} 23 | defaults: 24 | run: 25 | shell: bash 26 | 27 | steps: 28 | - uses: actions/checkout@v3 29 | with: 30 | submodules: true 31 | 32 | - name: Setup Go 33 | uses: actions/setup-go@v3 34 | with: 35 | go-version: ^1.18 36 | 37 | - name: Build 38 | run: | 39 | mkdir build 40 | export GOAMD64=v3 41 | go build -v -trimpath -ldflags '-s -w' -o build/ ./cmd/outline-ss-client 42 | go build -v -trimpath -ldflags '-s -w' -o build/ ./cmd/outline-ss-server 43 | 44 | - name: Test 45 | if: matrix.os != 'macos-latest' 46 | run: go test -v -benchmem -bench . ./... 47 | 48 | - name: Upload Binaries 49 | uses: actions/upload-artifact@v3 50 | with: 51 | name: outline-shadowsocks-${{ matrix.os }}-x86-64-v3 52 | path: build/ 53 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | tags: 6 | - 'v*' 7 | 8 | jobs: 9 | publish_upload: 10 | name: Publish and upload 11 | runs-on: ubuntu-latest 12 | container: 13 | image: archlinux/archlinux:base-devel 14 | 15 | steps: 16 | - name: Install Git & Go 17 | run: pacman -Syu --needed --noconfirm git go 18 | 19 | - name: Git Checkout Tag 20 | run: | 21 | cd ../ 22 | rm -rf ${{ github.event.repository.name }} 23 | git clone --branch ${{ github.ref_name }} https://github.com/${{ github.repository }}.git 24 | 25 | - name: Build 26 | run: | 27 | mkdir package-archlinux-x86-64-v2 \ 28 | package-archlinux-x86-64-v3 \ 29 | package-linux-x86-64-v2 \ 30 | package-linux-x86-64-v3 \ 31 | package-linux-arm64 \ 32 | package-windows-x86-64-v2 \ 33 | package-windows-x86-64-v3 \ 34 | package-windows-arm64 35 | 36 | env GOAMD64=v2 go build -v -trimpath -ldflags '-s -w' -o package-archlinux-x86-64-v2/ ./... 37 | env GOAMD64=v3 go build -v -trimpath -ldflags '-s -w' -o package-archlinux-x86-64-v3/ ./... 38 | env CGO_ENABLED=0 GOAMD64=v2 go build -v -trimpath -ldflags '-s -w' -o package-linux-x86-64-v2/ ./... 39 | env CGO_ENABLED=0 GOAMD64=v3 go build -v -trimpath -ldflags '-s -w' -o package-linux-x86-64-v3/ ./... 40 | env CGO_ENABLED=0 GOARCH=arm64 go build -v -trimpath -ldflags '-s -w' -o package-linux-arm64/ ./... 41 | env CGO_ENABLED=0 GOAMD64=v2 GOOS=windows go build -v -trimpath -ldflags '-s -w' -o package-windows-x86-64-v2/ ./... 42 | env CGO_ENABLED=0 GOAMD64=v3 GOOS=windows go build -v -trimpath -ldflags '-s -w' -o package-windows-x86-64-v3/ ./... 43 | env CGO_ENABLED=0 GOOS=windows GOARCH=arm64 go build -v -trimpath -ldflags '-s -w' -o package-windows-arm64/ ./... 44 | 45 | - name: Package 46 | env: 47 | ZSTD_CLEVEL: 19 48 | ZSTD_NBTHREADS: 2 49 | run: | 50 | cd package-archlinux-x86-64-v2/ 51 | tar -acf ../outline-ss-${{ github.ref_name }}-archlinux-x86-64-v2.tar.zst . 52 | cd ../package-archlinux-x86-64-v3/ 53 | tar -acf ../outline-ss-${{ github.ref_name }}-archlinux-x86-64-v3.tar.zst . 54 | cd ../package-linux-x86-64-v2/ 55 | tar -acf ../outline-ss-${{ github.ref_name }}-linux-x86-64-v2.tar.zst . 56 | cd ../package-linux-x86-64-v3/ 57 | tar -acf ../outline-ss-${{ github.ref_name }}-linux-x86-64-v3.tar.zst . 58 | cd ../package-linux-arm64/ 59 | tar -acf ../outline-ss-${{ github.ref_name }}-linux-arm64.tar.zst . 60 | cd ../package-windows-x86-64-v2/ 61 | tar -acf ../outline-ss-${{ github.ref_name }}-windows-x86-64-v2.tar.zst . 62 | cd ../package-windows-x86-64-v3/ 63 | tar -acf ../outline-ss-${{ github.ref_name }}-windows-x86-64-v3.tar.zst . 64 | cd ../package-windows-arm64/ 65 | tar -acf ../outline-ss-${{ github.ref_name }}-windows-arm64.tar.zst . 66 | 67 | - name: Upload release assets 68 | uses: svenstaro/upload-release-action@v2 69 | with: 70 | repo_token: ${{ secrets.GITHUB_TOKEN }} 71 | file: outline-ss-*.tar.zst 72 | tag: ${{ github.ref }} 73 | file_glob: true 74 | prerelease: true 75 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | /outline-ss-server 3 | /outline-ss-server.exe 4 | /outline-ss-client 5 | /outline-ss-client.exe 6 | *.exe 7 | *.dll 8 | *.so 9 | *.dylib 10 | 11 | # Test binary, build with `go test -c` 12 | *.test 13 | 14 | # Output of the go coverage tool, specifically when used with LiteIDE 15 | *.out 16 | 17 | # Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736 18 | .glide/ 19 | 20 | # goreleaser temporary files. 21 | /dist/ 22 | 23 | .vscode 24 | config.yml 25 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "metrics/test-data"] 2 | path = third_party/maxmind 3 | url = https://github.com/maxmind/MaxMind-DB.git 4 | -------------------------------------------------------------------------------- /.goreleaser.yml: -------------------------------------------------------------------------------- 1 | # Copyright 2018 Jigsaw Operations LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # https:#www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | project_name: outline-ss-server 16 | 17 | # Build for macOS, Linux, and Windows. 18 | # Skip 32 bit macOS builds. 19 | builds: 20 | - 21 | env: 22 | - CGO_ENABLED=0 23 | goos: 24 | - darwin 25 | - windows 26 | - linux 27 | goarch: 28 | - 386 29 | - amd64 30 | - arm 31 | - arm64 32 | goarm: 33 | - 6 34 | - 7 35 | ignore: 36 | - goos: darwin 37 | goarch: 386 38 | 39 | archives: 40 | - 41 | replacements: 42 | darwin: macos 43 | 386: i386 44 | amd64: x86_64 45 | 46 | checksum: 47 | name_template: 'checksums.txt' 48 | 49 | snapshot: 50 | name_template: "{{ .Tag }}-next" 51 | 52 | release: 53 | # This prevents auto-publishing the release. You need to manually publish it. 54 | draft: true 55 | # Marks the release as not ready for production in case there's a tag indicator (e.g. -rc1) 56 | prerelease: auto 57 | 58 | changelog: 59 | sort: asc 60 | filters: 61 | exclude: 62 | - '^docs:' 63 | - '^test:' 64 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | 2 | language: go 3 | 4 | go: 5 | - "1.14" 6 | - "stable" 7 | 8 | script: 9 | - go test -v -race -bench=. ./... -benchtime=100ms 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ⚠️ This fork is longer being maintained. The Shadowsocks 2022 implementation in this repo is outdated and is incompatible with the spec. Use https://github.com/database64128/shadowsocks-go instead. 2 | 3 | # Outline Shadowsocks 4 | 5 | [![Build](https://github.com/Shadowsocks-NET/outline-ss-server/actions/workflows/build.yml/badge.svg)](https://github.com/Shadowsocks-NET/outline-ss-server/actions/workflows/build.yml) 6 | [![Release](https://github.com/Shadowsocks-NET/outline-ss-server/actions/workflows/release.yml/badge.svg)](https://github.com/Shadowsocks-NET/outline-ss-server/actions/workflows/release.yml) 7 | 8 | An opinionated fork of Jigsaw-Code/outline-ss-server. 9 | -------------------------------------------------------------------------------- /client/client_test.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "bytes" 5 | "io" 6 | "net" 7 | "sync" 8 | "testing" 9 | "time" 10 | 11 | onet "github.com/Shadowsocks-NET/outline-ss-server/net" 12 | "github.com/Shadowsocks-NET/outline-ss-server/service" 13 | ss "github.com/Shadowsocks-NET/outline-ss-server/shadowsocks" 14 | "github.com/Shadowsocks-NET/outline-ss-server/socks" 15 | ) 16 | 17 | const ( 18 | testPassword = "testPassword" 19 | testTargetAddr = "test.local:1111" 20 | ) 21 | 22 | var testTargetSocksAddr socks.Addr 23 | 24 | func init() { 25 | var err error 26 | testTargetSocksAddr, err = socks.ParseAddr(testTargetAddr) 27 | if err != nil { 28 | panic(err) 29 | } 30 | } 31 | 32 | func TestShadowsocksClient_DialTCP(t *testing.T) { 33 | proxy, running := startShadowsocksTCPEchoProxy(testTargetAddr, t) 34 | d, err := NewClient(proxy.Addr().String(), ss.TestCipher, testPassword, nil) 35 | if err != nil { 36 | t.Fatalf("Failed to create ShadowsocksClient: %v", err) 37 | } 38 | conn, err := d.DialTCP(nil, testTargetSocksAddr, false) 39 | if err != nil { 40 | t.Fatalf("ShadowsocksClient.DialTCP failed: %v", err) 41 | } 42 | conn.SetReadDeadline(time.Now().Add(time.Second * 5)) 43 | expectEchoPayload(conn, ss.MakeTestPayload(1024), make([]byte, 1024), t) 44 | conn.Close() 45 | 46 | proxy.Close() 47 | running.Wait() 48 | } 49 | 50 | func TestShadowsocksClient_DialTCPNoPayload(t *testing.T) { 51 | proxy, running := startShadowsocksTCPEchoProxy(testTargetAddr, t) 52 | d, err := NewClient(proxy.Addr().String(), ss.TestCipher, testPassword, nil) 53 | if err != nil { 54 | t.Fatalf("Failed to create ShadowsocksClient: %v", err) 55 | } 56 | conn, err := d.DialTCP(nil, testTargetSocksAddr, false) 57 | if err != nil { 58 | t.Fatalf("ShadowsocksClient.DialTCP failed: %v", err) 59 | } 60 | 61 | // Wait for more than 100 milliseconds to ensure that the target 62 | // address is sent. 63 | time.Sleep(200 * time.Millisecond) 64 | // Force the echo server to verify the target address. 65 | conn.Close() 66 | 67 | proxy.Close() 68 | running.Wait() 69 | } 70 | 71 | func TestShadowsocksClient_DialTCPFastClose(t *testing.T) { 72 | // Set up a listener that verifies no data is sent. 73 | listener, err := net.ListenTCP("tcp", &net.TCPAddr{IP: net.IPv6loopback, Port: 0}) 74 | if err != nil { 75 | t.Fatalf("ListenTCP failed: %v", err) 76 | } 77 | 78 | done := make(chan struct{}) 79 | go func() { 80 | conn, err := listener.Accept() 81 | if err != nil { 82 | t.Error(err) 83 | } 84 | buf := make([]byte, 64) 85 | n, err := conn.Read(buf) 86 | if n > 0 || err != io.EOF { 87 | t.Errorf("Expected EOF, got %v, %v", buf[:n], err) 88 | } 89 | listener.Close() 90 | close(done) 91 | }() 92 | 93 | d, err := NewClient(listener.Addr().String(), ss.TestCipher, testPassword, nil) 94 | if err != nil { 95 | t.Fatalf("Failed to create ShadowsocksClient: %v", err) 96 | } 97 | 98 | conn, err := d.DialTCP(nil, testTargetSocksAddr, false) 99 | if err != nil { 100 | t.Fatalf("ShadowsocksClient.DialTCP failed: %v", err) 101 | } 102 | 103 | // Wait for less than 10 milliseconds to ensure that the target 104 | // address is not sent. 105 | time.Sleep(1 * time.Millisecond) 106 | // Close the connection before the target address is sent. 107 | conn.Close() 108 | // Wait for the listener to verify the close. 109 | <-done 110 | } 111 | 112 | func TestShadowsocksClient_ListenUDP(t *testing.T) { 113 | proxy, running := startShadowsocksUDPEchoServer(testTargetAddr, t) 114 | d, err := NewClient(proxy.LocalAddr().String(), ss.TestCipher, testPassword, nil) 115 | if err != nil { 116 | t.Fatalf("Failed to create ShadowsocksClient: %v", err) 117 | } 118 | conn, err := d.ListenUDP(nil) 119 | if err != nil { 120 | t.Fatalf("ShadowsocksClient.ListenUDP failed: %v", err) 121 | } 122 | defer conn.Close() 123 | conn.SetReadDeadline(time.Now().Add(time.Second * 5)) 124 | pcrw := &packetConnReadWriter{PacketConn: conn, targetAddr: onet.NewAddr(testTargetAddr, "udp")} 125 | expectEchoPayload(pcrw, ss.MakeTestPayload(1024), make([]byte, 1024), t) 126 | 127 | proxy.Close() 128 | running.Wait() 129 | } 130 | 131 | func BenchmarkShadowsocksClient_DialTCP(b *testing.B) { 132 | b.StopTimer() 133 | b.ResetTimer() 134 | 135 | proxy, running := startShadowsocksTCPEchoProxy(testTargetAddr, b) 136 | d, err := NewClient(proxy.Addr().String(), ss.TestCipher, testPassword, nil) 137 | if err != nil { 138 | b.Fatalf("Failed to create ShadowsocksClient: %v", err) 139 | } 140 | conn, err := d.DialTCP(nil, testTargetSocksAddr, false) 141 | if err != nil { 142 | b.Fatalf("ShadowsocksClient.DialTCP failed: %v", err) 143 | } 144 | conn.SetReadDeadline(time.Now().Add(time.Second * 5)) 145 | buf := make([]byte, 1024) 146 | for n := 0; n < b.N; n++ { 147 | payload := ss.MakeTestPayload(1024) 148 | b.StartTimer() 149 | expectEchoPayload(conn, payload, buf, b) 150 | b.StopTimer() 151 | } 152 | 153 | conn.Close() 154 | proxy.Close() 155 | running.Wait() 156 | } 157 | 158 | func BenchmarkShadowsocksClient_ListenUDP(b *testing.B) { 159 | b.StopTimer() 160 | b.ResetTimer() 161 | 162 | proxy, running := startShadowsocksUDPEchoServer(testTargetAddr, b) 163 | d, err := NewClient(proxy.LocalAddr().String(), ss.TestCipher, testPassword, nil) 164 | if err != nil { 165 | b.Fatalf("Failed to create ShadowsocksClient: %v", err) 166 | } 167 | conn, err := d.ListenUDP(nil) 168 | if err != nil { 169 | b.Fatalf("ShadowsocksClient.ListenUDP failed: %v", err) 170 | } 171 | defer conn.Close() 172 | conn.SetReadDeadline(time.Now().Add(time.Second * 5)) 173 | buf := make([]byte, service.UDPPacketBufferSize) 174 | for n := 0; n < b.N; n++ { 175 | payload := ss.MakeTestPayload(1024) 176 | pcrw := &packetConnReadWriter{PacketConn: conn, targetAddr: onet.NewAddr(testTargetAddr, "udp")} 177 | b.StartTimer() 178 | expectEchoPayload(pcrw, payload, buf, b) 179 | b.StopTimer() 180 | } 181 | 182 | proxy.Close() 183 | running.Wait() 184 | } 185 | 186 | func startShadowsocksTCPEchoProxy(expectedTgtAddr string, t testing.TB) (net.Listener, *sync.WaitGroup) { 187 | listener, err := net.ListenTCP("tcp", &net.TCPAddr{IP: net.IPv6loopback, Port: 0}) 188 | if err != nil { 189 | t.Fatalf("ListenTCP failed: %v", err) 190 | } 191 | t.Logf("Starting SS TCP echo proxy at %v\n", listener.Addr()) 192 | cipher, err := ss.NewCipher(ss.TestCipher, testPassword) 193 | if err != nil { 194 | t.Fatalf("Failed to create cipher: %v", err) 195 | } 196 | var running sync.WaitGroup 197 | running.Add(1) 198 | go func() { 199 | defer running.Done() 200 | defer listener.Close() 201 | for { 202 | clientConn, err := listener.AcceptTCP() 203 | if err != nil { 204 | t.Logf("AcceptTCP failed: %v", err) 205 | return 206 | } 207 | running.Add(1) 208 | go func() { 209 | defer running.Done() 210 | defer clientConn.Close() 211 | ssr := ss.NewShadowsocksReader(clientConn, cipher) 212 | ssw, err := ss.NewShadowsocksWriter(clientConn, cipher, nil, nil, cipher.Config().IsSpec2022) 213 | if err != nil { 214 | t.Fatalf("Failed to create shadowsocks writer: %v", err) 215 | } 216 | ssClientConn := onet.WrapDuplexConn(clientConn, ssr, ssw) 217 | 218 | tgtAddr, err := socks.AddrFromReader(ssClientConn) 219 | if err != nil { 220 | t.Fatalf("Failed to read target address: %v", err) 221 | } 222 | if tgtAddr.String() != expectedTgtAddr { 223 | t.Fatalf("Expected target address '%v'. Got '%v'", expectedTgtAddr, tgtAddr) 224 | } 225 | io.Copy(ssw, ssr) 226 | }() 227 | } 228 | }() 229 | return listener, &running 230 | } 231 | 232 | func startShadowsocksUDPEchoServer(expectedTgtAddr string, t testing.TB) (net.Conn, *sync.WaitGroup) { 233 | conn, err := net.ListenUDP("udp", &net.UDPAddr{IP: net.IPv6loopback, Port: 0}) 234 | if err != nil { 235 | t.Fatalf("Proxy ListenUDP failed: %v", err) 236 | } 237 | t.Logf("Starting SS UDP echo proxy at %v\n", conn.LocalAddr()) 238 | cipherBuf := make([]byte, service.UDPPacketBufferSize) 239 | clientBuf := make([]byte, service.UDPPacketBufferSize) 240 | cipher, err := ss.NewCipher(ss.TestCipher, testPassword) 241 | if err != nil { 242 | t.Fatalf("Failed to create cipher: %v", err) 243 | } 244 | var running sync.WaitGroup 245 | running.Add(1) 246 | go func() { 247 | defer running.Done() 248 | defer conn.Close() 249 | for { 250 | n, clientAddr, err := conn.ReadFromUDP(cipherBuf) 251 | if err != nil { 252 | t.Logf("Failed to read from UDP conn: %v", err) 253 | return 254 | } 255 | _, buf, err := ss.Unpack(clientBuf, cipherBuf[:n], cipher) 256 | if err != nil { 257 | t.Fatalf("Failed to decrypt: %v", err) 258 | } 259 | tgtAddr, err := socks.SplitAddr(buf) 260 | if err != nil { 261 | t.Fatalf("Failed to read target address: %v", err) 262 | } 263 | if tgtAddr.String() != expectedTgtAddr { 264 | t.Fatalf("Expected target address '%v'. Got '%v'", expectedTgtAddr, tgtAddr) 265 | } 266 | // Echo both the payload and SOCKS address. 267 | buf, err = ss.Pack(cipherBuf, buf, cipher) 268 | if err != nil { 269 | t.Fatalf("Failed to encrypt: %v", err) 270 | } 271 | conn.WriteTo(buf, clientAddr) 272 | if err != nil { 273 | t.Fatalf("Failed to write: %v", err) 274 | } 275 | } 276 | }() 277 | return conn, &running 278 | } 279 | 280 | // io.ReadWriter adapter for net.PacketConn. Used to share code between UDP and TCP tests. 281 | type packetConnReadWriter struct { 282 | net.PacketConn 283 | io.ReadWriter 284 | targetAddr net.Addr 285 | } 286 | 287 | func (pc *packetConnReadWriter) Read(b []byte) (n int, err error) { 288 | n, _, err = pc.PacketConn.ReadFrom(b) 289 | return 290 | } 291 | 292 | func (pc *packetConnReadWriter) Write(b []byte) (int, error) { 293 | return pc.PacketConn.WriteTo(b, pc.targetAddr) 294 | } 295 | 296 | // Writes `payload` to `conn` and reads it into `buf`, which we take as a parameter to avoid 297 | // reallocations in benchmarks and memory profiles. Fails the test if the read payload does not match. 298 | func expectEchoPayload(conn io.ReadWriter, payload, buf []byte, t testing.TB) { 299 | _, err := conn.Write(payload) 300 | if err != nil { 301 | t.Fatalf("Failed to write payload: %v", err) 302 | } 303 | n, err := conn.Read(buf) 304 | if err != nil { 305 | t.Fatalf("Failed to read payload: %v", err) 306 | } 307 | if !bytes.Equal(payload, buf[:n]) { 308 | t.Fatalf("Expected output '%v'. Got '%v'", payload, buf[:n]) 309 | } 310 | } 311 | 312 | // Sends UDP packets into a black hole as fast as possible, in order to 313 | // benchmark the CPU and memory cost of encrypting and sending UDP packes. 314 | func BenchmarkShadowsocksClient_UDPWrite(b *testing.B) { 315 | d, err := NewClient("192.0.2.1:1", ss.TestCipher, testPassword, nil) 316 | if err != nil { 317 | b.Fatalf("Failed to create ShadowsocksClient: %v", err) 318 | } 319 | conn, err := d.ListenUDP(nil) 320 | if err != nil { 321 | b.Fatalf("ShadowsocksClient.ListenUDP failed: %v", err) 322 | } 323 | defer conn.Close() 324 | payload := ss.MakeTestPayload(1024) 325 | destAddr := &net.UDPAddr{ 326 | IP: net.ParseIP("192.0.2.2"), 327 | Port: 1, 328 | } 329 | b.ResetTimer() 330 | for n := 0; n < b.N; n++ { 331 | conn.WriteTo(payload, destAddr) 332 | } 333 | } 334 | -------------------------------------------------------------------------------- /client/logger.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import "go.uber.org/zap" 4 | 5 | // logger is the shared logger instance used by this package. 6 | // This variable must be assigned before calling any functions in this package. 7 | var logger *zap.Logger 8 | 9 | func SetLogger(l *zap.Logger) { 10 | logger = l 11 | } 12 | -------------------------------------------------------------------------------- /client/nat.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "errors" 5 | "net" 6 | "os" 7 | "sync" 8 | "time" 9 | 10 | onet "github.com/Shadowsocks-NET/outline-ss-server/net" 11 | "github.com/Shadowsocks-NET/outline-ss-server/service" 12 | "go.uber.org/zap" 13 | ) 14 | 15 | type natconn struct { 16 | // Stores reference to proxy conn. 17 | proxyConn ShadowsocksPacketConn 18 | 19 | // NAT timeout to apply for non-DNS packets. 20 | defaultTimeout time.Duration 21 | 22 | // Current read deadline of PacketConn. Used to avoid decreasing the 23 | // deadline. Initially zero. 24 | readDeadline time.Time 25 | 26 | // If the connection has only sent one DNS query, it will close 27 | // if it receives a DNS response. 28 | fastClose sync.Once 29 | 30 | // oobCache stores the interface index and IP where the requests are received from the client. 31 | // This is used to send UDP packets from the correct interface and IP. 32 | oobCache []byte 33 | 34 | // The fixed client address where the first packet of the session was received from. 35 | clientAddr *net.UDPAddr 36 | 37 | // The UDPConn where the last client packet was received from. 38 | clientConn onet.UDPPacketConn 39 | 40 | // packetAdapter translates packets between clientConn and proxyConn. 41 | packetAdapter PacketAdapter 42 | } 43 | 44 | func isDNS(addr *net.UDPAddr) bool { 45 | return addr != nil && addr.Port == 53 46 | } 47 | 48 | func (c *natconn) onWrite(addr *net.UDPAddr) { 49 | // Fast close is only allowed if there has been exactly one write, 50 | // and it was a DNS query. 51 | isDNS := isDNS(addr) 52 | isFirstWrite := c.readDeadline.IsZero() 53 | if !isDNS || !isFirstWrite { 54 | // Disable fast close. (Idempotent.) 55 | c.fastClose.Do(func() {}) 56 | } 57 | 58 | timeout := c.defaultTimeout 59 | if isDNS { 60 | // Shorten timeout as required by RFC 5452 Section 10. 61 | timeout = 17 * time.Second 62 | } 63 | 64 | newDeadline := time.Now().Add(timeout) 65 | if newDeadline.After(c.readDeadline) { 66 | c.readDeadline = newDeadline 67 | c.SetReadDeadline(newDeadline) 68 | } 69 | } 70 | 71 | func (c *natconn) onRead(addr *net.UDPAddr) { 72 | c.fastClose.Do(func() { 73 | if isDNS(addr) { 74 | // The next ReadFrom() should time out immediately. 75 | c.SetReadDeadline(time.Now()) 76 | } 77 | }) 78 | } 79 | 80 | func (c *natconn) WriteToZeroCopy(b []byte, start, length int, socksAddr []byte) (n int, err error) { 81 | c.onWrite(nil) 82 | return c.proxyConn.WriteToZeroCopy(b, start, length, socksAddr) 83 | } 84 | 85 | func (c *natconn) ReadFromZeroCopy(b []byte) (socksAddrStart, payloadStart, payloadLength int, err error) { 86 | socksAddrStart, payloadStart, payloadLength, err = c.proxyConn.ReadFromZeroCopy(b) 87 | if err == nil { 88 | c.onRead(nil) 89 | } 90 | return 91 | } 92 | 93 | func (c *natconn) SetReadDeadline(t time.Time) error { 94 | return c.proxyConn.SetReadDeadline(t) 95 | } 96 | 97 | func (c *natconn) LocalAddr() net.Addr { 98 | return c.proxyConn.LocalAddr() 99 | } 100 | 101 | func (c *natconn) RemoteAddr() net.Addr { 102 | return c.proxyConn.RemoteAddr() 103 | } 104 | 105 | func (c *natconn) Close() error { 106 | return c.proxyConn.Close() 107 | } 108 | 109 | // timedCopy copies from proxy to client until read timeout. 110 | func (c *natconn) timedCopy() { 111 | packetBuf := make([]byte, service.UDPPacketBufferSize) 112 | 113 | for { 114 | socksAddrStart, payloadStart, payloadLength, err := c.ReadFromZeroCopy(packetBuf) 115 | if err != nil { 116 | if errors.Is(err, os.ErrDeadlineExceeded) { 117 | return 118 | } 119 | logger.Warn("Failed to read from proxy", 120 | zap.Stringer("listenAddress", c.clientConn.LocalAddr()), 121 | zap.Stringer("clientAddress", c.clientAddr), 122 | zap.Stringer("proxyConnLocalAddress", c.proxyConn.LocalAddr()), 123 | zap.Stringer("proxyConnRemoteAddress", c.proxyConn.RemoteAddr()), 124 | zap.Error(err), 125 | ) 126 | continue 127 | } 128 | 129 | writeBuf, err := c.packetAdapter.EncapsulatePacket(packetBuf, socksAddrStart, payloadStart, payloadLength) 130 | if err != nil { 131 | logger.Warn("Failed to encapsulate decrypted proxy packet for sending on clientConn", 132 | zap.Stringer("listenAddress", c.clientConn.LocalAddr()), 133 | zap.Stringer("clientAddress", c.clientAddr), 134 | zap.Stringer("proxyConnLocalAddress", c.proxyConn.LocalAddr()), 135 | zap.Stringer("proxyConnRemoteAddress", c.proxyConn.RemoteAddr()), 136 | zap.Stringer("packetAdapter", c.packetAdapter), 137 | zap.Error(err), 138 | ) 139 | continue 140 | } 141 | 142 | _, _, err = c.clientConn.WriteMsgUDP(writeBuf, c.oobCache, c.clientAddr) 143 | if err != nil { 144 | logger.Warn("Failed to write to clientConn", 145 | zap.Stringer("listenAddress", c.clientConn.LocalAddr()), 146 | zap.Stringer("clientAddress", c.clientAddr), 147 | zap.Stringer("proxyConnLocalAddress", c.proxyConn.LocalAddr()), 148 | zap.Stringer("proxyConnRemoteAddress", c.proxyConn.RemoteAddr()), 149 | zap.Error(err), 150 | ) 151 | } 152 | } 153 | } 154 | 155 | // Packet NAT table 156 | type natmap struct { 157 | sync.RWMutex 158 | keyConn map[string]*natconn 159 | timeout time.Duration 160 | } 161 | 162 | func newNATmap(timeout time.Duration) *natmap { 163 | return &natmap{ 164 | keyConn: make(map[string]*natconn), 165 | timeout: timeout, 166 | } 167 | } 168 | 169 | func (m *natmap) GetByClientAddress(key string) *natconn { 170 | m.RLock() 171 | defer m.RUnlock() 172 | return m.keyConn[key] 173 | } 174 | 175 | func (m *natmap) Add(clientAddr *net.UDPAddr, clientConn onet.UDPPacketConn, proxyConn ShadowsocksPacketConn, packetAdapter PacketAdapter) *natconn { 176 | entry := &natconn{ 177 | proxyConn: proxyConn, 178 | defaultTimeout: m.timeout, 179 | clientAddr: clientAddr, 180 | clientConn: clientConn, 181 | packetAdapter: packetAdapter, 182 | } 183 | 184 | m.Lock() 185 | m.keyConn[clientAddr.String()] = entry 186 | m.Unlock() 187 | 188 | go func() { 189 | entry.timedCopy() 190 | entry.Close() 191 | 192 | m.Lock() 193 | delete(m.keyConn, clientAddr.String()) 194 | m.Unlock() 195 | }() 196 | 197 | return entry 198 | } 199 | 200 | func (m *natmap) Close() error { 201 | m.Lock() 202 | defer m.Unlock() 203 | 204 | var err error 205 | now := time.Now() 206 | for _, pc := range m.keyConn { 207 | if e := pc.SetReadDeadline(now); e != nil { 208 | err = e 209 | } 210 | } 211 | return err 212 | } 213 | -------------------------------------------------------------------------------- /client/service.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | // Service defines the management interface for client services. 4 | type Service interface { 5 | // String returns the service's name. 6 | // This method may be called on a nil pointer. 7 | String() string 8 | 9 | // Start starts the service. 10 | Start() error 11 | 12 | // Stop stops the service. 13 | Stop() error 14 | } 15 | -------------------------------------------------------------------------------- /client/tcp.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "context" 7 | "errors" 8 | "fmt" 9 | "io" 10 | "net" 11 | "net/http" 12 | 13 | onet "github.com/Shadowsocks-NET/outline-ss-server/net" 14 | "github.com/Shadowsocks-NET/outline-ss-server/socks" 15 | "github.com/database64128/tfo-go" 16 | "go.uber.org/zap" 17 | ) 18 | 19 | type TCPTunnel struct { 20 | listenAddress string 21 | listenerTFO bool 22 | dialerTFO bool 23 | 24 | client Client 25 | listener *net.TCPListener 26 | handshaker Handshaker 27 | } 28 | 29 | func (s *TCPTunnel) String() string { 30 | return fmt.Sprint("TCP ", s.handshaker.String(), " relay service") 31 | } 32 | 33 | func (s *TCPTunnel) Start() error { 34 | lc := tfo.ListenConfig{ 35 | DisableTFO: !s.listenerTFO, 36 | } 37 | l, err := lc.Listen(context.Background(), "tcp", s.listenAddress) 38 | if err != nil { 39 | return err 40 | } 41 | s.listener = l.(*net.TCPListener) 42 | 43 | go func() { 44 | defer s.listener.Close() 45 | 46 | for { 47 | clientConn, err := l.(*net.TCPListener).AcceptTCP() 48 | if err != nil { 49 | if errors.Is(err, net.ErrClosed) { 50 | return 51 | } 52 | logger.Warn("Failed to accept TCP connection", 53 | zap.Stringer("service", s), 54 | zap.String("listenAddress", s.listenAddress), 55 | zap.Error(err), 56 | ) 57 | continue 58 | } 59 | 60 | go s.handleConn(clientConn) 61 | } 62 | }() 63 | logger.Info("Started listener", zap.Stringer("service", s), zap.String("listenAddress", s.listenAddress)) 64 | return nil 65 | } 66 | 67 | func (s *TCPTunnel) handleConn(clientConn *net.TCPConn) { 68 | defer clientConn.Close() 69 | 70 | socksaddr, err := s.handshaker.Handshake(clientConn) 71 | if err != nil { 72 | logger.Warn("Failed to handshake with client", 73 | zap.Stringer("service", s), 74 | zap.Stringer("clientConnLocalAddress", clientConn.LocalAddr()), 75 | zap.Stringer("clientConnRemoteAddress", clientConn.RemoteAddr()), 76 | zap.Error(err), 77 | ) 78 | return 79 | } 80 | if socksaddr == nil { 81 | // Keep the connection open until EOF. 82 | // Example use case: SOCKS5 UDP ASSOCIATE command. 83 | logger.Debug("Keeping TCP connection open for UDP association", 84 | zap.Stringer("service", s), 85 | zap.Stringer("clientConnLocalAddress", clientConn.LocalAddr()), 86 | zap.Stringer("clientConnRemoteAddress", clientConn.RemoteAddr()), 87 | ) 88 | 89 | b := make([]byte, 1) 90 | _, err = io.ReadFull(clientConn, b) 91 | if err != nil && err != io.ErrUnexpectedEOF { 92 | logger.Warn("TCP connection for UDP association failed", 93 | zap.Stringer("service", s), 94 | zap.Stringer("clientConnLocalAddress", clientConn.LocalAddr()), 95 | zap.Stringer("clientConnRemoteAddress", clientConn.RemoteAddr()), 96 | zap.Error(err), 97 | ) 98 | } 99 | return 100 | } 101 | 102 | logger.Debug("Dialing target", 103 | zap.Stringer("service", s), 104 | zap.Stringer("clientConnLocalAddress", clientConn.LocalAddr()), 105 | zap.Stringer("clientConnRemoteAddress", clientConn.RemoteAddr()), 106 | zap.Stringer("targetAddress", socksaddr), 107 | zap.Bool("dialerTFO", s.dialerTFO), 108 | ) 109 | 110 | proxyConn, err := s.client.DialTCP(nil, socksaddr, s.dialerTFO) 111 | if err != nil { 112 | logger.Warn("Failed to dial target via proxy", 113 | zap.Stringer("service", s), 114 | zap.Stringer("clientConnLocalAddress", clientConn.LocalAddr()), 115 | zap.Stringer("clientConnRemoteAddress", clientConn.RemoteAddr()), 116 | zap.Stringer("targetAddress", socksaddr), 117 | zap.Error(err), 118 | ) 119 | return 120 | } 121 | defer proxyConn.Close() 122 | 123 | logger.Info("Relaying", 124 | zap.Stringer("service", s), 125 | zap.Stringer("clientConnRemoteAddress", clientConn.RemoteAddr()), 126 | zap.Stringer("proxyConnRemoteAddress", proxyConn.RemoteAddr()), 127 | zap.Stringer("targetAddress", socksaddr), 128 | ) 129 | 130 | err = relay(clientConn, proxyConn) 131 | if err != nil { 132 | logger.Warn("TCP relay failed", 133 | zap.Stringer("service", s), 134 | zap.Stringer("clientConnLocalAddress", clientConn.LocalAddr()), 135 | zap.Stringer("clientConnRemoteAddress", clientConn.RemoteAddr()), 136 | zap.Stringer("proxyConnLocalAddress", proxyConn.LocalAddr()), 137 | zap.Stringer("proxyConnRemoteAddress", proxyConn.RemoteAddr()), 138 | zap.Error(err), 139 | ) 140 | } 141 | } 142 | 143 | func relay(leftConn, rightConn onet.DuplexConn) error { 144 | ch := make(chan error, 1) 145 | 146 | go func() { 147 | _, err := io.Copy(leftConn, rightConn) 148 | leftConn.CloseWrite() 149 | ch <- err 150 | }() 151 | 152 | _, err := io.Copy(rightConn, leftConn) 153 | rightConn.CloseWrite() 154 | 155 | innerErr := <-ch 156 | 157 | if err != nil { 158 | return err 159 | } 160 | if innerErr != nil { 161 | return innerErr 162 | } 163 | return nil 164 | } 165 | 166 | func (s *TCPTunnel) Stop() error { 167 | if s.listener != nil { 168 | return s.listener.Close() 169 | } 170 | return nil 171 | } 172 | 173 | // Handshaker handles the handshake with clientConn for TCPTunnel. 174 | // 175 | // An implementation of Handshaker must be thread-safe. 176 | // Handshake(1) may be called simultaneously from different goroutines. 177 | // 178 | // If both the returned socks address and error are nil, the connection is kept open until EOF. 179 | type Handshaker interface { 180 | String() string 181 | Handshake(*net.TCPConn) (socks.Addr, error) 182 | } 183 | 184 | // SimpleTunnelHandshaker simply tunnels traffic between clientConn and proxyConn. 185 | type SimpleTunnelHandshaker struct { 186 | remoteSocksAddr socks.Addr 187 | } 188 | 189 | func NewSimpleTunnelHandshaker(remoteSocksAddr socks.Addr) *SimpleTunnelHandshaker { 190 | return &SimpleTunnelHandshaker{ 191 | remoteSocksAddr: remoteSocksAddr, 192 | } 193 | } 194 | 195 | func (h *SimpleTunnelHandshaker) String() string { 196 | return "simple tunnel" 197 | } 198 | 199 | func (h *SimpleTunnelHandshaker) Handshake(_ *net.TCPConn) (socks.Addr, error) { 200 | return h.remoteSocksAddr, nil 201 | } 202 | 203 | // SimpleSocks5Handshaker is a minimal implementation of SOCKS5 server. 204 | // SOCKS5 spec: https://datatracker.ietf.org/doc/html/rfc1928 205 | type SimpleSocks5Handshaker struct { 206 | enableTCP bool 207 | enableUDP bool 208 | } 209 | 210 | func NewSimpleSocks5Handshaker(enableTCP, enableUDP bool) *SimpleSocks5Handshaker { 211 | return &SimpleSocks5Handshaker{ 212 | enableTCP: enableTCP, 213 | enableUDP: enableUDP, 214 | } 215 | } 216 | 217 | func (h *SimpleSocks5Handshaker) String() string { 218 | return "simple SOCKS5" 219 | } 220 | 221 | func (h *SimpleSocks5Handshaker) Handshake(conn *net.TCPConn) (socks.Addr, error) { 222 | buf := make([]byte, socks.MaxAddrLen) 223 | 224 | // Authenticate. 225 | // Read VER, NMETHODS. 226 | if _, err := io.ReadFull(conn, buf[:2]); err != nil { 227 | return nil, err 228 | } 229 | // Check VER. 230 | if buf[0] != 5 { 231 | return nil, fmt.Errorf("unsupported socks version %d", buf[0]) 232 | } 233 | // Check NMETHODS. 234 | nmethods := buf[1] 235 | if nmethods < 1 { 236 | return nil, fmt.Errorf("NMETHODS is %d", nmethods) 237 | } 238 | // Read METHODS. 239 | if _, err := io.ReadFull(conn, buf[:nmethods]); err != nil { 240 | return nil, err 241 | } 242 | // Check METHODS. 243 | if bytes.IndexByte(buf[:nmethods], 0) == -1 { 244 | return nil, fmt.Errorf("no supported authentication method, only 0 (no authentication) is supported") 245 | } 246 | // Write method selection message (VER METHOD). 247 | if _, err := conn.Write([]byte{5, 0}); err != nil { 248 | return nil, err 249 | } 250 | 251 | // Read request. 252 | // Read VER CMD RSV. 253 | if _, err := io.ReadFull(conn, buf[:3]); err != nil { 254 | return nil, err 255 | } 256 | // Check VER. 257 | if buf[0] != 5 { 258 | return nil, fmt.Errorf("unsupported socks version %d", buf[0]) 259 | } 260 | cmd := buf[1] 261 | // Read ATYP DST.ADDR DST.PORT. 262 | n, err := socks.ReadAddr(buf, conn) 263 | if err != nil { 264 | return nil, fmt.Errorf("failed to read socks address: %w", err) 265 | } 266 | addr := buf[:n] 267 | 268 | switch { 269 | case cmd == socks.CmdConnect && h.enableTCP: 270 | err = replyWithStatus(conn, socks.Succeeded) 271 | case cmd == socks.CmdUDPAssociate && h.enableUDP: 272 | bndAddr, err := socks.ParseAddr(conn.LocalAddr().String()) 273 | if err != nil { 274 | replyWithStatus(conn, socks.ErrAddressNotSupported) 275 | return nil, fmt.Errorf("failed to parse conn.LocalAddr().String(): %w", err) 276 | } 277 | _, err = conn.Write(append([]byte{5, 0, 0}, bndAddr...)) 278 | // Set addr to nil to indicate blocking. 279 | addr = nil 280 | default: 281 | err = replyWithStatus(conn, socks.ErrCommandNotSupported) 282 | } 283 | 284 | return addr, err 285 | } 286 | 287 | func replyWithStatus(conn *net.TCPConn, socks5err byte) error { 288 | _, err := conn.Write([]byte{5, socks5err, 0, 1, 0, 0, 0, 0, 0, 0}) 289 | return err 290 | } 291 | 292 | type SimpleHttpConnectHandshaker struct{} 293 | 294 | func (h *SimpleHttpConnectHandshaker) String() string { 295 | return "simple HTTP/1.1 CONNECT" 296 | } 297 | 298 | func (h *SimpleHttpConnectHandshaker) Handshake(conn *net.TCPConn) (socks.Addr, error) { 299 | br := bufio.NewReader(conn) 300 | req, err := http.ReadRequest(br) 301 | if err != nil { 302 | return nil, err 303 | } 304 | 305 | if req.Method != http.MethodConnect { 306 | if err := send418(conn); err != nil { 307 | return nil, err 308 | } 309 | return nil, fmt.Errorf("unsupported HTTP method %s", req.Method) 310 | } 311 | 312 | socksAddr, err := socks.ParseAddr(req.Host) 313 | if err != nil { 314 | if err := send418(conn); err != nil { 315 | return nil, err 316 | } 317 | return nil, fmt.Errorf("bad Host header %s", req.Host) 318 | } 319 | 320 | if _, err := fmt.Fprint(conn, "HTTP/1.1 200 Connection established\r\n\r\n"); err != nil { 321 | return nil, err 322 | } 323 | return socksAddr, nil 324 | } 325 | 326 | func send418(w io.Writer) error { 327 | if _, err := fmt.Fprint(w, "HTTP/1.1 418 I'm a teapot\r\n\r\n"); err != nil { 328 | return err 329 | } 330 | return nil 331 | } 332 | 333 | // ShadowsocksNoneHandshaker implements the 'none' mode of Shadowsocks. 334 | type ShadowsocksNoneHandshaker struct{} 335 | 336 | func (h *ShadowsocksNoneHandshaker) String() string { 337 | return "Shadowsocks none" 338 | } 339 | 340 | func (h *ShadowsocksNoneHandshaker) Handshake(conn *net.TCPConn) (socks.Addr, error) { 341 | return socks.AddrFromReader(conn) 342 | } 343 | 344 | func NewTCPSimpleTunnelService(tunnelListenAddress string, tunnelRemoteSocksAddr socks.Addr, listenerTFO, dialerTFO bool, client Client) Service { 345 | return &TCPTunnel{ 346 | listenAddress: tunnelListenAddress, 347 | listenerTFO: listenerTFO, 348 | dialerTFO: dialerTFO, 349 | client: client, 350 | handshaker: NewSimpleTunnelHandshaker(tunnelRemoteSocksAddr), 351 | } 352 | } 353 | 354 | func NewTCPSimpleSocks5Service(socks5ListenAddress string, enableTCP, enableUDP, listenerTFO, dialerTFO bool, client Client) Service { 355 | return &TCPTunnel{ 356 | listenAddress: socks5ListenAddress, 357 | listenerTFO: listenerTFO, 358 | dialerTFO: dialerTFO, 359 | client: client, 360 | handshaker: NewSimpleSocks5Handshaker(enableTCP, enableUDP), 361 | } 362 | } 363 | 364 | func NewTCPSimpleHttpConnectService(httpListenAddress string, listenerTFO, dialerTFO bool, client Client) Service { 365 | return &TCPTunnel{ 366 | listenAddress: httpListenAddress, 367 | listenerTFO: listenerTFO, 368 | dialerTFO: dialerTFO, 369 | client: client, 370 | handshaker: &SimpleHttpConnectHandshaker{}, 371 | } 372 | } 373 | 374 | func NewTCPShadowsocksNoneService(ssNoneListenAddress string, listenerTFO, dialerTFO bool, client Client) Service { 375 | return &TCPTunnel{ 376 | listenAddress: ssNoneListenAddress, 377 | listenerTFO: listenerTFO, 378 | dialerTFO: dialerTFO, 379 | client: client, 380 | handshaker: &ShadowsocksNoneHandshaker{}, 381 | } 382 | } 383 | -------------------------------------------------------------------------------- /client/udp.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "net" 7 | "time" 8 | 9 | onet "github.com/Shadowsocks-NET/outline-ss-server/net" 10 | "github.com/Shadowsocks-NET/outline-ss-server/service" 11 | ss "github.com/Shadowsocks-NET/outline-ss-server/shadowsocks" 12 | "github.com/Shadowsocks-NET/outline-ss-server/socks" 13 | "go.uber.org/zap" 14 | ) 15 | 16 | type UDPTunnel struct { 17 | listenAddress string 18 | natTimeout time.Duration 19 | 20 | client Client 21 | conn *net.UDPConn 22 | packetAdapter PacketAdapter 23 | } 24 | 25 | func (s *UDPTunnel) String() string { 26 | return fmt.Sprint("UDP ", s.packetAdapter.String(), " relay service") 27 | } 28 | 29 | func (s *UDPTunnel) Start() error { 30 | conn, err, serr := onet.ListenUDP("udp", s.listenAddress, 0) 31 | if err != nil { 32 | return err 33 | } 34 | if serr != nil { 35 | logger.Warn("Failed to set IP_PKTINFO, IPV6_RECVPKTINFO socket options", 36 | zap.Stringer("service", s), 37 | zap.String("listenAddress", s.listenAddress), 38 | zap.Error(serr), 39 | ) 40 | } 41 | s.conn = conn 42 | 43 | go func() { 44 | defer s.conn.Close() 45 | 46 | nm := newNATmap(s.natTimeout) 47 | defer nm.Close() 48 | 49 | packetBuf := make([]byte, service.UDPPacketBufferSize) 50 | oobBuf := make([]byte, onet.UDPOOBBufferSize) 51 | 52 | for { 53 | n, oobn, flags, clientAddr, err := s.conn.ReadMsgUDP(packetBuf[ShadowsocksPacketConnFrontReserve:], oobBuf) 54 | if err != nil { 55 | if errors.Is(err, net.ErrClosed) { 56 | break 57 | } 58 | logger.Warn("Failed to read from UDPConn", 59 | zap.Stringer("service", s), 60 | zap.String("listenAddress", s.listenAddress), 61 | zap.Error(err), 62 | ) 63 | continue 64 | } 65 | err = onet.ParseFlagsForError(flags) 66 | if err != nil { 67 | logger.Warn("Failed to read from UDPConn", 68 | zap.Stringer("service", s), 69 | zap.String("listenAddress", s.listenAddress), 70 | zap.Error(err), 71 | ) 72 | continue 73 | } 74 | 75 | payloadStart, payloadLength, detachedSocksAddr, err := s.packetAdapter.ParsePacket(packetBuf, ShadowsocksPacketConnFrontReserve, n) 76 | if err != nil { 77 | logger.Warn("Failed to parse client packet", 78 | zap.Stringer("service", s), 79 | zap.String("listenAddress", s.listenAddress), 80 | zap.Stringer("clientAddress", clientAddr), 81 | zap.Error(err), 82 | ) 83 | continue 84 | } 85 | 86 | proxyConn := nm.GetByClientAddress(clientAddr.String()) 87 | if proxyConn == nil { 88 | logger.Info("New UDP session", 89 | zap.Stringer("service", s), 90 | zap.String("listenAddress", s.listenAddress), 91 | zap.Stringer("clientAddress", clientAddr), 92 | ) 93 | 94 | spc, err := s.client.ListenUDP(nil) 95 | if err != nil { 96 | logger.Warn("Failed to open UDP proxy session", 97 | zap.Stringer("service", s), 98 | zap.String("listenAddress", s.listenAddress), 99 | zap.Stringer("clientAddress", clientAddr), 100 | zap.Error(err), 101 | ) 102 | continue 103 | } 104 | 105 | proxyConn = nm.Add(clientAddr, s.conn, spc, s.packetAdapter) 106 | } else { 107 | logger.Debug("found UDP session in NAT table", 108 | zap.Stringer("service", s), 109 | zap.String("listenAddress", s.listenAddress), 110 | zap.Stringer("clientAddress", clientAddr), 111 | zap.Stringer("proxyConnLocalAddress", proxyConn.LocalAddr()), 112 | zap.Stringer("proxyConnRemoteAddress", proxyConn.RemoteAddr()), 113 | zap.Duration("defaultTimeout", proxyConn.defaultTimeout), 114 | zap.Time("readDeadline", proxyConn.readDeadline), 115 | ) 116 | } 117 | 118 | oob := oobBuf[:oobn] 119 | proxyConn.oobCache, err = onet.UpdateOobCache(proxyConn.oobCache, oob, logger) 120 | if err != nil { 121 | logger.Debug("Failed to process OOB", 122 | zap.Stringer("service", s), 123 | zap.String("listenAddress", s.listenAddress), 124 | zap.Stringer("clientAddress", clientAddr), 125 | zap.Error(err), 126 | ) 127 | } 128 | 129 | _, err = proxyConn.WriteToZeroCopy(packetBuf, payloadStart, payloadLength, detachedSocksAddr) 130 | if err != nil { 131 | logger.Warn("Failed to relay packet", 132 | zap.Stringer("service", s), 133 | zap.String("listenAddress", s.listenAddress), 134 | zap.Stringer("clientAddress", clientAddr), 135 | zap.Stringer("proxyConnLocalAddress", proxyConn.LocalAddr()), 136 | zap.Stringer("proxyConnRemoteAddress", proxyConn.RemoteAddr()), 137 | zap.Error(err), 138 | ) 139 | } 140 | } 141 | }() 142 | logger.Info("Started listener", zap.Stringer("service", s), zap.String("listenAddress", s.listenAddress)) 143 | return nil 144 | } 145 | 146 | func (s *UDPTunnel) Stop() error { 147 | if s.conn != nil { 148 | return s.conn.Close() 149 | } 150 | return nil 151 | } 152 | 153 | // PacketAdapter translates packets between a local interface and the proxy interface. 154 | type PacketAdapter interface { 155 | String() string 156 | 157 | // ParsePacket parses an incoming packet and returns payload start index, payload length, 158 | // a detached socks address (if applicable), or an error. 159 | // 160 | // The detached socks address is only returned when the payload does not start with a socks address. 161 | ParsePacket(pkt []byte, start, length int) (payloadStart, payloadLength int, detachedSocksAddr []byte, err error) 162 | 163 | // EncapsulatePacket encapsulates the decrypted packet from proxy 164 | // into a new form so it's ready to be sent on the local interface. 165 | // The encapsulation must not extend beyond the range of the full decrypted packet. 166 | EncapsulatePacket(decryptedFullPacket []byte, socksAddrStart, payloadStart, payloadLength int) (pkt []byte, err error) 167 | } 168 | 169 | // SimpleTunnelPacketAdapter simply relays packets between clientConn and proxyConn. 170 | type SimpleTunnelPacketAdapter struct { 171 | remoteSocksAddr socks.Addr 172 | } 173 | 174 | func NewSimpleTunnelPacketAdapter(remoteSocksAddr socks.Addr) *SimpleTunnelPacketAdapter { 175 | return &SimpleTunnelPacketAdapter{ 176 | remoteSocksAddr: remoteSocksAddr, 177 | } 178 | } 179 | 180 | func (p *SimpleTunnelPacketAdapter) String() string { 181 | return "simple tunnel" 182 | } 183 | 184 | func (p *SimpleTunnelPacketAdapter) ParsePacket(_ []byte, start, length int) (payloadStart, payloadLength int, detachedSocksAddr []byte, err error) { 185 | return start, length, p.remoteSocksAddr, nil 186 | } 187 | 188 | func (p *SimpleTunnelPacketAdapter) EncapsulatePacket(decryptedFullPacket []byte, _, payloadStart, payloadLength int) (pkt []byte, err error) { 189 | return decryptedFullPacket[payloadStart : payloadStart+payloadLength], nil 190 | } 191 | 192 | // SimpleSocks5PacketAdapter is a minimal implementation of SOCKS5 UDP server. 193 | // It unconditionally accepts SOCKS5 UDP packets, no matter a corresponding UDP association exists or not. 194 | type SimpleSocks5PacketAdapter struct{} 195 | 196 | func (p *SimpleSocks5PacketAdapter) String() string { 197 | return "simple SOCKS5" 198 | } 199 | 200 | func (p *SimpleSocks5PacketAdapter) ParsePacket(pkt []byte, start, length int) (payloadStart, payloadLength int, detachedSocksAddr []byte, err error) { 201 | payloadStart = start + 3 202 | if len(pkt) <= payloadStart { 203 | return 0, 0, nil, ss.ErrShortPacket 204 | } 205 | 206 | // Validate RSV FRAG. 207 | if pkt[start] != 0 || pkt[start+1] != 0 || pkt[start+2] != 0 { 208 | return 0, 0, nil, fmt.Errorf("unexpected RSV FRAG: %v, RSV must be 0, fragmentation is not supported", pkt[start:start+3]) 209 | } 210 | 211 | // Validate socks address. 212 | _, err = socks.SplitAddr(pkt[payloadStart:]) 213 | if err != nil { 214 | return 0, 0, nil, fmt.Errorf("socks address validation failed: %w", err) 215 | } 216 | 217 | payloadLength = length 218 | return 219 | } 220 | 221 | func (p *SimpleSocks5PacketAdapter) EncapsulatePacket(decryptedFullPacket []byte, socksAddrStart, payloadStart, payloadLength int) (pkt []byte, err error) { 222 | start := socksAddrStart - 3 223 | // RSV 224 | decryptedFullPacket[start] = 0 225 | decryptedFullPacket[start+1] = 0 226 | // FRAG 227 | decryptedFullPacket[start+2] = 0 228 | return decryptedFullPacket[start : payloadStart+payloadLength], nil 229 | } 230 | 231 | // ShadowsocksNonePacketAdapter implements the 'none' mode of Shadowsocks. 232 | type ShadowsocksNonePacketAdapter struct{} 233 | 234 | func (p *ShadowsocksNonePacketAdapter) String() string { 235 | return "Shadowsocks none" 236 | } 237 | 238 | func (p *ShadowsocksNonePacketAdapter) ParsePacket(pkt []byte, start, length int) (payloadStart, payloadLength int, detachedSocksAddr []byte, err error) { 239 | // Validate socks address. 240 | _, err = socks.SplitAddr(pkt[start:]) 241 | if err != nil { 242 | return 0, 0, nil, fmt.Errorf("socks address validation failed: %w", err) 243 | } 244 | 245 | payloadStart = start 246 | payloadLength = length 247 | return 248 | } 249 | 250 | func (p *ShadowsocksNonePacketAdapter) EncapsulatePacket(decryptedFullPacket []byte, socksAddrStart, payloadStart, payloadLength int) (pkt []byte, err error) { 251 | return decryptedFullPacket[socksAddrStart : payloadStart+payloadLength], nil 252 | } 253 | 254 | func NewUDPSimpleTunnelService(tunnelListenAddress string, tunnelRemoteSocksAddr socks.Addr, natTimeout time.Duration, client Client) Service { 255 | return &UDPTunnel{ 256 | listenAddress: tunnelListenAddress, 257 | natTimeout: natTimeout, 258 | client: client, 259 | packetAdapter: NewSimpleTunnelPacketAdapter(tunnelRemoteSocksAddr), 260 | } 261 | } 262 | 263 | func NewUDPSimpleSocks5Service(socks5ListenAddress string, natTimeout time.Duration, client Client) Service { 264 | return &UDPTunnel{ 265 | listenAddress: socks5ListenAddress, 266 | natTimeout: natTimeout, 267 | client: client, 268 | packetAdapter: &SimpleSocks5PacketAdapter{}, 269 | } 270 | } 271 | 272 | func NewUDPShadowsocksNoneService(ssNoneListenAddress string, natTimeout time.Duration, client Client) Service { 273 | return &UDPTunnel{ 274 | listenAddress: ssNoneListenAddress, 275 | natTimeout: natTimeout, 276 | client: client, 277 | packetAdapter: &ShadowsocksNonePacketAdapter{}, 278 | } 279 | } 280 | -------------------------------------------------------------------------------- /cmd/outline-ss-client/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "log" 6 | "os" 7 | "os/signal" 8 | "syscall" 9 | "time" 10 | 11 | "github.com/Shadowsocks-NET/outline-ss-server/client" 12 | "github.com/Shadowsocks-NET/outline-ss-server/logging" 13 | "github.com/Shadowsocks-NET/outline-ss-server/service" 14 | "github.com/Shadowsocks-NET/outline-ss-server/socks" 15 | "go.uber.org/zap" 16 | ) 17 | 18 | const ( 19 | // A UDP NAT timeout of at least 5 minutes is recommended in RFC 4787 Section 4.3. 20 | defaultNatTimeout time.Duration = 5 * time.Minute 21 | ) 22 | 23 | func main() { 24 | var address string 25 | var method string 26 | var psk string 27 | 28 | var tunnelListenAddress string 29 | var tunnelRemoteAddress string 30 | var tunnelTCP bool 31 | var tunnelUDP bool 32 | 33 | var socks5ListenAddress string 34 | var socks5EnableTCP bool 35 | var socks5EnableUDP bool 36 | 37 | var ssNoneListenAddress string 38 | var ssNoneEnableTCP bool 39 | var ssNoneEnableUDP bool 40 | 41 | var httpListenAddress string 42 | var httpEnable bool 43 | 44 | var TCPFastOpen bool 45 | var listenerTFO bool 46 | var dialerTFO bool 47 | 48 | var natTimeout time.Duration 49 | 50 | var suppressTimestamps bool 51 | var logLevel string 52 | 53 | flag.StringVar(&address, "address", "", "shadowsocks server address host:port") 54 | flag.StringVar(&method, "method", "2022-blake3-aes-256-gcm", "shadowsocks server method") 55 | flag.StringVar(&psk, "psk", "", "shadowsocks server pre-shared key") 56 | 57 | flag.StringVar(&tunnelListenAddress, "tunnelListenAddress", "", "shadowsocks tunnel local listen address") 58 | flag.StringVar(&tunnelRemoteAddress, "tunnelRemoteAddress", "", "shadowsocks tunnel remote address") 59 | flag.BoolVar(&tunnelTCP, "tunnelTCP", false, "Whether to tunnel TCP traffic") 60 | flag.BoolVar(&tunnelUDP, "tunnelUDP", false, "Whether to tunnel UDP traffic") 61 | 62 | flag.StringVar(&socks5ListenAddress, "socks5ListenAddress", "", "SOCKS5 proxy listen address") 63 | flag.BoolVar(&socks5EnableTCP, "socks5EnableTCP", false, "Enables SOCKS5 TCP proxy") 64 | flag.BoolVar(&socks5EnableUDP, "socks5EnableUDP", false, "Enables SOCKS5 UDP proxy") 65 | 66 | flag.StringVar(&ssNoneListenAddress, "ssNoneListenAddress", "", "Shadowsocks None proxy listen address") 67 | flag.BoolVar(&ssNoneEnableTCP, "ssNoneEnableTCP", false, "Enables Shadowsocks None TCP proxy") 68 | flag.BoolVar(&ssNoneEnableUDP, "ssNoneEnableUDP", false, "Enables Shadowsocks None UDP proxy") 69 | 70 | flag.StringVar(&httpListenAddress, "httpListenAddress", "", "HTTP/1.1 CONNECT proxy listen address") 71 | flag.BoolVar(&httpEnable, "httpEnable", false, "Enables HTTP/1.1 CONNECT proxy") 72 | 73 | flag.BoolVar(&TCPFastOpen, "tfo", false, "Enables TFO for both TCP listener and dialer") 74 | flag.BoolVar(&listenerTFO, "tfoListener", false, "Enables TFO for TCP listener") 75 | flag.BoolVar(&dialerTFO, "tfoDialer", false, "Enables TFO for TCP dialer") 76 | 77 | flag.DurationVar(&natTimeout, "natTimeout", defaultNatTimeout, "UDP NAT timeout") 78 | 79 | flag.BoolVar(&suppressTimestamps, "suppressTimestamps", false, "Omit timestamps in logs") 80 | flag.StringVar(&logLevel, "logLevel", "info", "Set custom log level. Available levels: debug, info, warn, error, dpanic, panic, fatal") 81 | 82 | flag.Parse() 83 | 84 | if TCPFastOpen { 85 | listenerTFO = true 86 | dialerTFO = true 87 | } 88 | 89 | if suppressTimestamps { 90 | log.SetFlags(log.Flags() &^ (log.Ldate | log.Ltime)) 91 | } 92 | 93 | logger, err := logging.NewProductionConsole(suppressTimestamps, logLevel) 94 | if err != nil { 95 | log.Fatal(err) 96 | } 97 | defer logger.Sync() 98 | client.SetLogger(logger) 99 | 100 | saltPool := service.NewSaltPool() 101 | 102 | c, err := client.NewClient(address, method, psk, saltPool) 103 | if err != nil { 104 | logger.Fatal("Failed to create Shadowsocks client", zap.Error(err)) 105 | } 106 | 107 | var services []client.Service 108 | 109 | var tunnelRemoteSocksAddr socks.Addr 110 | if tunnelRemoteAddress != "" { 111 | tunnelRemoteSocksAddr, err = socks.ParseAddr(tunnelRemoteAddress) 112 | if err != nil { 113 | logger.Fatal("Failed to parse tunnel remote address", 114 | zap.String("tunnelRemoteAddress", tunnelRemoteAddress), 115 | zap.Error(err), 116 | ) 117 | } 118 | } 119 | 120 | if tunnelTCP { 121 | s := client.NewTCPSimpleTunnelService(tunnelListenAddress, tunnelRemoteSocksAddr, listenerTFO, dialerTFO, c) 122 | err = s.Start() 123 | if err != nil { 124 | logger.Fatal("Failed to start service", 125 | zap.Stringer("service", s), 126 | zap.Error(err), 127 | ) 128 | } 129 | services = append(services, s) 130 | } 131 | 132 | if tunnelUDP { 133 | s := client.NewUDPSimpleTunnelService(tunnelListenAddress, tunnelRemoteSocksAddr, natTimeout, c) 134 | err = s.Start() 135 | if err != nil { 136 | logger.Fatal("Failed to start service", 137 | zap.Stringer("service", s), 138 | zap.Error(err), 139 | ) 140 | } 141 | services = append(services, s) 142 | } 143 | 144 | if socks5EnableTCP { 145 | s := client.NewTCPSimpleSocks5Service(socks5ListenAddress, socks5EnableTCP, socks5EnableUDP, listenerTFO, dialerTFO, c) 146 | err = s.Start() 147 | if err != nil { 148 | logger.Fatal("Failed to start service", 149 | zap.Stringer("service", s), 150 | zap.Error(err), 151 | ) 152 | } 153 | services = append(services, s) 154 | } 155 | 156 | if socks5EnableUDP { 157 | s := client.NewUDPSimpleSocks5Service(socks5ListenAddress, natTimeout, c) 158 | err = s.Start() 159 | if err != nil { 160 | logger.Fatal("Failed to start service", 161 | zap.Stringer("service", s), 162 | zap.Error(err), 163 | ) 164 | } 165 | services = append(services, s) 166 | } 167 | 168 | if httpEnable { 169 | s := client.NewTCPSimpleHttpConnectService(httpListenAddress, listenerTFO, dialerTFO, c) 170 | err = s.Start() 171 | if err != nil { 172 | logger.Fatal("Failed to start service", 173 | zap.Stringer("service", s), 174 | zap.Error(err), 175 | ) 176 | } 177 | services = append(services, s) 178 | } 179 | 180 | if ssNoneEnableTCP { 181 | s := client.NewTCPShadowsocksNoneService(ssNoneListenAddress, listenerTFO, dialerTFO, c) 182 | err = s.Start() 183 | if err != nil { 184 | logger.Fatal("Failed to start service", 185 | zap.Stringer("service", s), 186 | zap.Error(err), 187 | ) 188 | } 189 | services = append(services, s) 190 | } 191 | 192 | if ssNoneEnableUDP { 193 | s := client.NewUDPShadowsocksNoneService(ssNoneListenAddress, natTimeout, c) 194 | err = s.Start() 195 | if err != nil { 196 | logger.Fatal("Failed to start service", 197 | zap.Stringer("service", s), 198 | zap.Error(err), 199 | ) 200 | } 201 | services = append(services, s) 202 | } 203 | 204 | sigCh := make(chan os.Signal, 1) 205 | signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM) 206 | sig := <-sigCh 207 | 208 | logger.Info("Received signal, stopping...", zap.Stringer("signal", sig)) 209 | 210 | for _, s := range services { 211 | s.Stop() 212 | logger.Info("Stopped service", zap.Stringer("service", s)) 213 | } 214 | } 215 | -------------------------------------------------------------------------------- /cmd/outline-ss-server/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "container/list" 5 | "context" 6 | "flag" 7 | "fmt" 8 | "log" 9 | "net" 10 | "net/http" 11 | "os" 12 | "os/signal" 13 | "syscall" 14 | "time" 15 | 16 | "github.com/Shadowsocks-NET/outline-ss-server/logging" 17 | onet "github.com/Shadowsocks-NET/outline-ss-server/net" 18 | "github.com/Shadowsocks-NET/outline-ss-server/service" 19 | "github.com/Shadowsocks-NET/outline-ss-server/service/metrics" 20 | ss "github.com/Shadowsocks-NET/outline-ss-server/shadowsocks" 21 | "github.com/database64128/tfo-go" 22 | "github.com/oschwald/geoip2-golang" 23 | "github.com/prometheus/client_golang/prometheus" 24 | "github.com/prometheus/client_golang/prometheus/promhttp" 25 | "go.uber.org/zap" 26 | "gopkg.in/yaml.v3" 27 | ) 28 | 29 | var ( 30 | version = "dev" 31 | logger *zap.Logger 32 | ) 33 | 34 | const ( 35 | // 59 seconds is most common timeout for servers that do not respond to invalid requests 36 | //TODO: Consider removing this since we now use a random timeout. 37 | tcpReadTimeout time.Duration = 59 * time.Second 38 | 39 | // A UDP NAT timeout of at least 5 minutes is recommended in RFC 4787 Section 4.3. 40 | defaultNatTimeout time.Duration = 5 * time.Minute 41 | ) 42 | 43 | type ssPort struct { 44 | tcpService service.TCPService 45 | udpService service.UDPService 46 | cipherList service.CipherList 47 | } 48 | 49 | type SSServer struct { 50 | natTimeout time.Duration 51 | m metrics.ShadowsocksMetrics 52 | replayCache service.ReplayCache 53 | saltPool *service.SaltPool 54 | ports map[int]*ssPort 55 | blockPrivateNet bool 56 | listenerTFO bool 57 | dialerTFO bool 58 | udpPreferIPv6 bool 59 | } 60 | 61 | func (s *SSServer) startPort(portNum int) (err error) { 62 | lc := tfo.ListenConfig{ 63 | DisableTFO: !s.listenerTFO, 64 | } 65 | listener, err := lc.Listen(context.Background(), "tcp", fmt.Sprintf(":%d", portNum)) 66 | if err != nil { 67 | return fmt.Errorf("failed to start TCP on port %v: %v", portNum, err) 68 | } 69 | udpPacketConn, err, serr := onet.ListenUDP("udp", fmt.Sprintf(":%d", portNum), 0) 70 | if err != nil { 71 | return fmt.Errorf("failed to start UDP on port %v: %v", portNum, err) 72 | } 73 | if serr != nil { 74 | logger.Warn("Failed to set IP_PKTINFO, IPV6_RECVPKTINFO socket options", 75 | zap.Error(serr), 76 | ) 77 | } 78 | logger.Info("Started TCP and UDP listeners", zap.Int("port", portNum)) 79 | port := &ssPort{cipherList: service.NewCipherList()} 80 | // TODO: Register initial data metrics at zero. 81 | port.tcpService = service.NewTCPService(port.cipherList, &s.replayCache, s.saltPool, s.m, tcpReadTimeout, s.dialerTFO) 82 | port.udpService = service.NewUDPService(s.natTimeout, port.cipherList, s.m, s.udpPreferIPv6) 83 | if s.blockPrivateNet { 84 | port.tcpService.SetTargetIPValidator(onet.RequirePublicIP) 85 | port.udpService.SetTargetIPValidator(onet.RequirePublicIP) 86 | } 87 | s.ports[portNum] = port 88 | go port.tcpService.Serve(listener.(*net.TCPListener)) 89 | go port.udpService.Serve(udpPacketConn) 90 | return nil 91 | } 92 | 93 | func (s *SSServer) removePort(portNum int) error { 94 | port, ok := s.ports[portNum] 95 | if !ok { 96 | return fmt.Errorf("port %v doesn't exist", portNum) 97 | } 98 | tcpErr := port.tcpService.Stop() 99 | udpErr := port.udpService.Stop() 100 | delete(s.ports, portNum) 101 | if tcpErr != nil { 102 | return fmt.Errorf("failed to close listener on %v: %v", portNum, tcpErr) 103 | } 104 | if udpErr != nil { 105 | return fmt.Errorf("failed to close packetConn on %v: %v", portNum, udpErr) 106 | } 107 | logger.Info("Stopped TCP and UDP listeners", zap.Int("port", portNum)) 108 | return nil 109 | } 110 | 111 | func (s *SSServer) loadConfig(filename string) error { 112 | config, err := readConfig(filename) 113 | if err != nil { 114 | return fmt.Errorf("failed to read config file %v: %v", filename, err) 115 | } 116 | 117 | portChanges := make(map[int]int) 118 | portCiphers := make(map[int]*list.List) // Values are *List of *CipherEntry. 119 | for _, keyConfig := range config.Keys { 120 | portChanges[keyConfig.Port] = 1 121 | cipherList, ok := portCiphers[keyConfig.Port] 122 | if !ok { 123 | cipherList = list.New() 124 | portCiphers[keyConfig.Port] = cipherList 125 | } 126 | cipher, err := ss.NewCipher(keyConfig.Cipher, keyConfig.Secret) 127 | if err != nil { 128 | return fmt.Errorf("failed to create cipher for key %v: %v", keyConfig.ID, err) 129 | } 130 | entry := service.MakeCipherEntry(keyConfig.ID, cipher, keyConfig.Secret) 131 | cipherList.PushBack(&entry) 132 | } 133 | for port := range s.ports { 134 | portChanges[port] = portChanges[port] - 1 135 | } 136 | for portNum, count := range portChanges { 137 | if count == -1 { 138 | if err := s.removePort(portNum); err != nil { 139 | return fmt.Errorf("failed to remove port %v: %v", portNum, err) 140 | } 141 | } else if count == +1 { 142 | if err := s.startPort(portNum); err != nil { 143 | return fmt.Errorf("failed to start port %v: %v", portNum, err) 144 | } 145 | } 146 | } 147 | for portNum, cipherList := range portCiphers { 148 | s.ports[portNum].cipherList.Update(cipherList) 149 | } 150 | logger.Info("Loaded access keys", zap.Int("keys", len(config.Keys))) 151 | s.m.SetNumAccessKeys(len(config.Keys), len(portCiphers)) 152 | return nil 153 | } 154 | 155 | // Stop serving on all ports. 156 | func (s *SSServer) Stop() error { 157 | for portNum := range s.ports { 158 | if err := s.removePort(portNum); err != nil { 159 | return err 160 | } 161 | } 162 | return nil 163 | } 164 | 165 | // RunSSServer starts a shadowsocks server running, and returns the server or an error. 166 | func RunSSServer(filename string, natTimeout time.Duration, sm metrics.ShadowsocksMetrics, replayHistory int, blockPrivateNet, listenerTFO, dialerTFO, udpPreferIPv6 bool) (*SSServer, error) { 167 | server := &SSServer{ 168 | natTimeout: natTimeout, 169 | m: sm, 170 | replayCache: service.NewReplayCache(replayHistory), 171 | saltPool: service.NewSaltPool(), 172 | ports: make(map[int]*ssPort), 173 | blockPrivateNet: blockPrivateNet, 174 | listenerTFO: listenerTFO, 175 | dialerTFO: dialerTFO, 176 | udpPreferIPv6: udpPreferIPv6, 177 | } 178 | err := server.loadConfig(filename) 179 | if err != nil { 180 | return nil, fmt.Errorf("failed to load config file %v: %v", filename, err) 181 | } 182 | sigHup := make(chan os.Signal, 1) 183 | signal.Notify(sigHup, syscall.SIGHUP) 184 | go func() { 185 | for range sigHup { 186 | logger.Info("Updating config") 187 | if err := server.loadConfig(filename); err != nil { 188 | logger.Error("Failed to reload config", zap.Error(err)) 189 | } 190 | } 191 | }() 192 | return server, nil 193 | } 194 | 195 | type Config struct { 196 | Keys []struct { 197 | ID string 198 | Port int 199 | Cipher string 200 | Secret string 201 | } 202 | } 203 | 204 | func readConfig(filename string) (*Config, error) { 205 | config := Config{} 206 | configData, err := os.ReadFile(filename) 207 | if err != nil { 208 | return nil, err 209 | } 210 | err = yaml.Unmarshal(configData, &config) 211 | return &config, err 212 | } 213 | 214 | func main() { 215 | var ( 216 | configFile string 217 | metricsAddr string 218 | ipCountryDbPath string 219 | natTimeout time.Duration 220 | replayHistory int 221 | blockPrivateNet bool 222 | tfo bool 223 | listenerTFO bool 224 | dialerTFO bool 225 | udpPreferIPv6 bool 226 | ver bool 227 | 228 | suppressTimestamps bool 229 | logLevel string 230 | 231 | err error 232 | ) 233 | 234 | flag.StringVar(&configFile, "config", "", "Configuration filename") 235 | flag.StringVar(&metricsAddr, "metrics", "", "Address for the Prometheus metrics") 236 | flag.StringVar(&ipCountryDbPath, "ip_country_db", "", "Path to the ip-to-country mmdb file") 237 | flag.DurationVar(&natTimeout, "udptimeout", defaultNatTimeout, "UDP tunnel timeout") 238 | flag.IntVar(&replayHistory, "replay_history", 0, "Replay buffer size (# of handshakes)") 239 | flag.BoolVar(&blockPrivateNet, "block_private_net", false, "Block access to private IP addresses") 240 | flag.BoolVar(&ver, "version", false, "The version of the server") 241 | 242 | flag.BoolVar(&tfo, "tfo", false, "Enables TFO for both TCP listener and dialer") 243 | flag.BoolVar(&listenerTFO, "tfoListener", false, "Enables TFO for TCP listener") 244 | flag.BoolVar(&dialerTFO, "tfoDialer", false, "Enables TFO for TCP dialer") 245 | 246 | flag.BoolVar(&udpPreferIPv6, "udpPreferIPv6", false, "Prefer IPv6 addresses when resolving domain names for UDP targets") 247 | 248 | flag.BoolVar(&suppressTimestamps, "suppressTimestamps", false, "Omit timestamps in logs") 249 | flag.StringVar(&logLevel, "logLevel", "info", "Set custom log level. Available levels: debug, info, warn, error, dpanic, panic, fatal") 250 | 251 | flag.Parse() 252 | 253 | if ver { 254 | fmt.Println(version) 255 | return 256 | } 257 | 258 | if configFile == "" { 259 | flag.Usage() 260 | return 261 | } 262 | 263 | if suppressTimestamps { 264 | log.SetFlags(log.Flags() &^ (log.Ldate | log.Ltime)) 265 | } 266 | 267 | logger, err = logging.NewProductionConsole(suppressTimestamps, logLevel) 268 | if err != nil { 269 | log.Fatal(err) 270 | } 271 | defer logger.Sync() 272 | service.SetLogger(logger) 273 | 274 | if metricsAddr != "" { 275 | http.Handle("/metrics", promhttp.Handler()) 276 | go func() { 277 | err := http.ListenAndServe(metricsAddr, nil) 278 | if err != nil { 279 | logger.Fatal("Failed to start metrics HTTP server", zap.Error(err)) 280 | } 281 | }() 282 | logger.Info(fmt.Sprintf("Started metrics http server at http://%s/metrics", metricsAddr)) 283 | } 284 | 285 | var ipCountryDB *geoip2.Reader 286 | if ipCountryDbPath != "" { 287 | ipCountryDB, err = geoip2.Open(ipCountryDbPath) 288 | if err != nil { 289 | logger.Fatal("Failed to open GeoIP database", 290 | zap.String("path", ipCountryDbPath), 291 | zap.Error(err), 292 | ) 293 | } 294 | logger.Info("Loaded GeoIP database from file", zap.String("path", ipCountryDbPath)) 295 | defer ipCountryDB.Close() 296 | } 297 | m := metrics.NewPrometheusShadowsocksMetrics(ipCountryDB, prometheus.DefaultRegisterer) 298 | m.SetBuildInfo(version) 299 | 300 | if tfo { 301 | listenerTFO = true 302 | dialerTFO = true 303 | } 304 | 305 | s, err := RunSSServer(configFile, natTimeout, m, replayHistory, blockPrivateNet, listenerTFO, dialerTFO, udpPreferIPv6) 306 | if err != nil { 307 | logger.Fatal("Failed to start Shadowsocks server", zap.Error(err)) 308 | } 309 | 310 | sigCh := make(chan os.Signal, 1) 311 | signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM) 312 | sig := <-sigCh 313 | 314 | logger.Info("Received signal, stopping...", zap.Stringer("signal", sig)) 315 | 316 | s.Stop() 317 | } 318 | -------------------------------------------------------------------------------- /config_example.yml: -------------------------------------------------------------------------------- 1 | keys: 2 | - id: user-0 3 | port: 9000 4 | cipher: chacha20-ietf-poly1305 5 | secret: Secret0 6 | 7 | - id: user-1 8 | port: 9000 9 | cipher: chacha20-ietf-poly1305 10 | secret: Secret1 11 | 12 | - id: user-2 13 | port: 9001 14 | cipher: chacha20-ietf-poly1305 15 | secret: Secret2 16 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/Shadowsocks-NET/outline-ss-server 2 | 3 | go 1.18 4 | 5 | require ( 6 | github.com/database64128/tfo-go v1.0.3 7 | github.com/oschwald/geoip2-golang v1.7.0 8 | github.com/prometheus/client_golang v1.12.2 9 | github.com/stretchr/testify v1.7.1 10 | go.uber.org/zap v1.21.0 11 | golang.org/x/crypto v0.0.0-20220331220935-ae2d96664a29 12 | golang.org/x/sys v0.0.0-20220405052023-b1e9470b6e64 13 | golang.zx2c4.com/wireguard v0.0.0-20220318042302-193cf8d6a5d6 14 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b 15 | lukechampine.com/blake3 v1.1.7 16 | ) 17 | 18 | require ( 19 | github.com/beorn7/perks v1.0.1 // indirect 20 | github.com/cespare/xxhash/v2 v2.1.2 // indirect 21 | github.com/davecgh/go-spew v1.1.1 // indirect 22 | github.com/golang/protobuf v1.5.2 // indirect 23 | github.com/klauspost/cpuid/v2 v2.0.12 // indirect 24 | github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect 25 | github.com/oschwald/maxminddb-golang v1.9.0 // indirect 26 | github.com/pmezard/go-difflib v1.0.0 // indirect 27 | github.com/prometheus/client_model v0.2.0 // indirect 28 | github.com/prometheus/common v0.33.0 // indirect 29 | github.com/prometheus/procfs v0.7.3 // indirect 30 | go.uber.org/atomic v1.9.0 // indirect 31 | go.uber.org/multierr v1.8.0 // indirect 32 | google.golang.org/protobuf v1.28.0 // indirect 33 | ) 34 | -------------------------------------------------------------------------------- /logging/zap.go: -------------------------------------------------------------------------------- 1 | package logging 2 | 3 | import ( 4 | "go.uber.org/zap" 5 | "go.uber.org/zap/zapcore" 6 | ) 7 | 8 | func NewProductionConsole(suppressTimestamps bool, logLevel string) (*zap.Logger, error) { 9 | config, err := NewProductionConsoleConfig(suppressTimestamps, logLevel) 10 | if err != nil { 11 | return nil, err 12 | } 13 | return config.Build() 14 | } 15 | 16 | // NewProductionConsoleConfig is a reasonable production logging configuration. 17 | // Logging is enabled at InfoLevel and above. 18 | // 19 | // It uses a console encoder, writes to standard error, and enables sampling. 20 | // Stacktraces are automatically included on logs of ErrorLevel and above. 21 | func NewProductionConsoleConfig(suppressTimestamps bool, logLevel string) (config zap.Config, err error) { 22 | var ( 23 | level zap.AtomicLevel 24 | development bool 25 | sampling *zap.SamplingConfig 26 | ) 27 | 28 | switch logLevel { 29 | case "", "info", "INFO": 30 | level = zap.NewAtomicLevelAt(zap.InfoLevel) 31 | default: 32 | level, err = zap.ParseAtomicLevel(logLevel) 33 | if err != nil { 34 | return 35 | } 36 | if level.Level() < zap.InfoLevel { 37 | // debug level 38 | development = true 39 | } else { 40 | // Enable sampling for non-debugging levels. 41 | sampling = &zap.SamplingConfig{ 42 | Initial: 100, 43 | Thereafter: 100, 44 | } 45 | } 46 | } 47 | 48 | return zap.Config{ 49 | Level: level, 50 | Development: development, 51 | Sampling: sampling, 52 | Encoding: "console", 53 | EncoderConfig: NewProductionConsoleEncoderConfig(suppressTimestamps), 54 | OutputPaths: []string{"stderr"}, 55 | ErrorOutputPaths: []string{"stderr"}, 56 | }, nil 57 | } 58 | 59 | // NewProductionConsoleEncoderConfig returns an opinionated EncoderConfig for 60 | // production console environments. 61 | func NewProductionConsoleEncoderConfig(suppressTimestamps bool) zapcore.EncoderConfig { 62 | var ( 63 | timeKey string 64 | encodeTime zapcore.TimeEncoder 65 | ) 66 | 67 | if !suppressTimestamps { 68 | timeKey = "T" 69 | encodeTime = zapcore.ISO8601TimeEncoder 70 | } 71 | 72 | return zapcore.EncoderConfig{ 73 | TimeKey: timeKey, 74 | LevelKey: "L", 75 | NameKey: "N", 76 | CallerKey: "C", 77 | FunctionKey: zapcore.OmitKey, 78 | MessageKey: "M", 79 | StacktraceKey: "S", 80 | LineEnding: zapcore.DefaultLineEnding, 81 | EncodeLevel: zapcore.CapitalLevelEncoder, 82 | EncodeTime: encodeTime, 83 | EncodeDuration: zapcore.StringDurationEncoder, 84 | EncodeCaller: zapcore.ShortCallerEncoder, 85 | ConsoleSeparator: " ", 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /net/net.go: -------------------------------------------------------------------------------- 1 | package net 2 | 3 | import ( 4 | "io" 5 | "net" 6 | ) 7 | 8 | // DuplexConn is a net.Conn that allows for closing only the reader or writer end of 9 | // it, supporting half-open state. 10 | type DuplexConn interface { 11 | net.Conn 12 | // Closes the Read end of the connection, allowing for the release of resources. 13 | // No more reads should happen. 14 | CloseRead() error 15 | // Closes the Write end of the connection. An EOF or FIN signal may be 16 | // sent to the connection target. 17 | CloseWrite() error 18 | } 19 | 20 | type duplexConnAdaptor struct { 21 | DuplexConn 22 | r io.Reader 23 | w io.Writer 24 | } 25 | 26 | func (dc *duplexConnAdaptor) Read(b []byte) (int, error) { 27 | return dc.r.Read(b) 28 | } 29 | 30 | func (dc *duplexConnAdaptor) WriteTo(w io.Writer) (int64, error) { 31 | return io.Copy(w, dc.r) 32 | } 33 | 34 | func (dc *duplexConnAdaptor) CloseRead() error { 35 | return dc.DuplexConn.CloseRead() 36 | } 37 | 38 | func (dc *duplexConnAdaptor) Write(b []byte) (int, error) { 39 | return dc.w.Write(b) 40 | } 41 | 42 | func (dc *duplexConnAdaptor) ReadFrom(r io.Reader) (int64, error) { 43 | return io.Copy(dc.w, r) 44 | } 45 | 46 | func (dc *duplexConnAdaptor) CloseWrite() error { 47 | return dc.DuplexConn.CloseWrite() 48 | } 49 | 50 | // WrapDuplexConn wraps an existing DuplexConn with new Reader and Writer, but 51 | // preserving the original CloseRead() and CloseWrite(). 52 | func WrapDuplexConn(c DuplexConn, r io.Reader, w io.Writer) DuplexConn { 53 | conn := c 54 | // We special-case duplexConnAdaptor to avoid multiple levels of nesting. 55 | if a, ok := c.(*duplexConnAdaptor); ok { 56 | conn = a.DuplexConn 57 | } 58 | return &duplexConnAdaptor{DuplexConn: conn, r: r, w: w} 59 | } 60 | 61 | func copyOneWay(leftConn, rightConn DuplexConn) (int64, error) { 62 | n, err := io.Copy(leftConn, rightConn) 63 | // Send FIN to indicate EOF 64 | leftConn.CloseWrite() 65 | // Release reader resources 66 | rightConn.CloseRead() 67 | return n, err 68 | } 69 | 70 | // Relay copies between left and right bidirectionally. Returns number of 71 | // bytes copied from right to left, from left to right, and any error occurred. 72 | // Relay allows for half-closed connections: if one side is done writing, it can 73 | // still read all remaining data from its peer. 74 | func Relay(leftConn, rightConn DuplexConn) (int64, int64, error) { 75 | type res struct { 76 | N int64 77 | Err error 78 | } 79 | ch := make(chan res) 80 | 81 | go func() { 82 | n, err := copyOneWay(rightConn, leftConn) 83 | ch <- res{n, err} 84 | }() 85 | 86 | n, err := copyOneWay(leftConn, rightConn) 87 | rs := <-ch 88 | 89 | if err == nil { 90 | err = rs.Err 91 | } 92 | return n, rs.N, err 93 | } 94 | 95 | type UDPPacketConn interface { 96 | net.PacketConn 97 | ReadFromUDP(b []byte) (n int, addr *net.UDPAddr, err error) 98 | WriteToUDP(b []byte, addr *net.UDPAddr) (int, error) 99 | ReadMsgUDP(b, oob []byte) (n, oobn, flags int, addr *net.UDPAddr, err error) 100 | WriteMsgUDP(b, oob []byte, addr *net.UDPAddr) (n, oobn int, err error) 101 | } 102 | 103 | type ConnectionError struct { 104 | // TODO: create status enums and move to metrics.go 105 | Status string 106 | Message string 107 | Cause error 108 | } 109 | 110 | func NewConnectionError(status, message string, cause error) *ConnectionError { 111 | return &ConnectionError{Status: status, Message: message, Cause: cause} 112 | } 113 | 114 | type addr struct { 115 | address string 116 | network string 117 | } 118 | 119 | func (a *addr) String() string { 120 | return a.address 121 | } 122 | 123 | func (a *addr) Network() string { 124 | return a.network 125 | } 126 | 127 | // NewAddr returns a net.Addr that holds an address of the form `host:port` with a domain name or IP as host. 128 | // Used for SOCKS addressing. 129 | func NewAddr(address, network string) net.Addr { 130 | return &addr{ 131 | address: address, 132 | network: network, 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /net/net_bsd.go: -------------------------------------------------------------------------------- 1 | //go:build darwin || freebsd 2 | 3 | package net 4 | 5 | import ( 6 | "context" 7 | "fmt" 8 | "net" 9 | "syscall" 10 | 11 | "golang.org/x/sys/unix" 12 | ) 13 | 14 | // ListenUDP wraps Go's net.ListenConfig.ListenPacket and sets socket options on supported platforms. 15 | // 16 | // On Linux and Windows, IP_PKTINFO and IPV6_RECVPKTINFO are set to 1; 17 | // IP_MTU_DISCOVER, IPV6_MTU_DISCOVER are set to IP_PMTUDISC_DO to disable IP fragmentation to encourage correct MTU settings. 18 | // 19 | // On Linux, SO_MARK is set to user-specified value. 20 | // 21 | // On macOS and FreeBSD, IP_DONTFRAG, IPV6_DONTFRAG are set to 1 (Don't Fragment). 22 | func ListenUDP(network string, laddr string, fwmark int) (conn *net.UDPConn, err error, serr error) { 23 | lc := &net.ListenConfig{ 24 | Control: func(network, address string, c syscall.RawConn) error { 25 | return c.Control(func(fd uintptr) { 26 | switch network { 27 | case "udp4": 28 | if err := unix.SetsockoptInt(int(fd), unix.IPPROTO_IP, unix.IP_DONTFRAG, 1); err != nil { 29 | serr = fmt.Errorf("failed to set socket option IP_DONTFRAG: %w", err) 30 | } 31 | case "udp6": 32 | if err := unix.SetsockoptInt(int(fd), unix.IPPROTO_IPV6, unix.IPV6_DONTFRAG, 1); err != nil { 33 | serr = fmt.Errorf("failed to set socket option IPV6_DONTFRAG: %w", err) 34 | } 35 | } 36 | }) 37 | }, 38 | } 39 | 40 | pconn, err := lc.ListenPacket(context.Background(), network, laddr) 41 | if err != nil { 42 | return 43 | } 44 | conn = pconn.(*net.UDPConn) 45 | return 46 | } 47 | -------------------------------------------------------------------------------- /net/net_default.go: -------------------------------------------------------------------------------- 1 | //go:build !linux && !windows && !darwin && !freebsd 2 | 3 | package net 4 | 5 | import ( 6 | "context" 7 | "net" 8 | ) 9 | 10 | // ListenUDP wraps Go's net.ListenConfig.ListenPacket and sets socket options on supported platforms. 11 | // 12 | // On Linux and Windows, IP_PKTINFO and IPV6_RECVPKTINFO are set to 1; 13 | // IP_MTU_DISCOVER, IPV6_MTU_DISCOVER are set to IP_PMTUDISC_DO to disable IP fragmentation to encourage correct MTU settings. 14 | // 15 | // On Linux, SO_MARK is set to user-specified value. 16 | // 17 | // On macOS and FreeBSD, IP_DONTFRAG, IPV6_DONTFRAG are set to 1 (Don't Fragment). 18 | func ListenUDP(network string, laddr string, fwmark int) (conn *net.UDPConn, err error, serr error) { 19 | var lc net.ListenConfig 20 | pconn, err := lc.ListenPacket(context.Background(), network, laddr) 21 | if err != nil { 22 | return 23 | } 24 | conn = pconn.(*net.UDPConn) 25 | return 26 | } 27 | -------------------------------------------------------------------------------- /net/net_linux.go: -------------------------------------------------------------------------------- 1 | package net 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "net" 7 | "syscall" 8 | "unsafe" 9 | 10 | "go.uber.org/zap" 11 | "golang.org/x/sys/unix" 12 | ) 13 | 14 | // ListenUDP wraps Go's net.ListenConfig.ListenPacket and sets socket options on supported platforms. 15 | // 16 | // On Linux and Windows, IP_PKTINFO and IPV6_RECVPKTINFO are set to 1; 17 | // IP_MTU_DISCOVER, IPV6_MTU_DISCOVER are set to IP_PMTUDISC_DO to disable IP fragmentation to encourage correct MTU settings. 18 | // 19 | // On Linux, SO_MARK is set to user-specified value. 20 | // 21 | // On macOS and FreeBSD, IP_DONTFRAG, IPV6_DONTFRAG are set to 1 (Don't Fragment). 22 | func ListenUDP(network string, laddr string, fwmark int) (conn *net.UDPConn, err error, serr error) { 23 | lc := &net.ListenConfig{ 24 | Control: func(network, address string, c syscall.RawConn) error { 25 | return c.Control(func(fd uintptr) { 26 | // Set IP_PKTINFO, IP_MTU_DISCOVER for both v4 and v6. 27 | if err := unix.SetsockoptInt(int(fd), unix.IPPROTO_IP, unix.IP_PKTINFO, 1); err != nil { 28 | serr = fmt.Errorf("failed to set socket option IP_PKTINFO: %w", err) 29 | } 30 | 31 | if err := unix.SetsockoptInt(int(fd), unix.IPPROTO_IP, unix.IP_MTU_DISCOVER, unix.IP_PMTUDISC_DO); err != nil { 32 | serr = fmt.Errorf("failed to set socket option IP_MTU_DISCOVER: %w", err) 33 | } 34 | 35 | if network == "udp6" { 36 | if err := unix.SetsockoptInt(int(fd), unix.IPPROTO_IPV6, unix.IPV6_RECVPKTINFO, 1); err != nil { 37 | serr = fmt.Errorf("failed to set socket option IPV6_RECVPKTINFO: %w", err) 38 | } 39 | 40 | if err := unix.SetsockoptInt(int(fd), unix.IPPROTO_IPV6, unix.IPV6_MTU_DISCOVER, unix.IP_PMTUDISC_DO); err != nil { 41 | serr = fmt.Errorf("failed to set socket option IPV6_MTU_DISCOVER: %w", err) 42 | } 43 | } 44 | 45 | if fwmark != 0 { 46 | if err := unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_MARK, fwmark); err != nil { 47 | serr = fmt.Errorf("failed to set socket option SO_MARK: %w", err) 48 | } 49 | } 50 | }) 51 | }, 52 | } 53 | 54 | pconn, err := lc.ListenPacket(context.Background(), network, laddr) 55 | if err != nil { 56 | return 57 | } 58 | conn = pconn.(*net.UDPConn) 59 | return 60 | } 61 | 62 | // UpdateOobCache filters out irrelevant OOB messages, saves 63 | // IP_PKTINFO or IPV6_PKTINFO socket control messages to the OOB cache, 64 | // and returns the updated OOB cache slice. 65 | // 66 | // IP_PKTINFO and IPV6_PKTINFO socket control messages are only supported 67 | // on Linux and Windows. 68 | // 69 | // The returned OOB cache is unchanged if no relevant control messages 70 | // are found. 71 | // 72 | // Errors returned by this function can be safely ignored, 73 | // or printed as debug logs. 74 | func UpdateOobCache(oobCache, oob []byte, logger *zap.Logger) ([]byte, error) { 75 | // Since we only set IP_PKTINFO and/or IPV6_PKTINFO, 76 | // Inet4Pktinfo or Inet6Pktinfo should be the first 77 | // and only socket control message returned. 78 | // Therefore we simplify the process by not looping 79 | // through the OOB data. 80 | if len(oob) < unix.SizeofCmsghdr { 81 | return oobCache, fmt.Errorf("oob length %d shorter than cmsghdr length", len(oob)) 82 | } 83 | 84 | cmsghdr := (*unix.Cmsghdr)(unsafe.Pointer(&oob[0])) 85 | 86 | switch { 87 | case cmsghdr.Level == unix.IPPROTO_IP && cmsghdr.Type == unix.IP_PKTINFO && len(oob) >= unix.SizeofCmsghdr+unix.SizeofInet4Pktinfo: 88 | pktinfo := (*unix.Inet4Pktinfo)(unsafe.Pointer(&oob[unix.SizeofCmsghdr])) 89 | // Clear destination address. 90 | pktinfo.Addr = [4]byte{} 91 | // logger.Debug("Matched Inet4Pktinfo", zap.Int32("ifindex", pktinfo.Ifindex)) 92 | 93 | case cmsghdr.Level == unix.IPPROTO_IPV6 && cmsghdr.Type == unix.IPV6_PKTINFO && len(oob) >= unix.SizeofCmsghdr+unix.SizeofInet6Pktinfo: 94 | // pktinfo := (*unix.Inet6Pktinfo)(unsafe.Pointer(&oob[unix.SizeofCmsghdr])) 95 | // logger.Debug("Matched Inet6Pktinfo", zap.Uint32("ifindex", pktinfo.Ifindex)) 96 | 97 | default: 98 | return oobCache, fmt.Errorf("unknown control message level %d type %d", cmsghdr.Level, cmsghdr.Type) 99 | } 100 | 101 | return append(oobCache[:0], oob...), nil 102 | } 103 | -------------------------------------------------------------------------------- /net/net_linuxwindows.go: -------------------------------------------------------------------------------- 1 | //go:build linux || windows 2 | 3 | package net 4 | 5 | // UDPOOBBufferSize specifies the size of buffer to allocate for receiving OOB data 6 | // when calling the ReadMsgUDP method on a *net.UDPConn returned by this package's ListenUDP function. 7 | const UDPOOBBufferSize = 128 8 | -------------------------------------------------------------------------------- /net/net_notlinuxwindows.go: -------------------------------------------------------------------------------- 1 | //go:build !linux && !windows 2 | 3 | package net 4 | 5 | import "go.uber.org/zap" 6 | 7 | // UDPOOBBufferSize specifies the size of buffer to allocate for receiving OOB data 8 | // when calling the ReadMsgUDP method on a *net.UDPConn returned by this package's ListenUDP function. 9 | const UDPOOBBufferSize = 0 10 | 11 | // UpdateOobCache filters out irrelevant OOB messages, saves 12 | // IP_PKTINFO or IPV6_PKTINFO socket control messages to the OOB cache, 13 | // and returns the updated OOB cache slice. 14 | // 15 | // IP_PKTINFO and IPV6_PKTINFO socket control messages are only supported 16 | // on Linux and Windows. 17 | // 18 | // The returned OOB cache is unchanged if no relevant control messages 19 | // are found. 20 | // 21 | // Errors returned by this function can be safely ignored, 22 | // or printed as debug logs. 23 | func UpdateOobCache(oobCache, oob []byte, logger *zap.Logger) ([]byte, error) { 24 | return nil, nil 25 | } 26 | -------------------------------------------------------------------------------- /net/net_notunix.go: -------------------------------------------------------------------------------- 1 | //go:build !linux && !darwin && !freebsd 2 | 3 | package net 4 | 5 | // ParseFlagsForError parses the message flags returned by 6 | // the ReadMsgUDPAddrPort method and returns an error if MSG_TRUNC 7 | // is set, indicating that the returned packet was truncated. 8 | // 9 | // The check is skipped on Windows, because an error (WSAEMSGSIZE) 10 | // is also returned when MSG_PARTIAL is set. 11 | func ParseFlagsForError(flags int) error { 12 | return nil 13 | } 14 | -------------------------------------------------------------------------------- /net/net_unix.go: -------------------------------------------------------------------------------- 1 | //go:build linux || darwin || freebsd 2 | 3 | package net 4 | 5 | import ( 6 | "errors" 7 | 8 | "golang.org/x/sys/unix" 9 | ) 10 | 11 | var ( 12 | ErrMessageTruncated = errors.New("the packet is larger than the supplied buffer") 13 | ErrControlMessageTruncated = errors.New("the control message is larger than the supplied buffer") 14 | ) 15 | 16 | // ParseFlagsForError parses the message flags returned by 17 | // the ReadMsgUDPAddrPort method and returns an error if MSG_TRUNC 18 | // is set, indicating that the returned packet was truncated. 19 | // 20 | // The check is skipped on Windows, because an error (WSAEMSGSIZE) 21 | // is also returned when MSG_PARTIAL is set. 22 | func ParseFlagsForError(flags int) error { 23 | if flags&unix.MSG_TRUNC == unix.MSG_TRUNC { 24 | return ErrMessageTruncated 25 | } 26 | 27 | if flags&unix.MSG_CTRUNC == unix.MSG_CTRUNC { 28 | return ErrControlMessageTruncated 29 | } 30 | 31 | return nil 32 | } 33 | -------------------------------------------------------------------------------- /net/net_windows.go: -------------------------------------------------------------------------------- 1 | package net 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "net" 7 | "syscall" 8 | "unsafe" 9 | 10 | "go.uber.org/zap" 11 | "golang.org/x/sys/windows" 12 | ) 13 | 14 | const ( 15 | IP_MTU_DISCOVER = 71 16 | IPV6_MTU_DISCOVER = 71 17 | ) 18 | 19 | // enum PMTUD_STATE from ws2ipdef.h 20 | const ( 21 | IP_PMTUDISC_NOT_SET = iota 22 | IP_PMTUDISC_DO 23 | IP_PMTUDISC_DONT 24 | IP_PMTUDISC_PROBE 25 | IP_PMTUDISC_MAX 26 | ) 27 | 28 | // ListenUDP wraps Go's net.ListenConfig.ListenPacket and sets socket options on supported platforms. 29 | // 30 | // On Linux and Windows, IP_PKTINFO and IPV6_RECVPKTINFO are set to 1; 31 | // IP_MTU_DISCOVER, IPV6_MTU_DISCOVER are set to IP_PMTUDISC_DO to disable IP fragmentation to encourage correct MTU settings. 32 | // 33 | // On Linux, SO_MARK is set to user-specified value. 34 | // 35 | // On macOS and FreeBSD, IP_DONTFRAG, IPV6_DONTFRAG are set to 1 (Don't Fragment). 36 | func ListenUDP(network string, laddr string, fwmark int) (conn *net.UDPConn, err error, serr error) { 37 | lc := &net.ListenConfig{ 38 | Control: func(network, address string, c syscall.RawConn) error { 39 | return c.Control(func(fd uintptr) { 40 | // Set IP_PKTINFO, IP_MTU_DISCOVER for both v4 and v6. 41 | if err := windows.SetsockoptInt(windows.Handle(fd), windows.IPPROTO_IP, windows.IP_PKTINFO, 1); err != nil { 42 | serr = fmt.Errorf("failed to set socket option IP_PKTINFO: %w", err) 43 | } 44 | 45 | if err := windows.SetsockoptInt(windows.Handle(fd), windows.IPPROTO_IP, IP_MTU_DISCOVER, IP_PMTUDISC_DO); err != nil { 46 | serr = fmt.Errorf("failed to set socket option IP_MTU_DISCOVER: %w", err) 47 | } 48 | 49 | if network == "udp6" { 50 | if err := windows.SetsockoptInt(windows.Handle(fd), windows.IPPROTO_IPV6, windows.IPV6_PKTINFO, 1); err != nil { 51 | serr = fmt.Errorf("failed to set socket option IPV6_PKTINFO: %w", err) 52 | } 53 | 54 | if err := windows.SetsockoptInt(windows.Handle(fd), windows.IPPROTO_IPV6, IPV6_MTU_DISCOVER, IP_PMTUDISC_DO); err != nil { 55 | serr = fmt.Errorf("failed to set socket option IPV6_MTU_DISCOVER: %w", err) 56 | } 57 | } 58 | }) 59 | }, 60 | } 61 | 62 | pconn, err := lc.ListenPacket(context.Background(), network, laddr) 63 | if err != nil { 64 | return 65 | } 66 | conn = pconn.(*net.UDPConn) 67 | return 68 | } 69 | 70 | // Structure CMSGHDR from ws2def.h 71 | type Cmsghdr struct { 72 | Len uint 73 | Level int32 74 | Type int32 75 | } 76 | 77 | // Structure IN_PKTINFO from ws2ipdef.h 78 | type Inet4Pktinfo struct { 79 | Addr [4]byte 80 | Ifindex uint32 81 | } 82 | 83 | // Structure IN6_PKTINFO from ws2ipdef.h 84 | type Inet6Pktinfo struct { 85 | Addr [16]byte 86 | Ifindex uint32 87 | } 88 | 89 | // Defined for getting structure size using unsafe.Sizeof. 90 | var ( 91 | cmsghdrForSize Cmsghdr 92 | inet4PktinfoForSize Inet4Pktinfo 93 | inet6PktinfoForSize Inet6Pktinfo 94 | ) 95 | 96 | // UpdateOobCache filters out irrelevant OOB messages, saves 97 | // IP_PKTINFO or IPV6_PKTINFO socket control messages to the OOB cache, 98 | // and returns the updated OOB cache slice. 99 | // 100 | // IP_PKTINFO and IPV6_PKTINFO socket control messages are only supported 101 | // on Linux and Windows. 102 | // 103 | // The returned OOB cache is unchanged if no relevant control messages 104 | // are found. 105 | // 106 | // Errors returned by this function can be safely ignored, 107 | // or printed as debug logs. 108 | func UpdateOobCache(oobCache, oob []byte, logger *zap.Logger) ([]byte, error) { 109 | // Since we only set IP_PKTINFO and/or IPV6_PKTINFO, 110 | // Inet4Pktinfo or Inet6Pktinfo should be the first 111 | // and only socket control message returned. 112 | // Therefore we simplify the process by not looping 113 | // through the OOB data. 114 | if len(oob) < int(unsafe.Sizeof(cmsghdrForSize)) { 115 | return oobCache, fmt.Errorf("oob length %d shorter than cmsghdr length", len(oob)) 116 | } 117 | 118 | cmsghdr := (*Cmsghdr)(unsafe.Pointer(&oob[0])) 119 | 120 | switch { 121 | case cmsghdr.Level == windows.IPPROTO_IP && cmsghdr.Type == windows.IP_PKTINFO && len(oob) >= int(unsafe.Sizeof(cmsghdrForSize)+unsafe.Sizeof(inet4PktinfoForSize)): 122 | // pktinfo := (*Inet4Pktinfo)(unsafe.Pointer(&oob[unsafe.Sizeof(cmsghdrForSize)])) 123 | // logger.Debug("Matched Inet4Pktinfo", zap.Uint32("ifindex", pktinfo.Ifindex)) 124 | case cmsghdr.Level == windows.IPPROTO_IPV6 && cmsghdr.Type == windows.IPV6_PKTINFO && len(oob) >= int(unsafe.Sizeof(cmsghdrForSize)+unsafe.Sizeof(inet6PktinfoForSize)): 125 | // pktinfo := (*Inet6Pktinfo)(unsafe.Pointer(&oob[unsafe.Sizeof(cmsghdrForSize)])) 126 | // logger.Debug("Matched Inet6Pktinfo", zap.Uint32("ifindex", pktinfo.Ifindex)) 127 | default: 128 | return oobCache, fmt.Errorf("unknown control message level %d type %d", cmsghdr.Level, cmsghdr.Type) 129 | } 130 | 131 | return append(oobCache[:0], oob...), nil 132 | } 133 | -------------------------------------------------------------------------------- /net/private_net.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Jigsaw Operations LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package net 16 | 17 | import ( 18 | "fmt" 19 | "net" 20 | ) 21 | 22 | var privateNetworks []*net.IPNet 23 | 24 | func init() { 25 | for _, cidr := range []string{ 26 | // RFC 1918: private IPv4 networks 27 | "10.0.0.0/8", 28 | "172.16.0.0/12", 29 | "192.168.0.0/16", 30 | // RFC 4193: IPv6 ULAs 31 | "fc00::/7", 32 | // RFC 6598: reserved prefix for CGNAT 33 | "100.64.0.0/10", 34 | } { 35 | _, subnet, _ := net.ParseCIDR(cidr) 36 | privateNetworks = append(privateNetworks, subnet) 37 | } 38 | } 39 | 40 | // IsPrivateAddress returns whether an IP address belongs to the LAN. 41 | func IsPrivateAddress(ip net.IP) bool { 42 | for _, network := range privateNetworks { 43 | if network.Contains(ip) { 44 | return true 45 | } 46 | } 47 | return false 48 | } 49 | 50 | // TargetIPValidator is a type alias for checking if an IP is allowed. 51 | type TargetIPValidator = func(net.IP) *ConnectionError 52 | 53 | // RequirePublicIP returns an error if the destination IP is not a 54 | // standard public IP. 55 | func RequirePublicIP(ip net.IP) *ConnectionError { 56 | if !ip.IsGlobalUnicast() { 57 | return NewConnectionError("ERR_ADDRESS_INVALID", fmt.Sprintf("Address is not global unicast: %s", ip.String()), nil) 58 | } 59 | if IsPrivateAddress(ip) { 60 | return NewConnectionError("ERR_ADDRESS_PRIVATE", fmt.Sprintf("Address is private: %s", ip.String()), nil) 61 | } 62 | return nil 63 | } 64 | -------------------------------------------------------------------------------- /net/private_net_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Jigsaw Operations LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package net 16 | 17 | import ( 18 | "net" 19 | "testing" 20 | ) 21 | 22 | var privateAddressTests = []struct { 23 | address string 24 | expected bool 25 | }{ 26 | {"10.0.2.11", true}, 27 | {"172.16.1.2", true}, 28 | {"172.32.0.0", false}, 29 | {"192.168.0.23", true}, 30 | {"192.169.1.1", false}, 31 | {"127.0.0.1", false}, 32 | {"8.8.8.8", false}, 33 | {"::", false}, 34 | {"fd66:f83a:c650::1", true}, 35 | {"fde4:8dba:82e1::", true}, 36 | {"fe::123", false}, 37 | } 38 | 39 | func TestIsLanAddress(t *testing.T) { 40 | for _, tt := range privateAddressTests { 41 | actual := IsPrivateAddress(net.ParseIP(tt.address)) 42 | if actual != tt.expected { 43 | t.Errorf("IsLanAddress(%s): expected %t, actual %t", tt.address, tt.expected, actual) 44 | } 45 | } 46 | } 47 | 48 | func TestRequirePublicIP(t *testing.T) { 49 | if err := RequirePublicIP(net.ParseIP("8.8.8.8")); err != nil { 50 | t.Error(err) 51 | } 52 | 53 | if err := RequirePublicIP(net.ParseIP("2001:4860:4860::8888")); err != nil { 54 | t.Error(err) 55 | } 56 | 57 | err := RequirePublicIP(net.ParseIP("192.168.0.23")) 58 | if err == nil { 59 | t.Error("Expected error") 60 | } else if err.Status != "ERR_ADDRESS_PRIVATE" { 61 | t.Errorf("Wrong status %s", err.Status) 62 | } 63 | 64 | err = RequirePublicIP(net.ParseIP("::1")) 65 | if err == nil { 66 | t.Error("Expected error") 67 | } else if err.Status != "ERR_ADDRESS_INVALID" { 68 | t.Errorf("Wrong status %s", err.Status) 69 | } 70 | 71 | err = RequirePublicIP(net.ParseIP("224.0.0.251")) 72 | if err == nil { 73 | t.Error("Expected error") 74 | } else if err.Status != "ERR_ADDRESS_INVALID" { 75 | t.Errorf("Wrong status %s", err.Status) 76 | } 77 | 78 | err = RequirePublicIP(net.ParseIP("ff02::fb")) 79 | if err == nil { 80 | t.Error("Expected error") 81 | } else if err.Status != "ERR_ADDRESS_INVALID" { 82 | t.Errorf("Wrong status %s", err.Status) 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /prometheus_example.yml: -------------------------------------------------------------------------------- 1 | # Copyright 2018 Jigsaw Operations LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # https://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | global: 16 | scrape_interval: 5s 17 | external_labels: 18 | monitor: 'outline-monitor' 19 | 20 | scrape_configs: 21 | - job_name: 'prometheus' 22 | static_configs: 23 | - targets: ['localhost:9090'] 24 | 25 | - job_name: 'ss-server' 26 | static_configs: 27 | - targets: ['localhost:9091'] 28 | -------------------------------------------------------------------------------- /service/PROBES.md: -------------------------------------------------------------------------------- 1 | # Outline Shadowsocks Probing and Replay Defenses 2 | 3 | ## Attacks 4 | 5 | To ensure that proxied connections have not been modified in transit, the Outline implementation of Shadowsocks only supports modern [AEAD cipher suites](https://shadowsocks.org/en/spec/AEAD-Ciphers.html). This protects users from a wide range of potential attacks. However, even with [AEAD's authenticity guarantees](https://en.wikipedia.org/wiki/Authenticated_encryption), there are still ways for an attacker to abuse the Shadowsocks protocol. 6 | 7 | One category of attacks are "probing" attacks, in which the adversary sends test data to the proxy in order to confirm that it is actually a Shadowsocks proxy. This is a violation of the Shadowsocks security design, which is intended to ensure that only an authenticated user can identify the proxy. For example, one [probing attack against Shadowsocks](https://scholar.google.com/scholar?cluster=8542824533765048218) sends different numbers of random bytes to a target server, and identifies how many bytes the server reads before detecting an error and closing the connection. This number can be distinctive, identifying the server software. 8 | 9 | Another [reported](https://gfw.report/blog/gfw_shadowsocks/) category of attacks are "replay" attacks, in which an adversary records a conversation between a Shadowsocks client and server, then replays the contents of that connection. The contents are valid Shadowsocks AEAD data, so the proxy will forward the connection to the specified destination, as usual. In some cases, this can cause a duplicated action (e.g. uploading a file twice with HTTP POST). However, modern secure protocols such as HTTPS are not replayable, so this will normally have no ill effect. 10 | 11 | A greater concern for Outline is the use of replays in probing attacks to identify Shadowsocks proxies. By sending modified and unmodified replays, an attacker might be able to confirm that a server is in fact a Shadowsocks proxy, by observing distinctive behaviors. 12 | 13 | ## Outline's defenses 14 | 15 | Outline contains several defenses against probing and replay attacks. 16 | 17 | ### Invalid probe data 18 | 19 | If Outline detects that the initial data is invalid, it will continue to read data (exactly as if it were valid), but will not reply, and will not close the connection until a timeout. This leaves the attacker with minimal information about the server. 20 | 21 | ### Client replays 22 | 23 | When client replay protection is enabled, every incoming valid handshake is reduced to a 32-bit checksum and stored in a hash table. When the table is full, it is archived and replaced with a fresh one, ensuring that the recent history is always in memory. Using 32-bit checksums results in a false-positive detection rate of 1 in 4 billion for each entry in the history. At the maximum history size (two sets of 20,000 checksums each), that results in a false-positive failure rate of 1 in 100,000 sockets ... still far lower than the error rate expected from network unreliability. 24 | 25 | This feature is on by default in Outline. Admins who are using outline-ss-server directly can enable this feature by adding "--replay_history 10000" to their outline-ss-server invocation. This costs approximately 20 bytes of memory per checksum. 26 | 27 | ### Server replays 28 | 29 | Shadowsocks uses the same Key Derivation Function for both upstream and downstream flows, so in principle an attacker could record data sent from the server to the client, and use it in a "reflected replay" attack as simulated client->server data. The data would appear to be valid and authenticated to the server, but the connection would most likely fail when attempting to parse the destination address header, perhaps leading to a distinctive failure behavior. 30 | 31 | To avoid this class of attacks, outline-ss-server uses an [HMAC](https://en.wikipedia.org/wiki/HMAC) with a 32-bit tag to mark all server handshakes, and checks for the presence of this tag in all incoming handshakes. If the tag is present, the connection is a reflected replay, with a false positive probability of 1 in 4 billion. 32 | 33 | ## Metrics 34 | 35 | Outline provides server operators with metrics on a variety of aspects of server activity, including any detected attacks. To observe attacks detected by your server, look at the `tcp_probes` histogram vector in Prometheus. The `status` field will be `"ERR_CIPHER"` (indicating invalid probe data), `"ERR_REPLAY_CLIENT"`, or `"ERR_REPLAY_SERVER"`, depending on the kind of attack your server observed. You can also see what country each probe appeared to originate from, and approximately how many bytes were sent before giving up. -------------------------------------------------------------------------------- /service/cipher_list.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Jigsaw Operations LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package service 16 | 17 | import ( 18 | "container/list" 19 | "net" 20 | "sync" 21 | 22 | ss "github.com/Shadowsocks-NET/outline-ss-server/shadowsocks" 23 | ) 24 | 25 | // Don't add a tag if it would reduce the salt entropy below this amount. 26 | const minSaltEntropy = 16 27 | 28 | // CipherEntry holds a Cipher with an identifier. 29 | // The public fields are constant, but lastClientIP is mutable under cipherList.mu. 30 | type CipherEntry struct { 31 | ID string 32 | Cipher *ss.Cipher 33 | SaltGenerator ServerSaltGenerator 34 | lastClientIP net.IP 35 | } 36 | 37 | // MakeCipherEntry constructs a CipherEntry. 38 | func MakeCipherEntry(id string, cipher *ss.Cipher, secret string) CipherEntry { 39 | var saltGenerator ServerSaltGenerator 40 | if !cipher.Config().IsSpec2022 && cipher.SaltSize()-ServerSaltMarkLen >= minSaltEntropy { 41 | // Mark salts with a tag for reverse replay protection. 42 | saltGenerator = NewServerSaltGenerator(secret) 43 | } else { 44 | // Adding a tag would leave too little randomness to protect 45 | // against accidental salt reuse, so don't mark the salts. 46 | saltGenerator = RandomServerSaltGenerator 47 | } 48 | return CipherEntry{ 49 | ID: id, 50 | Cipher: cipher, 51 | SaltGenerator: saltGenerator, 52 | } 53 | } 54 | 55 | // CipherList is a thread-safe collection of CipherEntry elements that allows for 56 | // snapshotting and moving to front. 57 | type CipherList interface { 58 | // Returns a snapshot of the cipher list optimized for this client IP 59 | SnapshotForClientIP(clientIP net.IP) []*list.Element 60 | MarkUsedByClientIP(e *list.Element, clientIP net.IP) 61 | // Update replaces the current contents of the CipherList with `contents`, 62 | // which is a List of *CipherEntry. Update takes ownership of `contents`, 63 | // which must not be read or written after this call. 64 | Update(contents *list.List) 65 | } 66 | 67 | type cipherList struct { 68 | CipherList 69 | list *list.List 70 | mu sync.RWMutex 71 | } 72 | 73 | // NewCipherList creates an empty CipherList 74 | func NewCipherList() CipherList { 75 | return &cipherList{list: list.New()} 76 | } 77 | 78 | func matchesIP(e *list.Element, clientIP net.IP) bool { 79 | c := e.Value.(*CipherEntry) 80 | return clientIP != nil && clientIP.Equal(c.lastClientIP) 81 | } 82 | 83 | func (cl *cipherList) SnapshotForClientIP(clientIP net.IP) []*list.Element { 84 | cl.mu.RLock() 85 | defer cl.mu.RUnlock() 86 | cipherArray := make([]*list.Element, cl.list.Len()) 87 | i := 0 88 | // First pass: put all ciphers with matching last known IP at the front. 89 | for e := cl.list.Front(); e != nil; e = e.Next() { 90 | if matchesIP(e, clientIP) { 91 | cipherArray[i] = e 92 | i++ 93 | } 94 | } 95 | // Second pass: include all remaining ciphers in recency order. 96 | for e := cl.list.Front(); e != nil; e = e.Next() { 97 | if !matchesIP(e, clientIP) { 98 | cipherArray[i] = e 99 | i++ 100 | } 101 | } 102 | return cipherArray 103 | } 104 | 105 | func (cl *cipherList) MarkUsedByClientIP(e *list.Element, clientIP net.IP) { 106 | cl.mu.Lock() 107 | defer cl.mu.Unlock() 108 | cl.list.MoveToFront(e) 109 | 110 | c := e.Value.(*CipherEntry) 111 | c.lastClientIP = clientIP 112 | } 113 | 114 | func (cl *cipherList) Update(src *list.List) { 115 | cl.mu.Lock() 116 | cl.list = src 117 | cl.mu.Unlock() 118 | } 119 | -------------------------------------------------------------------------------- /service/cipher_list_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Jigsaw Operations LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package service 16 | 17 | import ( 18 | "math/rand" 19 | "net" 20 | "testing" 21 | 22 | ss "github.com/Shadowsocks-NET/outline-ss-server/shadowsocks" 23 | ) 24 | 25 | func BenchmarkLocking(b *testing.B) { 26 | var ip net.IP 27 | 28 | ciphers, _ := MakeTestCiphers(ss.MakeTestSecrets(1)) 29 | b.ResetTimer() 30 | b.RunParallel(func(pb *testing.PB) { 31 | for pb.Next() { 32 | entries := ciphers.SnapshotForClientIP(nil) 33 | ciphers.MarkUsedByClientIP(entries[0], ip) 34 | } 35 | }) 36 | } 37 | 38 | func BenchmarkSnapshot(b *testing.B) { 39 | // Create a list of cipher entries in a random order. 40 | 41 | // Small cipher lists (N~1e3) fit entirely in cache, and are ~10 times 42 | // faster to copy (per entry) than very large cipher lists (N~1e5). 43 | const N = 1e3 44 | ciphers, _ := MakeTestCiphers(ss.MakeTestSecrets(N)) 45 | 46 | // Shuffling simulates the behavior of a real server, where successive 47 | // ciphers are not expected to be nearby in memory. 48 | entries := ciphers.SnapshotForClientIP(nil) 49 | rand.Shuffle(N, func(i, j int) { 50 | entries[i], entries[j] = entries[j], entries[i] 51 | }) 52 | for _, entry := range entries { 53 | // Reorder the list to match the shuffle 54 | // (actually in reverse, but it doesn't matter). 55 | ciphers.MarkUsedByClientIP(entry, nil) 56 | } 57 | 58 | b.ResetTimer() 59 | b.RunParallel(func(pb *testing.PB) { 60 | for pb.Next() { 61 | ciphers.SnapshotForClientIP(nil) 62 | } 63 | }) 64 | } 65 | -------------------------------------------------------------------------------- /service/cipher_list_testing.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Jigsaw Operations LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package service 16 | 17 | import ( 18 | "container/list" 19 | "fmt" 20 | 21 | ss "github.com/Shadowsocks-NET/outline-ss-server/shadowsocks" 22 | ) 23 | 24 | // MakeTestCiphers creates a CipherList containing one fresh AEAD cipher 25 | // for each secret in `secrets`. 26 | func MakeTestCiphers(secrets []string) (CipherList, error) { 27 | l := list.New() 28 | for i := 0; i < len(secrets); i++ { 29 | cipherID := fmt.Sprintf("id-%v", i) 30 | cipher, err := ss.NewCipher(ss.TestCipher, secrets[i]) 31 | if err != nil { 32 | return nil, fmt.Errorf("failed to create cipher %v: %v", i, err) 33 | } 34 | entry := MakeCipherEntry(cipherID, cipher, secrets[i]) 35 | l.PushBack(&entry) 36 | } 37 | cipherList := NewCipherList() 38 | cipherList.Update(l) 39 | return cipherList, nil 40 | } 41 | -------------------------------------------------------------------------------- /service/logger.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import "go.uber.org/zap" 4 | 5 | // logger is the shared logger instance used by this package. 6 | // This variable must be assigned before calling any functions in this package. 7 | var logger *zap.Logger 8 | 9 | func SetLogger(l *zap.Logger) { 10 | logger = l 11 | } 12 | -------------------------------------------------------------------------------- /service/metrics/metrics.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Jigsaw Operations LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package metrics 16 | 17 | import ( 18 | "errors" 19 | "fmt" 20 | "io" 21 | "net" 22 | "strconv" 23 | "time" 24 | 25 | onet "github.com/Shadowsocks-NET/outline-ss-server/net" 26 | geoip2 "github.com/oschwald/geoip2-golang" 27 | "github.com/prometheus/client_golang/prometheus" 28 | ) 29 | 30 | // ShadowsocksMetrics registers metrics for the Shadowsocks service. 31 | type ShadowsocksMetrics interface { 32 | SetBuildInfo(version string) 33 | 34 | GetLocation(net.Addr) (string, error) 35 | 36 | SetNumAccessKeys(numKeys int, numPorts int) 37 | 38 | // TCP metrics 39 | AddOpenTCPConnection(clientLocation string) 40 | AddClosedTCPConnection(clientLocation, accessKey, status string, data ProxyMetrics, timeToCipher, duration time.Duration) 41 | AddTCPProbe(clientLocation, status, drainResult string, port int, data ProxyMetrics) 42 | 43 | // UDP metrics 44 | AddUDPPacketFromClient(clientLocation, accessKey, status string, clientProxyBytes, proxyTargetBytes int, timeToCipher time.Duration) 45 | AddUDPPacketFromTarget(clientLocation, accessKey, status string, targetProxyBytes, proxyClientBytes int) 46 | AddUDPNatEntry() 47 | RemoveUDPNatEntry() 48 | } 49 | 50 | type shadowsocksMetrics struct { 51 | ipCountryDB *geoip2.Reader 52 | 53 | buildInfo *prometheus.GaugeVec 54 | accessKeys prometheus.Gauge 55 | ports prometheus.Gauge 56 | dataBytes *prometheus.CounterVec 57 | timeToCipherMs *prometheus.HistogramVec 58 | // TODO: Add time to first byte. 59 | 60 | tcpProbes *prometheus.HistogramVec 61 | tcpOpenConnections *prometheus.CounterVec 62 | tcpClosedConnections *prometheus.CounterVec 63 | tcpConnectionDurationMs *prometheus.HistogramVec 64 | 65 | udpAddedNatEntries prometheus.Counter 66 | udpRemovedNatEntries prometheus.Counter 67 | } 68 | 69 | func newShadowsocksMetrics(ipCountryDB *geoip2.Reader) *shadowsocksMetrics { 70 | return &shadowsocksMetrics{ 71 | ipCountryDB: ipCountryDB, 72 | buildInfo: prometheus.NewGaugeVec(prometheus.GaugeOpts{ 73 | Namespace: "shadowsocks", 74 | Name: "build_info", 75 | Help: "Information on the outline-ss-server build", 76 | }, []string{"version"}), 77 | accessKeys: prometheus.NewGauge(prometheus.GaugeOpts{ 78 | Namespace: "shadowsocks", 79 | Name: "keys", 80 | Help: "Count of access keys", 81 | }), 82 | ports: prometheus.NewGauge(prometheus.GaugeOpts{ 83 | Namespace: "shadowsocks", 84 | Name: "ports", 85 | Help: "Count of open Shadowsocks ports", 86 | }), 87 | tcpOpenConnections: prometheus.NewCounterVec(prometheus.CounterOpts{ 88 | Namespace: "shadowsocks", 89 | Subsystem: "tcp", 90 | Name: "connections_opened", 91 | Help: "Count of open TCP connections", 92 | }, []string{"location"}), 93 | tcpClosedConnections: prometheus.NewCounterVec(prometheus.CounterOpts{ 94 | Namespace: "shadowsocks", 95 | Subsystem: "tcp", 96 | Name: "connections_closed", 97 | Help: "Count of closed TCP connections", 98 | }, []string{"location", "status", "access_key"}), 99 | tcpConnectionDurationMs: prometheus.NewHistogramVec( 100 | prometheus.HistogramOpts{ 101 | Namespace: "shadowsocks", 102 | Subsystem: "tcp", 103 | Name: "connection_duration_ms", 104 | Help: "TCP connection duration distributions.", 105 | Buckets: []float64{ 106 | 100, 107 | float64(time.Second.Milliseconds()), 108 | float64(time.Minute.Milliseconds()), 109 | float64(time.Hour.Milliseconds()), 110 | float64(24 * time.Hour.Milliseconds()), // Day 111 | float64(7 * 24 * time.Hour.Milliseconds()), // Week 112 | }, 113 | }, []string{"status"}), 114 | dataBytes: prometheus.NewCounterVec( 115 | prometheus.CounterOpts{ 116 | Namespace: "shadowsocks", 117 | Name: "data_bytes", 118 | Help: "Bytes transferred by the proxy", 119 | }, []string{"dir", "proto", "location", "status", "access_key"}), 120 | tcpProbes: prometheus.NewHistogramVec(prometheus.HistogramOpts{ 121 | Namespace: "shadowsocks", 122 | Name: "tcp_probes", 123 | Buckets: []float64{0, 49, 50, 51, 73, 91}, 124 | Help: "Histogram of number of bytes from client to proxy, for detecting possible probes", 125 | }, []string{"location", "port", "status", "error"}), 126 | timeToCipherMs: prometheus.NewHistogramVec( 127 | prometheus.HistogramOpts{ 128 | Namespace: "shadowsocks", 129 | Name: "time_to_cipher_ms", 130 | Help: "Time needed to find the cipher", 131 | Buckets: []float64{0.1, 1, 10, 100, 1000}, 132 | }, []string{"proto", "found_key"}), 133 | udpAddedNatEntries: prometheus.NewCounter( 134 | prometheus.CounterOpts{ 135 | Namespace: "shadowsocks", 136 | Subsystem: "udp", 137 | Name: "nat_entries_added", 138 | Help: "Entries added to the UDP NAT table", 139 | }), 140 | udpRemovedNatEntries: prometheus.NewCounter( 141 | prometheus.CounterOpts{ 142 | Namespace: "shadowsocks", 143 | Subsystem: "udp", 144 | Name: "nat_entries_removed", 145 | Help: "Entries removed from the UDP NAT table", 146 | }), 147 | } 148 | } 149 | 150 | // NewPrometheusShadowsocksMetrics constructs a metrics object that uses 151 | // `ipCountryDB` to convert IP addresses to countries, and reports all 152 | // metrics to Prometheus via `registerer`. `ipCountryDB` may be nil, but 153 | // `registerer` must not be. 154 | func NewPrometheusShadowsocksMetrics(ipCountryDB *geoip2.Reader, registerer prometheus.Registerer) ShadowsocksMetrics { 155 | m := newShadowsocksMetrics(ipCountryDB) 156 | // TODO: Is it possible to pass where to register the collectors? 157 | registerer.MustRegister(m.buildInfo, m.accessKeys, m.ports, m.tcpOpenConnections, m.tcpProbes, m.tcpClosedConnections, m.tcpConnectionDurationMs, 158 | m.dataBytes, m.timeToCipherMs, m.udpAddedNatEntries, m.udpRemovedNatEntries) 159 | return m 160 | } 161 | 162 | const ( 163 | errParseAddr = "XA" 164 | errDbLookupError = "XD" 165 | localLocation = "XL" 166 | unknownLocation = "ZZ" 167 | ) 168 | 169 | func (m *shadowsocksMetrics) SetBuildInfo(version string) { 170 | m.buildInfo.WithLabelValues(version).Set(1) 171 | } 172 | 173 | func (m *shadowsocksMetrics) GetLocation(addr net.Addr) (string, error) { 174 | if m.ipCountryDB == nil { 175 | return "", nil 176 | } 177 | hostname, _, err := net.SplitHostPort(addr.String()) 178 | if err != nil { 179 | return errParseAddr, errors.New("Failed to split hostname and port") 180 | } 181 | ip := net.ParseIP(hostname) 182 | if ip == nil { 183 | return errParseAddr, errors.New("Failed to parse address as IP") 184 | } 185 | if ip.IsLoopback() { 186 | return localLocation, nil 187 | } 188 | if !ip.IsGlobalUnicast() { 189 | return localLocation, nil 190 | } 191 | record, err := m.ipCountryDB.Country(ip) 192 | if err != nil { 193 | return errDbLookupError, errors.New("IP lookup failed") 194 | } 195 | if record == nil { 196 | return unknownLocation, errors.New("IP lookup returned nil") 197 | } 198 | if record.Country.IsoCode == "" { 199 | return unknownLocation, errors.New("IP Lookup has empty ISO code") 200 | } 201 | return record.Country.IsoCode, nil 202 | } 203 | 204 | func (m *shadowsocksMetrics) SetNumAccessKeys(numKeys int, ports int) { 205 | m.accessKeys.Set(float64(numKeys)) 206 | m.ports.Set(float64(ports)) 207 | } 208 | 209 | func (m *shadowsocksMetrics) AddOpenTCPConnection(clientLocation string) { 210 | m.tcpOpenConnections.WithLabelValues(clientLocation).Inc() 211 | } 212 | 213 | // Converts accessKey to "true" or "false" 214 | func isFound(accessKey string) string { 215 | return fmt.Sprintf("%t", accessKey != "") 216 | } 217 | 218 | // addIfNonZero helps avoid the creation of series that are always zero. 219 | func addIfNonZero(counter prometheus.Counter, value int64) { 220 | if value > 0 { 221 | counter.Add(float64(value)) 222 | } 223 | } 224 | 225 | func (m *shadowsocksMetrics) AddClosedTCPConnection(clientLocation, accessKey, status string, data ProxyMetrics, timeToCipher, duration time.Duration) { 226 | m.tcpClosedConnections.WithLabelValues(clientLocation, status, accessKey).Inc() 227 | m.tcpConnectionDurationMs.WithLabelValues(status).Observe(duration.Seconds() * 1000) 228 | m.timeToCipherMs.WithLabelValues("tcp", isFound(accessKey)).Observe(timeToCipher.Seconds() * 1000) 229 | addIfNonZero(m.dataBytes.WithLabelValues("c>p", "tcp", clientLocation, status, accessKey), data.ClientProxy) 230 | addIfNonZero(m.dataBytes.WithLabelValues("p>t", "tcp", clientLocation, status, accessKey), data.ProxyTarget) 231 | addIfNonZero(m.dataBytes.WithLabelValues("pp", "udp", clientLocation, status, accessKey), int64(clientProxyBytes)) 242 | addIfNonZero(m.dataBytes.WithLabelValues("p>t", "udp", clientLocation, status, accessKey), int64(proxyTargetBytes)) 243 | } 244 | 245 | func (m *shadowsocksMetrics) AddUDPPacketFromTarget(clientLocation, accessKey, status string, targetProxyBytes, proxyClientBytes int) { 246 | addIfNonZero(m.dataBytes.WithLabelValues("p MaxCapacity { 48 | panic("ReplayCache capacity would result in too many false positives") 49 | } 50 | return ReplayCache{ 51 | capacity: capacity, 52 | active: make(map[uint32]empty, capacity), 53 | // `archive` is read-only and initially empty. 54 | } 55 | } 56 | 57 | // Trivially reduces the key and salt to a uint32, avoiding collisions 58 | // in case of salts with a shared prefix or suffix. Salts are normally 59 | // random, but in principle a client might use a counter instead, so 60 | // using only the prefix or suffix is not sufficient. Including the key 61 | // ID in the hash avoids accidental collisions when the same salt is used 62 | // by different access keys, as might happen in the case of a counter. 63 | // 64 | // Secure hashing is not required, because only authenticated handshakes 65 | // are added to the cache. A hostile client could produce colliding salts, 66 | // but this would not impact other users. Each map uses a new random hash 67 | // function, so it is not trivial for a hostile client to mount an 68 | // algorithmic complexity attack with nearly-colliding hashes: 69 | // https://dave.cheney.net/2018/05/29/how-the-go-runtime-implements-maps-efficiently-without-generics 70 | func preHash(id string, salt []byte) uint32 { 71 | buf := [4]byte{} 72 | for i := 0; i < len(id); i++ { 73 | buf[i&0x3] ^= id[i] 74 | } 75 | for i, v := range salt { 76 | buf[i&0x3] ^= v 77 | } 78 | return binary.BigEndian.Uint32(buf[:]) 79 | } 80 | 81 | // Add a handshake with this key ID and salt to the cache. 82 | // Returns false if it is already present. 83 | func (c *ReplayCache) Add(id string, salt []byte) bool { 84 | if c == nil || c.capacity == 0 { 85 | // Cache is disabled, so every salt is new. 86 | return true 87 | } 88 | hash := preHash(id, salt) 89 | c.mutex.Lock() 90 | defer c.mutex.Unlock() 91 | if _, ok := c.active[hash]; ok { 92 | // Fast replay: `salt` is already in the active set. 93 | return false 94 | } 95 | _, inArchive := c.archive[hash] 96 | if len(c.active) == c.capacity { 97 | // Discard the archive and move active to archive. 98 | c.archive = c.active 99 | c.active = make(map[uint32]empty, c.capacity) 100 | } 101 | c.active[hash] = empty{} 102 | return !inArchive 103 | } 104 | 105 | type SaltPool struct { 106 | mu sync.Mutex 107 | pool map[[32]byte]int64 108 | } 109 | 110 | // Add cleans the pool, checks if the salt already exists in the pool, 111 | // and adds the salt to the pool if the salt is not already in the pool. 112 | // Server time, instead of the header timestamp, is used, to prevent potential issues when cleaning up. 113 | func (p *SaltPool) Add(salt [32]byte) bool { 114 | if p == nil { 115 | return true 116 | } 117 | 118 | nowEpoch := time.Now().Unix() 119 | 120 | p.mu.Lock() 121 | defer p.mu.Unlock() 122 | 123 | // Clean the pool 124 | for salt, epoch := range p.pool { 125 | // We allow up to 30s of time diff. 126 | // Therefore the pool retention should be 2*30s. 127 | if nowEpoch-epoch > 60 { 128 | delete(p.pool, salt) 129 | } 130 | } 131 | 132 | // Test existence 133 | if _, ok := p.pool[salt]; ok { 134 | return false 135 | } 136 | 137 | // Add to pool 138 | p.pool[salt] = nowEpoch 139 | return true 140 | } 141 | 142 | func NewSaltPool() *SaltPool { 143 | return &SaltPool{ 144 | pool: make(map[[32]byte]int64), 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /service/replay_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Jigsaw Operations LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package service 16 | 17 | import ( 18 | "encoding/binary" 19 | "testing" 20 | ) 21 | 22 | const keyID = "the key" 23 | 24 | var counter uint32 = 0 25 | 26 | func makeSalts(n int) [][]byte { 27 | salts := make([][]byte, n) 28 | for i := 0; i < n; i++ { 29 | salts[i] = make([]byte, 4) 30 | binary.BigEndian.PutUint32(salts[i], counter) 31 | counter++ 32 | if counter == 0 { 33 | panic("Salt counter overflow") 34 | } 35 | } 36 | return salts 37 | } 38 | 39 | func TestReplayCache_Active(t *testing.T) { 40 | salts := makeSalts(2) 41 | cache := NewReplayCache(10) 42 | if !cache.Add(keyID, salts[0]) { 43 | t.Error("First addition to a clean cache should succeed") 44 | } 45 | if cache.Add(keyID, salts[0]) { 46 | t.Error("Duplicate add should fail") 47 | } 48 | if !cache.Add(keyID, salts[1]) { 49 | t.Error("Addition of a new vector should succeed") 50 | } 51 | if cache.Add(keyID, salts[1]) { 52 | t.Error("Second duplicate add should fail") 53 | } 54 | } 55 | 56 | func TestReplayCache_Archive(t *testing.T) { 57 | salts0 := makeSalts(10) 58 | salts1 := makeSalts(10) 59 | cache := NewReplayCache(10) 60 | // Add vectors to the active set until it hits the limit 61 | // and spills into the archive. 62 | for _, s := range salts0 { 63 | if !cache.Add(keyID, s) { 64 | t.Error("Addition of a new vector should succeed") 65 | } 66 | } 67 | 68 | for _, s := range salts0 { 69 | if cache.Add(keyID, s) { 70 | t.Error("Duplicate add should fail") 71 | } 72 | } 73 | 74 | // Repopulate the active set. 75 | for _, s := range salts1 { 76 | if !cache.Add(keyID, s) { 77 | t.Error("Addition of a new vector should succeed") 78 | } 79 | } 80 | 81 | // Both active and archive are full. Adding another vector 82 | // should wipe the archive. 83 | lastStraw := makeSalts(1)[0] 84 | if !cache.Add(keyID, lastStraw) { 85 | t.Error("Addition of a new vector should succeed") 86 | } 87 | for _, s := range salts0 { 88 | if !cache.Add(keyID, s) { 89 | t.Error("First 10 vectors should have been forgotten") 90 | } 91 | } 92 | } 93 | 94 | // Benchmark to determine the memory usage of ReplayCache. 95 | // Note that NewReplayCache only allocates the active set, 96 | // so the eventual memory usage will be roughly double. 97 | func BenchmarkReplayCache_Creation(b *testing.B) { 98 | for i := 0; i < b.N; i++ { 99 | NewReplayCache(MaxCapacity) 100 | } 101 | } 102 | 103 | func BenchmarkReplayCache_Max(b *testing.B) { 104 | salts := makeSalts(b.N) 105 | // Archive replacements will be infrequent. 106 | cache := NewReplayCache(MaxCapacity) 107 | b.ResetTimer() 108 | for i := 0; i < b.N; i++ { 109 | cache.Add(keyID, salts[i]) 110 | } 111 | } 112 | 113 | func BenchmarkReplayCache_Min(b *testing.B) { 114 | salts := makeSalts(b.N) 115 | // Every addition will archive the active set. 116 | cache := NewReplayCache(1) 117 | b.ResetTimer() 118 | for i := 0; i < b.N; i++ { 119 | cache.Add(keyID, salts[i]) 120 | } 121 | } 122 | 123 | func BenchmarkReplayCache_Parallel(b *testing.B) { 124 | c := make(chan []byte, b.N) 125 | for _, s := range makeSalts(b.N) { 126 | c <- s 127 | } 128 | close(c) 129 | // Exercise both expansion and archiving. 130 | cache := NewReplayCache(100) 131 | b.ResetTimer() 132 | b.RunParallel(func(pb *testing.PB) { 133 | for pb.Next() { 134 | cache.Add(keyID, <-c) 135 | } 136 | }) 137 | } 138 | -------------------------------------------------------------------------------- /service/server_salt.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Jigsaw Operations LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package service 16 | 17 | import ( 18 | "bytes" 19 | "crypto" 20 | "crypto/hmac" 21 | "crypto/rand" 22 | "fmt" 23 | "io" 24 | 25 | ss "github.com/Shadowsocks-NET/outline-ss-server/shadowsocks" 26 | "golang.org/x/crypto/hkdf" 27 | ) 28 | 29 | // ServerSaltGenerator offers the ability to check if a salt was marked as 30 | // server-originated. 31 | type ServerSaltGenerator interface { 32 | ss.SaltGenerator 33 | // IsServerSalt returns true if the salt was created by this generator 34 | // and is marked as server-originated. 35 | IsServerSalt(salt []byte) bool 36 | } 37 | 38 | // randomServerSaltGenerator generates a new random salt. 39 | type randomServerSaltGenerator struct{} 40 | 41 | // GetSalt outputs a random salt. 42 | func (randomServerSaltGenerator) GetSalt(salt []byte) error { 43 | _, err := rand.Read(salt) 44 | return err 45 | } 46 | 47 | func (randomServerSaltGenerator) IsServerSalt(salt []byte) bool { 48 | return false 49 | } 50 | 51 | // RandomServerSaltGenerator is a basic ServerSaltGenerator. 52 | var RandomServerSaltGenerator ServerSaltGenerator = randomServerSaltGenerator{} 53 | 54 | // serverSaltGenerator generates unique salts that are secretly marked. 55 | type serverSaltGenerator struct { 56 | key []byte 57 | } 58 | 59 | // ServerSaltMarkLen is the number of bytes of salt to use as a marker. 60 | // Increasing this value reduces the false positive rate, but increases 61 | // the likelihood of salt collisions. 62 | const ServerSaltMarkLen = 4 // Must be less than or equal to SHA1.Size() 63 | 64 | // Constant to identify this marking scheme. 65 | var serverSaltLabel = []byte("outline-server-salt") 66 | 67 | // NewServerSaltGenerator returns a SaltGenerator whose output is apparently 68 | // random, but is secretly marked as being issued by the server. 69 | // This is useful to prevent the server from accepting its own output in a 70 | // reflection attack. 71 | func NewServerSaltGenerator(secret string) ServerSaltGenerator { 72 | // Shadowsocks already uses HKDF-SHA1 to derive the AEAD key, so we use 73 | // the same derivation with a different "info" to generate our HMAC key. 74 | keySource := hkdf.New(crypto.SHA1.New, []byte(secret), nil, serverSaltLabel) 75 | // The key can be any size, but matching the block size is most efficient. 76 | key := make([]byte, crypto.SHA1.Size()) 77 | io.ReadFull(keySource, key) 78 | return serverSaltGenerator{key} 79 | } 80 | 81 | func (sg serverSaltGenerator) splitSalt(salt []byte) (prefix, mark []byte, err error) { 82 | prefixLen := len(salt) - ServerSaltMarkLen 83 | if prefixLen < 0 { 84 | return nil, nil, fmt.Errorf("salt is too short: %d < %d", len(salt), ServerSaltMarkLen) 85 | } 86 | return salt[:prefixLen], salt[prefixLen:], nil 87 | } 88 | 89 | // getTag takes in a salt prefix and returns the tag. 90 | func (sg serverSaltGenerator) getTag(prefix []byte) []byte { 91 | // Use HMAC-SHA1, even though SHA1 is broken, because HMAC-SHA1 is still 92 | // secure, and we're already using HKDF-SHA1. 93 | hmac := hmac.New(crypto.SHA1.New, sg.key) 94 | hmac.Write(prefix) // Hash.Write never returns an error. 95 | return hmac.Sum(nil) 96 | } 97 | 98 | // GetSalt returns an apparently random salt that can be identified 99 | // as server-originated by anyone who knows the Shadowsocks key. 100 | func (sg serverSaltGenerator) GetSalt(salt []byte) error { 101 | prefix, mark, err := sg.splitSalt(salt) 102 | if err != nil { 103 | return err 104 | } 105 | if _, err := rand.Read(prefix); err != nil { 106 | return err 107 | } 108 | tag := sg.getTag(prefix) 109 | copy(mark, tag) 110 | return nil 111 | } 112 | 113 | func (sg serverSaltGenerator) IsServerSalt(salt []byte) bool { 114 | prefix, mark, err := sg.splitSalt(salt) 115 | if err != nil { 116 | return false 117 | } 118 | tag := sg.getTag(prefix) 119 | return bytes.Equal(tag[:ServerSaltMarkLen], mark) 120 | } 121 | -------------------------------------------------------------------------------- /service/server_salt_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Jigsaw Operations LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package service 16 | 17 | import ( 18 | "bytes" 19 | "testing" 20 | ) 21 | 22 | // Test that ServerSaltGenerator recognizes its own salts 23 | func TestServerSaltRecognized(t *testing.T) { 24 | ssg := NewServerSaltGenerator("test") 25 | 26 | salt := make([]byte, 32) 27 | if err := ssg.GetSalt(salt); err != nil { 28 | t.Fatal(err) 29 | } 30 | if !ssg.IsServerSalt(salt) { 31 | t.Error("Server salt was not recognized") 32 | } 33 | } 34 | 35 | // Test that ServerSaltGenerator doesn't recognize random salts 36 | func TestServerSaltUnrecognized(t *testing.T) { 37 | ssg := NewServerSaltGenerator("test") 38 | 39 | salt := make([]byte, 32) 40 | if err := RandomServerSaltGenerator.GetSalt(salt); err != nil { 41 | t.Fatal(err) 42 | } 43 | if ssg.IsServerSalt(salt) { 44 | t.Error("Client salt was recognized as a server salt") 45 | } 46 | } 47 | 48 | // Test that ServerSaltGenerator produces different output on each call 49 | func TestServerSaltDifferent(t *testing.T) { 50 | ssg := NewServerSaltGenerator("test") 51 | 52 | salt1 := make([]byte, 32) 53 | if err := ssg.GetSalt(salt1); err != nil { 54 | t.Fatal(err) 55 | } 56 | salt2 := make([]byte, 32) 57 | if err := ssg.GetSalt(salt2); err != nil { 58 | t.Fatal(err) 59 | } 60 | 61 | if bytes.Equal(salt1, salt2) { 62 | t.Error("salts should be random") 63 | } 64 | } 65 | 66 | // Test that two ServerSaltGenerators derived from the same secret 67 | // produce different outputs and recognize each other's output. 68 | func TestServerSaltSameSecret(t *testing.T) { 69 | ssg1 := NewServerSaltGenerator("test") 70 | ssg2 := NewServerSaltGenerator("test") 71 | 72 | salt1 := make([]byte, 32) 73 | if err := ssg1.GetSalt(salt1); err != nil { 74 | t.Fatal(err) 75 | } 76 | salt2 := make([]byte, 32) 77 | if err := ssg2.GetSalt(salt2); err != nil { 78 | t.Fatal(err) 79 | } 80 | 81 | if bytes.Equal(salt1, salt2) { 82 | t.Error("salts should be random") 83 | } 84 | 85 | if !ssg1.IsServerSalt(salt2) || !ssg2.IsServerSalt(salt1) { 86 | t.Error("Cross-recognition failed") 87 | } 88 | } 89 | 90 | // Test that two ServerSaltGenerators derived from different secrets 91 | // do not recognize each other's output. 92 | func TestServerSaltDifferentCiphers(t *testing.T) { 93 | ssg1 := NewServerSaltGenerator("test1") 94 | ssg2 := NewServerSaltGenerator("test2") 95 | 96 | salt1 := make([]byte, 32) 97 | if err := ssg1.GetSalt(salt1); err != nil { 98 | t.Fatal(err) 99 | } 100 | salt2 := make([]byte, 32) 101 | if err := ssg2.GetSalt(salt2); err != nil { 102 | t.Fatal(err) 103 | } 104 | 105 | if bytes.Equal(salt1, salt2) { 106 | t.Error("salts should be random") 107 | } 108 | 109 | if ssg1.IsServerSalt(salt2) || ssg2.IsServerSalt(salt1) { 110 | t.Error("Different ciphers should not recognize each other") 111 | } 112 | } 113 | 114 | func TestServerSaltShort(t *testing.T) { 115 | ssg := NewServerSaltGenerator("test") 116 | 117 | salt5 := make([]byte, 5) 118 | if err := ssg.GetSalt(salt5); err != nil { 119 | t.Fatal(err) 120 | } 121 | if !ssg.IsServerSalt(salt5) { 122 | t.Error("Server salt was not recognized") 123 | } 124 | 125 | salt4 := make([]byte, 4) 126 | if err := ssg.GetSalt(salt4); err != nil { 127 | t.Fatal(err) 128 | } 129 | if !ssg.IsServerSalt(salt4) { 130 | t.Error("Server salt was not recognized") 131 | } 132 | 133 | salt3 := make([]byte, 3) 134 | if err := ssg.GetSalt(salt3); err == nil { 135 | t.Error("Expected error for too-short salt") 136 | } 137 | } 138 | 139 | func BenchmarkServerSaltGenerator(b *testing.B) { 140 | ssg := NewServerSaltGenerator("test") 141 | b.ResetTimer() 142 | b.RunParallel(func(pb *testing.PB) { 143 | salt := make([]byte, 32) 144 | for pb.Next() { 145 | if err := ssg.GetSalt(salt); err != nil { 146 | b.Fatal(err) 147 | } 148 | if !ssg.IsServerSalt(salt) { 149 | b.Fatal("Failed to recognize salt") 150 | } 151 | } 152 | }) 153 | } 154 | -------------------------------------------------------------------------------- /shadowsocks/cipher.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Jigsaw Operations LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package shadowsocks 16 | 17 | import ( 18 | "crypto/aes" 19 | "crypto/cipher" 20 | "crypto/md5" 21 | "crypto/sha1" 22 | "encoding/base64" 23 | "fmt" 24 | "io" 25 | 26 | "golang.org/x/crypto/chacha20poly1305" 27 | "golang.org/x/crypto/hkdf" 28 | "lukechampine.com/blake3" 29 | ) 30 | 31 | // SupportedCipherNames lists the names of the AEAD ciphers that are supported. 32 | func SupportedCipherNames() []string { 33 | names := make([]string, len(supportedAEADs)) 34 | for i, spec := range supportedAEADs { 35 | names[i] = spec.name 36 | } 37 | return names 38 | } 39 | 40 | type aeadSpec struct { 41 | name string 42 | newInstance func(key []byte) (cipher.AEAD, error) 43 | keySize int 44 | saltSize int 45 | tagSize int 46 | } 47 | 48 | // List of supported AEAD ciphers, as specified at https://shadowsocks.org/en/spec/AEAD-Ciphers.html 49 | var supportedAEADs = [...]aeadSpec{ 50 | newAEADSpec("chacha20-poly1305", chacha20poly1305.New, chacha20poly1305.KeySize, 32), 51 | newAEADSpec("aes-256-gcm", newAesGCM, 32, 32), 52 | newAEADSpec("aes-192-gcm", newAesGCM, 24, 24), 53 | newAEADSpec("aes-128-gcm", newAesGCM, 16, 16), 54 | } 55 | 56 | func newAEADSpec(name string, newInstance func(key []byte) (cipher.AEAD, error), keySize, saltSize int) aeadSpec { 57 | dummyAead, err := newInstance(make([]byte, keySize)) 58 | if err != nil { 59 | panic(fmt.Sprintf("Failed to initialize AEAD %v", name)) 60 | } 61 | return aeadSpec{name, newInstance, keySize, saltSize, dummyAead.Overhead()} 62 | } 63 | 64 | func newAesGCM(key []byte) (cipher.AEAD, error) { 65 | blk, err := aes.NewCipher(key) 66 | if err != nil { 67 | return nil, err 68 | } 69 | return cipher.NewGCM(blk) 70 | } 71 | 72 | func maxTagSize() int { 73 | max := 0 74 | for _, spec := range supportedAEADs { 75 | if spec.tagSize > max { 76 | max = spec.tagSize 77 | } 78 | } 79 | return max 80 | } 81 | 82 | type CipherConfig struct { 83 | IsSpec2022 bool 84 | UDPHasSeparateHeader bool 85 | } 86 | 87 | // Cipher encapsulates a Shadowsocks AEAD spec and a secret 88 | type Cipher struct { 89 | aead aeadSpec 90 | secret []byte 91 | 92 | // spec 2022 93 | config CipherConfig 94 | // Only used by 2022-blake3-aes-256-gcm for encrypting/decrypting the separate header. 95 | separateHeaderCipher cipher.Block 96 | // Only used by 2022-blake3-chacha20-poly1305, initialized with the main key. 97 | // Packets with separate header should instead use the AEAD cipher in session. 98 | udpAEAD cipher.AEAD 99 | } 100 | 101 | // SaltSize is the size of the salt for this Cipher 102 | func (c *Cipher) SaltSize() int { 103 | return c.aead.saltSize 104 | } 105 | 106 | // TagSize is the size of the AEAD tag for this Cipher 107 | func (c *Cipher) TagSize() int { 108 | return c.aead.tagSize 109 | } 110 | 111 | func (c *Cipher) Config() CipherConfig { 112 | return c.config 113 | } 114 | 115 | // NewAEAD creates the AEAD for this cipher 116 | func (c *Cipher) NewAEAD(salt []byte) (cipher.AEAD, error) { 117 | sessionKey := make([]byte, c.aead.keySize) 118 | if c.config.IsSpec2022 { 119 | keyMaterial := make([]byte, len(c.secret)+len(salt)) 120 | copy(keyMaterial, c.secret) 121 | copy(keyMaterial[len(c.secret):], salt) 122 | blake3.DeriveKey(sessionKey, "shadowsocks 2022 session subkey", keyMaterial) 123 | } else { 124 | r := hkdf.New(sha1.New, c.secret, salt, []byte("ss-subkey")) 125 | if _, err := io.ReadFull(r, sessionKey); err != nil { 126 | return nil, err 127 | } 128 | } 129 | return c.aead.newInstance(sessionKey) 130 | } 131 | 132 | // Function definition at https://www.openssl.org/docs/manmaster/man3/EVP_BytesToKey.html 133 | func simpleEVPBytesToKey(data []byte, keyLen int) []byte { 134 | var derived, di []byte 135 | h := md5.New() 136 | for len(derived) < keyLen { 137 | h.Write(di) 138 | h.Write(data) 139 | derived = h.Sum(derived) 140 | di = derived[len(derived)-h.Size():] 141 | h.Reset() 142 | } 143 | return derived[:keyLen] 144 | } 145 | 146 | // NewCipher creates a Cipher given a cipher name and a secret 147 | func NewCipher(cipherName string, secretText string) (*Cipher, error) { 148 | var c Cipher 149 | 150 | switch cipherName { 151 | case "aes-128-gcm", "AEAD_AES_128_GCM": 152 | c.aead = supportedAEADs[3] 153 | case "aes-192-gcm", "AEAD_AES_192_GCM": 154 | c.aead = supportedAEADs[2] 155 | case "2022-blake3-aes-256-gcm", "aes-256-gcm", "AEAD_AES_256_GCM": 156 | c.aead = supportedAEADs[1] 157 | case "2022-blake3-chacha20-poly1305", "chacha20-poly1305", "chacha20-ietf-poly1305": 158 | c.aead = supportedAEADs[0] 159 | default: 160 | return nil, fmt.Errorf("unknown method %s", cipherName) 161 | } 162 | 163 | switch cipherName { 164 | case "aes-128-gcm", "AEAD_AES_128_GCM", "aes-192-gcm", "AEAD_AES_192_GCM", "aes-256-gcm", "AEAD_AES_256_GCM", "chacha20-poly1305", "chacha20-ietf-poly1305": 165 | c.secret = simpleEVPBytesToKey([]byte(secretText), c.aead.keySize) 166 | case "2022-blake3-aes-256-gcm", "2022-blake3-chacha20-poly1305": 167 | key, err := base64.StdEncoding.DecodeString(secretText) 168 | if err != nil { 169 | return nil, fmt.Errorf("invalid key %s", secretText) 170 | } 171 | if len(key) != c.aead.keySize { 172 | return nil, fmt.Errorf("bad key length %d", len(key)) 173 | } 174 | c.secret = key 175 | c.config.IsSpec2022 = true 176 | } 177 | 178 | switch cipherName { 179 | case "2022-blake3-chacha20-poly1305": 180 | udpAEAD, err := chacha20poly1305.NewX(c.secret) 181 | if err != nil { 182 | return nil, fmt.Errorf("failed to create UDP AEAD: %w", err) 183 | } 184 | c.udpAEAD = udpAEAD 185 | case "2022-blake3-aes-256-gcm": 186 | c.config.UDPHasSeparateHeader = true 187 | cb, err := aes.NewCipher(c.secret) 188 | if err != nil { 189 | return nil, fmt.Errorf("failed to create block cipher for 2022 UDP header: %w", err) 190 | } 191 | c.separateHeaderCipher = cb 192 | } 193 | 194 | return &c, nil 195 | } 196 | 197 | // Assumes all ciphers have NonceSize() <= 12. 198 | var zeroNonce [12]byte 199 | 200 | // DecryptOnce will decrypt the cipherText using the cipher and salt, appending the output to plainText. 201 | func DecryptOnce(cipher *Cipher, salt []byte, plainText, cipherText []byte) ([]byte, error) { 202 | aead, err := cipher.NewAEAD(salt) 203 | if err != nil { 204 | return nil, err 205 | } 206 | if len(cipherText) < aead.Overhead() { 207 | return nil, io.ErrUnexpectedEOF 208 | } 209 | if cap(plainText)-len(plainText) < len(cipherText)-aead.Overhead() { 210 | return nil, io.ErrShortBuffer 211 | } 212 | return aead.Open(plainText, zeroNonce[:aead.NonceSize()], cipherText, nil) 213 | } 214 | 215 | func DecryptSeparateHeader(cipher *Cipher, dst, src []byte) error { 216 | if !cipher.config.IsSpec2022 || !cipher.config.UDPHasSeparateHeader { 217 | return nil 218 | } 219 | 220 | blockLen := cipher.separateHeaderCipher.BlockSize() 221 | 222 | if len(dst) < blockLen || len(src) < blockLen { 223 | return io.ErrShortBuffer 224 | } 225 | 226 | cipher.separateHeaderCipher.Decrypt(dst[:blockLen], src[:blockLen]) 227 | return nil 228 | } 229 | -------------------------------------------------------------------------------- /shadowsocks/cipher_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Jigsaw Operations LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package shadowsocks 16 | 17 | import ( 18 | "testing" 19 | ) 20 | 21 | func assertCipher(t *testing.T, name string, saltSize, tagSize int) { 22 | cipher, err := NewCipher(name, "") 23 | if err != nil { 24 | t.Fatal(err) 25 | } 26 | if cipher.SaltSize() != saltSize || cipher.TagSize() != tagSize { 27 | t.Fatalf("Bad spec for %v", name) 28 | } 29 | } 30 | 31 | func TestSizes(t *testing.T) { 32 | // Values from https://shadowsocks.org/en/spec/AEAD-Ciphers.html 33 | assertCipher(t, "chacha20-ietf-poly1305", 32, 16) 34 | assertCipher(t, "aes-256-gcm", 32, 16) 35 | assertCipher(t, "aes-192-gcm", 24, 16) 36 | assertCipher(t, "aes-128-gcm", 16, 16) 37 | } 38 | 39 | func TestUnsupportedCipher(t *testing.T) { 40 | _, err := NewCipher("aes-256-cfb", "") 41 | if err == nil { 42 | t.Errorf("Should get an error for unsupported cipher") 43 | } 44 | } 45 | 46 | func TestMaxNonceSize(t *testing.T) { 47 | for _, aeadName := range SupportedCipherNames() { 48 | cipher, err := NewCipher(aeadName, "") 49 | if err != nil { 50 | t.Errorf("Failed to create Cipher %v: %v", aeadName, err) 51 | } 52 | aead, err := cipher.NewAEAD(make([]byte, cipher.SaltSize())) 53 | if err != nil { 54 | t.Errorf("Failed to create AEAD %v: %v", aeadName, err) 55 | } 56 | if aead.NonceSize() > len(zeroNonce) { 57 | t.Errorf("Cipher %v has nonce size %v > zeroNonce (%v)", aeadName, aead.NonceSize(), len(zeroNonce)) 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /shadowsocks/cipher_testing.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Jigsaw Operations LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package shadowsocks 16 | 17 | import ( 18 | "fmt" 19 | ) 20 | 21 | // TestCipher is a preferred cipher to use in testing. 22 | const TestCipher = "chacha20-ietf-poly1305" 23 | 24 | // MakeTestSecrets returns a slice of `n` test passwords. Not secure! 25 | func MakeTestSecrets(n int) []string { 26 | secrets := make([]string, n) 27 | for i := 0; i < n; i++ { 28 | secrets[i] = fmt.Sprintf("secret-%v", i) 29 | } 30 | return secrets 31 | } 32 | 33 | // MakeTestPayload returns a slice of `size` arbitrary bytes. 34 | func MakeTestPayload(size int) []byte { 35 | payload := make([]byte, size) 36 | for i := 0; i < size; i++ { 37 | payload[i] = byte(i) 38 | } 39 | return payload 40 | } 41 | -------------------------------------------------------------------------------- /shadowsocks/header.go: -------------------------------------------------------------------------------- 1 | package shadowsocks 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | "errors" 7 | "fmt" 8 | "math/rand" 9 | "net" 10 | "strconv" 11 | "time" 12 | 13 | "github.com/Shadowsocks-NET/outline-ss-server/socks" 14 | ) 15 | 16 | const ( 17 | HeaderTypeClientStream = 0 18 | HeaderTypeServerStream = 1 19 | 20 | HeaderTypeClientPacket = 0 21 | HeaderTypeServerPacket = 1 22 | 23 | MinPaddingLength = 0 24 | MaxPaddingLength = 900 25 | 26 | // type + 64-bit timestamp + socks address + padding length + padding 27 | TCPReqHeaderMaxLength = 1 + 8 + socks.MaxAddrLen + 2 + MaxPaddingLength 28 | 29 | // type + 64-bit timestamp + max salt length 30 | TCPRespHeaderMaxLength = 1 + 8 + 32 31 | 32 | // server session id + packet id + type + timestamp + client session id + padding length 33 | UDPServerMessageHeaderFixedLength = 8 + 8 + 1 + 8 + 8 + 2 34 | 35 | // client session id + packet id + type + timestamp + padding length 36 | UDPClientMessageHeaderFixedLength = 8 + 8 + 1 + 8 + 2 37 | ) 38 | 39 | var ( 40 | ErrIncompleteHeaderInFirstChunk = errors.New("header in first chunk is missing or incomplete") 41 | ErrPaddingExceedChunkBorder = errors.New("padding in first chunk is shorter than advertised") 42 | ErrBadTimestamp = errors.New("time diff is over 30 seconds") 43 | ErrTypeMismatch = errors.New("header type mismatch") 44 | ErrPaddingLengthOutOfRange = errors.New("padding length is less than 0 or greater than 900") 45 | ErrClientSaltMismatch = errors.New("client salt in response header does not match request") 46 | ErrClientSessionIDMismatch = errors.New("client session ID in server message header does not match current session") 47 | ErrTooManyServerSessions = errors.New("server session changed more than once during the last minute") 48 | ) 49 | 50 | // ParseTCPReqHeader reads the first payload chunk, validates the header, 51 | // and returns the target address, initial payload, or an error. 52 | // 53 | // For Shadowsocks 2022, the first payload chunk MUST contain 54 | // either a non-zero-length padding or initial payload, or both (not recommended client behavior but allowed). 55 | // If the padding length is 0 and there's no initial payload, an error is returned. 56 | func ParseTCPReqHeader(r Reader, cipherConfig CipherConfig) (string, []byte, error) { 57 | // Read first payload chunk. 58 | if err := r.EnsureLeftover(); err != nil { 59 | return "", nil, err 60 | } 61 | b := r.LeftoverZeroCopy() 62 | 63 | var offset int 64 | 65 | if cipherConfig.IsSpec2022 { 66 | if len(b) < 1+8 { 67 | return "", nil, ErrIncompleteHeaderInFirstChunk 68 | } 69 | 70 | // Verify type 71 | if b[0] != HeaderTypeClientStream { 72 | return "", nil, ErrTypeMismatch 73 | } 74 | 75 | // Verify timestamp 76 | epoch := int64(binary.BigEndian.Uint64(b[1 : 1+8])) 77 | nowEpoch := time.Now().Unix() 78 | diff := epoch - nowEpoch 79 | if diff < -30 || diff > 30 { 80 | return "", nil, ErrBadTimestamp 81 | } 82 | 83 | offset = 1 + 8 84 | } 85 | 86 | // Read socks address 87 | socksaddr, err := socks.SplitAddr(b[offset:]) 88 | if err != nil { 89 | return "", nil, fmt.Errorf("failed to read socks address: %w", err) 90 | } 91 | offset += len(socksaddr) 92 | 93 | if cipherConfig.IsSpec2022 { 94 | // Make sure the remaining length > 2 (padding length + either padding or payload) 95 | if len(b)-offset <= 2 { 96 | return "", nil, ErrIncompleteHeaderInFirstChunk 97 | } 98 | 99 | // Verify padding length 100 | paddingLen := int(binary.BigEndian.Uint16(b[offset : offset+2])) 101 | if paddingLen < MinPaddingLength || paddingLen > MaxPaddingLength { 102 | return "", nil, ErrPaddingLengthOutOfRange 103 | } 104 | offset += 2 105 | 106 | // Skip padding. 107 | offset += paddingLen 108 | if offset > len(b) { 109 | return "", nil, ErrPaddingExceedChunkBorder 110 | } 111 | } 112 | 113 | return socksaddr.String(), b[offset:], nil 114 | } 115 | 116 | func WriteTCPReqHeader(dst, socksaddr []byte, addPadding bool, cipherConfig CipherConfig) (n int) { 117 | if !cipherConfig.IsSpec2022 { 118 | copy(dst, socksaddr) 119 | return len(socksaddr) 120 | } 121 | 122 | // Write type 123 | dst[0] = HeaderTypeClientStream 124 | 125 | // Write timestamp 126 | nowEpoch := time.Now().Unix() 127 | binary.BigEndian.PutUint64(dst[1:], uint64(nowEpoch)) 128 | 129 | n = 1 + 8 130 | 131 | // Write socks address 132 | n += copy(dst[n:], socksaddr) 133 | 134 | // Write padding if applicable 135 | // We pass 53 as port number so whether padding is added depends on maxPaddingLen. 136 | var maxPaddingLen int 137 | if addPadding { 138 | maxPaddingLen = MaxPaddingLength 139 | } 140 | n += WriteRandomPadding(dst[n:], 53, maxPaddingLen) 141 | 142 | return 143 | } 144 | 145 | func ParseTCPRespHeader(r Reader, clientSalt []byte, cipherConfig CipherConfig) ([]byte, error) { 146 | if !cipherConfig.IsSpec2022 { 147 | return nil, nil 148 | } 149 | 150 | // Read first payload chunk. 151 | if err := r.EnsureLeftover(); err != nil { 152 | return nil, err 153 | } 154 | b := r.LeftoverZeroCopy() 155 | 156 | headerLen := 1 + 8 + len(clientSalt) 157 | if len(b) < headerLen { 158 | return nil, ErrIncompleteHeaderInFirstChunk 159 | } 160 | 161 | // Verify type 162 | if b[0] != HeaderTypeServerStream { 163 | return nil, ErrTypeMismatch 164 | } 165 | 166 | // Verify timestamp 167 | epoch := int64(binary.BigEndian.Uint64(b[1 : 1+8])) 168 | nowEpoch := time.Now().Unix() 169 | diff := epoch - nowEpoch 170 | if diff < -30 || diff > 30 { 171 | return nil, ErrBadTimestamp 172 | } 173 | 174 | // Verify client salt 175 | if !bytes.Equal(clientSalt, b[1+8:headerLen]) { 176 | return nil, ErrClientSaltMismatch 177 | } 178 | 179 | return b[headerLen:], nil 180 | } 181 | 182 | func WriteTCPRespHeader(dst, clientSalt []byte, cipherConfig CipherConfig) (n int) { 183 | if !cipherConfig.IsSpec2022 { 184 | return 0 185 | } 186 | 187 | dst[0] = HeaderTypeServerStream 188 | 189 | nowEpoch := time.Now().Unix() 190 | binary.BigEndian.PutUint64(dst[1:1+8], uint64(nowEpoch)) 191 | 192 | n = 1 + 8 193 | n += copy(dst[1+8:], clientSalt) 194 | return 195 | } 196 | 197 | // For spec 2022, this function only parses the decrypted AEAD header. 198 | // csid is the expected client session id, used when verifying a server packet. 199 | func ParseUDPHeader(plaintext []byte, htype byte, csid []byte, cipherConfig CipherConfig) (socksAddrStart int, socksAddr socks.Addr, payload []byte, err error) { 200 | var offset int 201 | 202 | if cipherConfig.IsSpec2022 { 203 | // Filter out short packets 204 | if len(plaintext) < 16+1+8+3 { 205 | err = fmt.Errorf("packet too short: %d", len(plaintext)) 206 | return 207 | } 208 | 209 | // Session ID, packet ID 210 | offset += 16 211 | 212 | // Verify type 213 | if plaintext[offset] != htype { 214 | err = ErrTypeMismatch 215 | return 216 | } 217 | 218 | offset++ 219 | 220 | // Verify timestamp 221 | epoch := int64(binary.BigEndian.Uint64(plaintext[offset : offset+8])) 222 | nowEpoch := time.Now().Unix() 223 | diff := epoch - nowEpoch 224 | if diff < -30 || diff > 30 { 225 | err = ErrBadTimestamp 226 | return 227 | } 228 | 229 | offset += 8 230 | 231 | // Verify client session ID 232 | if csid != nil { 233 | if len(plaintext) < offset+8 { 234 | err = fmt.Errorf("packet too short to contain client session ID: %d", len(plaintext)) 235 | return 236 | } 237 | 238 | if !bytes.Equal(csid, plaintext[offset:offset+8]) { 239 | err = ErrClientSessionIDMismatch 240 | return 241 | } 242 | 243 | offset += 8 244 | } 245 | 246 | // Verify padding length 247 | if len(plaintext) < offset+2 { 248 | err = fmt.Errorf("packet too short to contain padding length field: %d", len(plaintext)) 249 | return 250 | } 251 | 252 | paddingLen := int(binary.BigEndian.Uint16(plaintext[offset : offset+2])) 253 | if paddingLen < MinPaddingLength || paddingLen > MaxPaddingLength { 254 | err = ErrPaddingLengthOutOfRange 255 | return 256 | } 257 | 258 | offset += 2 259 | 260 | // Verify padding 261 | if len(plaintext) < offset+paddingLen { 262 | err = fmt.Errorf("packet too short (%d) to contain specified length (%d) of padding", len(plaintext), paddingLen) 263 | return 264 | } 265 | 266 | offset += paddingLen 267 | } 268 | 269 | socksAddrStart = offset 270 | 271 | // Parse socks address 272 | socksAddr, err = socks.SplitAddr(plaintext[offset:]) 273 | if err != nil { 274 | err = fmt.Errorf("failed to parse target address: %w", err) 275 | return 276 | } 277 | 278 | offset += len(socksAddr) 279 | payload = plaintext[offset:] 280 | 281 | return 282 | } 283 | 284 | func WritePadding(b []byte, paddingLen int) int { 285 | if paddingLen == 0 { 286 | b[0] = 0 287 | b[1] = 0 288 | return 2 289 | } 290 | binary.BigEndian.PutUint16(b, uint16(paddingLen)) 291 | return 2 + paddingLen 292 | } 293 | 294 | // WriteRandomPadding writes padding with a random length 295 | // in the half-open interval (0, max]. 296 | func WriteRandomPadding(b []byte, targetPort int, max int) int { 297 | if max == 0 || targetPort != 53 || len(b) < max { 298 | b[0] = 0 299 | b[1] = 0 300 | return 2 301 | } 302 | 303 | paddingLen := rand.Intn(max) 304 | paddingLen++ 305 | binary.BigEndian.PutUint16(b, uint16(paddingLen)) 306 | return 2 + paddingLen 307 | } 308 | 309 | // WriteUDPHeader fills a Shadowsocks 2022 UDP header into the buffer. 310 | // Make sure to increment packet ID upon returning. 311 | // To write a client header, pass csid as sid, and nil as csid. 312 | // To write a server header, pass ssid as sid, and csid as csid. 313 | // Pass either targetUDPAddr or targetSocksAddr. 314 | // 315 | // For legacy Shadowsocks, call WriteUDPAddrToSocksAddr directly. 316 | // 317 | // No buffer length checks are performed. 318 | // Make sure the buffer can hold the socks address. 319 | func WriteUDPHeader(plaintext []byte, htype byte, sid []byte, pid uint64, csid []byte, targetUDPAddr *net.UDPAddr, targetSocksAddr []byte, paddingLen int) (n int) { 320 | // Write session ID 321 | n = copy(plaintext[:8], sid) 322 | 323 | // Write packet ID 324 | binary.BigEndian.PutUint64(plaintext[n:n+8], pid) 325 | n += 8 326 | 327 | // Write type 328 | plaintext[n] = htype 329 | n++ 330 | 331 | // Write timestamp 332 | nowEpoch := time.Now().Unix() 333 | binary.BigEndian.PutUint64(plaintext[n:n+8], uint64(nowEpoch)) 334 | n += 8 335 | 336 | // Write client session ID 337 | n += copy(plaintext[n:], csid) 338 | 339 | // Write padding length and optionally padding 340 | n += WritePadding(plaintext[n:], paddingLen) 341 | 342 | // Write socks address 343 | switch { 344 | case targetUDPAddr != nil: 345 | n += socks.WriteUDPAddrAsSocksAddr(plaintext[n:], targetUDPAddr) 346 | case targetSocksAddr != nil: 347 | n += copy(plaintext[n:], targetSocksAddr) 348 | } 349 | 350 | return 351 | } 352 | 353 | // WriteClientUDPHeader fills a Shadowsocks UDP header into the buffer. 354 | // 355 | // For Shadowsocks 2022, make sure to increment packet ID upon returning. 356 | // 357 | // No buffer length checks are performed. 358 | // Make sure the buffer can hold the socks address. 359 | func WriteClientUDPHeader(plaintext []byte, cipherConfig CipherConfig, sid []byte, pid uint64, targetAddr net.Addr, maxPacketSize int) (n int, err error) { 360 | if cipherConfig.IsSpec2022 { 361 | n += WriteUDPHeader(plaintext[n:], HeaderTypeClientPacket, sid, pid, nil, nil, nil, 0) 362 | // Go back so we can rewrite/overwrite padding length. 363 | n -= 2 364 | } 365 | 366 | // Determine port and write padding, socks address 367 | var port int 368 | 369 | if udpaddr, ok := targetAddr.(*net.UDPAddr); ok { 370 | port = udpaddr.Port 371 | 372 | // Write padding length and optionally padding 373 | if cipherConfig.IsSpec2022 { 374 | n += WriteRandomPadding(plaintext[n:], port, maxPacketSize-n-2) 375 | } 376 | 377 | n += socks.WriteUDPAddrAsSocksAddr(plaintext[n:], udpaddr) 378 | } else { 379 | host, portString, err := net.SplitHostPort(targetAddr.String()) 380 | if err != nil { 381 | return n, fmt.Errorf("failed to split host:port: %w", err) 382 | } 383 | 384 | portnum, err := strconv.ParseUint(portString, 10, 16) 385 | if err != nil { 386 | return n, fmt.Errorf("failed to parse port string: %w", err) 387 | } 388 | port = int(portnum) 389 | 390 | // Write padding length and optionally padding 391 | if cipherConfig.IsSpec2022 { 392 | n += WriteRandomPadding(plaintext[n:], port, maxPacketSize-n-2) 393 | } 394 | 395 | if ip := net.ParseIP(host); ip != nil { 396 | if ip4 := ip.To4(); ip4 != nil { 397 | plaintext[n] = socks.AtypIPv4 398 | n++ 399 | n += copy(plaintext[n:], ip4) 400 | } else { 401 | plaintext[n] = socks.AtypIPv6 402 | n++ 403 | n += copy(plaintext[n:], ip) 404 | } 405 | } else { 406 | if len(host) > 255 { 407 | return n, fmt.Errorf("host is too long: %d, must not be greater than 255", len(host)) 408 | } 409 | plaintext[n] = socks.AtypDomainName 410 | n++ 411 | plaintext[n] = byte(len(host)) 412 | n++ 413 | n += copy(plaintext[n:], host) 414 | } 415 | 416 | binary.BigEndian.PutUint16(plaintext[n:], uint16(port)) 417 | n += 2 418 | } 419 | 420 | return 421 | } 422 | -------------------------------------------------------------------------------- /shadowsocks/packet.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Jigsaw Operations LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package shadowsocks 16 | 17 | import ( 18 | "crypto/cipher" 19 | "errors" 20 | "io" 21 | ) 22 | 23 | var ErrShortPacket = errors.New("short packet") 24 | 25 | // Pack encrypts a Shadowsocks-UDP packet and returns a slice containing the encrypted packet. 26 | // dst must be big enough to hold the encrypted packet. 27 | // If plaintext and dst overlap but are not aligned for in-place encryption, this 28 | // function will panic. 29 | func Pack(dst, plaintext []byte, cipher *Cipher) ([]byte, error) { 30 | if !cipher.config.IsSpec2022 { 31 | saltSize := cipher.SaltSize() 32 | if len(dst) < saltSize { 33 | return nil, io.ErrShortBuffer 34 | } 35 | salt := dst[:saltSize] 36 | if err := RandomSaltGenerator.GetSalt(salt); err != nil { 37 | return nil, err 38 | } 39 | 40 | aead, err := cipher.NewAEAD(salt) 41 | if err != nil { 42 | return nil, err 43 | } 44 | 45 | if len(dst) < saltSize+len(plaintext)+aead.Overhead() { 46 | return nil, io.ErrShortBuffer 47 | } 48 | return aead.Seal(salt, zeroNonce[:aead.NonceSize()], plaintext, nil), nil 49 | } 50 | 51 | nonceSize := cipher.udpAEAD.NonceSize() 52 | 53 | if len(dst) < nonceSize+len(plaintext)+cipher.udpAEAD.Overhead() { 54 | return nil, io.ErrShortBuffer 55 | } 56 | 57 | // Random nonce 58 | if err := Blake3KeyedHashSaltGenerator.GetSalt(dst[:nonceSize]); err != nil { 59 | return nil, err 60 | } 61 | 62 | // Seal AEAD plaintext 63 | return cipher.udpAEAD.Seal(dst[:nonceSize], dst[:nonceSize], plaintext, nil), nil 64 | } 65 | 66 | // Pack function for 2022-blake3-aes-256-gcm. 67 | // Do not encrypt header before calling this function. 68 | // This function encrypts the separate header after sealing AEAD. 69 | // 70 | // plaintext should start with the separate header. 71 | func PackAesWithSeparateHeader(dst, plaintext []byte, cipher *Cipher, sessionAEAD cipher.AEAD) ([]byte, error) { 72 | if len(dst) < 16+len(plaintext)+sessionAEAD.Overhead() { 73 | return nil, io.ErrShortBuffer 74 | } 75 | 76 | // Seal AEAD plaintext 77 | ciphertext := sessionAEAD.Seal(dst[:16], dst[4:16], plaintext[16:], nil) 78 | 79 | // Encrypt header 80 | cipher.separateHeaderCipher.Encrypt(ciphertext[:16], ciphertext[:16]) 81 | 82 | return ciphertext, nil 83 | } 84 | 85 | // Unpack decrypts a Shadowsocks UDP packet and returns 86 | // the plaintext offset in the original packet buffer, 87 | // a slice containing the decrypted plaintext (header + payload) or an error. 88 | // 89 | // If dst is present, it is used to store the plaintext, and must have enough capacity. 90 | // If dst is nil, decryption proceeds in-place. 91 | func Unpack(dst, pkt []byte, cipher *Cipher) (plaintextStart int, plaintext []byte, err error) { 92 | switch { 93 | case cipher.config.IsSpec2022: 94 | plaintextStart = cipher.udpAEAD.NonceSize() 95 | default: 96 | plaintextStart = cipher.SaltSize() 97 | } 98 | 99 | if len(pkt) < plaintextStart { 100 | err = ErrShortPacket 101 | return 102 | } 103 | 104 | if dst == nil { 105 | dst = pkt[plaintextStart:] 106 | } 107 | 108 | // Open AEAD ciphertext 109 | switch { 110 | case cipher.config.IsSpec2022: 111 | plaintext, err = cipher.udpAEAD.Open(dst[:0], pkt[:plaintextStart], pkt[plaintextStart:], nil) 112 | default: 113 | plaintext, err = DecryptOnce(cipher, pkt[:plaintextStart], dst[:0], pkt[plaintextStart:]) 114 | } 115 | 116 | return 117 | } 118 | 119 | // Unpack function for 2022-blake3-aes-256-gcm. 120 | // If separateHeader is nil, DecryptSeparateheader MUST be called to decrypte the separate header in-place 121 | // before passing the ciphertext. 122 | // The returned buffer includes the separate header. 123 | func UnpackAesWithSeparateHeader(dst, pkt, separateHeader []byte, unpackAEAD cipher.AEAD) ([]byte, error) { 124 | if len(pkt) <= 16+unpackAEAD.Overhead() { 125 | return nil, ErrShortPacket 126 | } 127 | 128 | if dst == nil { 129 | dst = pkt 130 | } 131 | 132 | if separateHeader == nil { 133 | separateHeader = pkt[:16] 134 | } 135 | 136 | copy(dst, separateHeader) 137 | 138 | // Open AEAD ciphertext 139 | buf, err := unpackAEAD.Open(dst[:16], separateHeader[4:], pkt[16:], nil) 140 | if err != nil { 141 | return nil, err 142 | } 143 | 144 | return buf, nil 145 | } 146 | -------------------------------------------------------------------------------- /shadowsocks/salt.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Jigsaw Operations LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package shadowsocks 16 | 17 | import ( 18 | "crypto/rand" 19 | "io" 20 | 21 | "lukechampine.com/blake3" 22 | ) 23 | 24 | // SaltGenerator generates unique salts to use in Shadowsocks connections. 25 | type SaltGenerator interface { 26 | // Returns a new salt 27 | GetSalt(salt []byte) error 28 | } 29 | 30 | // randomSaltGenerator generates a new random salt. 31 | type randomSaltGenerator struct{} 32 | 33 | // GetSalt outputs a random salt. 34 | func (randomSaltGenerator) GetSalt(salt []byte) error { 35 | _, err := rand.Read(salt) 36 | return err 37 | } 38 | 39 | type blake3KeyedHashSaltGenerator struct { 40 | r io.Reader 41 | } 42 | 43 | func NewBlake3KeyedHashReader(size int) SaltGenerator { 44 | key := make([]byte, 32) 45 | _, err := rand.Read(key) 46 | if err != nil { 47 | panic(err) 48 | } 49 | 50 | h := blake3.New(size, key) 51 | return &blake3KeyedHashSaltGenerator{ 52 | r: h.XOF(), 53 | } 54 | } 55 | 56 | func (g *blake3KeyedHashSaltGenerator) GetSalt(salt []byte) error { 57 | _, err := g.r.Read(salt) 58 | return err 59 | } 60 | 61 | var ( 62 | // RandomSaltGenerator is a basic SaltGenerator. 63 | RandomSaltGenerator SaltGenerator = randomSaltGenerator{} 64 | 65 | // High performance random salt/nonce generator 66 | Blake3KeyedHashSaltGenerator = NewBlake3KeyedHashReader(24) 67 | ) 68 | -------------------------------------------------------------------------------- /shadowsocks/salt_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Jigsaw Operations LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package shadowsocks 16 | 17 | import ( 18 | "bytes" 19 | "testing" 20 | ) 21 | 22 | func TestRandomSaltGenerator(t *testing.T) { 23 | if err := RandomSaltGenerator.GetSalt(nil); err != nil { 24 | t.Error(err) 25 | } 26 | salt := make([]byte, 16) 27 | if err := RandomSaltGenerator.GetSalt(salt); err != nil { 28 | t.Error(err) 29 | } 30 | if bytes.Equal(salt, make([]byte, 16)) { 31 | t.Error("Salt is all zeros") 32 | } 33 | } 34 | 35 | func BenchmarkRandomSaltGenerator(b *testing.B) { 36 | b.RunParallel(func(pb *testing.PB) { 37 | salt := make([]byte, 32) 38 | for pb.Next() { 39 | if err := RandomSaltGenerator.GetSalt(salt); err != nil { 40 | b.Fatal(err) 41 | } 42 | } 43 | }) 44 | } 45 | -------------------------------------------------------------------------------- /shadowsocks/stream_test.go: -------------------------------------------------------------------------------- 1 | package shadowsocks 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "io" 7 | "strings" 8 | "sync" 9 | "testing" 10 | "time" 11 | 12 | "golang.org/x/crypto/chacha20poly1305" 13 | ) 14 | 15 | func newTestCipher(t *testing.T) *Cipher { 16 | cipher, err := NewCipher("chacha20-ietf-poly1305", "test secret") 17 | if err != nil { 18 | t.Fatal(err) 19 | } 20 | return cipher 21 | } 22 | 23 | // Overhead for cipher chacha20poly1305 24 | const testCipherOverhead = 16 25 | 26 | func TestCipherReaderAuthenticationFailure(t *testing.T) { 27 | cipher := newTestCipher(t) 28 | 29 | clientReader := strings.NewReader("Fails Authentication") 30 | reader := NewShadowsocksReader(clientReader, cipher) 31 | _, err := reader.Read(make([]byte, 1)) 32 | if err == nil { 33 | t.Fatalf("Expected authentication failure, got %v", err) 34 | } 35 | } 36 | 37 | func TestCipherReaderUnexpectedEOF(t *testing.T) { 38 | cipher := newTestCipher(t) 39 | 40 | clientReader := strings.NewReader("short") 41 | server := NewShadowsocksReader(clientReader, cipher) 42 | _, err := server.Read(make([]byte, 10)) 43 | if err != io.ErrUnexpectedEOF { 44 | t.Fatalf("Expected ErrUnexpectedEOF, got %v", err) 45 | } 46 | } 47 | 48 | func TestCipherReaderEOF(t *testing.T) { 49 | cipher := newTestCipher(t) 50 | 51 | clientReader := strings.NewReader("") 52 | server := NewShadowsocksReader(clientReader, cipher) 53 | _, err := server.Read(make([]byte, 10)) 54 | if err != io.EOF { 55 | t.Fatalf("Expected EOF, got %v", err) 56 | } 57 | _, err = server.Read([]byte{}) 58 | if err != io.EOF { 59 | t.Fatalf("Expected EOF, got %v", err) 60 | } 61 | } 62 | 63 | func encryptBlocks(cipher *Cipher, salt []byte, blocks [][]byte) (io.Reader, error) { 64 | var ssText bytes.Buffer 65 | aead, err := cipher.NewAEAD(salt) 66 | if err != nil { 67 | return nil, fmt.Errorf("Failed to create AEAD: %v", err) 68 | } 69 | ssText.Write(salt) 70 | // buf must fit the larges block ciphertext 71 | buf := make([]byte, 2+100+testCipherOverhead) 72 | var expectedCipherSize int 73 | nonce := make([]byte, chacha20poly1305.NonceSize) 74 | for _, block := range blocks { 75 | ssText.Write(aead.Seal(buf[:0], nonce, []byte{0, byte(len(block))}, nil)) 76 | nonce[0]++ 77 | expectedCipherSize += 2 + testCipherOverhead 78 | ssText.Write(aead.Seal(buf[:0], nonce, block, nil)) 79 | nonce[0]++ 80 | expectedCipherSize += len(block) + testCipherOverhead 81 | } 82 | if ssText.Len() != cipher.SaltSize()+expectedCipherSize { 83 | return nil, fmt.Errorf("cipherText has size %v. Expected %v", ssText.Len(), cipher.SaltSize()+expectedCipherSize) 84 | } 85 | return &ssText, nil 86 | } 87 | 88 | func TestCipherReaderGoodReads(t *testing.T) { 89 | cipher := newTestCipher(t) 90 | 91 | salt := []byte("12345678901234567890123456789012") 92 | if len(salt) != cipher.SaltSize() { 93 | t.Fatalf("Salt has size %v. Expected %v", len(salt), cipher.SaltSize()) 94 | } 95 | ssText, err := encryptBlocks(cipher, salt, [][]byte{ 96 | []byte("[First Block]"), 97 | []byte(""), // Corner case: empty block 98 | []byte("[Third Block]")}) 99 | if err != nil { 100 | t.Fatal(err) 101 | } 102 | 103 | reader := NewShadowsocksReader(ssText, cipher) 104 | plainText := make([]byte, len("[First Block]")+len("[Third Block]")) 105 | n, err := io.ReadFull(reader, plainText) 106 | if err != nil { 107 | t.Fatalf("Failed to fully read plain text. Got %v bytes: %v", n, err) 108 | } 109 | _, err = reader.Read([]byte{}) 110 | if err != io.EOF { 111 | t.Fatalf("Expected EOF, got %v", err) 112 | } 113 | _, err = reader.Read(make([]byte, 1)) 114 | if err != io.EOF { 115 | t.Fatalf("Expected EOF, got %v", err) 116 | } 117 | } 118 | 119 | func TestCipherReaderClose(t *testing.T) { 120 | cipher := newTestCipher(t) 121 | 122 | pipeReader, pipeWriter := io.Pipe() 123 | server := NewShadowsocksReader(pipeReader, cipher) 124 | result := make(chan error) 125 | go func() { 126 | _, err := server.Read(make([]byte, 10)) 127 | result <- err 128 | }() 129 | pipeWriter.Close() 130 | err := <-result 131 | if err != io.EOF { 132 | t.Fatalf("Expected ErrUnexpectedEOF, got %v", err) 133 | } 134 | } 135 | 136 | func TestCipherReaderCloseError(t *testing.T) { 137 | cipher := newTestCipher(t) 138 | 139 | pipeReader, pipeWriter := io.Pipe() 140 | server := NewShadowsocksReader(pipeReader, cipher) 141 | result := make(chan error) 142 | go func() { 143 | _, err := server.Read(make([]byte, 10)) 144 | result <- err 145 | }() 146 | pipeWriter.CloseWithError(fmt.Errorf("xx!!ERROR!!xx")) 147 | err := <-result 148 | if err == nil || !strings.Contains(err.Error(), "xx!!ERROR!!xx") { 149 | t.Fatalf("Unexpected error: %v", err) 150 | } 151 | } 152 | 153 | func TestEndToEnd(t *testing.T) { 154 | cipher := newTestCipher(t) 155 | 156 | connReader, connWriter := io.Pipe() 157 | writer, err := NewShadowsocksWriter(connWriter, cipher, nil, nil, false) 158 | if err != nil { 159 | t.Fatalf("Failed NewShadowsocksWriter: %v", err) 160 | } 161 | reader := NewShadowsocksReader(connReader, cipher) 162 | expected := "Test" 163 | ch := make(chan error, 1) 164 | go func() { 165 | defer connWriter.Close() 166 | _, err := writer.Write([]byte(expected)) 167 | ch <- err 168 | }() 169 | var output bytes.Buffer 170 | _, err = reader.WriteTo(&output) 171 | if err != nil { 172 | t.Fatalf("Failed WriteTo: %v", err) 173 | } 174 | if output.String() != expected { 175 | t.Fatalf("Expected output '%v'. Got '%v'", expected, output.String()) 176 | } 177 | err = <-ch 178 | if err != nil { 179 | t.Fatalf("Failed Write: %v", err) 180 | } 181 | } 182 | 183 | func TestLazyWriteFlush(t *testing.T) { 184 | cipher := newTestCipher(t) 185 | buf := new(bytes.Buffer) 186 | header := []byte{1, 2, 3, 4} 187 | writer, err := NewShadowsocksWriter(buf, cipher, nil, header, false) 188 | if err != nil { 189 | t.Fatalf("Failed NewShadowsocksWriter: %v", err) 190 | } 191 | if buf.Len() != 0 { 192 | t.Errorf("LazyWrite isn't lazy: %v", buf.Bytes()) 193 | } 194 | if err = writer.Flush(); err != nil { 195 | t.Errorf("Flush failed: %v", err) 196 | } 197 | len1 := buf.Len() 198 | if len1 <= len(header) { 199 | t.Errorf("Not enough bytes flushed: %d", len1) 200 | } 201 | 202 | // Check that normal writes now work 203 | body := []byte{5, 6, 7} 204 | n, err := writer.Write(body) 205 | if n != len(body) { 206 | t.Errorf("Wrong write size: %d", n) 207 | } 208 | if err != nil { 209 | t.Errorf("Write failed: %v", err) 210 | } 211 | if buf.Len() == len1 { 212 | t.Errorf("No write observed") 213 | } 214 | 215 | // Verify content arrives in two blocks 216 | reader := NewShadowsocksReader(buf, cipher) 217 | decrypted := make([]byte, len(header)+len(body)) 218 | n, err = reader.Read(decrypted) 219 | if n != len(header) { 220 | t.Errorf("Wrong number of bytes out: %d", n) 221 | } 222 | if err != nil { 223 | t.Errorf("Read failed: %v", err) 224 | } 225 | if !bytes.Equal(decrypted[:n], header) { 226 | t.Errorf("Wrong final content: %v", decrypted) 227 | } 228 | n, err = reader.Read(decrypted[n:]) 229 | if n != len(body) { 230 | t.Errorf("Wrong number of bytes out: %d", n) 231 | } 232 | if err != nil { 233 | t.Errorf("Read failed: %v", err) 234 | } 235 | if !bytes.Equal(decrypted[len(header):], body) { 236 | t.Errorf("Wrong final content: %v", decrypted) 237 | } 238 | } 239 | 240 | func TestLazyWriteConcat(t *testing.T) { 241 | cipher := newTestCipher(t) 242 | buf := new(bytes.Buffer) 243 | header := []byte{1, 2, 3, 4} 244 | writer, err := NewShadowsocksWriter(buf, cipher, nil, header, false) 245 | if err != nil { 246 | t.Fatalf("Failed NewShadowsocksWriter: %v", err) 247 | } 248 | if buf.Len() != 0 { 249 | t.Errorf("LazyWrite isn't lazy: %v", buf.Bytes()) 250 | } 251 | 252 | // Write additional data and flush the header. 253 | body := []byte{5, 6, 7} 254 | n, err := writer.Write(body) 255 | if n != len(body) { 256 | t.Errorf("Wrong write size: %d", n) 257 | } 258 | if err != nil { 259 | t.Errorf("Write failed: %v", err) 260 | } 261 | len1 := buf.Len() 262 | if len1 <= len(body)+len(header) { 263 | t.Errorf("Not enough bytes flushed: %d", len1) 264 | } 265 | 266 | // Flush after write should have no effect 267 | if err = writer.Flush(); err != nil { 268 | t.Errorf("Flush failed: %v", err) 269 | } 270 | if buf.Len() != len1 { 271 | t.Errorf("Flush should have no effect") 272 | } 273 | 274 | // Verify content arrives in one block 275 | reader := NewShadowsocksReader(buf, cipher) 276 | decrypted := make([]byte, len(body)+len(header)) 277 | n, err = reader.Read(decrypted) 278 | if n != len(decrypted) { 279 | t.Errorf("Wrong number of bytes out: %d", n) 280 | } 281 | if err != nil { 282 | t.Errorf("Read failed: %v", err) 283 | } 284 | if !bytes.Equal(decrypted[:len(header)], header) || 285 | !bytes.Equal(decrypted[len(header):], body) { 286 | t.Errorf("Wrong final content: %v", decrypted) 287 | } 288 | } 289 | 290 | func TestLazyWriteConcurrentFlush(t *testing.T) { 291 | cipher := newTestCipher(t) 292 | buf := new(bytes.Buffer) 293 | header := []byte{1, 2, 3, 4} 294 | writer, err := NewShadowsocksWriter(buf, cipher, nil, header, false) 295 | if err != nil { 296 | t.Fatalf("Failed NewShadowsocksWriter: %v", err) 297 | } 298 | if buf.Len() != 0 { 299 | t.Errorf("LazyWrite isn't lazy: %v", buf.Bytes()) 300 | } 301 | 302 | body := []byte{5, 6, 7} 303 | r, w := io.Pipe() 304 | wg := sync.WaitGroup{} 305 | wg.Add(1) 306 | go func() { 307 | n, err := writer.ReadFrom(r) 308 | if n != int64(len(body)) { 309 | t.Errorf("ReadFrom: Wrong read size %d", n) 310 | } 311 | if err != nil { 312 | t.Errorf("ReadFrom: %v", err) 313 | } 314 | wg.Done() 315 | }() 316 | 317 | // Wait for ReadFrom to start and get blocked. 318 | time.Sleep(200 * time.Millisecond) 319 | 320 | // Flush while ReadFrom is blocked. 321 | if err := writer.Flush(); err != nil { 322 | t.Errorf("Flush error: %v", err) 323 | } 324 | len1 := buf.Len() 325 | if len1 == 0 { 326 | t.Errorf("No bytes flushed") 327 | } 328 | 329 | // Check that normal writes now work 330 | n, err := w.Write(body) 331 | if n != len(body) { 332 | t.Errorf("Wrong write size: %d", n) 333 | } 334 | if err != nil { 335 | t.Errorf("Write failed: %v", err) 336 | } 337 | w.Close() 338 | wg.Wait() 339 | if buf.Len() == len1 { 340 | t.Errorf("No write observed") 341 | } 342 | 343 | // Verify content arrives in two blocks 344 | reader := NewShadowsocksReader(buf, cipher) 345 | decrypted := make([]byte, len(header)+len(body)) 346 | n, err = reader.Read(decrypted) 347 | if n != len(header) { 348 | t.Errorf("Wrong number of bytes out: %d", n) 349 | } 350 | if err != nil { 351 | t.Errorf("Read failed: %v", err) 352 | } 353 | if !bytes.Equal(decrypted[:len(header)], header) { 354 | t.Errorf("Wrong final content: %v", decrypted) 355 | } 356 | n, err = reader.Read(decrypted[len(header):]) 357 | if n != len(body) { 358 | t.Errorf("Wrong number of bytes out: %d", n) 359 | } 360 | if err != nil { 361 | t.Errorf("Read failed: %v", err) 362 | } 363 | if !bytes.Equal(decrypted[len(header):], body) { 364 | t.Errorf("Wrong final content: %v", decrypted) 365 | } 366 | } 367 | -------------------------------------------------------------------------------- /slicepool/slicepool.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Jigsaw Operations LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package slicepool 16 | 17 | import ( 18 | "sync" 19 | ) 20 | 21 | // Pool wraps a sync.Pool of *[]byte. To encourage correct usage, 22 | // all public methods are on slicepool.LazySlice. 23 | // 24 | // All copies of a Pool refer to the same underlying pool. 25 | // 26 | // "*[]byte" is used to avoid a heap allocation when passing a 27 | // []byte to sync.Pool.Put, which leaks its argument to the heap. 28 | type Pool struct { 29 | pool *sync.Pool 30 | len int 31 | } 32 | 33 | // MakePool returns a Pool of slices with the specified length. 34 | func MakePool(sliceLen int) Pool { 35 | return Pool{ 36 | pool: &sync.Pool{ 37 | New: func() interface{} { 38 | slice := make([]byte, sliceLen) 39 | // Return a *[]byte instead of []byte ensures that 40 | // the []byte is not copied, which would cause a heap 41 | // allocation on every call to sync.pool.Put 42 | return &slice 43 | }, 44 | }, 45 | len: sliceLen, 46 | } 47 | } 48 | 49 | func (p *Pool) get() *[]byte { 50 | return p.pool.Get().(*[]byte) 51 | } 52 | 53 | func (p *Pool) put(b *[]byte) { 54 | if len(*b) != p.len || cap(*b) != p.len { 55 | panic("Buffer length mismatch") 56 | } 57 | p.pool.Put(b) 58 | } 59 | 60 | // LazySlice returns an empty LazySlice tied to this Pool. 61 | func (p *Pool) LazySlice() LazySlice { 62 | return LazySlice{pool: p} 63 | } 64 | 65 | // LazySlice holds 0 or 1 slices from a particular Pool. 66 | type LazySlice struct { 67 | slice *[]byte 68 | pool *Pool 69 | } 70 | 71 | // Acquire this slice from the pool and return it. 72 | // This slice must not already be acquired. 73 | func (b *LazySlice) Acquire() []byte { 74 | if b.slice != nil { 75 | panic("buffer already acquired") 76 | } 77 | b.slice = b.pool.get() 78 | return *b.slice 79 | } 80 | 81 | // Release the buffer back to the pool, unless the box is empty. 82 | // The caller must discard any references to the buffer. 83 | func (b *LazySlice) Release() { 84 | if b.slice != nil { 85 | b.pool.put(b.slice) 86 | b.slice = nil 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /slicepool/slicepool_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Jigsaw Operations LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package slicepool 16 | 17 | import ( 18 | "testing" 19 | ) 20 | 21 | func TestPool(t *testing.T) { 22 | pool := MakePool(10) 23 | slice := pool.LazySlice() 24 | buf := slice.Acquire() 25 | if len(buf) != 10 { 26 | t.Errorf("Wrong slice length: %d", len(buf)) 27 | } 28 | slice.Release() 29 | } 30 | 31 | func BenchmarkPool(b *testing.B) { 32 | pool := MakePool(10) 33 | for i := 0; i < b.N; i++ { 34 | slice := pool.LazySlice() 35 | slice.Acquire() 36 | slice.Release() 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /socks/socks.go: -------------------------------------------------------------------------------- 1 | package socks 2 | 3 | import ( 4 | "encoding/binary" 5 | "errors" 6 | "fmt" 7 | "io" 8 | "math/rand" 9 | "net" 10 | "strconv" 11 | 12 | onet "github.com/Shadowsocks-NET/outline-ss-server/net" 13 | ) 14 | 15 | // SOCKS request commands as defined in RFC 1928 section 4. 16 | const ( 17 | CmdConnect = 1 18 | CmdBind = 2 19 | CmdUDPAssociate = 3 20 | ) 21 | 22 | // SOCKS address types as defined in RFC 1928 section 5. 23 | const ( 24 | AtypIPv4 = 1 25 | AtypDomainName = 3 26 | AtypIPv6 = 4 27 | ) 28 | 29 | // SOCKS errors as defined in RFC 1928 section 6. 30 | const ( 31 | Succeeded = 0 32 | ErrGeneralFailure = 1 33 | ErrConnectionNotAllowed = 2 34 | ErrNetworkUnreachable = 3 35 | ErrHostUnreachable = 4 36 | ErrConnectionRefused = 5 37 | ErrTTLExpired = 6 38 | ErrCommandNotSupported = 7 39 | ErrAddressNotSupported = 8 40 | ) 41 | 42 | const ( 43 | SocksAddressIPv4Length = 1 + net.IPv4len + 2 44 | SocksAddressIPv6Length = 1 + net.IPv6len + 2 45 | 46 | // MaxAddrLen is the maximum size of SOCKS address in bytes. 47 | MaxAddrLen = 1 + 1 + 255 + 2 48 | ) 49 | 50 | // Addr represents a SOCKS address as defined in RFC 1928 section 5. 51 | type Addr []byte 52 | 53 | // String serializes SOCKS address a to string form. 54 | func (a Addr) String() string { 55 | var host, port string 56 | 57 | switch a[0] { // address type 58 | case AtypDomainName: 59 | host = string(a[2 : 2+int(a[1])]) 60 | port = strconv.Itoa((int(a[2+int(a[1])]) << 8) | int(a[2+int(a[1])+1])) 61 | case AtypIPv4: 62 | host = net.IP(a[1 : 1+net.IPv4len]).String() 63 | port = strconv.Itoa((int(a[1+net.IPv4len]) << 8) | int(a[1+net.IPv4len+1])) 64 | case AtypIPv6: 65 | host = net.IP(a[1 : 1+net.IPv6len]).String() 66 | port = strconv.Itoa((int(a[1+net.IPv6len]) << 8) | int(a[1+net.IPv6len+1])) 67 | } 68 | 69 | return net.JoinHostPort(host, port) 70 | } 71 | 72 | func (a Addr) Addr(network string) (net.Addr, error) { 73 | var ip net.IP 74 | var port int 75 | 76 | switch a[0] { 77 | case AtypDomainName: 78 | return onet.NewAddr(a.String(), network), nil 79 | case AtypIPv4: 80 | ip = net.IP(a[1 : 1+4]) 81 | port = int(binary.BigEndian.Uint16(a[1+4 : 1+4+2])) 82 | case AtypIPv6: 83 | ip = net.IP(a[1 : 1+16]) 84 | port = int(binary.BigEndian.Uint16(a[1+16 : 1+16+2])) 85 | default: 86 | return nil, fmt.Errorf("unknown atyp %v", a[0]) 87 | } 88 | 89 | switch network { 90 | case "tcp", "tcp4", "tcp6": 91 | return &net.TCPAddr{ 92 | IP: ip, 93 | Port: port, 94 | }, nil 95 | case "udp", "udp4", "udp6": 96 | return &net.UDPAddr{ 97 | IP: ip, 98 | Port: port, 99 | }, nil 100 | default: 101 | return nil, fmt.Errorf("unknown network %s", network) 102 | } 103 | } 104 | 105 | func (a Addr) UDPAddr(preferIPv6 bool) (*net.UDPAddr, error) { 106 | switch a[0] { 107 | case AtypDomainName: 108 | return resolveUDPAddr(a.String(), preferIPv6) 109 | case AtypIPv4: 110 | return &net.UDPAddr{ 111 | IP: net.IP(a[1 : 1+4]), 112 | Port: int(binary.BigEndian.Uint16(a[1+4 : 1+4+2])), 113 | }, nil 114 | case AtypIPv6: 115 | return &net.UDPAddr{ 116 | IP: net.IP(a[1 : 1+16]), 117 | Port: int(binary.BigEndian.Uint16(a[1+16 : 1+16+2])), 118 | }, nil 119 | default: 120 | return nil, fmt.Errorf("unknown atyp %v", a[0]) 121 | } 122 | } 123 | 124 | // resolveUDPAddr resolves an address in domain:port format into *net.UDPAddr. 125 | // ip:port is not supported. 126 | func resolveUDPAddr(address string, preferIPv6 bool) (*net.UDPAddr, error) { 127 | if !preferIPv6 { 128 | return net.ResolveUDPAddr("udp", address) 129 | } 130 | 131 | // Although !preferIPv6 was short-circuited to use net.ResolveUDPAddr(), 132 | // the following code is generic and takes preferIPv6 into account. 133 | 134 | host, port, err := net.SplitHostPort(address) 135 | if err != nil { 136 | return nil, err 137 | } 138 | portNum, err := net.LookupPort("udp", port) 139 | if err != nil { 140 | return nil, err 141 | } 142 | ips, err := net.LookupIP(host) 143 | if err != nil { 144 | return nil, err 145 | } 146 | 147 | // We can't actually do fallbacks here. 148 | // If preferIPv6 is true, v6 -> primaries, v4 -> fallbacks. 149 | // And vice versa. 150 | // Then we select a random IP from primaries, or fallbacks if primaries is empty. 151 | var primaries, fallbacks []net.IP 152 | 153 | for _, ipc := range ips { 154 | ip4 := ipc.To4() 155 | switch { 156 | case preferIPv6 && ip4 == nil || !preferIPv6 && ip4 != nil: // Prefer 6/4 and got 6/4 157 | primaries = append(primaries, ipc) 158 | case preferIPv6 && ip4 != nil || !preferIPv6 && ip4 == nil: // Prefer 6/4 and got 4/6 159 | fallbacks = append(fallbacks, ipc) 160 | } 161 | } 162 | 163 | var ip net.IP 164 | 165 | switch { 166 | case len(primaries) > 0: 167 | ip = primaries[rand.Intn(len(primaries))] 168 | case len(fallbacks) > 0: 169 | ip = fallbacks[rand.Intn(len(fallbacks))] 170 | default: 171 | return nil, errors.New("lookup returned no addresses and no error") 172 | } 173 | 174 | return &net.UDPAddr{ 175 | IP: ip, 176 | Port: portNum, 177 | }, nil 178 | } 179 | 180 | // WriteAddr parses an address string into a socks address 181 | // and writes to the destination slice. 182 | // 183 | // The destination slice must be big enough to hold the socks address. 184 | // Otherwise, this function might panic. 185 | func WriteAddr(dst []byte, s string) (n int, host string, port int, err error) { 186 | host, portString, err := net.SplitHostPort(s) 187 | if err != nil { 188 | err = fmt.Errorf("failed to split host:port: %w", err) 189 | return 190 | } 191 | 192 | if ip := net.ParseIP(host); ip != nil { 193 | if ip4 := ip.To4(); ip4 != nil { 194 | dst[n] = AtypIPv4 195 | n++ 196 | n += copy(dst[n:], ip4) 197 | } else { 198 | dst[n] = AtypIPv6 199 | n++ 200 | n += copy(dst[n:], ip) 201 | } 202 | } else { 203 | if len(host) > 255 { 204 | err = fmt.Errorf("host is too long: %d, must not be greater than 255", len(host)) 205 | return 206 | } 207 | dst[n] = AtypDomainName 208 | n++ 209 | dst[n] = byte(len(host)) 210 | n++ 211 | n += copy(dst[n:], host) 212 | } 213 | 214 | portnum, err := strconv.ParseUint(portString, 10, 16) 215 | if err != nil { 216 | err = fmt.Errorf("failed to parse port string: %w", err) 217 | return 218 | } 219 | binary.BigEndian.PutUint16(dst[n:], uint16(portnum)) 220 | n += 2 221 | port = int(portnum) 222 | 223 | return 224 | } 225 | 226 | // WriteUDPAddrAsSocksAddr converts a UDP address 227 | // into socks address and writes to the buffer. 228 | // 229 | // No buffer length checks are performed. 230 | // Make sure the buffer can hold the socks address. 231 | func WriteUDPAddrAsSocksAddr(b []byte, addr *net.UDPAddr) (n int) { 232 | n = 1 233 | 234 | if ip4 := addr.IP.To4(); ip4 != nil { 235 | b[0] = AtypIPv4 236 | n += copy(b[n:], ip4) 237 | } else { 238 | b[0] = AtypIPv6 239 | n += copy(b[n:], addr.IP) 240 | } 241 | 242 | binary.BigEndian.PutUint16(b[n:], uint16(addr.Port)) 243 | n += 2 244 | return 245 | } 246 | 247 | // ParseAddr parses an address string into a socks address. 248 | // 249 | // To avoid allocation, use WriteAddr instead. 250 | func ParseAddr(s string) (Addr, error) { 251 | dst := make([]byte, MaxAddrLen) 252 | n, _, _, err := WriteAddr(dst, s) 253 | if err != nil { 254 | return nil, err 255 | } 256 | return dst[:n], nil 257 | } 258 | 259 | // ReadAddr reads just enough bytes from r to get a valid Addr. 260 | // 261 | // The destination slice must be big enough to hold the socks address. 262 | // Otherwise, this function might panic. 263 | func ReadAddr(dst []byte, r io.Reader) (n int, err error) { 264 | n, err = io.ReadFull(r, dst[:1]) // read 1st byte for address type 265 | if err != nil { 266 | return 267 | } 268 | 269 | switch dst[0] { 270 | case AtypDomainName: 271 | _, err = io.ReadFull(r, dst[1:2]) // read 2nd byte for domain length 272 | if err != nil { 273 | return 274 | } 275 | domainLen := int(dst[1]) 276 | n += 1 + domainLen + 2 277 | _, err = io.ReadFull(r, dst[2:n]) 278 | return 279 | case AtypIPv4: 280 | n += net.IPv4len + 2 281 | _, err = io.ReadFull(r, dst[1:n]) 282 | return 283 | case AtypIPv6: 284 | n += net.IPv6len + 2 285 | _, err = io.ReadFull(r, dst[1:n]) 286 | return 287 | } 288 | 289 | err = fmt.Errorf("unknown atyp %v", dst[0]) 290 | return 291 | } 292 | 293 | // AddrFromReader allocates and reads a socks address from an io.Reader. 294 | // 295 | // To avoid allocation, use ReadAddr instead. 296 | func AddrFromReader(r io.Reader) (Addr, error) { 297 | dst := make([]byte, MaxAddrLen) 298 | n, err := ReadAddr(dst, r) 299 | if err != nil { 300 | return nil, err 301 | } 302 | return dst[:n], nil 303 | } 304 | 305 | // SplitAddr slices a SOCKS address from beginning of b. Returns nil if failed. 306 | func SplitAddr(b []byte) (Addr, error) { 307 | addrLen := 1 308 | if len(b) < addrLen { 309 | return nil, io.ErrShortBuffer 310 | } 311 | 312 | switch b[0] { 313 | case AtypDomainName: 314 | if len(b) < 2 { 315 | return nil, io.ErrShortBuffer 316 | } 317 | addrLen = 1 + 1 + int(b[1]) + 2 318 | case AtypIPv4: 319 | addrLen = SocksAddressIPv4Length 320 | case AtypIPv6: 321 | addrLen = SocksAddressIPv6Length 322 | default: 323 | return nil, fmt.Errorf("unknown atyp %v", b[0]) 324 | } 325 | 326 | if len(b) < addrLen { 327 | return nil, io.ErrShortBuffer 328 | } 329 | 330 | return b[:addrLen], nil 331 | } 332 | --------------------------------------------------------------------------------