├── .github ├── renovate.json ├── update_dependencies.sh └── workflows │ ├── lint.yml │ └── test.yml ├── .gitignore ├── .golangci.yml ├── LICENSE ├── Makefile ├── README.md ├── chunk_aead.go ├── chunk_length_aead.go ├── chunk_length_stream.go ├── chunk_stream.go ├── chunk_stream_checksum.go ├── client.go ├── client_option.go ├── go.mod ├── go.sum ├── kdf.go ├── mux.go ├── mux_wrapper.go ├── packetaddr ├── conn.go └── packetaddr.go ├── protocol.go ├── service.go ├── service_option.go ├── vless ├── client.go ├── constant.go ├── protocol.go ├── service.go ├── vision.go └── vision_utls.go └── xudp.go /.github/renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "commitMessagePrefix": "[dependencies]", 4 | "extends": [ 5 | "config:base", 6 | ":disableRateLimiting" 7 | ], 8 | "golang": { 9 | "enabled": false 10 | }, 11 | "packageRules": [ 12 | { 13 | "matchManagers": [ 14 | "github-actions" 15 | ], 16 | "groupName": "github-actions" 17 | } 18 | ] 19 | } -------------------------------------------------------------------------------- /.github/update_dependencies.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | PROJECTS=$(dirname "$0")/../.. 4 | go get -x github.com/sagernet/$1@$(git -C $PROJECTS/$1 rev-parse HEAD) 5 | go mod tidy 6 | -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | name: lint 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | - dev 8 | paths-ignore: 9 | - '**.md' 10 | - '.github/**' 11 | - '!.github/workflows/lint.yml' 12 | pull_request: 13 | branches: 14 | - main 15 | - dev 16 | 17 | jobs: 18 | build: 19 | name: Build 20 | runs-on: ubuntu-latest 21 | steps: 22 | - name: Checkout 23 | uses: actions/checkout@v4 24 | with: 25 | fetch-depth: 0 26 | - name: Setup Go 27 | uses: actions/setup-go@v5 28 | with: 29 | go-version: ^1.23 30 | - name: Cache go module 31 | uses: actions/cache@v4 32 | with: 33 | path: | 34 | ~/go/pkg/mod 35 | key: go-${{ hashFiles('**/go.sum') }} 36 | - name: golangci-lint 37 | uses: golangci/golangci-lint-action@v6 38 | with: 39 | version: latest 40 | args: . -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: test 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | - dev 8 | paths-ignore: 9 | - '**.md' 10 | - '.github/**' 11 | - '!.github/workflows/debug.yml' 12 | pull_request: 13 | branches: 14 | - main 15 | - dev 16 | 17 | jobs: 18 | build: 19 | name: Linux 20 | runs-on: ubuntu-latest 21 | steps: 22 | - name: Checkout 23 | uses: actions/checkout@v4 24 | with: 25 | fetch-depth: 0 26 | - name: Setup Go 27 | uses: actions/setup-go@v5 28 | with: 29 | go-version: ^1.23 30 | - name: Build 31 | run: | 32 | make test 33 | build_go120: 34 | name: Linux (Go 1.20) 35 | runs-on: ubuntu-latest 36 | steps: 37 | - name: Checkout 38 | uses: actions/checkout@v4 39 | with: 40 | fetch-depth: 0 41 | - name: Setup Go 42 | uses: actions/setup-go@v5 43 | with: 44 | go-version: ~1.20 45 | continue-on-error: true 46 | - name: Build 47 | run: | 48 | make test 49 | build_go121: 50 | name: Linux (Go 1.21) 51 | runs-on: ubuntu-latest 52 | steps: 53 | - name: Checkout 54 | uses: actions/checkout@v4 55 | with: 56 | fetch-depth: 0 57 | - name: Setup Go 58 | uses: actions/setup-go@v5 59 | with: 60 | go-version: ~1.21 61 | continue-on-error: true 62 | - name: Build 63 | run: | 64 | make test 65 | build_go122: 66 | name: Linux (Go 1.22) 67 | runs-on: ubuntu-latest 68 | steps: 69 | - name: Checkout 70 | uses: actions/checkout@v4 71 | with: 72 | fetch-depth: 0 73 | - name: Setup Go 74 | uses: actions/setup-go@v5 75 | with: 76 | go-version: ~1.22 77 | continue-on-error: true 78 | - name: Build 79 | run: | 80 | make test 81 | build_windows: 82 | name: Windows 83 | runs-on: windows-latest 84 | steps: 85 | - name: Checkout 86 | uses: actions/checkout@v4 87 | with: 88 | fetch-depth: 0 89 | - name: Setup Go 90 | uses: actions/setup-go@v5 91 | with: 92 | go-version: ^1.23 93 | continue-on-error: true 94 | - name: Build 95 | run: | 96 | make test 97 | build_darwin: 98 | name: macOS 99 | runs-on: macos-latest 100 | steps: 101 | - name: Checkout 102 | uses: actions/checkout@v4 103 | with: 104 | fetch-depth: 0 105 | - name: Setup Go 106 | uses: actions/setup-go@v5 107 | with: 108 | go-version: ^1.23 109 | continue-on-error: true 110 | - name: Build 111 | run: | 112 | make test -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.idea/ 2 | /vendor/ 3 | -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | linters: 2 | disable-all: true 3 | enable: 4 | - gofumpt 5 | - govet 6 | - gci 7 | - staticcheck 8 | - paralleltest 9 | - ineffassign 10 | 11 | linters-settings: 12 | gci: 13 | custom-order: true 14 | sections: 15 | - standard 16 | - prefix(github.com/sagernet/) 17 | - default 18 | 19 | run: 20 | go: "1.23" -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (C) 2022 by nekohasekai 2 | 3 | This program is free software: you can redistribute it and/or modify 4 | it under the terms of the GNU General Public License as published by 5 | the Free Software Foundation, either version 3 of the License, or 6 | (at your option) any later version. 7 | 8 | This program is distributed in the hope that it will be useful, 9 | but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | GNU General Public License for more details. 12 | 13 | You should have received a copy of the GNU General Public License 14 | along with this program. If not, see . -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | fmt: 2 | @gofumpt -l -w . 3 | @gofmt -s -w . 4 | @gci write --custom-order -s standard -s "prefix(github.com/sagernet/)" -s "default" . 5 | 6 | fmt_install: 7 | go install -v mvdan.cc/gofumpt@latest 8 | go install -v github.com/daixiang0/gci@latest 9 | 10 | lint: 11 | GOOS=linux golangci-lint run ./... 12 | GOOS=android golangci-lint run ./... 13 | GOOS=windows golangci-lint run ./... 14 | GOOS=darwin golangci-lint run ./... 15 | GOOS=freebsd golangci-lint run ./... 16 | 17 | lint_install: 18 | go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest 19 | 20 | test: 21 | go test -v ./... -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # sing-vmess 2 | 3 | Some confusing protocol. 4 | 5 | ### Features 6 | 7 | 100% compatible with `v2ray-core`. 8 | 9 | * Stream length chunk with padding and masking 10 | * AEAD length chunk with padding 11 | * Stream chunk 12 | * AEAD chunk 13 | * Legacy client 14 | * AEAD client 15 | * Legacy server 16 | * AEAD server 17 | 18 | Extra features: 19 | 20 | * Mux server 21 | * XUDP client 22 | * VLESS client 23 | -------------------------------------------------------------------------------- /chunk_aead.go: -------------------------------------------------------------------------------- 1 | package vmess 2 | 3 | import ( 4 | "crypto/cipher" 5 | "encoding/binary" 6 | "io" 7 | 8 | "github.com/sagernet/sing/common/buf" 9 | "github.com/sagernet/sing/common/bufio" 10 | N "github.com/sagernet/sing/common/network" 11 | ) 12 | 13 | type AEADReader struct { 14 | upstream N.ExtendedReader 15 | cipher cipher.AEAD 16 | nonce []byte 17 | nonceCount uint16 18 | } 19 | 20 | func NewAEADReader(upstream io.Reader, cipher cipher.AEAD, nonce []byte) *AEADReader { 21 | readNonce := make([]byte, cipher.NonceSize()) 22 | copy(readNonce, nonce) 23 | return &AEADReader{ 24 | upstream: bufio.NewExtendedReader(upstream), 25 | cipher: cipher, 26 | nonce: readNonce, 27 | } 28 | } 29 | 30 | func NewAes128GcmReader(upstream io.Reader, key []byte, nonce []byte) *AEADReader { 31 | return NewAEADReader(upstream, newAesGcm(key), nonce) 32 | } 33 | 34 | func NewChacha20Poly1305Reader(upstream io.Reader, key []byte, nonce []byte) *AEADReader { 35 | return NewAEADReader(upstream, newChacha20Poly1305(GenerateChacha20Poly1305Key(key)), nonce) 36 | } 37 | 38 | func (r *AEADReader) Read(p []byte) (n int, err error) { 39 | n, err = r.upstream.Read(p) 40 | if err != nil { 41 | return 42 | } 43 | binary.BigEndian.PutUint16(r.nonce, r.nonceCount) 44 | r.nonceCount += 1 45 | _, err = r.cipher.Open(p[:0], r.nonce, p[:n], nil) 46 | if err != nil { 47 | return 48 | } 49 | n -= CipherOverhead 50 | return 51 | } 52 | 53 | func (r *AEADReader) ReadBuffer(buffer *buf.Buffer) error { 54 | err := r.upstream.ReadBuffer(buffer) 55 | if err != nil { 56 | return err 57 | } 58 | binary.BigEndian.PutUint16(r.nonce, r.nonceCount) 59 | r.nonceCount += 1 60 | _, err = r.cipher.Open(buffer.Index(0), r.nonce, buffer.Bytes(), nil) 61 | if err != nil { 62 | return err 63 | } 64 | buffer.Truncate(buffer.Len() - CipherOverhead) 65 | return nil 66 | } 67 | 68 | func (r *AEADReader) Upstream() any { 69 | return r.upstream 70 | } 71 | 72 | type AEADWriter struct { 73 | upstream N.ExtendedWriter 74 | cipher cipher.AEAD 75 | nonce []byte 76 | nonceCount uint16 77 | } 78 | 79 | func NewAEADWriter(upstream io.Writer, cipher cipher.AEAD, nonce []byte) *AEADWriter { 80 | writeNonce := make([]byte, cipher.NonceSize()) 81 | copy(writeNonce, nonce) 82 | return &AEADWriter{ 83 | upstream: bufio.NewExtendedWriter(upstream), 84 | cipher: cipher, 85 | nonce: writeNonce, 86 | } 87 | } 88 | 89 | func NewAes128GcmWriter(upstream io.Writer, key []byte, nonce []byte) *AEADWriter { 90 | return NewAEADWriter(upstream, newAesGcm(key), nonce) 91 | } 92 | 93 | func NewChacha20Poly1305Writer(upstream io.Writer, key []byte, nonce []byte) *AEADWriter { 94 | return NewAEADWriter(upstream, newChacha20Poly1305(GenerateChacha20Poly1305Key(key)), nonce) 95 | } 96 | 97 | func (w *AEADWriter) Write(p []byte) (n int, err error) { 98 | // TODO: fix stack buffer 99 | return bufio.WriteBuffer(w, buf.As(p)) 100 | /*_buffer := buf.StackNewSize(len(p) + CipherOverhead) 101 | defer common.KeepAlive(_buffer) 102 | buffer := _buffer 103 | defer buffer.Release() 104 | binary.BigEndian.PutUint16(w.nonce, w.nonceCount) 105 | w.nonceCount += 1 106 | w.cipher.Seal(buffer.Index(0), w.nonce, p, nil) 107 | buffer.Truncate(buffer.FreeLen()) 108 | _, err = w.upstream.Write(buffer.Bytes()) 109 | if err == nil { 110 | n = len(p) 111 | } 112 | return*/ 113 | } 114 | 115 | func (w *AEADWriter) WriteBuffer(buffer *buf.Buffer) error { 116 | binary.BigEndian.PutUint16(w.nonce, w.nonceCount) 117 | w.nonceCount += 1 118 | w.cipher.Seal(buffer.Index(0), w.nonce, buffer.Bytes(), nil) 119 | buffer.Extend(CipherOverhead) 120 | return w.upstream.WriteBuffer(buffer) 121 | } 122 | 123 | func (w *AEADWriter) RearHeadroom() int { 124 | return CipherOverhead 125 | } 126 | 127 | func (w *AEADWriter) Upstream() any { 128 | return w.upstream 129 | } 130 | -------------------------------------------------------------------------------- /chunk_length_aead.go: -------------------------------------------------------------------------------- 1 | package vmess 2 | 3 | import ( 4 | "crypto/cipher" 5 | "crypto/rand" 6 | "encoding/binary" 7 | "io" 8 | "sync" 9 | 10 | "github.com/sagernet/sing/common" 11 | "github.com/sagernet/sing/common/buf" 12 | "github.com/sagernet/sing/common/bufio" 13 | E "github.com/sagernet/sing/common/exceptions" 14 | N "github.com/sagernet/sing/common/network" 15 | 16 | "golang.org/x/crypto/sha3" 17 | ) 18 | 19 | type AEADChunkReader struct { 20 | upstream io.Reader 21 | cipher cipher.AEAD 22 | globalPadding sha3.ShakeHash 23 | nonce []byte 24 | nonceCount uint16 25 | } 26 | 27 | func NewAEADChunkReader(upstream io.Reader, cipher cipher.AEAD, nonce []byte, globalPadding sha3.ShakeHash) *AEADChunkReader { 28 | readNonce := make([]byte, cipher.NonceSize()) 29 | copy(readNonce, nonce) 30 | return &AEADChunkReader{ 31 | upstream: upstream, 32 | cipher: cipher, 33 | nonce: readNonce, 34 | globalPadding: globalPadding, 35 | } 36 | } 37 | 38 | func NewAes128GcmChunkReader(upstream io.Reader, key []byte, nonce []byte, globalPadding sha3.ShakeHash) *AEADChunkReader { 39 | return NewAEADChunkReader(upstream, newAesGcm(KDF(key, "auth_len")[:16]), nonce, globalPadding) 40 | } 41 | 42 | func NewChacha20Poly1305ChunkReader(upstream io.Reader, key []byte, nonce []byte, globalPadding sha3.ShakeHash) *AEADChunkReader { 43 | return NewAEADChunkReader(upstream, newChacha20Poly1305(GenerateChacha20Poly1305Key(KDF(key, "auth_len")[:16])), nonce, globalPadding) 44 | } 45 | 46 | func (r *AEADChunkReader) Read(p []byte) (n int, err error) { 47 | if cap(p) < 2+CipherOverhead { 48 | return 0, E.Extend(io.ErrShortBuffer, "AEAD chunk need ", 2+CipherOverhead) 49 | } 50 | _, err = io.ReadFull(r.upstream, p[:2+CipherOverhead]) 51 | if err != nil { 52 | return 53 | } 54 | binary.BigEndian.PutUint16(r.nonce, r.nonceCount) 55 | r.nonceCount += 1 56 | _, err = r.cipher.Open(p[:0], r.nonce, p[:2+CipherOverhead], nil) 57 | if err != nil { 58 | return 59 | } 60 | length := binary.BigEndian.Uint16(p[:2]) 61 | length += CipherOverhead 62 | dataLen := int(length) 63 | var paddingLen int 64 | if r.globalPadding != nil { 65 | var hashCode uint16 66 | common.Must(binary.Read(r.globalPadding, binary.BigEndian, &hashCode)) 67 | paddingLen = int(hashCode % 64) 68 | dataLen -= paddingLen 69 | } 70 | if dataLen < 0 { 71 | err = E.Extend(ErrBadLengthChunk, "length=", length, ", padding=", paddingLen) 72 | return 73 | } 74 | if dataLen == 0 { 75 | err = io.EOF 76 | return 77 | } 78 | var readLen int 79 | readLen = len(p) 80 | if readLen > dataLen { 81 | readLen = dataLen 82 | } else if readLen < dataLen { 83 | return 0, E.Extend(io.ErrShortBuffer, "AEAD chunk need ", dataLen) 84 | } 85 | n, err = io.ReadFull(r.upstream, p[:readLen]) 86 | if err != nil { 87 | return 88 | } 89 | _, err = io.CopyN(io.Discard, r.upstream, int64(paddingLen)) 90 | return 91 | } 92 | 93 | func (r *AEADChunkReader) Upstream() any { 94 | return r.upstream 95 | } 96 | 97 | type AEADChunkWriter struct { 98 | upstream N.ExtendedWriter 99 | cipher cipher.AEAD 100 | globalPadding sha3.ShakeHash 101 | nonce []byte 102 | nonceCount uint16 103 | hashAccess sync.Mutex 104 | writeAccess sync.Mutex 105 | } 106 | 107 | func NewAEADChunkWriter(upstream io.Writer, cipher cipher.AEAD, nonce []byte, globalPadding sha3.ShakeHash) *AEADChunkWriter { 108 | writeNonce := make([]byte, cipher.NonceSize()) 109 | copy(writeNonce, nonce) 110 | return &AEADChunkWriter{ 111 | upstream: bufio.NewExtendedWriter(upstream), 112 | cipher: cipher, 113 | nonce: writeNonce, 114 | globalPadding: globalPadding, 115 | } 116 | } 117 | 118 | func NewAes128GcmChunkWriter(upstream io.Writer, key []byte, nonce []byte, globalPadding sha3.ShakeHash) *AEADChunkWriter { 119 | return NewAEADChunkWriter(upstream, newAesGcm(KDF(key, "auth_len")[:16]), nonce, globalPadding) 120 | } 121 | 122 | func NewChacha20Poly1305ChunkWriter(upstream io.Writer, key []byte, nonce []byte, globalPadding sha3.ShakeHash) *AEADChunkWriter { 123 | return NewAEADChunkWriter(upstream, newChacha20Poly1305(GenerateChacha20Poly1305Key(KDF(key, "auth_len")[:16])), nonce, globalPadding) 124 | } 125 | 126 | func (w *AEADChunkWriter) Write(p []byte) (n int, err error) { 127 | dataLength := uint16(len(p)) 128 | var paddingLen uint16 129 | if w.globalPadding != nil { 130 | w.hashAccess.Lock() 131 | var hashCode uint16 132 | common.Must(binary.Read(w.globalPadding, binary.BigEndian, &hashCode)) 133 | paddingLen = hashCode % MaxPaddingSize 134 | dataLength += paddingLen 135 | w.hashAccess.Unlock() 136 | } 137 | dataLength -= CipherOverhead 138 | 139 | lengthBuffer := buf.NewSize(2 + CipherOverhead) 140 | binary.BigEndian.PutUint16(lengthBuffer.Extend(2), dataLength) 141 | 142 | binary.BigEndian.PutUint16(w.nonce, w.nonceCount) 143 | w.nonceCount += 1 144 | w.cipher.Seal(lengthBuffer.Index(0), w.nonce, lengthBuffer.Bytes(), nil) 145 | lengthBuffer.Extend(CipherOverhead) 146 | 147 | w.writeAccess.Lock() 148 | _, err = lengthBuffer.WriteTo(w.upstream) 149 | if err != nil { 150 | return 151 | } 152 | 153 | lengthBuffer.Release() 154 | 155 | n, err = w.upstream.Write(p) 156 | if err != nil { 157 | return 158 | } 159 | if paddingLen > 0 { 160 | _, err = io.CopyN(w.upstream, rand.Reader, int64(paddingLen)) 161 | if err != nil { 162 | return 163 | } 164 | } 165 | w.writeAccess.Unlock() 166 | return 167 | } 168 | 169 | func (w *AEADChunkWriter) WriteBuffer(buffer *buf.Buffer) error { 170 | dataLength := uint16(buffer.Len()) 171 | var paddingLen uint16 172 | if w.globalPadding != nil { 173 | w.hashAccess.Lock() 174 | var hashCode uint16 175 | common.Must(binary.Read(w.globalPadding, binary.BigEndian, &hashCode)) 176 | paddingLen = hashCode % MaxPaddingSize 177 | dataLength += paddingLen 178 | w.hashAccess.Unlock() 179 | } 180 | dataLength -= CipherOverhead 181 | lengthBuffer := buffer.ExtendHeader(2 + CipherOverhead) 182 | binary.BigEndian.PutUint16(lengthBuffer, dataLength) 183 | binary.BigEndian.PutUint16(w.nonce, w.nonceCount) 184 | w.nonceCount += 1 185 | w.cipher.Seal(lengthBuffer[:0], w.nonce, lengthBuffer[:2], nil) 186 | if paddingLen > 0 { 187 | _, err := buffer.ReadFullFrom(rand.Reader, int(paddingLen)) 188 | if err != nil { 189 | buffer.Release() 190 | return err 191 | } 192 | } 193 | return w.upstream.WriteBuffer(buffer) 194 | } 195 | 196 | func (w *AEADChunkWriter) FrontHeadroom() int { 197 | return 2 + CipherOverhead 198 | } 199 | 200 | func (w *AEADChunkWriter) RearHeadroom() int { 201 | if w.globalPadding != nil { 202 | return CipherOverhead + MaxPaddingSize 203 | } else { 204 | return CipherOverhead 205 | } 206 | } 207 | 208 | func (w *AEADChunkWriter) Upstream() any { 209 | return w.upstream 210 | } 211 | -------------------------------------------------------------------------------- /chunk_length_stream.go: -------------------------------------------------------------------------------- 1 | package vmess 2 | 3 | import ( 4 | "crypto/rand" 5 | "encoding/binary" 6 | "io" 7 | "sync" 8 | 9 | "github.com/sagernet/sing/common" 10 | "github.com/sagernet/sing/common/buf" 11 | "github.com/sagernet/sing/common/bufio" 12 | E "github.com/sagernet/sing/common/exceptions" 13 | N "github.com/sagernet/sing/common/network" 14 | 15 | "golang.org/x/crypto/sha3" 16 | ) 17 | 18 | var ErrBadLengthChunk = E.New("bad length chunk") 19 | 20 | type StreamChunkReader struct { 21 | upstream io.Reader 22 | chunkMasking sha3.ShakeHash 23 | globalPadding sha3.ShakeHash 24 | } 25 | 26 | func NewStreamChunkReader(upstream io.Reader, chunkMasking sha3.ShakeHash, globalPadding sha3.ShakeHash) *StreamChunkReader { 27 | return &StreamChunkReader{ 28 | upstream: upstream, 29 | chunkMasking: chunkMasking, 30 | globalPadding: globalPadding, 31 | } 32 | } 33 | 34 | func (r *StreamChunkReader) Read(p []byte) (n int, err error) { 35 | var length uint16 36 | err = binary.Read(r.upstream, binary.BigEndian, &length) 37 | if err != nil { 38 | return 39 | } 40 | var paddingLen int 41 | if r.globalPadding != nil { 42 | var hashCode uint16 43 | common.Must(binary.Read(r.globalPadding, binary.BigEndian, &hashCode)) 44 | paddingLen = int(hashCode % 64) 45 | } 46 | if r.chunkMasking != nil { 47 | var hashCode uint16 48 | common.Must(binary.Read(r.chunkMasking, binary.BigEndian, &hashCode)) 49 | length ^= hashCode 50 | } 51 | dataLen := int(length) 52 | if paddingLen > 0 { 53 | dataLen -= paddingLen 54 | } 55 | if dataLen < 0 { 56 | err = E.Extend(ErrBadLengthChunk, "length=", length, ", padding=", paddingLen) 57 | return 58 | } 59 | if dataLen == 0 { 60 | err = io.EOF 61 | return 62 | } 63 | var readLen int 64 | readLen = len(p) 65 | if readLen > dataLen { 66 | readLen = dataLen 67 | } else if readLen < dataLen { 68 | return 0, E.Extend(io.ErrShortBuffer, "stream chunk need ", dataLen) 69 | } 70 | n, err = io.ReadFull(r.upstream, p[:readLen]) 71 | if err != nil { 72 | return 73 | } 74 | _, err = io.CopyN(io.Discard, r.upstream, int64(paddingLen)) 75 | return 76 | } 77 | 78 | func (r *StreamChunkReader) Upstream() any { 79 | return r.upstream 80 | } 81 | 82 | type StreamChunkWriter struct { 83 | upstream N.ExtendedWriter 84 | chunkMasking sha3.ShakeHash 85 | globalPadding sha3.ShakeHash 86 | hashAccess sync.Mutex 87 | writeAccess sync.Mutex 88 | } 89 | 90 | func NewStreamChunkWriter(upstream io.Writer, chunkMasking sha3.ShakeHash, globalPadding sha3.ShakeHash) *StreamChunkWriter { 91 | return &StreamChunkWriter{ 92 | upstream: bufio.NewExtendedWriter(upstream), 93 | chunkMasking: chunkMasking, 94 | globalPadding: globalPadding, 95 | } 96 | } 97 | 98 | func (w *StreamChunkWriter) Write(p []byte) (n int, err error) { 99 | dataLen := uint16(len(p)) 100 | var paddingLen uint16 101 | if w.globalPadding != nil || w.chunkMasking != nil { 102 | w.hashAccess.Lock() 103 | if w.globalPadding != nil { 104 | var hashCode uint16 105 | common.Must(binary.Read(w.globalPadding, binary.BigEndian, &hashCode)) 106 | paddingLen = hashCode % MaxPaddingSize 107 | dataLen += paddingLen 108 | } 109 | if w.chunkMasking != nil { 110 | var hashCode uint16 111 | common.Must(binary.Read(w.chunkMasking, binary.BigEndian, &hashCode)) 112 | dataLen ^= hashCode 113 | } 114 | w.hashAccess.Unlock() 115 | } 116 | w.writeAccess.Lock() 117 | err = binary.Write(w.upstream, binary.BigEndian, dataLen) 118 | if err != nil { 119 | return 120 | } 121 | n, err = w.upstream.Write(p) 122 | if err != nil { 123 | return 124 | } 125 | if paddingLen > 0 { 126 | _, err = io.CopyN(w.upstream, rand.Reader, int64(paddingLen)) 127 | if err != nil { 128 | return 129 | } 130 | } 131 | w.writeAccess.Unlock() 132 | return 133 | } 134 | 135 | func (w *StreamChunkWriter) WriteBuffer(buffer *buf.Buffer) error { 136 | dataLen := uint16(buffer.Len()) 137 | var paddingLen uint16 138 | if w.globalPadding != nil || w.chunkMasking != nil { 139 | w.hashAccess.Lock() 140 | if w.globalPadding != nil { 141 | var hashCode uint16 142 | common.Must(binary.Read(w.globalPadding, binary.BigEndian, &hashCode)) 143 | paddingLen = hashCode % MaxPaddingSize 144 | dataLen += paddingLen 145 | } 146 | if w.chunkMasking != nil { 147 | var hashCode uint16 148 | common.Must(binary.Read(w.chunkMasking, binary.BigEndian, &hashCode)) 149 | dataLen ^= hashCode 150 | } 151 | w.hashAccess.Unlock() 152 | } 153 | binary.BigEndian.PutUint16(buffer.ExtendHeader(2), dataLen) 154 | if paddingLen > 0 { 155 | _, err := buffer.ReadFullFrom(rand.Reader, int(paddingLen)) 156 | if err != nil { 157 | buffer.Release() 158 | return err 159 | } 160 | } 161 | return w.upstream.WriteBuffer(buffer) 162 | } 163 | 164 | func (w *StreamChunkWriter) WriteWithChecksum(checksum uint32, p []byte) (n int, err error) { 165 | dataLen := uint16(4 + len(p)) 166 | var paddingLen uint16 167 | if w.globalPadding != nil || w.chunkMasking != nil { 168 | w.hashAccess.Lock() 169 | if w.globalPadding != nil { 170 | var hashCode uint16 171 | common.Must(binary.Read(w.globalPadding, binary.BigEndian, &hashCode)) 172 | paddingLen = hashCode % MaxPaddingSize 173 | dataLen += paddingLen 174 | } 175 | if w.chunkMasking != nil { 176 | var hashCode uint16 177 | common.Must(binary.Read(w.chunkMasking, binary.BigEndian, &hashCode)) 178 | dataLen ^= hashCode 179 | } 180 | w.hashAccess.Unlock() 181 | } 182 | w.writeAccess.Lock() 183 | err = binary.Write(w.upstream, binary.BigEndian, dataLen) 184 | if err != nil { 185 | return 186 | } 187 | err = binary.Write(w.upstream, binary.BigEndian, checksum) 188 | if err != nil { 189 | return 190 | } 191 | n, err = w.upstream.Write(p) 192 | if err != nil { 193 | return 194 | } 195 | if paddingLen > 0 { 196 | _, err = io.CopyN(w.upstream, rand.Reader, int64(paddingLen)) 197 | if err != nil { 198 | return 199 | } 200 | } 201 | w.writeAccess.Unlock() 202 | return 203 | } 204 | 205 | func (w *StreamChunkWriter) FrontHeadroom() int { 206 | return 2 207 | } 208 | 209 | func (w *StreamChunkWriter) RearHeadroom() int { 210 | if w.globalPadding != nil { 211 | return MaxPaddingSize 212 | } else { 213 | return 0 214 | } 215 | } 216 | 217 | func (w *StreamChunkWriter) Upstream() any { 218 | return w.upstream 219 | } 220 | -------------------------------------------------------------------------------- /chunk_stream.go: -------------------------------------------------------------------------------- 1 | package vmess 2 | 3 | import ( 4 | "crypto/cipher" 5 | "io" 6 | 7 | "github.com/sagernet/sing/common/buf" 8 | "github.com/sagernet/sing/common/bufio" 9 | N "github.com/sagernet/sing/common/network" 10 | ) 11 | 12 | type StreamReader struct { 13 | upstream N.ExtendedReader 14 | cipher cipher.Stream 15 | } 16 | 17 | func NewStreamReader(upstream io.Reader, key []byte, iv []byte) *StreamReader { 18 | return &StreamReader{ 19 | upstream: bufio.NewExtendedReader(upstream), 20 | cipher: newAesStream(key, iv, cipher.NewCFBDecrypter), 21 | } 22 | } 23 | 24 | func (r *StreamReader) Read(p []byte) (n int, err error) { 25 | n, err = r.upstream.Read(p) 26 | if err != nil { 27 | return 28 | } 29 | r.cipher.XORKeyStream(p[:n], p[:n]) 30 | return 31 | } 32 | 33 | func (r *StreamReader) ReadBuffer(buffer *buf.Buffer) error { 34 | err := r.upstream.ReadBuffer(buffer) 35 | if err != nil { 36 | return err 37 | } 38 | r.cipher.XORKeyStream(buffer.Bytes(), buffer.Bytes()) 39 | return nil 40 | } 41 | 42 | func (r *StreamReader) Upstream() any { 43 | return r.upstream 44 | } 45 | 46 | type StreamWriter struct { 47 | upstream N.ExtendedWriter 48 | cipher cipher.Stream 49 | } 50 | 51 | func NewStreamWriter(upstream io.Writer, key []byte, iv []byte) *StreamWriter { 52 | return &StreamWriter{ 53 | upstream: bufio.NewExtendedWriter(upstream), 54 | cipher: newAesStream(key, iv, cipher.NewCFBEncrypter), 55 | } 56 | } 57 | 58 | func (w *StreamWriter) Write(p []byte) (n int, err error) { 59 | w.cipher.XORKeyStream(p, p) 60 | return w.upstream.Write(p) 61 | } 62 | 63 | func (w *StreamWriter) WriteBuffer(buffer *buf.Buffer) error { 64 | w.cipher.XORKeyStream(buffer.Bytes(), buffer.Bytes()) 65 | return w.upstream.WriteBuffer(buffer) 66 | } 67 | 68 | func (w *StreamWriter) Upstream() any { 69 | return w.upstream 70 | } 71 | -------------------------------------------------------------------------------- /chunk_stream_checksum.go: -------------------------------------------------------------------------------- 1 | package vmess 2 | 3 | import ( 4 | "encoding/binary" 5 | "hash/fnv" 6 | "io" 7 | 8 | "github.com/sagernet/sing/common" 9 | "github.com/sagernet/sing/common/buf" 10 | "github.com/sagernet/sing/common/bufio" 11 | N "github.com/sagernet/sing/common/network" 12 | ) 13 | 14 | type StreamChecksumReader struct { 15 | upstream N.ExtendedReader 16 | } 17 | 18 | func NewStreamChecksumReader(reader io.Reader) *StreamChecksumReader { 19 | return &StreamChecksumReader{bufio.NewExtendedReader(reader)} 20 | } 21 | 22 | func (r *StreamChecksumReader) Read(p []byte) (n int, err error) { 23 | n, err = r.upstream.Read(p) 24 | if err != nil { 25 | return 26 | } 27 | hash := fnv.New32a() 28 | common.Must1(hash.Write(p[4:n])) 29 | if hash.Sum32() != binary.BigEndian.Uint32(p) { 30 | return 0, ErrInvalidChecksum 31 | } 32 | n = copy(p, p[4:n]) 33 | return 34 | } 35 | 36 | func (r *StreamChecksumReader) ReadBuffer(buffer *buf.Buffer) error { 37 | err := r.upstream.ReadBuffer(buffer) 38 | if err != nil { 39 | return err 40 | } 41 | hash := fnv.New32a() 42 | common.Must1(hash.Write(buffer.From(4))) 43 | if hash.Sum32() != binary.BigEndian.Uint32(buffer.To(4)) { 44 | return ErrInvalidChecksum 45 | } 46 | buffer.Advance(4) 47 | return nil 48 | } 49 | 50 | func (r *StreamChecksumReader) Upstream() any { 51 | return r.upstream 52 | } 53 | 54 | type StreamChecksumWriter struct { 55 | upstream *StreamChunkWriter 56 | } 57 | 58 | func NewStreamChecksumWriter(upstream *StreamChunkWriter) *StreamChecksumWriter { 59 | return &StreamChecksumWriter{upstream} 60 | } 61 | 62 | func (w *StreamChecksumWriter) Write(p []byte) (n int, err error) { 63 | hash := fnv.New32a() 64 | common.Must1(hash.Write(p)) 65 | return w.upstream.WriteWithChecksum(hash.Sum32(), p) 66 | } 67 | 68 | func (w *StreamChecksumWriter) WriteBuffer(buffer *buf.Buffer) error { 69 | hash := fnv.New32a() 70 | common.Must1(hash.Write(buffer.Bytes())) 71 | hash.Sum(buffer.ExtendHeader(4)[:0]) 72 | return common.Error(w.upstream.Write(buffer.Bytes())) 73 | } 74 | 75 | func (w *StreamChecksumWriter) FrontHeadroom() int { 76 | return 4 77 | } 78 | 79 | func (w *StreamChecksumWriter) Upstream() any { 80 | return w.upstream 81 | } 82 | -------------------------------------------------------------------------------- /client.go: -------------------------------------------------------------------------------- 1 | package vmess 2 | 3 | import ( 4 | "crypto/cipher" 5 | "crypto/hmac" 6 | "crypto/md5" 7 | "crypto/rand" 8 | "crypto/sha256" 9 | "encoding/binary" 10 | "hash/fnv" 11 | "io" 12 | mRand "math/rand" 13 | "net" 14 | "time" 15 | 16 | "github.com/sagernet/sing/common" 17 | "github.com/sagernet/sing/common/buf" 18 | "github.com/sagernet/sing/common/bufio" 19 | E "github.com/sagernet/sing/common/exceptions" 20 | M "github.com/sagernet/sing/common/metadata" 21 | N "github.com/sagernet/sing/common/network" 22 | 23 | "github.com/gofrs/uuid/v5" 24 | ) 25 | 26 | type Client struct { 27 | key [16]byte 28 | security byte 29 | globalPadding bool 30 | authenticatedLength bool 31 | time TimeFunc 32 | alterId int 33 | alterKey [16]byte 34 | } 35 | 36 | func NewClient(userId string, security string, alterId int, options ...ClientOption) (*Client, error) { 37 | user, err := uuid.FromString(userId) 38 | if err != nil { 39 | user = uuid.NewV5(uuid.Nil, userId) 40 | } 41 | 42 | var rawSecurity byte 43 | switch security { 44 | case "auto": 45 | rawSecurity = AutoSecurityType() 46 | case "none", "zero": 47 | rawSecurity = SecurityTypeNone 48 | case "aes-128-cfb": 49 | rawSecurity = SecurityTypeLegacy 50 | case "aes-128-gcm": 51 | rawSecurity = SecurityTypeAes128Gcm 52 | case "chacha20-poly1305": 53 | rawSecurity = SecurityTypeChacha20Poly1305 54 | default: 55 | return nil, E.Extend(ErrUnsupportedSecurityType, security) 56 | } 57 | client := &Client{ 58 | key: Key(user), 59 | security: rawSecurity, 60 | time: time.Now, 61 | alterId: alterId, 62 | } 63 | if alterId > 0 { 64 | client.alterKey = AlterId(user) 65 | } 66 | for _, option := range options { 67 | option(client) 68 | } 69 | return client, nil 70 | } 71 | 72 | func (c *Client) DialConn(upstream net.Conn, destination M.Socksaddr) (N.ExtendedConn, error) { 73 | conn := &clientConn{c.dialRaw(upstream, CommandTCP, destination)} 74 | return conn, conn.writeHandshake(nil) 75 | } 76 | 77 | func (c *Client) DialEarlyConn(upstream net.Conn, destination M.Socksaddr) N.ExtendedConn { 78 | return &clientConn{c.dialRaw(upstream, CommandTCP, destination)} 79 | } 80 | 81 | type PacketConn interface { 82 | net.Conn 83 | N.NetPacketConn 84 | } 85 | 86 | func (c *Client) DialPacketConn(upstream net.Conn, destination M.Socksaddr) (PacketConn, error) { 87 | conn := &clientPacketConn{clientConn{c.dialRaw(upstream, CommandUDP, destination)}, destination} 88 | return conn, conn.writeHandshake(nil) 89 | } 90 | 91 | func (c *Client) DialEarlyPacketConn(upstream net.Conn, destination M.Socksaddr) PacketConn { 92 | return &clientPacketConn{clientConn{c.dialRaw(upstream, CommandUDP, destination)}, destination} 93 | } 94 | 95 | func (c *Client) DialXUDPPacketConn(upstream net.Conn, destination M.Socksaddr) (PacketConn, error) { 96 | conn := &clientConn{c.dialRaw(upstream, CommandMux, destination)} 97 | err := conn.writeHandshake(nil) 98 | if err != nil { 99 | return nil, err 100 | } 101 | return NewXUDPConn(conn, destination), nil 102 | } 103 | 104 | func (c *Client) DialEarlyXUDPPacketConn(upstream net.Conn, destination M.Socksaddr) PacketConn { 105 | return NewXUDPConn(&clientConn{c.dialRaw(upstream, CommandMux, destination)}, destination) 106 | } 107 | 108 | type rawClientConn struct { 109 | *Client 110 | net.Conn 111 | command byte 112 | security byte 113 | option byte 114 | destination M.Socksaddr 115 | 116 | requestKey [16]byte 117 | requestNonce [16]byte 118 | responseHeader byte 119 | 120 | readBuffer bool 121 | reader N.ExtendedReader 122 | writer N.ExtendedWriter 123 | } 124 | 125 | func (c *Client) dialRaw(upstream net.Conn, command byte, destination M.Socksaddr) rawClientConn { 126 | conn := rawClientConn{ 127 | Client: c, 128 | Conn: upstream, 129 | command: command, 130 | destination: destination, 131 | } 132 | common.Must1(io.ReadFull(rand.Reader, conn.requestKey[:])) 133 | common.Must1(io.ReadFull(rand.Reader, conn.requestNonce[:])) 134 | 135 | security := c.security 136 | var option byte 137 | 138 | switch security { 139 | case SecurityTypeNone: 140 | if command == CommandUDP { 141 | option = RequestOptionChunkStream 142 | } 143 | case SecurityTypeLegacy: 144 | option = RequestOptionChunkStream 145 | case SecurityTypeAes128Gcm, SecurityTypeChacha20Poly1305: 146 | option = RequestOptionChunkStream | RequestOptionChunkMasking 147 | if c.globalPadding { 148 | option |= RequestOptionGlobalPadding 149 | } 150 | if c.authenticatedLength { 151 | option |= RequestOptionAuthenticatedLength 152 | } 153 | } 154 | 155 | if option&RequestOptionChunkStream != 0 && command == CommandTCP || command == CommandMux { 156 | conn.readBuffer = true 157 | } 158 | 159 | conn.security = security 160 | conn.option = option 161 | return conn 162 | } 163 | 164 | func (c *rawClientConn) NeedHandshake() bool { 165 | return c.writer == nil 166 | } 167 | 168 | func (c *rawClientConn) writeHandshake(payload []byte) error { 169 | paddingLen := mRand.Intn(16) 170 | 171 | var headerLen int 172 | headerLen += 1 // version 173 | headerLen += 16 // request iv 174 | headerLen += 16 // request key 175 | headerLen += 1 // response header 176 | headerLen += 1 // option 177 | headerLen += 1 // padding<<4 || security 178 | headerLen += 1 // reversed 179 | headerLen += 1 // command 180 | if c.command != CommandMux { 181 | headerLen += AddressSerializer.AddrPortLen(c.destination) 182 | } 183 | headerLen += paddingLen 184 | headerLen += 4 // fnv1a hash 185 | 186 | if c.alterId > 0 { 187 | var requestLen int 188 | requestLen += 16 // alter id 189 | requestLen += headerLen 190 | 191 | requestBuffer := buf.NewSize(requestLen) 192 | defer requestBuffer.Release() 193 | 194 | timestamp := uint64(c.time().Unix()) 195 | idHash := hmac.New(md5.New, c.alterKey[:]) 196 | common.Must(binary.Write(idHash, binary.BigEndian, timestamp)) 197 | idHash.Sum(requestBuffer.Extend(md5.Size)[:0]) 198 | 199 | headerBuffer := buf.With(requestBuffer.Extend(headerLen)) 200 | err := c.encodeHeader(headerBuffer, paddingLen) 201 | if err != nil { 202 | return err 203 | } 204 | 205 | timeHash := md5.New() 206 | common.Must(binary.Write(timeHash, binary.BigEndian, timestamp)) 207 | common.Must(binary.Write(timeHash, binary.BigEndian, timestamp)) 208 | common.Must(binary.Write(timeHash, binary.BigEndian, timestamp)) 209 | common.Must(binary.Write(timeHash, binary.BigEndian, timestamp)) 210 | newAesStream(c.key[:], timeHash.Sum(nil), cipher.NewCFBEncrypter).XORKeyStream(headerBuffer.Bytes(), headerBuffer.Bytes()) 211 | 212 | var writer io.Writer 213 | var bufferedWriter *bufio.BufferedWriter 214 | if len(payload) > 0 { 215 | bufferedWriter = bufio.NewBufferedWriter(c.Conn, buf.New()) 216 | _, err = bufferedWriter.Write(requestBuffer.Bytes()) 217 | writer = bufferedWriter 218 | } else { 219 | writer = c.Conn 220 | _, err = c.Conn.Write(requestBuffer.Bytes()) 221 | } 222 | if err != nil { 223 | return err 224 | } 225 | c.writer = bufio.NewExtendedWriter(CreateWriter(writer, nil, c.requestKey[:], c.requestNonce[:], c.requestKey[:], c.requestNonce[:], c.security, c.option)) 226 | if len(payload) > 0 { 227 | _, err = c.writer.Write(payload) 228 | if err != nil { 229 | return err 230 | } 231 | err = bufferedWriter.Fallthrough() 232 | if err != nil { 233 | return err 234 | } 235 | } 236 | } else { 237 | const headerLenBufferLen = 2 + CipherOverhead 238 | 239 | var requestLen int 240 | requestLen += 16 // auth id 241 | requestLen += headerLenBufferLen 242 | requestLen += 8 // connection nonce 243 | requestLen += headerLen + CipherOverhead 244 | 245 | requestBuffer := buf.NewSize(requestLen) 246 | defer requestBuffer.Release() 247 | 248 | AuthID(c.key, c.time(), requestBuffer) 249 | authId := requestBuffer.Bytes() 250 | 251 | headerLenBuffer := buf.With(requestBuffer.Extend(headerLenBufferLen)) 252 | connectionNonce := requestBuffer.WriteRandom(8) 253 | 254 | common.Must(binary.Write(headerLenBuffer, binary.BigEndian, uint16(headerLen))) 255 | lengthKey := KDF(c.key[:], KDFSaltConstVMessHeaderPayloadLengthAEADKey, authId, connectionNonce)[:16] 256 | lengthNonce := KDF(c.key[:], KDFSaltConstVMessHeaderPayloadLengthAEADIV, authId, connectionNonce)[:12] 257 | newAesGcm(lengthKey).Seal(headerLenBuffer.Index(0), lengthNonce, headerLenBuffer.Bytes(), authId) 258 | 259 | headerBuffer := buf.With(requestBuffer.Extend(headerLen + CipherOverhead)) 260 | c.encodeHeader(headerBuffer, paddingLen) 261 | headerKey := KDF(c.key[:], KDFSaltConstVMessHeaderPayloadAEADKey, authId, connectionNonce)[:16] 262 | headerNonce := KDF(c.key[:], KDFSaltConstVMessHeaderPayloadAEADIV, authId, connectionNonce)[:12] 263 | newAesGcm(headerKey).Seal(headerBuffer.Index(0), headerNonce, headerBuffer.Bytes(), authId) 264 | 265 | var writer io.Writer 266 | var bufferedWriter *bufio.BufferedWriter 267 | if len(payload) > 0 { 268 | bufferedWriter = bufio.NewBufferedWriter(c.Conn, buf.New()) 269 | writer = bufferedWriter 270 | } else { 271 | writer = c.Conn 272 | } 273 | _, err := writer.Write(requestBuffer.Bytes()) 274 | if err != nil { 275 | return err 276 | } 277 | c.writer = bufio.NewExtendedWriter(CreateWriter(writer, nil, c.requestKey[:], c.requestNonce[:], c.requestKey[:], c.requestNonce[:], c.security, c.option)) 278 | if len(payload) > 0 { 279 | _, err = c.writer.Write(payload) 280 | if err != nil { 281 | return err 282 | } 283 | err = bufferedWriter.Fallthrough() 284 | if err != nil { 285 | return err 286 | } 287 | } 288 | } 289 | return nil 290 | } 291 | 292 | func (c *rawClientConn) encodeHeader(headerBuffer *buf.Buffer, paddingLen int) error { 293 | common.Must(headerBuffer.WriteByte(Version)) 294 | common.Must1(headerBuffer.Write(c.requestNonce[:])) 295 | 296 | common.Must1(headerBuffer.Write(c.requestKey[:])) 297 | c.responseHeader = headerBuffer.WriteRandom(1)[0] 298 | common.Must(headerBuffer.WriteByte(c.option)) 299 | common.Must(headerBuffer.WriteByte(byte(paddingLen<<4) | c.security)) 300 | common.Must(headerBuffer.WriteZero()) 301 | common.Must(headerBuffer.WriteByte(c.command)) 302 | if c.command != CommandMux { 303 | err := AddressSerializer.WriteAddrPort(headerBuffer, c.destination) 304 | if err != nil { 305 | return err 306 | } 307 | } 308 | if paddingLen > 0 { 309 | headerBuffer.Extend(paddingLen) 310 | } 311 | headerHash := fnv.New32a() 312 | common.Must1(headerHash.Write(headerBuffer.Bytes())) 313 | headerHash.Sum(headerBuffer.Extend(4)[:0]) 314 | return nil 315 | } 316 | 317 | func (c *rawClientConn) readResponse() error { 318 | if c.alterId > 0 { 319 | responseKey := md5.Sum(c.requestKey[:]) 320 | responseIv := md5.Sum(c.requestNonce[:]) 321 | 322 | headerReader := NewStreamReader(c.Conn, responseKey[:], responseIv[:]) 323 | response := buf.NewSize(4) 324 | defer response.Release() 325 | _, err := response.ReadFullFrom(headerReader, response.FreeLen()) 326 | if err != nil { 327 | return err 328 | } 329 | 330 | if response.Byte(0) != c.responseHeader { 331 | return E.New("bad response header") 332 | } 333 | cmdLen := response.Byte(3) 334 | if cmdLen > 0 { 335 | _, err = io.CopyN(io.Discard, c.Conn, int64(cmdLen)) 336 | if err != nil { 337 | return err 338 | } 339 | } 340 | 341 | reader := CreateReader(c.Conn, headerReader, c.requestKey[:], c.requestNonce[:], responseKey[:], responseIv[:], c.security, c.option) 342 | if c.readBuffer { 343 | reader = bufio.NewChunkReader(reader, ReadChunkSize) 344 | } 345 | c.reader = bufio.NewExtendedReader(reader) 346 | } else { 347 | _responseKey := sha256.Sum256(c.requestKey[:]) 348 | responseKey := _responseKey[:16] 349 | _responseNonce := sha256.Sum256(c.requestNonce[:]) 350 | responseNonce := _responseNonce[:16] 351 | 352 | headerLenKey := KDF(responseKey, KDFSaltConstAEADRespHeaderLenKey)[:16] 353 | headerLenNonce := KDF(responseNonce, KDFSaltConstAEADRespHeaderLenIV)[:12] 354 | headerLenCipher := newAesGcm(headerLenKey) 355 | 356 | headerLenBuffer := buf.NewSize(2 + CipherOverhead) 357 | defer headerLenBuffer.Release() 358 | 359 | _, err := headerLenBuffer.ReadFullFrom(c.Conn, headerLenBuffer.FreeLen()) 360 | if err != nil { 361 | return err 362 | } 363 | 364 | _, err = headerLenCipher.Open(headerLenBuffer.Index(0), headerLenNonce, headerLenBuffer.Bytes(), nil) 365 | if err != nil { 366 | return err 367 | } 368 | 369 | var headerLen uint16 370 | err = binary.Read(headerLenBuffer, binary.BigEndian, &headerLen) 371 | if err != nil { 372 | return err 373 | } 374 | 375 | headerKey := KDF(responseKey, KDFSaltConstAEADRespHeaderPayloadKey)[:16] 376 | headerNonce := KDF(responseNonce, KDFSaltConstAEADRespHeaderPayloadIV)[:12] 377 | headerCipher := newAesGcm(headerKey) 378 | 379 | headerBuffer := buf.NewSize(int(headerLen) + CipherOverhead) 380 | defer headerBuffer.Release() 381 | 382 | _, err = headerBuffer.ReadFullFrom(c.Conn, headerBuffer.FreeLen()) 383 | if err != nil { 384 | return err 385 | } 386 | 387 | _, err = headerCipher.Open(headerBuffer.Index(0), headerNonce, headerBuffer.Bytes(), nil) 388 | if err != nil { 389 | return err 390 | } 391 | headerBuffer.Truncate(int(headerLen)) 392 | 393 | reader := CreateReader(c.Conn, nil, c.requestKey[:], c.requestNonce[:], responseKey, responseNonce, c.security, c.option) 394 | if c.readBuffer { 395 | reader = bufio.NewChunkReader(reader, ReadChunkSize) 396 | } 397 | c.reader = bufio.NewExtendedReader(reader) 398 | } 399 | return nil 400 | } 401 | 402 | func (c *rawClientConn) Close() error { 403 | return common.Close( 404 | c.Conn, 405 | c.reader, 406 | ) 407 | } 408 | 409 | func (c *rawClientConn) FrontHeadroom() int { 410 | return MaxFrontHeadroom 411 | } 412 | 413 | func (c *rawClientConn) RearHeadroom() int { 414 | return MaxRearHeadroom 415 | } 416 | 417 | func (c *rawClientConn) NeedAdditionalReadDeadline() bool { 418 | return true 419 | } 420 | 421 | func (c *rawClientConn) Upstream() any { 422 | return c.Conn 423 | } 424 | 425 | type clientConn struct { 426 | rawClientConn 427 | } 428 | 429 | func (c *clientConn) Read(p []byte) (n int, err error) { 430 | if c.reader == nil { 431 | err = c.readResponse() 432 | if err != nil { 433 | return 434 | } 435 | } 436 | return c.reader.Read(p) 437 | } 438 | 439 | func (c *clientConn) Write(p []byte) (n int, err error) { 440 | if c.writer == nil { 441 | err = c.writeHandshake(p) 442 | if err == nil { 443 | n = len(p) 444 | } 445 | return 446 | } 447 | return c.writer.Write(p) 448 | } 449 | 450 | func (c *clientConn) ReadBuffer(buffer *buf.Buffer) error { 451 | if c.reader == nil { 452 | err := c.readResponse() 453 | if err != nil { 454 | return err 455 | } 456 | } 457 | return c.reader.ReadBuffer(buffer) 458 | } 459 | 460 | func (c *clientConn) WriteBuffer(buffer *buf.Buffer) error { 461 | if c.writer == nil { 462 | return c.writeHandshake(buffer.Bytes()) 463 | } 464 | return c.writer.WriteBuffer(buffer) 465 | } 466 | 467 | /*func (c *clientConn) ReadFrom(r io.Reader) (n int64, err error) { 468 | if c.writer == nil { 469 | err = c.writeHandshake(nil) 470 | if err != nil { 471 | return 472 | } 473 | } 474 | return bufio.Copy(c.writer, r) 475 | }*/ 476 | 477 | func (c *clientConn) WriteTo(w io.Writer) (n int64, err error) { 478 | if c.reader == nil { 479 | err = c.readResponse() 480 | if err != nil { 481 | return 482 | } 483 | } 484 | return bufio.Copy(w, c.reader) 485 | } 486 | 487 | type clientPacketConn struct { 488 | clientConn 489 | destination M.Socksaddr 490 | } 491 | 492 | func (c *clientPacketConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) { 493 | if c.reader == nil { 494 | err = c.readResponse() 495 | if err != nil { 496 | return 497 | } 498 | } 499 | n, err = c.reader.Read(p) 500 | if err != nil { 501 | return 502 | } 503 | addr = c.destination.UDPAddr() 504 | return 505 | } 506 | 507 | func (c *clientPacketConn) WriteTo(p []byte, addr net.Addr) (n int, err error) { 508 | if c.writer == nil { 509 | err = c.writeHandshake(nil) 510 | if err != nil { 511 | return 512 | } 513 | } 514 | return c.writer.Write(p) 515 | } 516 | 517 | func (c *clientPacketConn) ReadPacket(buffer *buf.Buffer) (destination M.Socksaddr, err error) { 518 | if c.reader == nil { 519 | err = c.readResponse() 520 | if err != nil { 521 | return 522 | } 523 | } 524 | err = c.reader.ReadBuffer(buffer) 525 | if err != nil { 526 | return 527 | } 528 | destination = c.destination 529 | return 530 | } 531 | 532 | func (c *clientPacketConn) WritePacket(buffer *buf.Buffer, destination M.Socksaddr) error { 533 | if c.writer == nil { 534 | err := c.writeHandshake(nil) 535 | if err != nil { 536 | buffer.Release() 537 | return err 538 | } 539 | } 540 | return c.writer.WriteBuffer(buffer) 541 | } 542 | -------------------------------------------------------------------------------- /client_option.go: -------------------------------------------------------------------------------- 1 | package vmess 2 | 3 | type ClientOption func(*Client) 4 | 5 | func ClientWithGlobalPadding() ClientOption { 6 | return func(client *Client) { 7 | client.globalPadding = true 8 | } 9 | } 10 | 11 | func ClientWithAuthenticatedLength() ClientOption { 12 | return func(client *Client) { 13 | client.authenticatedLength = true 14 | } 15 | } 16 | 17 | func ClientWithTimeFunc(timeFunc TimeFunc) ClientOption { 18 | return func(client *Client) { 19 | client.time = timeFunc 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/sagernet/sing-vmess 2 | 3 | go 1.20 4 | 5 | require ( 6 | github.com/gofrs/uuid/v5 v5.3.2 7 | github.com/metacubex/utls v1.7.0-alpha.2 8 | github.com/sagernet/sing v0.6.7 9 | golang.org/x/crypto v0.33.0 10 | ) 11 | 12 | require ( 13 | github.com/andybalholm/brotli v1.0.6 // indirect 14 | github.com/cloudflare/circl v1.3.7 // indirect 15 | github.com/klauspost/compress v1.17.9 // indirect 16 | golang.org/x/exp v0.0.0-20240904232852-e7e105dedf7e // indirect 17 | golang.org/x/sys v0.30.0 // indirect 18 | ) 19 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/andybalholm/brotli v1.0.6 h1:Yf9fFpf49Zrxb9NlQaluyE92/+X7UVHlhMNJN2sxfOI= 2 | github.com/andybalholm/brotli v1.0.6/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= 3 | github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU= 4 | github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA= 5 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 6 | github.com/gofrs/uuid/v5 v5.3.2 h1:2jfO8j3XgSwlz/wHqemAEugfnTlikAYHhnqQ8Xh4fE0= 7 | github.com/gofrs/uuid/v5 v5.3.2/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8= 8 | github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= 9 | github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= 10 | github.com/metacubex/utls v1.7.0-alpha.2 h1:kLRg6zDV12R1uclL5qW9Tx4RD6ztGIIrTZWY5zrJXCg= 11 | github.com/metacubex/utls v1.7.0-alpha.2/go.mod h1:oknYT0qTOwE4hjPmZOEpzVdefnW7bAdGLvZcqmk4TLU= 12 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 13 | github.com/sagernet/sing v0.6.7 h1:NIWBLZ9AUWDXAQBKGleKwsitbQrI9M0nqoheXhUKnrI= 14 | github.com/sagernet/sing v0.6.7/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= 15 | github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= 16 | golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus= 17 | golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M= 18 | golang.org/x/exp v0.0.0-20240904232852-e7e105dedf7e h1:I88y4caeGeuDQxgdoFPUq097j7kNfw6uvuiNxUBfcBk= 19 | golang.org/x/exp v0.0.0-20240904232852-e7e105dedf7e/go.mod h1:akd2r19cwCdwSwWeIdzYQGa/EZZyqcOdwWiwj5L5eKQ= 20 | golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= 21 | golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 22 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 23 | -------------------------------------------------------------------------------- /kdf.go: -------------------------------------------------------------------------------- 1 | package vmess 2 | 3 | import ( 4 | "crypto/hmac" 5 | "crypto/sha256" 6 | "hash" 7 | ) 8 | 9 | func KDF(key []byte, salt string, path ...[]byte) []byte { 10 | hmacCreator := &hMacCreator{value: []byte(KDFSaltConstVMessAEADKDF)} 11 | hmacCreator = &hMacCreator{value: []byte(salt), parent: hmacCreator} 12 | for _, v := range path { 13 | hmacCreator = &hMacCreator{value: v, parent: hmacCreator} 14 | } 15 | hmacf := hmacCreator.Create() 16 | hmacf.Write(key) 17 | return hmacf.Sum(nil) 18 | } 19 | 20 | type hMacCreator struct { 21 | parent *hMacCreator 22 | value []byte 23 | } 24 | 25 | func (h *hMacCreator) Create() hash.Hash { 26 | if h.parent == nil { 27 | return hmac.New(sha256.New, h.value) 28 | } 29 | return hmac.New(h.parent.Create, h.value) 30 | } 31 | -------------------------------------------------------------------------------- /mux.go: -------------------------------------------------------------------------------- 1 | package vmess 2 | 3 | import ( 4 | std_bufio "bufio" 5 | "context" 6 | "encoding/binary" 7 | "io" 8 | "net" 9 | "os" 10 | "sync" 11 | "sync/atomic" 12 | "time" 13 | 14 | "github.com/sagernet/sing/common" 15 | "github.com/sagernet/sing/common/buf" 16 | "github.com/sagernet/sing/common/bufio" 17 | E "github.com/sagernet/sing/common/exceptions" 18 | M "github.com/sagernet/sing/common/metadata" 19 | N "github.com/sagernet/sing/common/network" 20 | ) 21 | 22 | func HandleMuxConnection(ctx context.Context, conn net.Conn, source M.Socksaddr, handler Handler) error { 23 | ctx, cancel := context.WithCancelCause(ctx) 24 | session := &serverSession{ 25 | ctx: ctx, 26 | source: source, 27 | conn: conn, 28 | directWriter: bufio.NewExtendedWriter(conn), 29 | handler: handler, 30 | streams: make(map[uint16]*serverStream), 31 | writer: std_bufio.NewWriter(conn), 32 | } 33 | go func() { 34 | <-ctx.Done() 35 | session.cleanup(ctx.Err()) 36 | }() 37 | return session.recvLoop(cancel) 38 | } 39 | 40 | type serverSession struct { 41 | ctx context.Context 42 | source M.Socksaddr 43 | conn net.Conn 44 | directWriter N.ExtendedWriter 45 | handler Handler 46 | streamAccess sync.RWMutex 47 | streams map[uint16]*serverStream 48 | writer *std_bufio.Writer 49 | writeAccess sync.Mutex 50 | writeRace uint32 51 | } 52 | 53 | type serverStream struct { 54 | network byte 55 | destination M.Socksaddr 56 | pipe *io.PipeWriter 57 | } 58 | 59 | func (c *serverSession) recvLoop(cancel context.CancelCauseFunc) error { 60 | for { 61 | err := c.recv() 62 | if err != nil { 63 | cancel(err) 64 | return E.Cause(err, "mux connection closed") 65 | } 66 | } 67 | } 68 | 69 | func (c *serverSession) cleanup(err error) { 70 | c.streamAccess.Lock() 71 | for _, stream := range c.streams { 72 | _ = stream.pipe.CloseWithError(err) 73 | } 74 | c.streamAccess.Unlock() 75 | } 76 | 77 | func (c *serverSession) recv() error { 78 | var length uint16 79 | err := binary.Read(c.conn, binary.BigEndian, &length) 80 | if err != nil { 81 | return E.Cause(err, "read frame header") 82 | } 83 | 84 | var sessionID uint16 85 | err = binary.Read(c.conn, binary.BigEndian, &sessionID) 86 | if err != nil { 87 | return err 88 | } 89 | 90 | var status byte 91 | err = binary.Read(c.conn, binary.BigEndian, &status) 92 | if err != nil { 93 | return err 94 | } 95 | 96 | var option byte 97 | err = binary.Read(c.conn, binary.BigEndian, &option) 98 | if err != nil { 99 | return err 100 | } 101 | 102 | var network byte 103 | var destination M.Socksaddr 104 | if length > 4 { 105 | limitReader := io.LimitReader(c.conn, int64(length-4)) 106 | err = binary.Read(limitReader, binary.BigEndian, &network) 107 | if err != nil { 108 | return err 109 | } 110 | destination, err = AddressSerializer.ReadAddrPort(limitReader) 111 | if err != nil { 112 | return err 113 | } 114 | if limitReader.(*io.LimitedReader).N > 0 { 115 | _, err = io.Copy(io.Discard, limitReader) 116 | if err != nil { 117 | return err 118 | } 119 | } 120 | } 121 | 122 | var stream *serverStream 123 | switch status { 124 | case StatusNew: 125 | pipeIn, pipeOut := io.Pipe() 126 | stream = &serverStream{ 127 | network, 128 | destination, 129 | pipeOut, 130 | } 131 | c.streamAccess.Lock() 132 | c.streams[sessionID] = stream 133 | c.streamAccess.Unlock() 134 | switch network { 135 | case NetworkTCP, NetworkUDP: 136 | default: 137 | return E.New("bad network: ", network) 138 | } 139 | go func() { 140 | if network == NetworkTCP { 141 | conn := &serverMuxConn{ 142 | sessionID, 143 | pipeIn, 144 | c, 145 | } 146 | c.handler.NewConnectionEx(c.ctx, conn, c.source, destination, nil) 147 | } else { 148 | conn := &serverMuxPacketConn{ 149 | sessionID, 150 | pipeIn, 151 | c, 152 | destination, 153 | } 154 | c.handler.NewPacketConnectionEx(c.ctx, conn, c.source, destination, nil) 155 | } 156 | }() 157 | case StatusKeep: 158 | var loaded bool 159 | c.streamAccess.Lock() 160 | stream, loaded = c.streams[sessionID] 161 | c.streamAccess.Unlock() 162 | if !loaded { 163 | go c.syncClose(sessionID, true) 164 | } 165 | case StatusEnd: 166 | if option&OptionError == OptionError { 167 | err = E.New("remote closed wth error") 168 | } 169 | c.localClose(sessionID, err) 170 | case StatusKeepAlive: 171 | default: 172 | return E.New("bad session status: ", status) 173 | } 174 | 175 | if option&OptionData != OptionData { 176 | return nil 177 | } 178 | 179 | err = binary.Read(c.conn, binary.BigEndian, &length) 180 | if err != nil { 181 | return err 182 | } 183 | 184 | if length == 0 { 185 | return nil 186 | } 187 | 188 | if stream == nil { 189 | return common.Error(io.CopyN(io.Discard, c.conn, int64(length))) 190 | } 191 | 192 | data := buf.NewSize(int(length)) 193 | defer data.Release() 194 | 195 | _, err = data.ReadFullFrom(c.conn, int(length)) 196 | if err != nil { 197 | return err 198 | } 199 | 200 | if !destination.IsValid() { 201 | destination = stream.destination 202 | } 203 | 204 | err = c.recvTo(stream, data, destination) 205 | if err != nil { 206 | return c.close(sessionID, err) 207 | } 208 | 209 | return nil 210 | } 211 | 212 | func (c *serverSession) recvTo(stream *serverStream, data *buf.Buffer, destination M.Socksaddr) error { 213 | if stream.network == NetworkTCP { 214 | return common.Error(stream.pipe.Write(data.Bytes())) 215 | } else { 216 | err := binary.Write(stream.pipe, binary.BigEndian, uint16(data.Len())) 217 | if err != nil { 218 | return err 219 | } 220 | _, err = stream.pipe.Write(data.Bytes()) 221 | if err != nil { 222 | return err 223 | } 224 | err = AddressSerializer.WriteAddrPort(stream.pipe, destination) 225 | if err != nil { 226 | return err 227 | } 228 | return nil 229 | } 230 | } 231 | 232 | func (c *serverSession) syncWrite(sessionID uint16, data []byte) (int, error) { 233 | writeRace := atomic.AddUint32(&c.writeRace, 1) 234 | c.writeAccess.Lock() 235 | defer c.writeAccess.Unlock() 236 | err := c.writeFrame(sessionID, data) 237 | if err != nil { 238 | return 0, err 239 | } 240 | if writeRace == atomic.LoadUint32(&c.writeRace) { 241 | err = c.writer.Flush() 242 | if err != nil { 243 | return 0, err 244 | } 245 | } 246 | return len(data), nil 247 | } 248 | 249 | func (c *serverSession) writeFrame(sessionID uint16, data []byte) error { 250 | err := binary.Write(c.writer, binary.BigEndian, uint16(4)) 251 | if err != nil { 252 | return err 253 | } 254 | err = binary.Write(c.writer, binary.BigEndian, sessionID) 255 | if err != nil { 256 | return err 257 | } 258 | err = binary.Write(c.writer, binary.BigEndian, uint8(StatusKeep)) 259 | if err != nil { 260 | return err 261 | } 262 | err = binary.Write(c.writer, binary.BigEndian, uint8(OptionData)) 263 | if err != nil { 264 | return err 265 | } 266 | err = binary.Write(c.writer, binary.BigEndian, uint16(len(data))) 267 | if err != nil { 268 | return err 269 | } 270 | return common.Error(c.writer.Write(data)) 271 | } 272 | 273 | func (c *serverSession) syncWritePacket(sessionID uint16, data []byte, destination M.Socksaddr) (int, error) { 274 | writeRace := atomic.AddUint32(&c.writeRace, 1) 275 | c.writeAccess.Lock() 276 | defer c.writeAccess.Unlock() 277 | err := c.writePacketFrame(sessionID, data, destination) 278 | if err != nil { 279 | return 0, err 280 | } 281 | if writeRace == atomic.LoadUint32(&c.writeRace) { 282 | err = c.writer.Flush() 283 | if err != nil { 284 | return 0, err 285 | } 286 | } 287 | return len(data), nil 288 | } 289 | 290 | func (c *serverSession) writePacketFrame(sessionID uint16, data []byte, destination M.Socksaddr) error { 291 | err := binary.Write(c.writer, binary.BigEndian, uint16(5+AddressSerializer.AddrPortLen(destination))) 292 | if err != nil { 293 | return err 294 | } 295 | err = binary.Write(c.writer, binary.BigEndian, sessionID) 296 | if err != nil { 297 | return err 298 | } 299 | err = binary.Write(c.writer, binary.BigEndian, uint8(StatusKeep)) 300 | if err != nil { 301 | return err 302 | } 303 | err = binary.Write(c.writer, binary.BigEndian, uint8(OptionData)) 304 | if err != nil { 305 | return err 306 | } 307 | if destination.IsValid() { 308 | err = binary.Write(c.writer, binary.BigEndian, uint8(NetworkUDP)) 309 | if err != nil { 310 | return err 311 | } 312 | err = AddressSerializer.WriteAddrPort(c.writer, destination) 313 | if err != nil { 314 | return err 315 | } 316 | } 317 | err = binary.Write(c.writer, binary.BigEndian, uint16(len(data))) 318 | if err != nil { 319 | return err 320 | } 321 | return common.Error(c.writer.Write(data)) 322 | } 323 | 324 | func (c *serverSession) close(sessionID uint16, err error) error { 325 | if c.localClose(sessionID, err) { 326 | return c.syncClose(sessionID, err != nil) 327 | } 328 | return nil 329 | } 330 | 331 | func (c *serverSession) localClose(sessionID uint16, err error) bool { 332 | var closed bool 333 | c.streamAccess.Lock() 334 | if session, loaded := c.streams[sessionID]; loaded { 335 | delete(c.streams, sessionID) 336 | _ = session.pipe.CloseWithError(err) 337 | closed = true 338 | } 339 | c.streamAccess.Unlock() 340 | return closed 341 | } 342 | 343 | func (c *serverSession) syncClose(sessionID uint16, hasError bool) error { 344 | writeRace := atomic.AddUint32(&c.writeRace, 1) 345 | c.writeAccess.Lock() 346 | defer c.writeAccess.Unlock() 347 | err := c.writeCloseFrame(sessionID, hasError) 348 | if err != nil { 349 | return err 350 | } 351 | if writeRace == atomic.LoadUint32(&c.writeRace) { 352 | err = c.writer.Flush() 353 | if err != nil { 354 | return err 355 | } 356 | } 357 | return nil 358 | } 359 | 360 | func (c *serverSession) writeCloseFrame(sessionID uint16, hasError bool) error { 361 | err := binary.Write(c.writer, binary.BigEndian, uint16(4)) 362 | if err != nil { 363 | return err 364 | } 365 | err = binary.Write(c.writer, binary.BigEndian, sessionID) 366 | if err != nil { 367 | return err 368 | } 369 | err = binary.Write(c.writer, binary.BigEndian, uint8(StatusEnd)) 370 | if err != nil { 371 | return err 372 | } 373 | var option uint8 374 | if hasError { 375 | option = OptionError 376 | } 377 | err = binary.Write(c.writer, binary.BigEndian, option) 378 | return err 379 | } 380 | 381 | type serverMuxConn struct { 382 | sessionID uint16 383 | pipe *io.PipeReader 384 | session *serverSession 385 | } 386 | 387 | func (c *serverMuxConn) Read(b []byte) (n int, err error) { 388 | return c.pipe.Read(b) 389 | } 390 | 391 | func (c *serverMuxConn) Write(b []byte) (n int, err error) { 392 | return c.session.syncWrite(c.sessionID, b) 393 | } 394 | 395 | func (c *serverMuxConn) WriteBuffer(buffer *buf.Buffer) error { 396 | dataLen := buffer.Len() 397 | header := buf.With(buffer.ExtendHeader(8)) 398 | common.Must( 399 | binary.Write(header, binary.BigEndian, uint16(4)), 400 | binary.Write(header, binary.BigEndian, c.sessionID), 401 | binary.Write(header, binary.BigEndian, uint8(StatusKeep)), 402 | binary.Write(header, binary.BigEndian, uint8(OptionData)), 403 | binary.Write(header, binary.BigEndian, uint16(dataLen)), 404 | ) 405 | return c.session.directWriter.WriteBuffer(buffer) 406 | } 407 | 408 | func (c *serverMuxConn) FrontHeadroom() int { 409 | return 8 410 | } 411 | 412 | func (c *serverMuxConn) UpstreamWriter() any { 413 | return c.session.directWriter 414 | } 415 | 416 | func (c *serverMuxConn) Close() error { 417 | return c.session.close(c.sessionID, nil) 418 | } 419 | 420 | func (c *serverMuxConn) LocalAddr() net.Addr { 421 | return M.Socksaddr{} 422 | } 423 | 424 | func (c *serverMuxConn) RemoteAddr() net.Addr { 425 | return M.Socksaddr{} 426 | } 427 | 428 | func (c *serverMuxConn) SetDeadline(t time.Time) error { 429 | return os.ErrInvalid 430 | } 431 | 432 | func (c *serverMuxConn) SetReadDeadline(t time.Time) error { 433 | return os.ErrInvalid 434 | } 435 | 436 | func (c *serverMuxConn) SetWriteDeadline(t time.Time) error { 437 | return os.ErrInvalid 438 | } 439 | 440 | func (c *serverMuxConn) NeedAdditionalReadDeadline() bool { 441 | return true 442 | } 443 | 444 | var _ PacketConn = (*serverMuxPacketConn)(nil) 445 | 446 | type serverMuxPacketConn struct { 447 | sessionID uint16 448 | pipe *io.PipeReader 449 | session *serverSession 450 | destination M.Socksaddr 451 | } 452 | 453 | func (c *serverMuxPacketConn) Read(b []byte) (n int, err error) { 454 | n, _, err = c.ReadFrom(b) 455 | return 456 | } 457 | 458 | func (c *serverMuxPacketConn) Write(b []byte) (n int, err error) { 459 | return c.WriteTo(b, c.destination) 460 | } 461 | 462 | func (c *serverMuxPacketConn) RemoteAddr() net.Addr { 463 | return c.destination.UDPAddr() 464 | } 465 | 466 | func (c *serverMuxPacketConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) { 467 | var length uint16 468 | err = binary.Read(c.pipe, binary.BigEndian, &length) 469 | if err != nil { 470 | return 471 | } 472 | if int(length) > len(p) { 473 | return 0, nil, E.Extend(io.ErrShortBuffer, "mux need ", length) 474 | } 475 | n, err = io.ReadFull(c.pipe, p[:length]) 476 | if err == nil { 477 | addr, err = AddressSerializer.ReadAddrPort(c.pipe) 478 | } 479 | return 480 | } 481 | 482 | func (c *serverMuxPacketConn) ReadPacket(buffer *buf.Buffer) (destination M.Socksaddr, err error) { 483 | var length uint16 484 | err = binary.Read(c.pipe, binary.BigEndian, &length) 485 | if err != nil { 486 | return 487 | } 488 | if int(length) > buffer.FreeLen() { 489 | return M.Socksaddr{}, E.Extend(io.ErrShortBuffer, "mux need ", length) 490 | } 491 | _, err = buffer.ReadFullFrom(c.pipe, int(length)) 492 | if err == nil { 493 | destination, err = AddressSerializer.ReadAddrPort(c.pipe) 494 | if err == nil { 495 | destination = destination.Unwrap() 496 | } 497 | } 498 | return 499 | } 500 | 501 | func (c *serverMuxPacketConn) WriteTo(p []byte, addr net.Addr) (n int, err error) { 502 | return c.session.syncWritePacket(c.sessionID, p, M.SocksaddrFromNet(addr)) 503 | } 504 | 505 | func (c *serverMuxPacketConn) WritePacket(buffer *buf.Buffer, destination M.Socksaddr) error { 506 | dataLen := buffer.Len() 507 | header := buf.With(buffer.ExtendHeader(9 + AddressSerializer.AddrPortLen(destination))) 508 | common.Must( 509 | binary.Write(header, binary.BigEndian, uint16(5+AddressSerializer.AddrPortLen(destination))), 510 | binary.Write(header, binary.BigEndian, c.sessionID), 511 | binary.Write(header, binary.BigEndian, uint8(StatusKeep)), 512 | binary.Write(header, binary.BigEndian, uint8(OptionData)), 513 | binary.Write(header, binary.BigEndian, uint8(NetworkUDP)), 514 | ) 515 | err := AddressSerializer.WriteAddrPort(header, destination) 516 | if err != nil { 517 | return err 518 | } 519 | common.Must(binary.Write(header, binary.BigEndian, uint16(dataLen))) 520 | return c.session.directWriter.WriteBuffer(buffer) 521 | } 522 | 523 | func (c *serverMuxPacketConn) FrontHeadroom() int { 524 | return 9 + M.MaxSocksaddrLength 525 | } 526 | 527 | func (c *serverMuxPacketConn) UpstreamWriter() any { 528 | return c.session.directWriter 529 | } 530 | 531 | func (c *serverMuxPacketConn) Close() error { 532 | return c.session.close(c.sessionID, nil) 533 | } 534 | 535 | func (c *serverMuxPacketConn) LocalAddr() net.Addr { 536 | return M.Socksaddr{} 537 | } 538 | 539 | func (c *serverMuxPacketConn) SetDeadline(t time.Time) error { 540 | return os.ErrInvalid 541 | } 542 | 543 | func (c *serverMuxPacketConn) SetReadDeadline(t time.Time) error { 544 | return os.ErrInvalid 545 | } 546 | 547 | func (c *serverMuxPacketConn) SetWriteDeadline(t time.Time) error { 548 | return os.ErrInvalid 549 | } 550 | 551 | func (c *serverMuxPacketConn) NeedAdditionalReadDeadline() bool { 552 | return true 553 | } 554 | -------------------------------------------------------------------------------- /mux_wrapper.go: -------------------------------------------------------------------------------- 1 | package vmess 2 | 3 | import ( 4 | "encoding/binary" 5 | "io" 6 | "net" 7 | 8 | "github.com/sagernet/sing/common" 9 | "github.com/sagernet/sing/common/buf" 10 | "github.com/sagernet/sing/common/bufio" 11 | E "github.com/sagernet/sing/common/exceptions" 12 | M "github.com/sagernet/sing/common/metadata" 13 | N "github.com/sagernet/sing/common/network" 14 | ) 15 | 16 | type MuxConnWrapper struct { 17 | net.Conn 18 | writer N.ExtendedWriter 19 | destination M.Socksaddr 20 | requestWritten bool 21 | remaining int 22 | } 23 | 24 | func NewMuxConnWrapper(conn net.Conn, destination M.Socksaddr) *MuxConnWrapper { 25 | return &MuxConnWrapper{ 26 | Conn: conn, 27 | writer: bufio.NewExtendedWriter(conn), 28 | destination: destination, 29 | } 30 | } 31 | 32 | func (c *MuxConnWrapper) Read(p []byte) (n int, err error) { 33 | buffer := buf.With(p) 34 | err = c.ReadBuffer(buffer) 35 | if err != nil { 36 | return 37 | } 38 | n = buffer.Len() 39 | return 40 | } 41 | 42 | func (c *MuxConnWrapper) Write(p []byte) (n int, err error) { 43 | return bufio.WriteBuffer(c, buf.As(p)) 44 | } 45 | 46 | func (c *MuxConnWrapper) ReadBuffer(buffer *buf.Buffer) error { 47 | if c.remaining > 0 { 48 | p := buffer.FreeBytes() 49 | if c.remaining < len(p) { 50 | p = p[:c.remaining] 51 | } 52 | n, err := c.Conn.Read(p) 53 | if err != nil { 54 | return err 55 | } 56 | c.remaining -= n 57 | buffer.Truncate(n) 58 | return nil 59 | } 60 | start := buffer.Start() 61 | _, err := buffer.ReadFullFrom(c.Conn, 6) 62 | if err != nil { 63 | return err 64 | } 65 | var length uint16 66 | err = binary.Read(buffer, binary.BigEndian, &length) 67 | if err != nil { 68 | return err 69 | } 70 | header, err := buffer.ReadBytes(4) 71 | if err != nil { 72 | return err 73 | } 74 | 75 | switch header[2] { 76 | case StatusNew: 77 | return E.New("unexpected frame new") 78 | case StatusKeep: 79 | if length > 4 { 80 | _, err = io.CopyN(io.Discard, c.Conn, int64(length-4)) 81 | if err != nil { 82 | return err 83 | } 84 | } 85 | case StatusEnd: 86 | return io.EOF 87 | case StatusKeepAlive: 88 | default: 89 | return E.New("unexpected frame: ", buffer.Byte(2)) 90 | } 91 | // option error 92 | if header[3]&2 == 2 { 93 | return E.Cause(net.ErrClosed, "remote closed") 94 | } 95 | // option data 96 | if header[3]&1 != 1 { 97 | buffer.Resize(start, 0) 98 | return c.ReadBuffer(buffer) 99 | } else { 100 | err = binary.Read(c.Conn, binary.BigEndian, &length) 101 | if err != nil { 102 | return err 103 | } 104 | c.remaining = int(length) 105 | buffer.Resize(start, 0) 106 | return c.ReadBuffer(buffer) 107 | } 108 | } 109 | 110 | func (c *MuxConnWrapper) WriteBuffer(buffer *buf.Buffer) error { 111 | dataLen := buffer.Len() 112 | addrLen := M.SocksaddrSerializer.AddrPortLen(c.destination) 113 | if !c.requestWritten { 114 | header := buf.With(buffer.ExtendHeader(c.frontHeadroom(addrLen))) 115 | common.Must( 116 | binary.Write(header, binary.BigEndian, uint16(5+addrLen)), 117 | header.WriteByte(0), 118 | header.WriteByte(0), 119 | header.WriteByte(1), // frame type new 120 | header.WriteByte(1), // option data 121 | header.WriteByte(NetworkTCP), 122 | ) 123 | err := AddressSerializer.WriteAddrPort(header, c.destination) 124 | if err != nil { 125 | return err 126 | } 127 | common.Must(binary.Write(header, binary.BigEndian, uint16(dataLen))) 128 | c.requestWritten = true 129 | } else { 130 | header := buffer.ExtendHeader(c.frontHeadroom(addrLen)) 131 | binary.BigEndian.PutUint16(header, 4) 132 | header[2] = 0 133 | header[3] = 0 134 | header[4] = 2 // frame keep 135 | header[5] = 1 // option data 136 | binary.BigEndian.PutUint16(header[6:], uint16(dataLen)) 137 | } 138 | return c.writer.WriteBuffer(buffer) 139 | } 140 | 141 | func (c *MuxConnWrapper) frontHeadroom(addrLen int) int { 142 | if !c.requestWritten { 143 | var headerLen int 144 | headerLen += 2 // frame len 145 | headerLen += 5 // frame header 146 | headerLen += addrLen 147 | headerLen += 2 // payload len 148 | return headerLen 149 | } else { 150 | return 8 151 | } 152 | } 153 | 154 | func (c *MuxConnWrapper) FrontHeadroom() int { 155 | return c.frontHeadroom(M.MaxSocksaddrLength) 156 | } 157 | 158 | func (c *MuxConnWrapper) Upstream() any { 159 | return c.Conn 160 | } 161 | -------------------------------------------------------------------------------- /packetaddr/conn.go: -------------------------------------------------------------------------------- 1 | package packetaddr 2 | 3 | import ( 4 | "net" 5 | 6 | "github.com/sagernet/sing/common" 7 | "github.com/sagernet/sing/common/buf" 8 | "github.com/sagernet/sing/common/bufio" 9 | E "github.com/sagernet/sing/common/exceptions" 10 | M "github.com/sagernet/sing/common/metadata" 11 | N "github.com/sagernet/sing/common/network" 12 | ) 13 | 14 | type PacketConn struct { 15 | N.NetPacketConn 16 | bindAddr M.Socksaddr 17 | } 18 | 19 | func NewConn(conn net.PacketConn, bindAddr M.Socksaddr) *PacketConn { 20 | return &PacketConn{ 21 | bufio.NewPacketConn(conn), 22 | bindAddr, 23 | } 24 | } 25 | 26 | func NewBindConn(conn net.Conn) *PacketConn { 27 | return &PacketConn{ 28 | bufio.NewUnbindPacketConn(conn), 29 | M.Socksaddr{}, 30 | } 31 | } 32 | 33 | func (c *PacketConn) RemoteAddr() net.Addr { 34 | return c.bindAddr 35 | } 36 | 37 | func (c *PacketConn) Read(b []byte) (n int, err error) { 38 | n, _, err = c.ReadFrom(b) 39 | return 40 | } 41 | 42 | func (c *PacketConn) Write(b []byte) (n int, err error) { 43 | return c.WriteTo(b, c.RemoteAddr()) 44 | } 45 | 46 | func (c *PacketConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) { 47 | buffer := buf.With(p) 48 | var destination M.Socksaddr 49 | destination, err = c.ReadPacket(buffer) 50 | if err != nil { 51 | return 52 | } 53 | n = copy(p, buffer.Bytes()) 54 | if destination.IsFqdn() { 55 | addr = destination 56 | } else { 57 | addr = destination.UDPAddr() 58 | } 59 | return 60 | } 61 | 62 | func (c *PacketConn) WriteTo(p []byte, addr net.Addr) (n int, err error) { 63 | destination := M.SocksaddrFromNet(addr) 64 | buffer := buf.NewSize(AddressSerializer.AddrPortLen(destination) + len(p)) 65 | defer buffer.Release() 66 | err = AddressSerializer.WriteAddrPort(buffer, destination) 67 | if err != nil { 68 | return 69 | } 70 | common.Must1(buffer.Write(p)) 71 | return c.NetPacketConn.WriteTo(buffer.Bytes(), c.bindAddr.UDPAddr()) 72 | } 73 | 74 | func (c *PacketConn) ReadPacket(buffer *buf.Buffer) (destination M.Socksaddr, err error) { 75 | _, err = c.NetPacketConn.ReadPacket(buffer) 76 | if err != nil { 77 | return 78 | } 79 | destination, err = AddressSerializer.ReadAddrPort(buffer) 80 | if err != nil { 81 | return 82 | } 83 | return destination.Unwrap(), nil 84 | } 85 | 86 | func (c *PacketConn) WritePacket(buffer *buf.Buffer, destination M.Socksaddr) error { 87 | if destination.IsFqdn() { 88 | return E.Extend(ErrFqdnUnsupported, destination.Fqdn) 89 | } 90 | header := buf.With(buffer.ExtendHeader(AddressSerializer.AddrPortLen(destination))) 91 | err := AddressSerializer.WriteAddrPort(header, destination) 92 | if err != nil { 93 | return err 94 | } 95 | return c.NetPacketConn.WritePacket(buffer, c.bindAddr) 96 | } 97 | 98 | func (c *PacketConn) FrontHeadroom() int { 99 | return M.MaxIPSocksaddrLength 100 | } 101 | 102 | func (c *PacketConn) Upstream() any { 103 | return c.NetPacketConn 104 | } 105 | -------------------------------------------------------------------------------- /packetaddr/packetaddr.go: -------------------------------------------------------------------------------- 1 | package packetaddr 2 | 3 | import ( 4 | E "github.com/sagernet/sing/common/exceptions" 5 | M "github.com/sagernet/sing/common/metadata" 6 | ) 7 | 8 | const SeqPacketMagicAddress = "sp.packet-addr.v2fly.arpa" 9 | 10 | var AddressSerializer = M.NewSerializer( 11 | M.AddressFamilyByte(0x01, M.AddressFamilyIPv4), 12 | M.AddressFamilyByte(0x02, M.AddressFamilyIPv6), 13 | ) 14 | 15 | var ErrFqdnUnsupported = E.New("packetaddr: fqdn unsupported") 16 | -------------------------------------------------------------------------------- /protocol.go: -------------------------------------------------------------------------------- 1 | package vmess 2 | 3 | import ( 4 | "crypto/aes" 5 | "crypto/cipher" 6 | "crypto/md5" 7 | "encoding/binary" 8 | "hash/crc32" 9 | "io" 10 | "runtime" 11 | "time" 12 | 13 | "github.com/sagernet/sing/common" 14 | "github.com/sagernet/sing/common/buf" 15 | "github.com/sagernet/sing/common/bufio" 16 | E "github.com/sagernet/sing/common/exceptions" 17 | M "github.com/sagernet/sing/common/metadata" 18 | 19 | "github.com/gofrs/uuid/v5" 20 | "golang.org/x/crypto/chacha20poly1305" 21 | "golang.org/x/crypto/sha3" 22 | ) 23 | 24 | const ( 25 | Version = 1 26 | ReadChunkSize = 16384 27 | WriteChunkSize = 15000 28 | CacheDurationSeconds = 120 29 | MaxPaddingSize = 64 30 | MaxFrontHeadroom = 2 + CipherOverhead 31 | MaxRearHeadroom = CipherOverhead*2 + MaxPaddingSize 32 | ) 33 | 34 | const ( 35 | SecurityTypeLegacy = 1 36 | SecurityTypeAuto = 2 37 | SecurityTypeAes128Gcm = 3 38 | SecurityTypeChacha20Poly1305 = 4 39 | SecurityTypeNone = 5 40 | SecurityTypeZero = 6 41 | ) 42 | 43 | const ( 44 | CommandTCP = 1 45 | CommandUDP = 2 46 | CommandMux = 3 47 | ) 48 | 49 | const ( 50 | RequestOptionChunkStream = 1 51 | RequestOptionConnectionReuse = 2 52 | RequestOptionChunkMasking = 4 53 | RequestOptionGlobalPadding = 8 54 | RequestOptionAuthenticatedLength = 16 55 | ) 56 | 57 | // nonce in java called iv 58 | 59 | const ( 60 | KDFSaltConstAuthIDEncryptionKey = "AES Auth ID Encryption" 61 | KDFSaltConstAEADRespHeaderLenKey = "AEAD Resp Header Len Key" 62 | KDFSaltConstAEADRespHeaderLenIV = "AEAD Resp Header Len IV" 63 | KDFSaltConstAEADRespHeaderPayloadKey = "AEAD Resp Header Key" 64 | KDFSaltConstAEADRespHeaderPayloadIV = "AEAD Resp Header IV" 65 | KDFSaltConstVMessAEADKDF = "VMess AEAD KDF" 66 | KDFSaltConstVMessHeaderPayloadAEADKey = "VMess Header AEAD Key" 67 | KDFSaltConstVMessHeaderPayloadAEADIV = "VMess Header AEAD Nonce" 68 | KDFSaltConstVMessHeaderPayloadLengthAEADKey = "VMess Header AEAD Key_Length" 69 | KDFSaltConstVMessHeaderPayloadLengthAEADIV = "VMess Header AEAD Nonce_Length" 70 | ) 71 | 72 | const ( 73 | CipherOverhead = 16 74 | ) 75 | 76 | const ( 77 | StatusNew = 1 78 | StatusKeep = 2 79 | StatusEnd = 3 80 | StatusKeepAlive = 4 81 | OptionData = 1 82 | OptionError = 2 83 | NetworkTCP = 1 84 | NetworkUDP = 2 85 | ) 86 | 87 | var MuxDestination = M.Socksaddr{ 88 | Fqdn: "v1.mux.cool", 89 | Port: 666, 90 | } 91 | 92 | type TimeFunc = func() time.Time 93 | 94 | var ( 95 | ErrUnsupportedSecurityType = E.New("vmess: unsupported security type") 96 | ErrInvalidChecksum = E.New("vmess: invalid chunk checksum") 97 | ) 98 | 99 | var AddressSerializer = M.NewSerializer( 100 | M.AddressFamilyByte(0x01, M.AddressFamilyIPv4), 101 | M.AddressFamilyByte(0x03, M.AddressFamilyIPv6), 102 | M.AddressFamilyByte(0x02, M.AddressFamilyFqdn), 103 | M.PortThenAddress(), 104 | ) 105 | 106 | func Key(user uuid.UUID) (key [16]byte) { 107 | md5hash := md5.New() 108 | common.Must1(md5hash.Write(user[:])) 109 | common.Must1(md5hash.Write([]byte("c48619fe-8f02-49e0-b9e9-edf763e17e21"))) 110 | md5hash.Sum(key[:0]) 111 | return 112 | } 113 | 114 | func AlterId(user uuid.UUID) uuid.UUID { 115 | md5hash := md5.New() 116 | common.Must1(md5hash.Write(user[:])) 117 | common.Must1(md5hash.Write([]byte("16167dc8-16b6-4e6d-b8bb-65dd68113a81"))) 118 | var newUser uuid.UUID 119 | for { 120 | md5hash.Sum(newUser[:0]) 121 | if user != newUser { 122 | return newUser 123 | } 124 | common.Must1(md5hash.Write([]byte("533eff8a-4113-4b10-b5ce-0f5d76b98cd2"))) 125 | } 126 | } 127 | 128 | func AuthID(key [16]byte, time time.Time, buffer *buf.Buffer) { 129 | common.Must(binary.Write(buffer, binary.BigEndian, time.Unix())) 130 | buffer.WriteRandom(4) 131 | common.Must(binary.Write(buffer, binary.BigEndian, crc32.ChecksumIEEE(buffer.Bytes()))) 132 | aesBlock, err := aes.NewCipher(KDF(key[:], KDFSaltConstAuthIDEncryptionKey)[:16]) 133 | common.Must(err) 134 | aesBlock.Encrypt(buffer.Bytes(), buffer.Bytes()) 135 | } 136 | 137 | func AutoSecurityType() byte { 138 | if runtime.GOARCH == "amd64" || runtime.GOARCH == "s390x" || runtime.GOARCH == "arm64" { 139 | return SecurityTypeAes128Gcm 140 | } 141 | return SecurityTypeChacha20Poly1305 142 | } 143 | 144 | func GenerateChacha20Poly1305Key(b []byte) []byte { 145 | key := make([]byte, 32) 146 | checksum := md5.Sum(b) 147 | copy(key, checksum[:]) 148 | checksum = md5.Sum(key[:16]) 149 | copy(key[16:], checksum[:]) 150 | return key 151 | } 152 | 153 | func CreateReader(upstream io.Reader, streamReader io.Reader, requestKey []byte, requestNonce []byte, key []byte, nonce []byte, security byte, option byte) io.Reader { 154 | switch security { 155 | case SecurityTypeNone: 156 | var reader io.Reader 157 | if option&RequestOptionChunkStream != 0 { 158 | var globalPadding sha3.ShakeHash 159 | if option&RequestOptionGlobalPadding != 0 { 160 | globalPadding = sha3.NewShake128() 161 | common.Must1(globalPadding.Write(nonce)) 162 | } 163 | if option&RequestOptionAuthenticatedLength != 0 { 164 | reader = NewAes128GcmChunkReader(upstream, requestKey, requestNonce, globalPadding) 165 | } else { 166 | var chunkMasking sha3.ShakeHash 167 | if option&RequestOptionChunkMasking != 0 { 168 | if globalPadding != nil { 169 | chunkMasking = globalPadding 170 | } else { 171 | chunkMasking = sha3.NewShake128() 172 | common.Must1(chunkMasking.Write(nonce)) 173 | } 174 | } 175 | reader = NewStreamChunkReader(upstream, chunkMasking, globalPadding) 176 | } 177 | } 178 | if reader != nil { 179 | return reader 180 | } else { 181 | return upstream 182 | } 183 | case SecurityTypeLegacy: 184 | if streamReader == nil { 185 | streamReader = NewStreamReader(upstream, key, nonce) 186 | } 187 | if option&RequestOptionChunkStream != 0 { 188 | var globalPadding sha3.ShakeHash 189 | if option&RequestOptionGlobalPadding != 0 { 190 | globalPadding = sha3.NewShake128() 191 | common.Must1(globalPadding.Write(nonce)) 192 | } 193 | var chunkMasking sha3.ShakeHash 194 | if option&RequestOptionChunkMasking != 0 { 195 | if globalPadding != nil { 196 | chunkMasking = globalPadding 197 | } else { 198 | chunkMasking = sha3.NewShake128() 199 | common.Must1(chunkMasking.Write(nonce)) 200 | } 201 | } 202 | return NewStreamChecksumReader(NewStreamChunkReader(streamReader, chunkMasking, globalPadding)) 203 | } 204 | return streamReader 205 | case SecurityTypeAes128Gcm: 206 | var chunkReader io.Reader 207 | var globalPadding sha3.ShakeHash 208 | if option&RequestOptionGlobalPadding != 0 { 209 | globalPadding = sha3.NewShake128() 210 | common.Must1(globalPadding.Write(nonce)) 211 | } 212 | if option&RequestOptionAuthenticatedLength != 0 { 213 | chunkReader = NewAes128GcmChunkReader(upstream, requestKey, requestNonce, globalPadding) 214 | } else { 215 | var chunkMasking sha3.ShakeHash 216 | if option&RequestOptionChunkMasking != 0 { 217 | if globalPadding != nil { 218 | chunkMasking = globalPadding 219 | } else { 220 | chunkMasking = sha3.NewShake128() 221 | common.Must1(chunkMasking.Write(nonce)) 222 | } 223 | } 224 | chunkReader = NewStreamChunkReader(upstream, chunkMasking, globalPadding) 225 | } 226 | return NewAes128GcmReader(chunkReader, key, nonce) 227 | case SecurityTypeChacha20Poly1305: 228 | var chunkReader io.Reader 229 | var globalPadding sha3.ShakeHash 230 | if option&RequestOptionGlobalPadding != 0 { 231 | globalPadding = sha3.NewShake128() 232 | common.Must1(globalPadding.Write(nonce)) 233 | } 234 | if option&RequestOptionAuthenticatedLength != 0 { 235 | chunkReader = NewChacha20Poly1305ChunkReader(upstream, requestKey, requestNonce, globalPadding) 236 | } else { 237 | var chunkMasking sha3.ShakeHash 238 | if option&RequestOptionChunkMasking != 0 { 239 | if globalPadding != nil { 240 | chunkMasking = globalPadding 241 | } else { 242 | chunkMasking = sha3.NewShake128() 243 | common.Must1(chunkMasking.Write(nonce)) 244 | } 245 | } 246 | chunkReader = NewStreamChunkReader(upstream, chunkMasking, globalPadding) 247 | } 248 | return NewChacha20Poly1305Reader(chunkReader, key, nonce) 249 | default: 250 | panic("unexpected security type") 251 | } 252 | } 253 | 254 | func CreateWriter(upstream io.Writer, streamWriter io.Writer, requestKey []byte, requestNonce []byte, key []byte, nonce []byte, security byte, option byte) io.Writer { 255 | switch security { 256 | case SecurityTypeNone: 257 | var writer io.Writer 258 | if option&RequestOptionChunkStream != 0 { 259 | var globalPadding sha3.ShakeHash 260 | if option&RequestOptionGlobalPadding != 0 { 261 | globalPadding = sha3.NewShake128() 262 | common.Must1(globalPadding.Write(nonce)) 263 | } 264 | if option&RequestOptionAuthenticatedLength != 0 { 265 | writer = NewAes128GcmChunkWriter(upstream, requestKey, requestNonce, globalPadding) 266 | } else { 267 | var chunkMasking sha3.ShakeHash 268 | if option&RequestOptionChunkMasking != 0 { 269 | if globalPadding != nil { 270 | chunkMasking = globalPadding 271 | } else { 272 | chunkMasking = sha3.NewShake128() 273 | common.Must1(chunkMasking.Write(nonce)) 274 | } 275 | } 276 | writer = NewStreamChunkWriter(upstream, chunkMasking, globalPadding) 277 | } 278 | } 279 | if writer != nil { 280 | return writer 281 | } else { 282 | return upstream 283 | } 284 | case SecurityTypeLegacy: 285 | if streamWriter == nil { 286 | streamWriter = NewStreamWriter(upstream, key, nonce) 287 | } 288 | if option&RequestOptionChunkStream != 0 { 289 | var globalPadding sha3.ShakeHash 290 | if option&RequestOptionGlobalPadding != 0 { 291 | globalPadding = sha3.NewShake128() 292 | common.Must1(globalPadding.Write(nonce)) 293 | } 294 | var chunkMasking sha3.ShakeHash 295 | if option&RequestOptionChunkMasking != 0 { 296 | if globalPadding != nil { 297 | chunkMasking = globalPadding 298 | } else { 299 | chunkMasking = sha3.NewShake128() 300 | common.Must1(chunkMasking.Write(nonce)) 301 | } 302 | } 303 | return bufio.NewChunkWriter(NewStreamChecksumWriter(NewStreamChunkWriter(streamWriter, chunkMasking, globalPadding)), WriteChunkSize) 304 | } 305 | return NewStreamWriter(upstream, key, nonce) 306 | case SecurityTypeAes128Gcm: 307 | var writer io.Writer 308 | var globalPadding sha3.ShakeHash 309 | if option&RequestOptionGlobalPadding != 0 { 310 | globalPadding = sha3.NewShake128() 311 | common.Must1(globalPadding.Write(nonce)) 312 | } 313 | if option&RequestOptionAuthenticatedLength != 0 { 314 | writer = NewAes128GcmChunkWriter(upstream, requestKey, requestNonce, globalPadding) 315 | } else { 316 | var chunkMasking sha3.ShakeHash 317 | if option&RequestOptionChunkMasking != 0 { 318 | if globalPadding != nil { 319 | chunkMasking = globalPadding 320 | } else { 321 | chunkMasking = sha3.NewShake128() 322 | common.Must1(chunkMasking.Write(nonce)) 323 | } 324 | } 325 | writer = NewStreamChunkWriter(upstream, chunkMasking, globalPadding) 326 | } 327 | return bufio.NewChunkWriter(NewAes128GcmWriter(writer, key, nonce), WriteChunkSize) 328 | case SecurityTypeChacha20Poly1305: 329 | var chunkWriter io.Writer 330 | var globalPadding sha3.ShakeHash 331 | if option&RequestOptionGlobalPadding != 0 { 332 | globalPadding = sha3.NewShake128() 333 | common.Must1(globalPadding.Write(nonce)) 334 | } 335 | if option&RequestOptionAuthenticatedLength != 0 { 336 | chunkWriter = NewChacha20Poly1305ChunkWriter(upstream, requestKey, requestNonce, globalPadding) 337 | } else { 338 | var chunkMasking sha3.ShakeHash 339 | if option&RequestOptionChunkMasking != 0 { 340 | if globalPadding != nil { 341 | chunkMasking = globalPadding 342 | } else { 343 | chunkMasking = sha3.NewShake128() 344 | common.Must1(chunkMasking.Write(nonce)) 345 | } 346 | } 347 | chunkWriter = NewStreamChunkWriter(upstream, chunkMasking, globalPadding) 348 | } 349 | return bufio.NewChunkWriter(NewChacha20Poly1305Writer(chunkWriter, key, nonce), WriteChunkSize) 350 | default: 351 | panic("unexpected security type") 352 | } 353 | } 354 | 355 | func newAesGcm(key []byte) cipher.AEAD { 356 | block, err := aes.NewCipher(key) 357 | common.Must(err) 358 | outCipher, err := cipher.NewGCM(block) 359 | common.Must(err) 360 | return outCipher 361 | } 362 | 363 | func newAesStream(key []byte, iv []byte, stream func(block cipher.Block, iv []byte) cipher.Stream) cipher.Stream { 364 | block, err := aes.NewCipher(key) 365 | common.Must(err) 366 | return stream(block, iv) 367 | } 368 | 369 | func newChacha20Poly1305(key []byte) cipher.AEAD { 370 | outCipher, err := chacha20poly1305.New(key) 371 | common.Must(err) 372 | return outCipher 373 | } 374 | -------------------------------------------------------------------------------- /service.go: -------------------------------------------------------------------------------- 1 | package vmess 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "crypto/aes" 7 | "crypto/cipher" 8 | "crypto/hmac" 9 | "crypto/md5" 10 | "crypto/sha256" 11 | "encoding/binary" 12 | "hash/crc32" 13 | "io" 14 | "math" 15 | "net" 16 | "time" 17 | "unsafe" 18 | 19 | "github.com/sagernet/sing/common" 20 | "github.com/sagernet/sing/common/auth" 21 | "github.com/sagernet/sing/common/buf" 22 | "github.com/sagernet/sing/common/bufio" 23 | E "github.com/sagernet/sing/common/exceptions" 24 | M "github.com/sagernet/sing/common/metadata" 25 | N "github.com/sagernet/sing/common/network" 26 | "github.com/sagernet/sing/common/replay" 27 | "github.com/sagernet/sing/common/rw" 28 | 29 | "github.com/gofrs/uuid/v5" 30 | ) 31 | 32 | type Handler interface { 33 | N.TCPConnectionHandlerEx 34 | N.UDPConnectionHandlerEx 35 | } 36 | 37 | var ( 38 | ErrBadHeader = E.New("bad header") 39 | ErrBadTimestamp = E.New("bad timestamp") 40 | ErrReplay = E.New("replayed request") 41 | ErrBadRequest = E.New("bad request") 42 | ErrBadVersion = E.New("bad version") 43 | ) 44 | 45 | type Service[U comparable] struct { 46 | userKey map[U][16]byte 47 | userIdCipher map[U]cipher.Block 48 | replayFilter replay.Filter 49 | handler Handler 50 | time func() time.Time 51 | disableHeaderProtect bool 52 | alterIds map[U][][16]byte 53 | alterIdUpdateTime map[U]int64 54 | alterIdMap map[[16]byte]legacyUserEntry[U] 55 | alterIdUpdateTask *time.Ticker 56 | alterIdUpdateDone chan struct{} 57 | } 58 | 59 | type legacyUserEntry[U comparable] struct { 60 | User U 61 | Time int64 62 | Index int 63 | } 64 | 65 | func NewService[U comparable](handler Handler, options ...ServiceOption) *Service[U] { 66 | service := &Service[U]{ 67 | replayFilter: replay.NewSimple(time.Second * 120), 68 | handler: handler, 69 | time: time.Now, 70 | } 71 | anyService := (*Service[string])(unsafe.Pointer(service)) 72 | for _, option := range options { 73 | option(anyService) 74 | } 75 | return service 76 | } 77 | 78 | func (s *Service[U]) UpdateUsers(userList []U, userIdList []string, alterIdList []int) error { 79 | userKeyMap := make(map[U][16]byte) 80 | userIdCipherMap := make(map[U]cipher.Block) 81 | userAlterIds := make(map[U][][16]byte) 82 | for i, user := range userList { 83 | userId := userIdList[i] 84 | userUUID, err := uuid.FromString(userId) 85 | if err != nil { 86 | userUUID = uuid.NewV5(uuid.Nil, userId) 87 | } 88 | userCmdKey := Key(userUUID) 89 | userKeyMap[user] = userCmdKey 90 | userIdCipher, err := aes.NewCipher(KDF(userCmdKey[:], KDFSaltConstAuthIDEncryptionKey)[:16]) 91 | if err != nil { 92 | return err 93 | } 94 | userIdCipherMap[user] = userIdCipher 95 | alterId := alterIdList[i] 96 | if alterId > 0 { 97 | alterIds := make([][16]byte, 0, alterId) 98 | currentId := userUUID 99 | for j := 0; j < alterId; j++ { 100 | currentId = AlterId(currentId) 101 | alterIds = append(alterIds, currentId) 102 | } 103 | userAlterIds[user] = alterIds 104 | } 105 | } 106 | s.userKey = userKeyMap 107 | s.userIdCipher = userIdCipherMap 108 | s.alterIds = userAlterIds 109 | s.alterIdUpdateTime = make(map[U]int64) 110 | s.generateLegacyKeys() 111 | return nil 112 | } 113 | 114 | func (s *Service[U]) Start() error { 115 | const updateInterval = 10 * time.Second 116 | if len(s.alterIds) > 0 { 117 | s.alterIdUpdateTask = time.NewTicker(updateInterval) 118 | s.alterIdUpdateDone = make(chan struct{}) 119 | go s.loopGenerateLegacyKeys() 120 | } 121 | return nil 122 | } 123 | 124 | func (s *Service[U]) Close() error { 125 | if s.alterIdUpdateTask != nil { 126 | s.alterIdUpdateTask.Stop() 127 | close(s.alterIdUpdateDone) 128 | } 129 | return nil 130 | } 131 | 132 | func (s *Service[U]) loopGenerateLegacyKeys() { 133 | for { 134 | select { 135 | case <-s.alterIdUpdateDone: 136 | return 137 | case <-s.alterIdUpdateTask.C: 138 | } 139 | s.generateLegacyKeys() 140 | } 141 | } 142 | 143 | func (s *Service[U]) generateLegacyKeys() { 144 | nowSec := s.time().Unix() 145 | endSec := nowSec + CacheDurationSeconds 146 | var hashValue [16]byte 147 | 148 | userAlterIdMap := make(map[[16]byte]legacyUserEntry[U]) 149 | userAlterIdUpdateTime := make(map[U]int64) 150 | 151 | for user, alterIds := range s.alterIds { 152 | beginSec := s.alterIdUpdateTime[user] 153 | if beginSec < nowSec-CacheDurationSeconds { 154 | beginSec = nowSec - CacheDurationSeconds 155 | } 156 | for i, alterId := range alterIds { 157 | idHash := hmac.New(md5.New, alterId[:]) 158 | for ts := beginSec; ts <= endSec; ts++ { 159 | common.Must(binary.Write(idHash, binary.BigEndian, uint64(ts))) 160 | idHash.Sum(hashValue[:0]) 161 | idHash.Reset() 162 | userAlterIdMap[hashValue] = legacyUserEntry[U]{user, ts, i} 163 | } 164 | } 165 | userAlterIdUpdateTime[user] = nowSec 166 | } 167 | s.alterIdUpdateTime = userAlterIdUpdateTime 168 | s.alterIdMap = userAlterIdMap 169 | } 170 | 171 | func (s *Service[U]) NewConnection(ctx context.Context, conn net.Conn, source M.Socksaddr, onClose N.CloseHandlerFunc) error { 172 | const headerLenBufferLen = 2 + CipherOverhead 173 | const aeadMinHeaderLen = 16 + headerLenBufferLen + 8 + CipherOverhead + 42 174 | minHeaderLen := aeadMinHeaderLen 175 | if len(s.alterIds) > 0 { 176 | minHeaderLen = 16 + 38 177 | } 178 | 179 | requestBuffer := buf.New() 180 | defer requestBuffer.Release() 181 | 182 | if !s.disableHeaderProtect { 183 | n, err := requestBuffer.ReadOnceFrom(conn) 184 | if err != nil { 185 | return err 186 | } 187 | if n < minHeaderLen { 188 | return ErrBadHeader 189 | } 190 | } else { 191 | _, err := requestBuffer.ReadAtLeastFrom(conn, minHeaderLen) 192 | if err != nil { 193 | return err 194 | } 195 | } 196 | 197 | authId := requestBuffer.To(16) 198 | var decodedId [16]byte 199 | var user U 200 | var found bool 201 | for currUser, userIdBlock := range s.userIdCipher { 202 | userIdBlock.Decrypt(decodedId[:], authId) 203 | timestamp := int64(binary.BigEndian.Uint64(decodedId[:])) 204 | checksum := binary.BigEndian.Uint32(decodedId[12:]) 205 | if crc32.ChecksumIEEE(decodedId[:12]) != checksum { 206 | continue 207 | } 208 | if math.Abs(math.Abs(float64(timestamp))-float64(time.Now().Unix())) > 120 { 209 | return ErrBadTimestamp 210 | } 211 | if !s.replayFilter.Check(decodedId[:]) { 212 | return ErrReplay 213 | } 214 | user = currUser 215 | found = true 216 | break 217 | } 218 | 219 | var legacyProtocol bool 220 | var legacyTimestamp uint64 221 | if !found { 222 | copy(decodedId[:], authId) 223 | if currUser, loaded := s.alterIdMap[decodedId]; loaded { 224 | found = true 225 | legacyProtocol = true 226 | user = currUser.User 227 | legacyTimestamp = uint64(currUser.Time) 228 | } 229 | } 230 | if !found { 231 | return ErrBadRequest 232 | } 233 | 234 | ctx = auth.ContextWithUser(ctx, user) 235 | cmdKey := s.userKey[user] 236 | var headerReader io.Reader 237 | var headerBuffer []byte 238 | 239 | var reader io.Reader 240 | var err error 241 | if legacyProtocol { 242 | requestBuffer.Advance(16) 243 | reader = io.MultiReader(bytes.NewReader(requestBuffer.Bytes()), conn) 244 | 245 | timeHash := md5.New() 246 | common.Must(binary.Write(timeHash, binary.BigEndian, legacyTimestamp)) 247 | common.Must(binary.Write(timeHash, binary.BigEndian, legacyTimestamp)) 248 | common.Must(binary.Write(timeHash, binary.BigEndian, legacyTimestamp)) 249 | common.Must(binary.Write(timeHash, binary.BigEndian, legacyTimestamp)) 250 | userKey := s.userKey[user] 251 | headerReader = NewStreamReader(reader, userKey[:], timeHash.Sum(nil)) 252 | headerBuffer = make([]byte, 38) 253 | _, err = io.ReadFull(headerReader, headerBuffer) 254 | if err != nil { 255 | return E.Extend(ErrBadHeader, io.ErrShortBuffer) 256 | } 257 | } else { 258 | if requestBuffer.Len() < aeadMinHeaderLen { 259 | return ErrBadHeader 260 | } 261 | 262 | reader = conn 263 | 264 | const nonceIndex = 16 + headerLenBufferLen 265 | connectionNonce := requestBuffer.Range(nonceIndex, nonceIndex+8) 266 | 267 | lengthKey := KDF(cmdKey[:], KDFSaltConstVMessHeaderPayloadLengthAEADKey, authId, connectionNonce)[:16] 268 | lengthNonce := KDF(cmdKey[:], KDFSaltConstVMessHeaderPayloadLengthAEADIV, authId, connectionNonce)[:12] 269 | lengthBuffer, err := newAesGcm(lengthKey).Open(requestBuffer.Index(16), lengthNonce, requestBuffer.Range(16, nonceIndex), authId) 270 | if err != nil { 271 | return err 272 | } 273 | 274 | const headerIndex = nonceIndex + 8 275 | headerLength := int(binary.BigEndian.Uint16(lengthBuffer)) 276 | needRead := headerLength + headerIndex + CipherOverhead - requestBuffer.Len() 277 | if needRead > 0 { 278 | _, err = requestBuffer.ReadFullFrom(conn, needRead) 279 | if err != nil { 280 | return err 281 | } 282 | } 283 | 284 | headerKey := KDF(cmdKey[:], KDFSaltConstVMessHeaderPayloadAEADKey, authId, connectionNonce)[:16] 285 | headerNonce := KDF(cmdKey[:], KDFSaltConstVMessHeaderPayloadAEADIV, authId, connectionNonce)[:12] 286 | headerBuffer, err = newAesGcm(headerKey).Open(requestBuffer.Index(headerIndex), headerNonce, requestBuffer.Range(headerIndex, headerIndex+headerLength+CipherOverhead), authId) 287 | if err != nil { 288 | return err 289 | } 290 | // replace with < if support mux 291 | if len(headerBuffer) <= 38 { 292 | return E.Extend(ErrBadHeader, io.ErrShortBuffer) 293 | } 294 | requestBuffer.Advance(headerIndex + headerLength + CipherOverhead) 295 | headerReader = bytes.NewReader(headerBuffer[38:]) 296 | } 297 | 298 | version := headerBuffer[0] 299 | if version != Version { 300 | return E.Extend(ErrBadVersion, version) 301 | } 302 | 303 | requestBodyKey := make([]byte, 16) 304 | requestBodyNonce := make([]byte, 16) 305 | 306 | copy(requestBodyKey, headerBuffer[17:33]) 307 | copy(requestBodyNonce, headerBuffer[1:17]) 308 | 309 | responseHeader := headerBuffer[33] 310 | option := headerBuffer[34] 311 | paddingLen := int(headerBuffer[35] >> 4) 312 | security := headerBuffer[35] & 0x0F 313 | command := headerBuffer[37] 314 | switch command { 315 | case CommandTCP, CommandUDP, CommandMux: 316 | default: 317 | return E.New("unknown command: ", command) 318 | } 319 | if command == CommandUDP && option == 0 { 320 | return E.New("bad packet connection") 321 | } 322 | var destination M.Socksaddr 323 | if command != CommandMux { 324 | destination, err = AddressSerializer.ReadAddrPort(headerReader) 325 | if err != nil { 326 | return err 327 | } 328 | } 329 | if paddingLen > 0 { 330 | _, err = io.CopyN(io.Discard, headerReader, int64(paddingLen)) 331 | if err != nil { 332 | return E.Extend(ErrBadHeader, "bad padding") 333 | } 334 | } 335 | err = rw.SkipN(headerReader, 4) 336 | if err != nil { 337 | return err 338 | } 339 | if !legacyProtocol && requestBuffer.Len() > 0 { 340 | reader = bufio.NewCachedReader(reader, requestBuffer.ToOwned()) 341 | } 342 | reader = CreateReader(reader, nil, requestBodyKey, requestBodyNonce, requestBodyKey, requestBodyNonce, security, option) 343 | if option&RequestOptionChunkStream != 0 && command == CommandTCP || command == CommandMux { 344 | reader = bufio.NewChunkReader(reader, ReadChunkSize) 345 | } 346 | rawConn := rawServerConn{ 347 | Conn: conn, 348 | legacyProtocol: legacyProtocol, 349 | requestKey: requestBodyKey, 350 | requestNonce: requestBodyNonce, 351 | responseHeader: responseHeader, 352 | security: security, 353 | option: option, 354 | reader: bufio.NewExtendedReader(reader), 355 | } 356 | 357 | switch command { 358 | case CommandTCP: 359 | s.handler.NewConnectionEx(ctx, &serverConn{rawConn}, source, destination, onClose) 360 | case CommandUDP: 361 | s.handler.NewPacketConnectionEx(ctx, &serverPacketConn{rawConn, destination}, source, destination, onClose) 362 | case CommandMux: 363 | return HandleMuxConnection(ctx, &serverConn{rawConn}, source, s.handler) 364 | default: 365 | return E.New("unknown command: ", command) 366 | } 367 | return nil 368 | } 369 | 370 | type rawServerConn struct { 371 | net.Conn 372 | legacyProtocol bool 373 | requestKey []byte 374 | requestNonce []byte 375 | responseHeader byte 376 | security byte 377 | option byte 378 | reader N.ExtendedReader 379 | writer N.ExtendedWriter 380 | } 381 | 382 | func (c *rawServerConn) writeResponse() error { 383 | if c.legacyProtocol { 384 | responseKey := md5.Sum(c.requestKey) 385 | responseNonce := md5.Sum(c.requestNonce) 386 | headerWriter := NewStreamWriter(c.Conn, responseKey[:], responseNonce[:]) 387 | _, err := headerWriter.Write([]byte{c.responseHeader, c.option, 0, 0}) 388 | if err != nil { 389 | return E.Cause(err, "write response") 390 | } 391 | c.writer = bufio.NewExtendedWriter(CreateWriter(c.Conn, headerWriter, c.requestKey, c.requestNonce, responseKey[:], responseNonce[:], c.security, c.option)) 392 | } else { 393 | responseBuffer := buf.NewSize(2 + CipherOverhead + 4 + CipherOverhead) 394 | defer responseBuffer.Release() 395 | 396 | _responseKey := sha256.Sum256(c.requestKey[:]) 397 | responseKey := _responseKey[:16] 398 | _responseNonce := sha256.Sum256(c.requestNonce[:]) 399 | responseNonce := _responseNonce[:16] 400 | 401 | headerLenKey := KDF(responseKey, KDFSaltConstAEADRespHeaderLenKey)[:16] 402 | headerLenNonce := KDF(responseNonce, KDFSaltConstAEADRespHeaderLenIV)[:12] 403 | headerLenCipher := newAesGcm(headerLenKey) 404 | binary.BigEndian.PutUint16(responseBuffer.Extend(2), 4) 405 | headerLenCipher.Seal(responseBuffer.Index(0), headerLenNonce, responseBuffer.Bytes(), nil) 406 | responseBuffer.Extend(CipherOverhead) 407 | 408 | headerKey := KDF(responseKey, KDFSaltConstAEADRespHeaderPayloadKey)[:16] 409 | headerNonce := KDF(responseNonce, KDFSaltConstAEADRespHeaderPayloadIV)[:12] 410 | headerCipher := newAesGcm(headerKey) 411 | common.Must( 412 | responseBuffer.WriteByte(c.responseHeader), 413 | responseBuffer.WriteByte(c.option), 414 | responseBuffer.WriteZeroN(2), 415 | ) 416 | const headerIndex = 2 + CipherOverhead 417 | headerCipher.Seal(responseBuffer.Index(headerIndex), headerNonce, responseBuffer.From(headerIndex), nil) 418 | responseBuffer.Extend(CipherOverhead) 419 | 420 | _, err := c.Conn.Write(responseBuffer.Bytes()) 421 | if err != nil { 422 | return err 423 | } 424 | 425 | c.writer = bufio.NewExtendedWriter(CreateWriter(c.Conn, nil, c.requestKey, c.requestNonce, responseKey, responseNonce, c.security, c.option)) 426 | } 427 | return nil 428 | } 429 | 430 | func (c *rawServerConn) Close() error { 431 | return common.Close( 432 | c.Conn, 433 | c.reader, 434 | ) 435 | } 436 | 437 | func (c *rawServerConn) FrontHeadroom() int { 438 | return MaxFrontHeadroom 439 | } 440 | 441 | func (c *rawServerConn) RearHeadroom() int { 442 | return MaxRearHeadroom 443 | } 444 | 445 | func (c *rawServerConn) NeedHandshake() bool { 446 | return c.writer == nil 447 | } 448 | 449 | func (c *rawServerConn) NeedAdditionalReadDeadline() bool { 450 | return true 451 | } 452 | 453 | func (c *rawServerConn) Upstream() any { 454 | return c.Conn 455 | } 456 | 457 | type serverConn struct { 458 | rawServerConn 459 | } 460 | 461 | func (c *serverConn) Read(b []byte) (n int, err error) { 462 | return c.reader.Read(b) 463 | } 464 | 465 | func (c *serverConn) Write(b []byte) (n int, err error) { 466 | if c.writer == nil { 467 | err = c.writeResponse() 468 | if err != nil { 469 | return 470 | } 471 | } 472 | return c.writer.Write(b) 473 | } 474 | 475 | func (c *serverConn) ReadBuffer(buffer *buf.Buffer) error { 476 | return c.reader.ReadBuffer(buffer) 477 | } 478 | 479 | func (c *serverConn) WriteBuffer(buffer *buf.Buffer) error { 480 | if c.writer == nil { 481 | err := c.writeResponse() 482 | if err != nil { 483 | buffer.Release() 484 | return err 485 | } 486 | } 487 | return c.writer.WriteBuffer(buffer) 488 | } 489 | 490 | func (c *serverConn) WriteTo(w io.Writer) (n int64, err error) { 491 | return bufio.Copy(w, c.reader) 492 | } 493 | 494 | func (c *serverConn) ReadFrom(r io.Reader) (n int64, err error) { 495 | if c.writer == nil { 496 | err = c.writeResponse() 497 | if err != nil { 498 | return 499 | } 500 | } 501 | return bufio.Copy(c.writer, r) 502 | } 503 | 504 | var _ PacketConn = (*serverPacketConn)(nil) 505 | 506 | type serverPacketConn struct { 507 | rawServerConn 508 | destination M.Socksaddr 509 | } 510 | 511 | func (c *serverPacketConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) { 512 | n, err = c.reader.Read(p) 513 | if err != nil { 514 | return 515 | } 516 | addr = c.destination.UDPAddr() 517 | return 518 | } 519 | 520 | func (c *serverPacketConn) WriteTo(p []byte, addr net.Addr) (n int, err error) { 521 | if c.writer == nil { 522 | err = c.writeResponse() 523 | if err != nil { 524 | return 525 | } 526 | } 527 | return c.writer.Write(p) 528 | } 529 | 530 | func (c *serverPacketConn) ReadPacket(buffer *buf.Buffer) (destination M.Socksaddr, err error) { 531 | err = c.reader.ReadBuffer(buffer) 532 | if err != nil { 533 | return 534 | } 535 | destination = c.destination 536 | return 537 | } 538 | 539 | func (c *serverPacketConn) WritePacket(buffer *buf.Buffer, destination M.Socksaddr) error { 540 | if c.writer == nil { 541 | err := c.writeResponse() 542 | if err != nil { 543 | return err 544 | } 545 | } 546 | return c.writer.WriteBuffer(buffer) 547 | } 548 | -------------------------------------------------------------------------------- /service_option.go: -------------------------------------------------------------------------------- 1 | package vmess 2 | 3 | type ServiceOption func(service *Service[string]) 4 | 5 | func ServiceWithTimeFunc(timeFunc TimeFunc) ServiceOption { 6 | return func(service *Service[string]) { 7 | service.time = timeFunc 8 | } 9 | } 10 | 11 | func ServiceWithDisableHeaderProtection() ServiceOption { 12 | return func(service *Service[string]) { 13 | service.disableHeaderProtect = true 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /vless/client.go: -------------------------------------------------------------------------------- 1 | package vless 2 | 3 | import ( 4 | "encoding/binary" 5 | "io" 6 | "net" 7 | "sync" 8 | 9 | "github.com/sagernet/sing-vmess" 10 | "github.com/sagernet/sing/common" 11 | "github.com/sagernet/sing/common/buf" 12 | "github.com/sagernet/sing/common/bufio" 13 | E "github.com/sagernet/sing/common/exceptions" 14 | "github.com/sagernet/sing/common/logger" 15 | M "github.com/sagernet/sing/common/metadata" 16 | N "github.com/sagernet/sing/common/network" 17 | 18 | "github.com/gofrs/uuid/v5" 19 | ) 20 | 21 | type Client struct { 22 | key [16]byte 23 | flow string 24 | logger logger.Logger 25 | } 26 | 27 | func NewClient(userId string, flow string, logger logger.Logger) (*Client, error) { 28 | user, err := uuid.FromString(userId) 29 | if err != nil { 30 | user = uuid.NewV5(uuid.Nil, userId) 31 | } 32 | switch flow { 33 | case "", "xtls-rprx-vision": 34 | default: 35 | return nil, E.New("unsupported flow: " + flow) 36 | } 37 | return &Client{user, flow, logger}, nil 38 | } 39 | 40 | func (c *Client) prepareConn(conn net.Conn, tlsConn net.Conn) (net.Conn, error) { 41 | if c.flow == FlowVision { 42 | protocolConn, err := NewVisionConn(conn, tlsConn, c.key, c.logger) 43 | if err != nil { 44 | return nil, E.Cause(err, "initialize vision") 45 | } 46 | conn = protocolConn 47 | } 48 | return conn, nil 49 | } 50 | 51 | func (c *Client) DialConn(conn net.Conn, destination M.Socksaddr) (net.Conn, error) { 52 | remoteConn := NewConn(conn, c.key, vmess.CommandTCP, destination, c.flow) 53 | protocolConn, err := c.prepareConn(remoteConn, conn) 54 | if err != nil { 55 | return nil, err 56 | } 57 | return protocolConn, common.Error(remoteConn.Write(nil)) 58 | } 59 | 60 | func (c *Client) DialEarlyConn(conn net.Conn, destination M.Socksaddr) (net.Conn, error) { 61 | return c.prepareConn(NewConn(conn, c.key, vmess.CommandTCP, destination, c.flow), conn) 62 | } 63 | 64 | func (c *Client) DialPacketConn(conn net.Conn, destination M.Socksaddr) (*PacketConn, error) { 65 | serverConn := &PacketConn{Conn: conn, key: c.key, destination: destination, flow: c.flow} 66 | return serverConn, common.Error(serverConn.Write(nil)) 67 | } 68 | 69 | func (c *Client) DialEarlyPacketConn(conn net.Conn, destination M.Socksaddr) (*PacketConn, error) { 70 | return &PacketConn{Conn: conn, key: c.key, destination: destination, flow: c.flow}, nil 71 | } 72 | 73 | func (c *Client) DialXUDPPacketConn(conn net.Conn, destination M.Socksaddr) (vmess.PacketConn, error) { 74 | remoteConn := NewConn(conn, c.key, vmess.CommandTCP, destination, c.flow) 75 | protocolConn, err := c.prepareConn(remoteConn, conn) 76 | if err != nil { 77 | return nil, err 78 | } 79 | return vmess.NewXUDPConn(protocolConn, destination), common.Error(remoteConn.Write(nil)) 80 | } 81 | 82 | func (c *Client) DialEarlyXUDPPacketConn(conn net.Conn, destination M.Socksaddr) (vmess.PacketConn, error) { 83 | remoteConn := NewConn(conn, c.key, vmess.CommandMux, destination, c.flow) 84 | protocolConn, err := c.prepareConn(remoteConn, conn) 85 | if err != nil { 86 | return nil, err 87 | } 88 | return vmess.NewXUDPConn(protocolConn, destination), common.Error(remoteConn.Write(nil)) 89 | } 90 | 91 | var ( 92 | _ N.EarlyConn = (*Conn)(nil) 93 | _ N.VectorisedWriter = (*Conn)(nil) 94 | ) 95 | 96 | type Conn struct { 97 | N.ExtendedConn 98 | writer N.VectorisedWriter 99 | request Request 100 | requestWritten bool 101 | responseRead bool 102 | } 103 | 104 | func NewConn(conn net.Conn, uuid [16]byte, command byte, destination M.Socksaddr, flow string) *Conn { 105 | return &Conn{ 106 | ExtendedConn: bufio.NewExtendedConn(conn), 107 | writer: bufio.NewVectorisedWriter(conn), 108 | request: Request{ 109 | UUID: uuid, 110 | Command: command, 111 | Destination: destination, 112 | Flow: flow, 113 | }, 114 | } 115 | } 116 | 117 | func (c *Conn) Read(b []byte) (n int, err error) { 118 | if !c.responseRead { 119 | err = ReadResponse(c.ExtendedConn) 120 | if err != nil { 121 | return 122 | } 123 | c.responseRead = true 124 | } 125 | return c.ExtendedConn.Read(b) 126 | } 127 | 128 | func (c *Conn) ReadBuffer(buffer *buf.Buffer) error { 129 | if !c.responseRead { 130 | err := ReadResponse(c.ExtendedConn) 131 | if err != nil { 132 | return err 133 | } 134 | c.responseRead = true 135 | } 136 | return c.ExtendedConn.ReadBuffer(buffer) 137 | } 138 | 139 | func (c *Conn) Write(b []byte) (n int, err error) { 140 | if !c.requestWritten { 141 | err = WriteRequest(c.ExtendedConn, c.request, b) 142 | if err == nil { 143 | n = len(b) 144 | } 145 | c.requestWritten = true 146 | return 147 | } 148 | return c.ExtendedConn.Write(b) 149 | } 150 | 151 | func (c *Conn) WriteBuffer(buffer *buf.Buffer) error { 152 | if !c.requestWritten { 153 | err := EncodeRequest(c.request, buf.With(buffer.ExtendHeader(RequestLen(c.request)))) 154 | if err != nil { 155 | return err 156 | } 157 | c.requestWritten = true 158 | } 159 | return c.ExtendedConn.WriteBuffer(buffer) 160 | } 161 | 162 | func (c *Conn) WriteVectorised(buffers []*buf.Buffer) error { 163 | if !c.requestWritten { 164 | buffer := buf.NewSize(RequestLen(c.request)) 165 | err := EncodeRequest(c.request, buffer) 166 | if err != nil { 167 | buffer.Release() 168 | return err 169 | } 170 | c.requestWritten = true 171 | return c.writer.WriteVectorised(append([]*buf.Buffer{buffer}, buffers...)) 172 | } 173 | return c.writer.WriteVectorised(buffers) 174 | } 175 | 176 | func (c *Conn) ReaderReplaceable() bool { 177 | return c.responseRead 178 | } 179 | 180 | func (c *Conn) WriterReplaceable() bool { 181 | return c.requestWritten 182 | } 183 | 184 | func (c *Conn) NeedHandshake() bool { 185 | return !c.requestWritten 186 | } 187 | 188 | func (c *Conn) FrontHeadroom() int { 189 | if c.requestWritten { 190 | return 0 191 | } 192 | return RequestLen(c.request) 193 | } 194 | 195 | func (c *Conn) NeedAdditionalReadDeadline() bool { 196 | return true 197 | } 198 | 199 | func (c *Conn) Upstream() any { 200 | return c.ExtendedConn 201 | } 202 | 203 | type PacketConn struct { 204 | net.Conn 205 | access sync.Mutex 206 | key [16]byte 207 | destination M.Socksaddr 208 | flow string 209 | requestWritten bool 210 | responseRead bool 211 | } 212 | 213 | func (c *PacketConn) Read(b []byte) (n int, err error) { 214 | if !c.responseRead { 215 | err = ReadResponse(c.Conn) 216 | if err != nil { 217 | return 218 | } 219 | c.responseRead = true 220 | } 221 | var length uint16 222 | err = binary.Read(c.Conn, binary.BigEndian, &length) 223 | if err != nil { 224 | return 225 | } 226 | if cap(b) < int(length) { 227 | return 0, io.ErrShortBuffer 228 | } 229 | return io.ReadFull(c.Conn, b[:length]) 230 | } 231 | 232 | func (c *PacketConn) Write(b []byte) (n int, err error) { 233 | if !c.requestWritten { 234 | c.access.Lock() 235 | if c.requestWritten { 236 | c.access.Unlock() 237 | } else { 238 | err = WritePacketRequest(c.Conn, Request{c.key, vmess.CommandUDP, c.destination, c.flow}, nil) 239 | if err == nil { 240 | n = len(b) 241 | } 242 | c.requestWritten = true 243 | c.access.Unlock() 244 | } 245 | } 246 | err = binary.Write(c.Conn, binary.BigEndian, uint16(len(b))) 247 | if err != nil { 248 | return 249 | } 250 | return c.Conn.Write(b) 251 | } 252 | 253 | func (c *PacketConn) WritePacket(buffer *buf.Buffer, destination M.Socksaddr) error { 254 | defer buffer.Release() 255 | dataLen := buffer.Len() 256 | binary.BigEndian.PutUint16(buffer.ExtendHeader(2), uint16(dataLen)) 257 | if !c.requestWritten { 258 | c.access.Lock() 259 | if c.requestWritten { 260 | c.access.Unlock() 261 | } else { 262 | err := WritePacketRequest(c.Conn, Request{c.key, vmess.CommandUDP, c.destination, c.flow}, buffer.Bytes()) 263 | c.requestWritten = true 264 | c.access.Unlock() 265 | return err 266 | } 267 | } 268 | return common.Error(c.Conn.Write(buffer.Bytes())) 269 | } 270 | 271 | func (c *PacketConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) { 272 | n, err = c.Read(p) 273 | if err != nil { 274 | return 275 | } 276 | if c.destination.IsFqdn() { 277 | addr = c.destination 278 | } else { 279 | addr = c.destination.UDPAddr() 280 | } 281 | return 282 | } 283 | 284 | func (c *PacketConn) WriteTo(p []byte, addr net.Addr) (n int, err error) { 285 | return c.Write(p) 286 | } 287 | 288 | func (c *PacketConn) FrontHeadroom() int { 289 | return 2 290 | } 291 | 292 | func (c *PacketConn) NeedAdditionalReadDeadline() bool { 293 | return true 294 | } 295 | 296 | func (c *PacketConn) Upstream() any { 297 | return c.Conn 298 | } 299 | -------------------------------------------------------------------------------- /vless/constant.go: -------------------------------------------------------------------------------- 1 | package vless 2 | 3 | import ( 4 | "bytes" 5 | 6 | "github.com/sagernet/sing/common/buf" 7 | ) 8 | 9 | var ( 10 | tls13SupportedVersions = []byte{0x00, 0x2b, 0x00, 0x02, 0x03, 0x04} 11 | tlsClientHandShakeStart = []byte{0x16, 0x03} 12 | tlsServerHandShakeStart = []byte{0x16, 0x03, 0x03} 13 | tlsApplicationDataStart = []byte{0x17, 0x03, 0x03} 14 | ) 15 | 16 | const ( 17 | commandPaddingContinue byte = iota 18 | commandPaddingEnd 19 | commandPaddingDirect 20 | ) 21 | 22 | var tls13CipherSuiteDic = map[uint16]string{ 23 | 0x1301: "TLS_AES_128_GCM_SHA256", 24 | 0x1302: "TLS_AES_256_GCM_SHA384", 25 | 0x1303: "TLS_CHACHA20_POLY1305_SHA256", 26 | 0x1304: "TLS_AES_128_CCM_SHA256", 27 | 0x1305: "TLS_AES_128_CCM_8_SHA256", 28 | } 29 | 30 | func reshapeBuffer(b []byte) []*buf.Buffer { 31 | const bufferLimit = 8192 - 21 32 | if len(b) < bufferLimit { 33 | return []*buf.Buffer{buf.As(b)} 34 | } 35 | index := int32(bytes.LastIndex(b, tlsApplicationDataStart)) 36 | if index <= 0 { 37 | index = 8192 / 2 38 | } 39 | return []*buf.Buffer{buf.As(b[:index]), buf.As(b[index:])} 40 | } 41 | -------------------------------------------------------------------------------- /vless/protocol.go: -------------------------------------------------------------------------------- 1 | package vless 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | "io" 7 | 8 | "github.com/sagernet/sing-vmess" 9 | "github.com/sagernet/sing/common" 10 | "github.com/sagernet/sing/common/buf" 11 | E "github.com/sagernet/sing/common/exceptions" 12 | M "github.com/sagernet/sing/common/metadata" 13 | "github.com/sagernet/sing/common/rw" 14 | "github.com/sagernet/sing/common/varbin" 15 | ) 16 | 17 | const ( 18 | Version = 0 19 | FlowVision = "xtls-rprx-vision" 20 | ) 21 | 22 | type Request struct { 23 | UUID [16]byte 24 | Command byte 25 | Destination M.Socksaddr 26 | Flow string 27 | } 28 | 29 | func ReadRequest(reader io.Reader) (*Request, error) { 30 | var request Request 31 | 32 | var version uint8 33 | err := binary.Read(reader, binary.BigEndian, &version) 34 | if err != nil { 35 | return nil, err 36 | } 37 | if version != Version { 38 | return nil, E.New("unknown version: ", version) 39 | } 40 | 41 | _, err = io.ReadFull(reader, request.UUID[:]) 42 | if err != nil { 43 | return nil, err 44 | } 45 | 46 | var addonsLen uint8 47 | err = binary.Read(reader, binary.BigEndian, &addonsLen) 48 | if err != nil { 49 | return nil, err 50 | } 51 | 52 | if addonsLen > 0 { 53 | addonsBytes := make([]byte, addonsLen) 54 | _, err = io.ReadFull(reader, addonsBytes) 55 | if err != nil { 56 | return nil, err 57 | } 58 | 59 | addons, err := readAddons(bytes.NewReader(addonsBytes)) 60 | if err != nil { 61 | return nil, err 62 | } 63 | request.Flow = addons.Flow 64 | } 65 | 66 | err = binary.Read(reader, binary.BigEndian, &request.Command) 67 | if err != nil { 68 | return nil, err 69 | } 70 | 71 | if request.Command != vmess.CommandMux { 72 | request.Destination, err = vmess.AddressSerializer.ReadAddrPort(reader) 73 | if err != nil { 74 | return nil, err 75 | } 76 | } 77 | 78 | return &request, nil 79 | } 80 | 81 | type Addons struct { 82 | Flow string 83 | Seed string 84 | } 85 | 86 | // func readAddons(reader varbin.Reader) (*Addons, error) { 87 | func readAddons(reader *bytes.Reader) (*Addons, error) { 88 | protoHeader, err := reader.ReadByte() 89 | if err != nil { 90 | return nil, err 91 | } 92 | if protoHeader != 10 { 93 | return nil, E.New("unknown protobuf message header: ", protoHeader) 94 | } 95 | 96 | var addons Addons 97 | 98 | flowLen, err := binary.ReadUvarint(reader) 99 | if err != nil { 100 | if err == io.EOF { 101 | return &addons, nil 102 | } 103 | return nil, err 104 | } 105 | flowBytes := make([]byte, flowLen) 106 | _, err = io.ReadFull(reader, flowBytes) 107 | if err != nil { 108 | return nil, err 109 | } 110 | addons.Flow = string(flowBytes) 111 | 112 | seedLen, err := binary.ReadUvarint(reader) 113 | if err != nil { 114 | if err == io.EOF { 115 | return &addons, nil 116 | } 117 | return nil, err 118 | } 119 | seedBytes := make([]byte, seedLen) 120 | _, err = io.ReadFull(reader, seedBytes) 121 | if err != nil { 122 | return nil, err 123 | } 124 | addons.Seed = string(seedBytes) 125 | 126 | return &addons, nil 127 | } 128 | 129 | func WriteRequest(writer io.Writer, request Request, payload []byte) error { 130 | var requestLen int 131 | requestLen += 1 // version 132 | requestLen += 16 // uuid 133 | requestLen += 1 // protobuf length 134 | 135 | var addonsLen int 136 | if request.Flow != "" { 137 | addonsLen += 1 // protobuf header 138 | addonsLen += varbin.UvarintLen(uint64(len(request.Flow))) 139 | // addonsLen += varbin.UvarintLen(uint64(len(request.Flow))) 140 | addonsLen += len(request.Flow) 141 | requestLen += addonsLen 142 | } 143 | requestLen += 1 // command 144 | if request.Command != vmess.CommandMux { 145 | requestLen += vmess.AddressSerializer.AddrPortLen(request.Destination) 146 | } 147 | requestLen += len(payload) 148 | buffer := buf.NewSize(requestLen) 149 | defer buffer.Release() 150 | common.Must( 151 | buffer.WriteByte(Version), 152 | common.Error(buffer.Write(request.UUID[:])), 153 | buffer.WriteByte(byte(addonsLen)), 154 | ) 155 | if addonsLen > 0 { 156 | common.Must(buffer.WriteByte(10)) 157 | binary.PutUvarint(buffer.Extend(varbin.UvarintLen(uint64(len(request.Flow)))), uint64(len(request.Flow))) 158 | common.Must(common.Error(buffer.WriteString(request.Flow))) 159 | } 160 | common.Must( 161 | buffer.WriteByte(request.Command), 162 | ) 163 | 164 | if request.Command != vmess.CommandMux { 165 | err := vmess.AddressSerializer.WriteAddrPort(buffer, request.Destination) 166 | if err != nil { 167 | return err 168 | } 169 | } 170 | 171 | common.Must1(buffer.Write(payload)) 172 | return common.Error(writer.Write(buffer.Bytes())) 173 | } 174 | 175 | func EncodeRequest(request Request, buffer *buf.Buffer) error { 176 | var addonsLen int 177 | if request.Flow != "" { 178 | addonsLen += 1 // protobuf header 179 | addonsLen += varbin.UvarintLen(uint64(len(request.Flow))) 180 | addonsLen += len(request.Flow) 181 | } 182 | common.Must( 183 | buffer.WriteByte(Version), 184 | common.Error(buffer.Write(request.UUID[:])), 185 | buffer.WriteByte(byte(addonsLen)), 186 | ) 187 | if addonsLen > 0 { 188 | common.Must(buffer.WriteByte(10)) 189 | binary.PutUvarint(buffer.Extend(varbin.UvarintLen(uint64(len(request.Flow)))), uint64(len(request.Flow))) 190 | common.Must(common.Error(buffer.WriteString(request.Flow))) 191 | } 192 | common.Must( 193 | buffer.WriteByte(request.Command), 194 | ) 195 | 196 | if request.Command != vmess.CommandMux { 197 | err := vmess.AddressSerializer.WriteAddrPort(buffer, request.Destination) 198 | if err != nil { 199 | return err 200 | } 201 | } 202 | return nil 203 | } 204 | 205 | func RequestLen(request Request) int { 206 | var requestLen int 207 | requestLen += 1 // version 208 | requestLen += 16 // uuid 209 | requestLen += 1 // protobuf length 210 | 211 | var addonsLen int 212 | if request.Flow != "" { 213 | addonsLen += 1 // protobuf header 214 | addonsLen += varbin.UvarintLen(uint64(len(request.Flow))) 215 | addonsLen += len(request.Flow) 216 | requestLen += addonsLen 217 | } 218 | requestLen += 1 // command 219 | if request.Command != vmess.CommandMux { 220 | requestLen += vmess.AddressSerializer.AddrPortLen(request.Destination) 221 | } 222 | return requestLen 223 | } 224 | 225 | func WritePacketRequest(writer io.Writer, request Request, payload []byte) error { 226 | var requestLen int 227 | requestLen += 1 // version 228 | requestLen += 16 // uuid 229 | requestLen += 1 // protobuf length 230 | var addonsLen int 231 | /*if request.Flow != "" { 232 | addonsLen += 1 // protobuf header 233 | addonsLen += varbin.UvarintLen(uint64(len(request.Flow))) 234 | addonsLen += len(request.Flow) 235 | requestLen += addonsLen 236 | }*/ 237 | requestLen += 1 // command 238 | requestLen += vmess.AddressSerializer.AddrPortLen(request.Destination) 239 | if len(payload) > 0 { 240 | requestLen += 2 241 | requestLen += len(payload) 242 | } 243 | buffer := buf.NewSize(requestLen) 244 | defer buffer.Release() 245 | common.Must( 246 | buffer.WriteByte(Version), 247 | common.Error(buffer.Write(request.UUID[:])), 248 | buffer.WriteByte(byte(addonsLen)), 249 | ) 250 | 251 | if addonsLen > 0 { 252 | common.Must(buffer.WriteByte(10)) 253 | binary.PutUvarint(buffer.Extend(varbin.UvarintLen(uint64(len(request.Flow)))), uint64(len(request.Flow))) 254 | common.Must(common.Error(buffer.WriteString(request.Flow))) 255 | } 256 | 257 | common.Must(buffer.WriteByte(vmess.CommandUDP)) 258 | 259 | err := vmess.AddressSerializer.WriteAddrPort(buffer, request.Destination) 260 | if err != nil { 261 | return err 262 | } 263 | 264 | if len(payload) > 0 { 265 | common.Must( 266 | binary.Write(buffer, binary.BigEndian, uint16(len(payload))), 267 | common.Error(buffer.Write(payload)), 268 | ) 269 | } 270 | 271 | return common.Error(writer.Write(buffer.Bytes())) 272 | } 273 | 274 | func ReadResponse(reader io.Reader) error { 275 | var version byte 276 | err := binary.Read(reader, binary.BigEndian, &version) 277 | if err != nil { 278 | return err 279 | } 280 | if version != Version { 281 | return E.New("unknown version: ", version) 282 | } 283 | var protobufLength byte 284 | err = binary.Read(reader, binary.BigEndian, &protobufLength) 285 | if err != nil { 286 | return err 287 | } 288 | if protobufLength > 0 { 289 | err = rw.SkipN(reader, int(protobufLength)) 290 | if err != nil { 291 | return err 292 | } 293 | } 294 | return nil 295 | } 296 | -------------------------------------------------------------------------------- /vless/service.go: -------------------------------------------------------------------------------- 1 | package vless 2 | 3 | import ( 4 | "context" 5 | "encoding/binary" 6 | "io" 7 | "net" 8 | 9 | "github.com/sagernet/sing-vmess" 10 | "github.com/sagernet/sing/common/auth" 11 | "github.com/sagernet/sing/common/buf" 12 | "github.com/sagernet/sing/common/bufio" 13 | E "github.com/sagernet/sing/common/exceptions" 14 | "github.com/sagernet/sing/common/logger" 15 | M "github.com/sagernet/sing/common/metadata" 16 | N "github.com/sagernet/sing/common/network" 17 | 18 | "github.com/gofrs/uuid/v5" 19 | ) 20 | 21 | type Service[T comparable] struct { 22 | userMap map[[16]byte]T 23 | userFlow map[T]string 24 | logger logger.Logger 25 | handler Handler 26 | } 27 | 28 | type Handler interface { 29 | N.TCPConnectionHandlerEx 30 | N.UDPConnectionHandlerEx 31 | } 32 | 33 | func NewService[T comparable](logger logger.Logger, handler Handler) *Service[T] { 34 | return &Service[T]{ 35 | logger: logger, 36 | handler: handler, 37 | } 38 | } 39 | 40 | func (s *Service[T]) UpdateUsers(userList []T, userUUIDList []string, userFlowList []string) { 41 | userMap := make(map[[16]byte]T) 42 | userFlowMap := make(map[T]string) 43 | for i, userName := range userList { 44 | userID, err := uuid.FromString(userUUIDList[i]) 45 | if err != nil { 46 | userID = uuid.NewV5(uuid.Nil, userUUIDList[i]) 47 | } 48 | userMap[userID] = userName 49 | userFlowMap[userName] = userFlowList[i] 50 | } 51 | s.userMap = userMap 52 | s.userFlow = userFlowMap 53 | } 54 | 55 | func (s *Service[T]) NewConnection(ctx context.Context, conn net.Conn, source M.Socksaddr, onClose N.CloseHandlerFunc) error { 56 | request, err := ReadRequest(conn) 57 | if err != nil { 58 | return err 59 | } 60 | user, loaded := s.userMap[request.UUID] 61 | if !loaded { 62 | return E.New("unknown UUID: ", uuid.FromBytesOrNil(request.UUID[:])) 63 | } 64 | ctx = auth.ContextWithUser(ctx, user) 65 | userFlow := s.userFlow[user] 66 | if request.Flow == FlowVision && request.Command == vmess.NetworkUDP { 67 | return E.New(FlowVision, " flow does not support UDP") 68 | } else if request.Flow != userFlow { 69 | return E.New("flow mismatch: expected ", flowName(userFlow), ", but got ", flowName(request.Flow)) 70 | } 71 | 72 | if request.Command == vmess.CommandUDP { 73 | s.handler.NewPacketConnectionEx(ctx, &serverPacketConn{ExtendedConn: bufio.NewExtendedConn(conn), destination: request.Destination}, source, request.Destination, onClose) 74 | return nil 75 | } 76 | responseConn := &serverConn{ExtendedConn: bufio.NewExtendedConn(conn), writer: bufio.NewVectorisedWriter(conn)} 77 | switch userFlow { 78 | case FlowVision: 79 | conn, err = NewVisionConn(responseConn, conn, request.UUID, s.logger) 80 | if err != nil { 81 | return E.Cause(err, "initialize vision") 82 | } 83 | case "": 84 | conn = responseConn 85 | default: 86 | return E.New("unknown flow: ", userFlow) 87 | } 88 | switch request.Command { 89 | case vmess.CommandTCP: 90 | s.handler.NewConnectionEx(ctx, conn, source, request.Destination, onClose) 91 | return nil 92 | case vmess.CommandMux: 93 | return vmess.HandleMuxConnection(ctx, conn, source, s.handler) 94 | default: 95 | return E.New("unknown command: ", request.Command) 96 | } 97 | } 98 | 99 | func flowName(value string) string { 100 | if value == "" { 101 | return "none" 102 | } 103 | return value 104 | } 105 | 106 | var _ N.VectorisedWriter = (*serverConn)(nil) 107 | 108 | type serverConn struct { 109 | N.ExtendedConn 110 | writer N.VectorisedWriter 111 | responseWritten bool 112 | } 113 | 114 | func (c *serverConn) Read(b []byte) (n int, err error) { 115 | return c.ExtendedConn.Read(b) 116 | } 117 | 118 | func (c *serverConn) Write(b []byte) (n int, err error) { 119 | if !c.responseWritten { 120 | _, err = bufio.WriteVectorised(c.writer, [][]byte{{Version, 0}, b}) 121 | if err == nil { 122 | n = len(b) 123 | } 124 | c.responseWritten = true 125 | return 126 | } 127 | return c.ExtendedConn.Write(b) 128 | } 129 | 130 | func (c *serverConn) WriteBuffer(buffer *buf.Buffer) error { 131 | if !c.responseWritten { 132 | header := buffer.ExtendHeader(2) 133 | header[0] = Version 134 | header[1] = 0 135 | c.responseWritten = true 136 | } 137 | return c.ExtendedConn.WriteBuffer(buffer) 138 | } 139 | 140 | func (c *serverConn) WriteVectorised(buffers []*buf.Buffer) error { 141 | if !c.responseWritten { 142 | err := c.writer.WriteVectorised(append([]*buf.Buffer{buf.As([]byte{Version, 0})}, buffers...)) 143 | c.responseWritten = true 144 | return err 145 | } 146 | return c.writer.WriteVectorised(buffers) 147 | } 148 | 149 | func (c *serverConn) FrontHeadroom() int { 150 | if c.responseWritten { 151 | return 0 152 | } 153 | return 2 154 | } 155 | 156 | func (c *serverConn) ReaderReplaceable() bool { 157 | return true 158 | } 159 | 160 | func (c *serverConn) WriterReplaceable() bool { 161 | return c.responseWritten 162 | } 163 | 164 | func (c *serverConn) NeedAdditionalReadDeadline() bool { 165 | return true 166 | } 167 | 168 | func (c *serverConn) Upstream() any { 169 | return c.ExtendedConn 170 | } 171 | 172 | type serverPacketConn struct { 173 | N.ExtendedConn 174 | responseWriter io.Writer 175 | responseWritten bool 176 | destination M.Socksaddr 177 | } 178 | 179 | func (c *serverPacketConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) { 180 | n, err = c.ExtendedConn.Read(p) 181 | if err != nil { 182 | return 183 | } 184 | if c.destination.IsFqdn() { 185 | addr = c.destination 186 | } else { 187 | addr = c.destination.UDPAddr() 188 | } 189 | return 190 | } 191 | 192 | func (c *serverPacketConn) WriteTo(p []byte, addr net.Addr) (n int, err error) { 193 | if !c.responseWritten { 194 | if c.responseWriter == nil { 195 | var packetLen [2]byte 196 | binary.BigEndian.PutUint16(packetLen[:], uint16(len(p))) 197 | _, err = bufio.WriteVectorised(bufio.NewVectorisedWriter(c.ExtendedConn), [][]byte{{Version, 0}, packetLen[:], p}) 198 | if err == nil { 199 | n = len(p) 200 | } 201 | c.responseWritten = true 202 | return 203 | } else { 204 | _, err = c.responseWriter.Write([]byte{Version, 0}) 205 | if err != nil { 206 | return 207 | } 208 | c.responseWritten = true 209 | } 210 | } 211 | return c.ExtendedConn.Write(p) 212 | } 213 | 214 | func (c *serverPacketConn) ReadPacket(buffer *buf.Buffer) (destination M.Socksaddr, err error) { 215 | var packetLen uint16 216 | err = binary.Read(c.ExtendedConn, binary.BigEndian, &packetLen) 217 | if err != nil { 218 | return 219 | } 220 | 221 | _, err = buffer.ReadFullFrom(c.ExtendedConn, int(packetLen)) 222 | if err != nil { 223 | return 224 | } 225 | 226 | destination = c.destination 227 | return 228 | } 229 | 230 | func (c *serverPacketConn) WritePacket(buffer *buf.Buffer, destination M.Socksaddr) error { 231 | if !c.responseWritten { 232 | if c.responseWriter == nil { 233 | var packetLen [2]byte 234 | binary.BigEndian.PutUint16(packetLen[:], uint16(buffer.Len())) 235 | err := bufio.NewVectorisedWriter(c.ExtendedConn).WriteVectorised([]*buf.Buffer{buf.As([]byte{Version, 0}), buf.As(packetLen[:]), buffer}) 236 | c.responseWritten = true 237 | return err 238 | } else { 239 | _, err := c.responseWriter.Write([]byte{Version, 0}) 240 | if err != nil { 241 | return err 242 | } 243 | c.responseWritten = true 244 | } 245 | } 246 | packetLen := buffer.Len() 247 | binary.BigEndian.PutUint16(buffer.ExtendHeader(2), uint16(packetLen)) 248 | return c.ExtendedConn.WriteBuffer(buffer) 249 | } 250 | 251 | func (c *serverPacketConn) FrontHeadroom() int { 252 | return 2 253 | } 254 | 255 | func (c *serverPacketConn) NeedAdditionalReadDeadline() bool { 256 | return true 257 | } 258 | 259 | func (c *serverPacketConn) Upstream() any { 260 | return c.ExtendedConn 261 | } 262 | -------------------------------------------------------------------------------- /vless/vision.go: -------------------------------------------------------------------------------- 1 | package vless 2 | 3 | import ( 4 | "bytes" 5 | "crypto/rand" 6 | "crypto/tls" 7 | "io" 8 | "math/big" 9 | "net" 10 | "reflect" 11 | "time" 12 | "unsafe" 13 | 14 | "github.com/sagernet/sing/common" 15 | "github.com/sagernet/sing/common/buf" 16 | "github.com/sagernet/sing/common/bufio" 17 | E "github.com/sagernet/sing/common/exceptions" 18 | "github.com/sagernet/sing/common/logger" 19 | N "github.com/sagernet/sing/common/network" 20 | ) 21 | 22 | var tlsRegistry []func(conn net.Conn) (loaded bool, netConn net.Conn, reflectType reflect.Type, reflectPointer uintptr) 23 | 24 | func init() { 25 | tlsRegistry = append(tlsRegistry, func(conn net.Conn) (loaded bool, netConn net.Conn, reflectType reflect.Type, reflectPointer uintptr) { 26 | tlsConn, loaded := common.Cast[*tls.Conn](conn) 27 | if !loaded { 28 | return 29 | } 30 | return true, tlsConn.NetConn(), reflect.TypeOf(tlsConn).Elem(), uintptr(unsafe.Pointer(tlsConn)) 31 | }) 32 | } 33 | 34 | const xrayChunkSize = 8192 35 | 36 | type VisionConn struct { 37 | net.Conn 38 | reader *bufio.ChunkReader 39 | writer N.VectorisedWriter 40 | input *bytes.Reader 41 | rawInput *bytes.Buffer 42 | netConn net.Conn 43 | logger logger.Logger 44 | 45 | userUUID [16]byte 46 | isTLS bool 47 | numberOfPacketToFilter int 48 | isTLS12orAbove bool 49 | remainingServerHello int32 50 | cipher uint16 51 | enableXTLS bool 52 | isPadding bool 53 | directWrite bool 54 | writeUUID bool 55 | withinPaddingBuffers bool 56 | remainingContent int 57 | remainingPadding int 58 | currentCommand byte 59 | directRead bool 60 | remainingReader io.Reader 61 | } 62 | 63 | func NewVisionConn(conn net.Conn, tlsConn net.Conn, userUUID [16]byte, logger logger.Logger) (*VisionConn, error) { 64 | var ( 65 | loaded bool 66 | reflectType reflect.Type 67 | reflectPointer uintptr 68 | netConn net.Conn 69 | ) 70 | for _, tlsCreator := range tlsRegistry { 71 | loaded, netConn, reflectType, reflectPointer = tlsCreator(tlsConn) 72 | if loaded { 73 | break 74 | } 75 | } 76 | if !loaded { 77 | return nil, E.New("vision: not a valid supported TLS connection: ", reflect.TypeOf(tlsConn)) 78 | } 79 | input, _ := reflectType.FieldByName("input") 80 | rawInput, _ := reflectType.FieldByName("rawInput") 81 | return &VisionConn{ 82 | Conn: conn, 83 | reader: bufio.NewChunkReader(conn, xrayChunkSize), 84 | writer: bufio.NewVectorisedWriter(conn), 85 | input: (*bytes.Reader)(unsafe.Pointer(reflectPointer + input.Offset)), 86 | rawInput: (*bytes.Buffer)(unsafe.Pointer(reflectPointer + rawInput.Offset)), 87 | netConn: netConn, 88 | logger: logger, 89 | 90 | userUUID: userUUID, 91 | numberOfPacketToFilter: 8, 92 | remainingServerHello: -1, 93 | isPadding: true, 94 | writeUUID: true, 95 | withinPaddingBuffers: true, 96 | remainingContent: -1, 97 | remainingPadding: -1, 98 | }, nil 99 | } 100 | 101 | func (c *VisionConn) Read(p []byte) (n int, err error) { 102 | if c.remainingReader != nil { 103 | n, err = c.remainingReader.Read(p) 104 | if err == io.EOF { 105 | err = nil 106 | c.remainingReader = nil 107 | } 108 | if n > 0 { 109 | return 110 | } 111 | } 112 | if c.directRead { 113 | return c.netConn.Read(p) 114 | } 115 | var bufferBytes []byte 116 | var chunkBuffer *buf.Buffer 117 | if len(p) > xrayChunkSize { 118 | n, err = c.Conn.Read(p) 119 | if err != nil { 120 | return 121 | } 122 | bufferBytes = p[:n] 123 | } else { 124 | chunkBuffer, err = c.reader.ReadChunk() 125 | if err != nil { 126 | return 0, err 127 | } 128 | bufferBytes = chunkBuffer.Bytes() 129 | } 130 | if c.withinPaddingBuffers || c.numberOfPacketToFilter > 0 { 131 | buffers := c.unPadding(bufferBytes) 132 | if chunkBuffer != nil { 133 | buffers = common.Map(buffers, func(it *buf.Buffer) *buf.Buffer { 134 | return it.ToOwned() 135 | }) 136 | chunkBuffer.Reset() 137 | } 138 | if c.remainingContent == 0 && c.remainingPadding == 0 { 139 | if c.currentCommand == commandPaddingEnd { 140 | c.withinPaddingBuffers = false 141 | c.remainingContent = -1 142 | c.remainingPadding = -1 143 | } else if c.currentCommand == commandPaddingDirect { 144 | c.withinPaddingBuffers = false 145 | c.directRead = true 146 | 147 | inputBuffer, err := io.ReadAll(c.input) 148 | if err != nil { 149 | return 0, err 150 | } 151 | buffers = append(buffers, buf.As(inputBuffer)) 152 | 153 | rawInputBuffer, err := io.ReadAll(c.rawInput) 154 | if err != nil { 155 | return 0, err 156 | } 157 | 158 | buffers = append(buffers, buf.As(rawInputBuffer)) 159 | 160 | c.logger.Trace("XtlsRead readV") 161 | } else if c.currentCommand == commandPaddingContinue { 162 | c.withinPaddingBuffers = true 163 | } else { 164 | return 0, E.New("unknown command ", c.currentCommand) 165 | } 166 | } else if c.remainingContent > 0 || c.remainingPadding > 0 { 167 | c.withinPaddingBuffers = true 168 | } else { 169 | c.withinPaddingBuffers = false 170 | } 171 | if c.numberOfPacketToFilter > 0 { 172 | c.filterTLS(buf.ToSliceMulti(buffers)) 173 | } 174 | c.remainingReader = io.MultiReader(common.Map(buffers, func(it *buf.Buffer) io.Reader { return it })...) 175 | return c.Read(p) 176 | } else { 177 | if c.numberOfPacketToFilter > 0 { 178 | c.filterTLS([][]byte{bufferBytes}) 179 | } 180 | if chunkBuffer != nil { 181 | n = copy(p, bufferBytes) 182 | chunkBuffer.Advance(n) 183 | } 184 | return 185 | } 186 | } 187 | 188 | func (c *VisionConn) Write(p []byte) (n int, err error) { 189 | if c.numberOfPacketToFilter > 0 { 190 | c.filterTLS([][]byte{p}) 191 | } 192 | if c.isPadding { 193 | inputLen := len(p) 194 | buffers := reshapeBuffer(p) 195 | var specIndex int 196 | for i, buffer := range buffers { 197 | if c.isTLS && buffer.Len() > 6 && bytes.Equal(tlsApplicationDataStart, buffer.To(3)) { 198 | var command byte = commandPaddingEnd 199 | if c.enableXTLS { 200 | c.directWrite = true 201 | specIndex = i 202 | command = commandPaddingDirect 203 | } 204 | c.isPadding = false 205 | buffers[i] = c.padding(buffer, command) 206 | break 207 | } else if !c.isTLS12orAbove && c.numberOfPacketToFilter <= 1 { 208 | c.isPadding = false 209 | buffers[i] = c.padding(buffer, commandPaddingEnd) 210 | break 211 | } 212 | buffers[i] = c.padding(buffer, commandPaddingContinue) 213 | } 214 | if c.directWrite { 215 | encryptedBuffer := buffers[:specIndex+1] 216 | err = c.writer.WriteVectorised(encryptedBuffer) 217 | if err != nil { 218 | return 219 | } 220 | buffers = buffers[specIndex+1:] 221 | c.writer = bufio.NewVectorisedWriter(c.netConn) 222 | c.logger.Trace("XtlsWrite writeV ", specIndex, " ", buf.LenMulti(encryptedBuffer), " ", len(buffers)) 223 | time.Sleep(5 * time.Millisecond) // wtf 224 | } 225 | err = c.writer.WriteVectorised(buffers) 226 | if err == nil { 227 | n = inputLen 228 | } 229 | return 230 | } 231 | if c.directWrite { 232 | return c.netConn.Write(p) 233 | } else { 234 | return c.Conn.Write(p) 235 | } 236 | } 237 | 238 | func (c *VisionConn) filterTLS(buffers [][]byte) { 239 | for _, buffer := range buffers { 240 | c.numberOfPacketToFilter-- 241 | if len(buffer) > 6 { 242 | if buffer[0] == 22 && buffer[1] == 3 && buffer[2] == 3 { 243 | c.isTLS = true 244 | if buffer[5] == 2 { 245 | c.isTLS12orAbove = true 246 | c.remainingServerHello = (int32(buffer[3])<<8 | int32(buffer[4])) + 5 247 | if len(buffer) >= 79 && c.remainingServerHello >= 79 { 248 | sessionIdLen := int32(buffer[43]) 249 | cipherSuite := buffer[43+sessionIdLen+1 : 43+sessionIdLen+3] 250 | c.cipher = uint16(cipherSuite[0])<<8 | uint16(cipherSuite[1]) 251 | } else { 252 | c.logger.Trace("XtlsFilterTls short server hello, tls 1.2 or older? ", len(buffer), " ", c.remainingServerHello) 253 | } 254 | } 255 | } else if bytes.Equal(tlsClientHandShakeStart, buffer[:2]) && buffer[5] == 1 { 256 | c.isTLS = true 257 | c.logger.Trace("XtlsFilterTls found tls client hello! ", len(buffer)) 258 | } 259 | } 260 | if c.remainingServerHello > 0 { 261 | end := int(c.remainingServerHello) 262 | if end > len(buffer) { 263 | end = len(buffer) 264 | } 265 | c.remainingServerHello -= int32(end) 266 | if bytes.Contains(buffer[:end], tls13SupportedVersions) { 267 | cipher, ok := tls13CipherSuiteDic[c.cipher] 268 | if ok && cipher != "TLS_AES_128_CCM_8_SHA256" { 269 | c.enableXTLS = true 270 | } 271 | c.logger.Trace("XtlsFilterTls found tls 1.3! ", len(buffer), " ", c.cipher, " ", c.enableXTLS) 272 | c.numberOfPacketToFilter = 0 273 | return 274 | } else if c.remainingServerHello == 0 { 275 | c.logger.Trace("XtlsFilterTls found tls 1.2! ", len(buffer)) 276 | c.numberOfPacketToFilter = 0 277 | return 278 | } 279 | } 280 | if c.numberOfPacketToFilter == 0 { 281 | c.logger.Trace("XtlsFilterTls stop filtering ", len(buffer)) 282 | } 283 | } 284 | } 285 | 286 | func (c *VisionConn) padding(buffer *buf.Buffer, command byte) *buf.Buffer { 287 | contentLen := 0 288 | paddingLen := 0 289 | if buffer != nil { 290 | contentLen = buffer.Len() 291 | } 292 | if contentLen < 900 && c.isTLS { 293 | l, _ := rand.Int(rand.Reader, big.NewInt(500)) 294 | paddingLen = int(l.Int64()) + 900 - contentLen 295 | } else { 296 | l, _ := rand.Int(rand.Reader, big.NewInt(256)) 297 | paddingLen = int(l.Int64()) 298 | } 299 | var bufferLen int 300 | if c.writeUUID { 301 | bufferLen += 16 302 | } 303 | bufferLen += 5 304 | if buffer != nil { 305 | bufferLen += buffer.Len() 306 | } 307 | bufferLen += paddingLen 308 | newBuffer := buf.NewSize(bufferLen) 309 | if c.writeUUID { 310 | common.Must1(newBuffer.Write(c.userUUID[:])) 311 | c.writeUUID = false 312 | } 313 | common.Must1(newBuffer.Write([]byte{command, byte(contentLen >> 8), byte(contentLen), byte(paddingLen >> 8), byte(paddingLen)})) 314 | if buffer != nil { 315 | common.Must1(newBuffer.Write(buffer.Bytes())) 316 | buffer.Release() 317 | } 318 | newBuffer.Extend(paddingLen) 319 | c.logger.Trace("XtlsPadding ", contentLen, " ", paddingLen, " ", command) 320 | return newBuffer 321 | } 322 | 323 | func (c *VisionConn) unPadding(buffer []byte) []*buf.Buffer { 324 | var bufferIndex int 325 | if c.remainingContent == -1 && c.remainingPadding == -1 { 326 | if len(buffer) >= 21 && bytes.Equal(c.userUUID[:], buffer[:16]) { 327 | bufferIndex = 16 328 | c.remainingContent = 0 329 | c.remainingPadding = 0 330 | c.currentCommand = 0 331 | } 332 | } 333 | if c.remainingContent == -1 && c.remainingPadding == -1 { 334 | return []*buf.Buffer{buf.As(buffer)} 335 | } 336 | var buffers []*buf.Buffer 337 | for bufferIndex < len(buffer) { 338 | if c.remainingContent <= 0 && c.remainingPadding <= 0 { 339 | if c.currentCommand == 1 { 340 | buffers = append(buffers, buf.As(buffer[bufferIndex:])) 341 | break 342 | } else { 343 | paddingInfo := buffer[bufferIndex : bufferIndex+5] 344 | c.currentCommand = paddingInfo[0] 345 | c.remainingContent = int(paddingInfo[1])<<8 | int(paddingInfo[2]) 346 | c.remainingPadding = int(paddingInfo[3])<<8 | int(paddingInfo[4]) 347 | bufferIndex += 5 348 | c.logger.Trace("Xtls Unpadding new block ", bufferIndex, " ", c.remainingContent, " padding ", c.remainingPadding, " ", c.currentCommand) 349 | } 350 | } else if c.remainingContent > 0 { 351 | end := c.remainingContent 352 | if end > len(buffer)-bufferIndex { 353 | end = len(buffer) - bufferIndex 354 | } 355 | buffers = append(buffers, buf.As(buffer[bufferIndex:bufferIndex+end])) 356 | c.remainingContent -= end 357 | bufferIndex += end 358 | } else { 359 | end := c.remainingPadding 360 | if end > len(buffer)-bufferIndex { 361 | end = len(buffer) - bufferIndex 362 | } 363 | c.remainingPadding -= end 364 | bufferIndex += end 365 | } 366 | if bufferIndex == len(buffer) { 367 | break 368 | } 369 | } 370 | return buffers 371 | } 372 | 373 | func (c *VisionConn) NeedAdditionalReadDeadline() bool { 374 | return true 375 | } 376 | 377 | func (c *VisionConn) Upstream() any { 378 | return c.Conn 379 | } 380 | -------------------------------------------------------------------------------- /vless/vision_utls.go: -------------------------------------------------------------------------------- 1 | //go:build with_utls 2 | 3 | package vless 4 | 5 | import ( 6 | "net" 7 | "reflect" 8 | "unsafe" 9 | 10 | utls "github.com/metacubex/utls" 11 | "github.com/sagernet/sing/common" 12 | ) 13 | 14 | func init() { 15 | tlsRegistry = append(tlsRegistry, func(conn net.Conn) (loaded bool, netConn net.Conn, reflectType reflect.Type, reflectPointer uintptr) { 16 | tlsConn, loaded := common.Cast[*utls.UConn](conn) 17 | if !loaded { 18 | return 19 | } 20 | return true, tlsConn.NetConn(), reflect.TypeOf(tlsConn.Conn).Elem(), uintptr(unsafe.Pointer(tlsConn.Conn)) 21 | }) 22 | } 23 | -------------------------------------------------------------------------------- /xudp.go: -------------------------------------------------------------------------------- 1 | package vmess 2 | 3 | import ( 4 | "encoding/binary" 5 | "io" 6 | "net" 7 | 8 | "github.com/sagernet/sing/common" 9 | "github.com/sagernet/sing/common/buf" 10 | "github.com/sagernet/sing/common/bufio" 11 | E "github.com/sagernet/sing/common/exceptions" 12 | M "github.com/sagernet/sing/common/metadata" 13 | N "github.com/sagernet/sing/common/network" 14 | ) 15 | 16 | type XUDPConn struct { 17 | net.Conn 18 | writer N.ExtendedWriter 19 | destination M.Socksaddr 20 | requestWritten bool 21 | } 22 | 23 | func NewXUDPConn(conn net.Conn, destination M.Socksaddr) *XUDPConn { 24 | return &XUDPConn{ 25 | Conn: conn, 26 | writer: bufio.NewExtendedWriter(conn), 27 | destination: destination, 28 | } 29 | } 30 | 31 | func (c *XUDPConn) Read(p []byte) (n int, err error) { 32 | n, _, err = c.ReadFrom(p) 33 | return 34 | } 35 | 36 | func (c *XUDPConn) Write(p []byte) (n int, err error) { 37 | return c.WriteTo(p, c.destination) 38 | } 39 | 40 | func (c *XUDPConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) { 41 | buffer := buf.With(p) 42 | var destination M.Socksaddr 43 | destination, err = c.ReadPacket(buffer) 44 | if err != nil { 45 | return 46 | } 47 | if destination.IsFqdn() { 48 | addr = destination 49 | } else { 50 | addr = destination.UDPAddr() 51 | } 52 | n = buffer.Len() 53 | return 54 | } 55 | 56 | func (c *XUDPConn) ReadPacket(buffer *buf.Buffer) (destination M.Socksaddr, err error) { 57 | start := buffer.Start() 58 | _, err = buffer.ReadFullFrom(c.Conn, 6) 59 | if err != nil { 60 | return 61 | } 62 | var length uint16 63 | err = binary.Read(buffer, binary.BigEndian, &length) 64 | if err != nil { 65 | return 66 | } 67 | header, err := buffer.ReadBytes(4) 68 | if err != nil { 69 | return 70 | } 71 | switch header[2] { 72 | case StatusNew: 73 | return M.Socksaddr{}, E.New("unexpected frame new") 74 | case StatusKeep: 75 | if length != 4 { 76 | _, err = buffer.ReadFullFrom(c.Conn, int(length)-2) 77 | if err != nil { 78 | return 79 | } 80 | buffer.Advance(1) 81 | destination, err = AddressSerializer.ReadAddrPort(buffer) 82 | if err != nil { 83 | return 84 | } 85 | destination = destination.Unwrap() 86 | } else { 87 | _, err = buffer.ReadFullFrom(c.Conn, 2) 88 | if err != nil { 89 | return 90 | } 91 | destination = c.destination 92 | } 93 | case StatusEnd: 94 | return M.Socksaddr{}, io.EOF 95 | case StatusKeepAlive: 96 | default: 97 | return M.Socksaddr{}, E.New("unexpected frame: ", buffer.Byte(2)) 98 | } 99 | // option error 100 | if header[3]&2 == 2 { 101 | return M.Socksaddr{}, E.Cause(net.ErrClosed, "remote closed") 102 | } 103 | // option data 104 | if header[3]&1 != 1 { 105 | buffer.Resize(start, 0) 106 | return c.ReadPacket(buffer) 107 | } else { 108 | err = binary.Read(buffer, binary.BigEndian, &length) 109 | if err != nil { 110 | return 111 | } 112 | buffer.Resize(start, 0) 113 | _, err = buffer.ReadFullFrom(c.Conn, int(length)) 114 | return 115 | } 116 | } 117 | 118 | func (c *XUDPConn) WriteTo(p []byte, addr net.Addr) (n int, err error) { 119 | return bufio.WritePacketBuffer(c, buf.As(p), M.SocksaddrFromNet(addr)) 120 | } 121 | 122 | func (c *XUDPConn) frontHeadroom(addrLen int) int { 123 | if !c.requestWritten { 124 | var headerLen int 125 | headerLen += 2 // frame len 126 | headerLen += 5 // frame header 127 | headerLen += addrLen 128 | headerLen += 2 // payload len 129 | return headerLen 130 | } else { 131 | return 7 + addrLen + 2 132 | } 133 | } 134 | 135 | func (c *XUDPConn) WritePacket(buffer *buf.Buffer, destination M.Socksaddr) error { 136 | dataLen := buffer.Len() 137 | addrLen := M.SocksaddrSerializer.AddrPortLen(destination) 138 | if !c.requestWritten { 139 | header := buf.With(buffer.ExtendHeader(c.frontHeadroom(addrLen))) 140 | common.Must( 141 | binary.Write(header, binary.BigEndian, uint16(5+addrLen)), 142 | header.WriteByte(0), 143 | header.WriteByte(0), 144 | header.WriteByte(1), // frame type new 145 | header.WriteByte(1), // option data 146 | header.WriteByte(NetworkUDP), 147 | ) 148 | err := AddressSerializer.WriteAddrPort(header, destination) 149 | if err != nil { 150 | return err 151 | } 152 | common.Must(binary.Write(header, binary.BigEndian, uint16(dataLen))) 153 | c.requestWritten = true 154 | } else { 155 | header := buffer.ExtendHeader(c.frontHeadroom(addrLen)) 156 | binary.BigEndian.PutUint16(header, uint16(5+addrLen)) 157 | header[2] = 0 158 | header[3] = 0 159 | header[4] = 2 // frame keep 160 | header[5] = 1 // option data 161 | header[6] = NetworkUDP 162 | err := AddressSerializer.WriteAddrPort(buf.With(header[7:]), destination) 163 | if err != nil { 164 | return err 165 | } 166 | binary.BigEndian.PutUint16(header[7+addrLen:], uint16(dataLen)) 167 | } 168 | return c.writer.WriteBuffer(buffer) 169 | } 170 | 171 | func (c *XUDPConn) FrontHeadroom() int { 172 | return c.frontHeadroom(M.MaxSocksaddrLength) 173 | } 174 | 175 | func (c *XUDPConn) NeedHandshake() bool { 176 | return !c.requestWritten 177 | } 178 | 179 | func (c *XUDPConn) NeedAdditionalReadDeadline() bool { 180 | return true 181 | } 182 | 183 | func (c *XUDPConn) Upstream() any { 184 | return c.Conn 185 | } 186 | --------------------------------------------------------------------------------