mirror of
https://gitclone.com/github.com/MetaCubeX/Clash.Meta
synced 2025-02-23 12:42:27 +08:00
Feature: ✨ add vmess support
This commit is contained in:
parent
af13acc171
commit
834baa9e27
70
Gopkg.lock
generated
70
Gopkg.lock
generated
@ -3,65 +3,92 @@
|
|||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
branch = "master"
|
branch = "master"
|
||||||
|
digest = "1:8fa55a6e302771a90a86ceae1ca3c0df4ef15d21092198e8313f61dde9eea963"
|
||||||
name = "github.com/Yawning/chacha20"
|
name = "github.com/Yawning/chacha20"
|
||||||
packages = ["."]
|
packages = ["."]
|
||||||
|
pruneopts = "UT"
|
||||||
revision = "e3b1f968fc6397b51d963fee8ec8711a47bc0ce8"
|
revision = "e3b1f968fc6397b51d963fee8ec8711a47bc0ce8"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
|
digest = "1:444b82bfe35c83bbcaf84e310fb81a1f9ece03edfed586483c869e2c046aef69"
|
||||||
name = "github.com/eapache/queue"
|
name = "github.com/eapache/queue"
|
||||||
packages = ["."]
|
packages = ["."]
|
||||||
|
pruneopts = "UT"
|
||||||
revision = "44cc805cf13205b55f69e14bcb69867d1ae92f98"
|
revision = "44cc805cf13205b55f69e14bcb69867d1ae92f98"
|
||||||
version = "v1.1.0"
|
version = "v1.1.0"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
|
digest = "1:b9914f85d95a0968bafd1be1908ba29e2eafafd88d6fd13696be42bf5368c380"
|
||||||
name = "github.com/go-chi/chi"
|
name = "github.com/go-chi/chi"
|
||||||
packages = ["."]
|
packages = ["."]
|
||||||
revision = "e83ac2304db3c50cf03d96a2fcd39009d458bc35"
|
pruneopts = "UT"
|
||||||
version = "v3.3.2"
|
revision = "b5294d10673813fac8558e7f47242bc9e61b4c25"
|
||||||
|
version = "v3.3.3"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
|
digest = "1:dfa416a1bb8139f30832543340f972f65c0db9932034cb6a1b42c5ac615a3fb8"
|
||||||
name = "github.com/go-chi/cors"
|
name = "github.com/go-chi/cors"
|
||||||
packages = ["."]
|
packages = ["."]
|
||||||
|
pruneopts = "UT"
|
||||||
revision = "dba6525398619dead495962a916728e7ee2ca322"
|
revision = "dba6525398619dead495962a916728e7ee2ca322"
|
||||||
version = "v1.0.0"
|
version = "v1.0.0"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
|
digest = "1:54d7b4b9ab2bb2bae35b55eea900bc8fe15cba05a95fc78bf7fd7b82a9a07afa"
|
||||||
name = "github.com/go-chi/render"
|
name = "github.com/go-chi/render"
|
||||||
packages = ["."]
|
packages = ["."]
|
||||||
|
pruneopts = "UT"
|
||||||
revision = "3215478343fbc559bd3fc08f7031bb134d6bdad5"
|
revision = "3215478343fbc559bd3fc08f7031bb134d6bdad5"
|
||||||
version = "v1.0.1"
|
version = "v1.0.1"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
|
digest = "1:ce579162ae1341f3e5ab30c0dce767f28b1eb6a81359aad01723f1ba6b4becdf"
|
||||||
|
name = "github.com/gofrs/uuid"
|
||||||
|
packages = ["."]
|
||||||
|
pruneopts = "UT"
|
||||||
|
revision = "370558f003bfe29580cd0f698d8640daccdcc45c"
|
||||||
|
version = "v3.1.1"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
digest = "1:2e8bdbc8a11d716dab1bf66d326285d7e5c92fa4c996c1574ba1153e57534b85"
|
||||||
name = "github.com/oschwald/geoip2-golang"
|
name = "github.com/oschwald/geoip2-golang"
|
||||||
packages = ["."]
|
packages = ["."]
|
||||||
|
pruneopts = "UT"
|
||||||
revision = "7118115686e16b77967cdbf55d1b944fe14ad312"
|
revision = "7118115686e16b77967cdbf55d1b944fe14ad312"
|
||||||
version = "v1.2.1"
|
version = "v1.2.1"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
|
digest = "1:07e8589503b7ec22430dae6eed6f2c17e4249ab245574f4fd0ba8fc9c597d138"
|
||||||
name = "github.com/oschwald/maxminddb-golang"
|
name = "github.com/oschwald/maxminddb-golang"
|
||||||
packages = ["."]
|
packages = ["."]
|
||||||
|
pruneopts = "UT"
|
||||||
revision = "c5bec84d1963260297932a1b7a1753c8420717a7"
|
revision = "c5bec84d1963260297932a1b7a1753c8420717a7"
|
||||||
version = "v1.3.0"
|
version = "v1.3.0"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
|
digest = "1:2bfa1a73654feb90893014b04779ce5205f3e19e843c0e32a89ea051d31f12d5"
|
||||||
name = "github.com/riobard/go-shadowsocks2"
|
name = "github.com/riobard/go-shadowsocks2"
|
||||||
packages = [
|
packages = [
|
||||||
"core",
|
"core",
|
||||||
"shadowaead",
|
"shadowaead",
|
||||||
"shadowstream",
|
"shadowstream",
|
||||||
"socks"
|
"socks",
|
||||||
]
|
]
|
||||||
|
pruneopts = "UT"
|
||||||
revision = "8346403248229fc7e10d7a259de8e9352a9d8830"
|
revision = "8346403248229fc7e10d7a259de8e9352a9d8830"
|
||||||
version = "v0.1.0"
|
version = "v0.1.0"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
|
digest = "1:d867dfa6751c8d7a435821ad3b736310c2ed68945d05b50fb9d23aee0540c8cc"
|
||||||
name = "github.com/sirupsen/logrus"
|
name = "github.com/sirupsen/logrus"
|
||||||
packages = ["."]
|
packages = ["."]
|
||||||
revision = "c155da19408a8799da419ed3eeb0cb5db0ad5dbc"
|
pruneopts = "UT"
|
||||||
version = "v1.0.5"
|
revision = "3e01752db0189b9157070a0e1668a620f9a85da2"
|
||||||
|
version = "v1.0.6"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
branch = "master"
|
branch = "master"
|
||||||
|
digest = "1:d33888518d56c3f0cc9009594f56be4faf33ffff358fe10ff8e7e8cccf0e6617"
|
||||||
name = "golang.org/x/crypto"
|
name = "golang.org/x/crypto"
|
||||||
packages = [
|
packages = [
|
||||||
"chacha20poly1305",
|
"chacha20poly1305",
|
||||||
@ -69,35 +96,54 @@
|
|||||||
"internal/chacha20",
|
"internal/chacha20",
|
||||||
"internal/subtle",
|
"internal/subtle",
|
||||||
"poly1305",
|
"poly1305",
|
||||||
"ssh/terminal"
|
"ssh/terminal",
|
||||||
]
|
]
|
||||||
revision = "a2144134853fc9a27a7b1e3eb4f19f1a76df13c9"
|
pruneopts = "UT"
|
||||||
|
revision = "0709b304e793a5edb4a2c0145f281ecdc20838a4"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
branch = "master"
|
branch = "master"
|
||||||
|
digest = "1:576f8d82185dc836ec6d10c0e5568dc4ff94e4d9f101d33ed5d6bae0cbba65b2"
|
||||||
name = "golang.org/x/sys"
|
name = "golang.org/x/sys"
|
||||||
packages = [
|
packages = [
|
||||||
"cpu",
|
"cpu",
|
||||||
"unix",
|
"unix",
|
||||||
"windows"
|
"windows",
|
||||||
]
|
]
|
||||||
revision = "ac767d655b305d4e9612f5f6e33120b9176c4ad4"
|
pruneopts = "UT"
|
||||||
|
revision = "ebe1bf3edb3325c393447059974de898d5133eb8"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
|
digest = "1:975a4480c40f2d0b95e1f83d3ec1aa29a2774e80179e08a9a4ba2aab86721b23"
|
||||||
name = "gopkg.in/eapache/channels.v1"
|
name = "gopkg.in/eapache/channels.v1"
|
||||||
packages = ["."]
|
packages = ["."]
|
||||||
|
pruneopts = "UT"
|
||||||
revision = "47238d5aae8c0fefd518ef2bee46290909cf8263"
|
revision = "47238d5aae8c0fefd518ef2bee46290909cf8263"
|
||||||
version = "v1.1.0"
|
version = "v1.1.0"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
|
digest = "1:5abd6a22805b1919f6a6bca0ae58b13cef1f3412812f38569978f43ef02743d4"
|
||||||
name = "gopkg.in/ini.v1"
|
name = "gopkg.in/ini.v1"
|
||||||
packages = ["."]
|
packages = ["."]
|
||||||
revision = "358ee7663966325963d4e8b2e1fbd570c5195153"
|
pruneopts = "UT"
|
||||||
version = "v1.38.1"
|
revision = "5cf292cae48347c2490ac1a58fe36735fb78df7e"
|
||||||
|
version = "v1.38.2"
|
||||||
|
|
||||||
[solve-meta]
|
[solve-meta]
|
||||||
analyzer-name = "dep"
|
analyzer-name = "dep"
|
||||||
analyzer-version = 1
|
analyzer-version = 1
|
||||||
inputs-digest = "0ccb06b3617c87e75bd650f92adc99e55b93070e0b2a0bc71634270226e125fc"
|
input-imports = [
|
||||||
|
"github.com/go-chi/chi",
|
||||||
|
"github.com/go-chi/cors",
|
||||||
|
"github.com/go-chi/render",
|
||||||
|
"github.com/gofrs/uuid",
|
||||||
|
"github.com/oschwald/geoip2-golang",
|
||||||
|
"github.com/riobard/go-shadowsocks2/core",
|
||||||
|
"github.com/riobard/go-shadowsocks2/socks",
|
||||||
|
"github.com/sirupsen/logrus",
|
||||||
|
"golang.org/x/crypto/chacha20poly1305",
|
||||||
|
"gopkg.in/eapache/channels.v1",
|
||||||
|
"gopkg.in/ini.v1",
|
||||||
|
]
|
||||||
solver-name = "gps-cdcl"
|
solver-name = "gps-cdcl"
|
||||||
solver-version = 1
|
solver-version = 1
|
||||||
|
16
Gopkg.toml
16
Gopkg.toml
@ -1,6 +1,6 @@
|
|||||||
# Gopkg.toml example
|
# Gopkg.toml example
|
||||||
#
|
#
|
||||||
# Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md
|
# Refer to https://golang.github.io/dep/docs/Gopkg.toml.html
|
||||||
# for detailed Gopkg.toml documentation.
|
# for detailed Gopkg.toml documentation.
|
||||||
#
|
#
|
||||||
# required = ["github.com/user/thing/cmd/thing"]
|
# required = ["github.com/user/thing/cmd/thing"]
|
||||||
@ -27,7 +27,7 @@
|
|||||||
|
|
||||||
[[constraint]]
|
[[constraint]]
|
||||||
name = "github.com/go-chi/chi"
|
name = "github.com/go-chi/chi"
|
||||||
version = "3.3.2"
|
version = "3.3.3"
|
||||||
|
|
||||||
[[constraint]]
|
[[constraint]]
|
||||||
name = "github.com/go-chi/cors"
|
name = "github.com/go-chi/cors"
|
||||||
@ -37,6 +37,10 @@
|
|||||||
name = "github.com/go-chi/render"
|
name = "github.com/go-chi/render"
|
||||||
version = "1.0.1"
|
version = "1.0.1"
|
||||||
|
|
||||||
|
[[constraint]]
|
||||||
|
name = "github.com/gofrs/uuid"
|
||||||
|
version = "3.1.1"
|
||||||
|
|
||||||
[[constraint]]
|
[[constraint]]
|
||||||
name = "github.com/oschwald/geoip2-golang"
|
name = "github.com/oschwald/geoip2-golang"
|
||||||
version = "1.2.1"
|
version = "1.2.1"
|
||||||
@ -47,7 +51,11 @@
|
|||||||
|
|
||||||
[[constraint]]
|
[[constraint]]
|
||||||
name = "github.com/sirupsen/logrus"
|
name = "github.com/sirupsen/logrus"
|
||||||
version = "1.0.5"
|
version = "1.0.6"
|
||||||
|
|
||||||
|
[[constraint]]
|
||||||
|
branch = "master"
|
||||||
|
name = "golang.org/x/crypto"
|
||||||
|
|
||||||
[[constraint]]
|
[[constraint]]
|
||||||
name = "gopkg.in/eapache/channels.v1"
|
name = "gopkg.in/eapache/channels.v1"
|
||||||
@ -55,7 +63,7 @@
|
|||||||
|
|
||||||
[[constraint]]
|
[[constraint]]
|
||||||
name = "gopkg.in/ini.v1"
|
name = "gopkg.in/ini.v1"
|
||||||
version = "1.38.1"
|
version = "1.38.2"
|
||||||
|
|
||||||
[prune]
|
[prune]
|
||||||
go-tests = true
|
go-tests = true
|
||||||
|
93
adapters/remote/vmess.go
Normal file
93
adapters/remote/vmess.go
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
package adapters
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/Dreamacro/clash/common/vmess"
|
||||||
|
C "github.com/Dreamacro/clash/constant"
|
||||||
|
)
|
||||||
|
|
||||||
|
// VmessAdapter is a vmess adapter
|
||||||
|
type VmessAdapter struct {
|
||||||
|
conn net.Conn
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close is used to close connection
|
||||||
|
func (v *VmessAdapter) Close() {
|
||||||
|
v.conn.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *VmessAdapter) Conn() net.Conn {
|
||||||
|
return v.conn
|
||||||
|
}
|
||||||
|
|
||||||
|
type Vmess struct {
|
||||||
|
name string
|
||||||
|
server string
|
||||||
|
client *vmess.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ss *Vmess) Name() string {
|
||||||
|
return ss.name
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ss *Vmess) Type() C.AdapterType {
|
||||||
|
return C.Vmess
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ss *Vmess) Generator(addr *C.Addr) (adapter C.ProxyAdapter, err error) {
|
||||||
|
c, err := net.Dial("tcp", ss.server)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("%s connect error", ss.server)
|
||||||
|
}
|
||||||
|
tcpKeepAlive(c)
|
||||||
|
c = ss.client.New(c, parseVmessAddr(addr))
|
||||||
|
return &VmessAdapter{conn: c}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewVmess(name string, server string, uuid string, alterID uint16, security string) (*Vmess, error) {
|
||||||
|
security = strings.ToLower(security)
|
||||||
|
client, err := vmess.NewClient(vmess.Config{
|
||||||
|
UUID: uuid,
|
||||||
|
AlterID: alterID,
|
||||||
|
Security: security,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &Vmess{
|
||||||
|
name: name,
|
||||||
|
server: server,
|
||||||
|
client: client,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseVmessAddr(info *C.Addr) *vmess.DstAddr {
|
||||||
|
var addrType byte
|
||||||
|
var addr []byte
|
||||||
|
switch info.AddrType {
|
||||||
|
case C.AtypIPv4:
|
||||||
|
addrType = byte(vmess.AtypIPv4)
|
||||||
|
addr = make([]byte, net.IPv4len)
|
||||||
|
copy(addr[:], info.IP.To4())
|
||||||
|
case C.AtypIPv6:
|
||||||
|
addrType = byte(vmess.AtypIPv6)
|
||||||
|
addr = make([]byte, net.IPv6len)
|
||||||
|
copy(addr[:], info.IP.To16())
|
||||||
|
case C.AtypDomainName:
|
||||||
|
addrType = byte(vmess.AtypDomainName)
|
||||||
|
addr = make([]byte, len(info.Host)+1)
|
||||||
|
addr[0] = byte(len(info.Host))
|
||||||
|
copy(addr[1:], []byte(info.Host))
|
||||||
|
}
|
||||||
|
|
||||||
|
port, _ := strconv.Atoi(info.Port)
|
||||||
|
return &vmess.DstAddr{
|
||||||
|
AddrType: addrType,
|
||||||
|
Addr: addr,
|
||||||
|
Port: uint(port),
|
||||||
|
}
|
||||||
|
}
|
115
common/vmess/aead.go
Normal file
115
common/vmess/aead.go
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
package vmess
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/cipher"
|
||||||
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
type aeadWriter struct {
|
||||||
|
io.Writer
|
||||||
|
cipher.AEAD
|
||||||
|
nonce [32]byte
|
||||||
|
count uint16
|
||||||
|
iv []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func newAEADWriter(w io.Writer, aead cipher.AEAD, iv []byte) *aeadWriter {
|
||||||
|
return &aeadWriter{Writer: w, AEAD: aead, iv: iv}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *aeadWriter) Write(b []byte) (n int, err error) {
|
||||||
|
buf := bufPool.Get().([]byte)
|
||||||
|
defer bufPool.Put(buf[:cap(buf)])
|
||||||
|
length := len(b)
|
||||||
|
for {
|
||||||
|
if length == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
readLen := chunkSize - w.Overhead()
|
||||||
|
if length < readLen {
|
||||||
|
readLen = length
|
||||||
|
}
|
||||||
|
payloadBuf := buf[lenSize : lenSize+chunkSize-w.Overhead()]
|
||||||
|
copy(payloadBuf, b[n:n+readLen])
|
||||||
|
|
||||||
|
binary.BigEndian.PutUint16(buf[:lenSize], uint16(readLen+w.Overhead()))
|
||||||
|
binary.BigEndian.PutUint16(w.nonce[:2], w.count)
|
||||||
|
copy(w.nonce[2:], w.iv[2:12])
|
||||||
|
|
||||||
|
w.Seal(payloadBuf[:0], w.nonce[:w.NonceSize()], payloadBuf[:readLen], nil)
|
||||||
|
w.count++
|
||||||
|
|
||||||
|
_, err = w.Writer.Write(buf[:lenSize+readLen+w.Overhead()])
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
n += readLen
|
||||||
|
length -= readLen
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
type aeadReader struct {
|
||||||
|
io.Reader
|
||||||
|
cipher.AEAD
|
||||||
|
nonce [32]byte
|
||||||
|
buf []byte
|
||||||
|
offset int
|
||||||
|
iv []byte
|
||||||
|
sizeBuf []byte
|
||||||
|
count uint16
|
||||||
|
}
|
||||||
|
|
||||||
|
func newAEADReader(r io.Reader, aead cipher.AEAD, iv []byte) *aeadReader {
|
||||||
|
return &aeadReader{Reader: r, AEAD: aead, iv: iv, sizeBuf: make([]byte, lenSize)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *aeadReader) Read(b []byte) (int, error) {
|
||||||
|
if r.buf != nil {
|
||||||
|
n := copy(b, r.buf[r.offset:])
|
||||||
|
r.offset += n
|
||||||
|
if r.offset == len(r.buf) {
|
||||||
|
bufPool.Put(r.buf[:cap(r.buf)])
|
||||||
|
r.buf = nil
|
||||||
|
}
|
||||||
|
return n, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := io.ReadFull(r.Reader, r.sizeBuf)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
size := int(binary.BigEndian.Uint16(r.sizeBuf))
|
||||||
|
if size > maxSize {
|
||||||
|
return 0, errors.New("Buffer is larger than standard")
|
||||||
|
}
|
||||||
|
|
||||||
|
buf := bufPool.Get().([]byte)
|
||||||
|
_, err = io.ReadFull(r.Reader, buf[:size])
|
||||||
|
if err != nil {
|
||||||
|
bufPool.Put(buf[:cap(buf)])
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
binary.BigEndian.PutUint16(r.nonce[:2], r.count)
|
||||||
|
copy(r.nonce[2:], r.iv[2:12])
|
||||||
|
|
||||||
|
_, err = r.Open(buf[:0], r.nonce[:r.NonceSize()], buf[:size], nil)
|
||||||
|
r.count++
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
realLen := size - r.Overhead()
|
||||||
|
n := copy(b, buf[:realLen])
|
||||||
|
if len(b) >= realLen {
|
||||||
|
bufPool.Put(buf[:cap(buf)])
|
||||||
|
return n, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
r.offset = n
|
||||||
|
r.buf = buf[:realLen]
|
||||||
|
return n, nil
|
||||||
|
}
|
103
common/vmess/chunk.go
Normal file
103
common/vmess/chunk.go
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
package vmess
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
lenSize = 2
|
||||||
|
chunkSize = 1 << 14 // 2 ** 14 == 16 * 1024
|
||||||
|
maxSize = 17 * 1024 // 2 + chunkSize + aead.Overhead()
|
||||||
|
)
|
||||||
|
|
||||||
|
var bufPool = sync.Pool{New: func() interface{} { return make([]byte, maxSize) }}
|
||||||
|
|
||||||
|
type chunkReader struct {
|
||||||
|
io.Reader
|
||||||
|
buf []byte
|
||||||
|
sizeBuf []byte
|
||||||
|
offset int
|
||||||
|
}
|
||||||
|
|
||||||
|
func newChunkReader(reader io.Reader) *chunkReader {
|
||||||
|
return &chunkReader{Reader: reader, sizeBuf: make([]byte, lenSize)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newChunkWriter(writer io.WriteCloser) *chunkWriter {
|
||||||
|
return &chunkWriter{Writer: writer}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cr *chunkReader) Read(b []byte) (int, error) {
|
||||||
|
if cr.buf != nil {
|
||||||
|
n := copy(b, cr.buf[cr.offset:])
|
||||||
|
cr.offset += n
|
||||||
|
if cr.offset == len(cr.buf) {
|
||||||
|
bufPool.Put(cr.buf[:cap(cr.buf)])
|
||||||
|
cr.buf = nil
|
||||||
|
}
|
||||||
|
return n, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := io.ReadFull(cr.Reader, cr.sizeBuf)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
size := int(binary.BigEndian.Uint16(cr.sizeBuf))
|
||||||
|
if size > maxSize {
|
||||||
|
return 0, errors.New("Buffer is larger than standard")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(b) >= size {
|
||||||
|
_, err := io.ReadFull(cr.Reader, b[:size])
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return size, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
buf := bufPool.Get().([]byte)
|
||||||
|
_, err = io.ReadFull(cr.Reader, buf[:size])
|
||||||
|
if err != nil {
|
||||||
|
bufPool.Put(buf[:cap(buf)])
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
n := copy(b, cr.buf[:])
|
||||||
|
cr.offset = n
|
||||||
|
cr.buf = buf[:size]
|
||||||
|
return n, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type chunkWriter struct {
|
||||||
|
io.Writer
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cw *chunkWriter) Write(b []byte) (n int, err error) {
|
||||||
|
buf := bufPool.Get().([]byte)
|
||||||
|
defer bufPool.Put(buf[:cap(buf)])
|
||||||
|
length := len(b)
|
||||||
|
for {
|
||||||
|
if length == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
readLen := chunkSize
|
||||||
|
if length < chunkSize {
|
||||||
|
readLen = length
|
||||||
|
}
|
||||||
|
payloadBuf := buf[lenSize : lenSize+chunkSize]
|
||||||
|
copy(payloadBuf, b[n:n+readLen])
|
||||||
|
|
||||||
|
binary.BigEndian.PutUint16(buf[:lenSize], uint16(readLen))
|
||||||
|
_, err = cw.Writer.Write(buf[:lenSize+readLen])
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
n += readLen
|
||||||
|
length -= readLen
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
213
common/vmess/conn.go
Normal file
213
common/vmess/conn.go
Normal file
@ -0,0 +1,213 @@
|
|||||||
|
package vmess
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/aes"
|
||||||
|
"crypto/cipher"
|
||||||
|
"crypto/hmac"
|
||||||
|
"crypto/md5"
|
||||||
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
|
"hash/fnv"
|
||||||
|
"io"
|
||||||
|
"math/rand"
|
||||||
|
"net"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/chacha20poly1305"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
rand.Seed(time.Now().UnixNano())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Conn wrapper a net.Conn with vmess protocol
|
||||||
|
type Conn struct {
|
||||||
|
net.Conn
|
||||||
|
reader io.Reader
|
||||||
|
writer io.Writer
|
||||||
|
dst *DstAddr
|
||||||
|
id *ID
|
||||||
|
reqBodyIV []byte
|
||||||
|
reqBodyKey []byte
|
||||||
|
respBodyIV []byte
|
||||||
|
respBodyKey []byte
|
||||||
|
respV byte
|
||||||
|
security byte
|
||||||
|
|
||||||
|
sent bool
|
||||||
|
received bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (vc *Conn) Write(b []byte) (int, error) {
|
||||||
|
if vc.sent {
|
||||||
|
return vc.writer.Write(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := vc.sendRequest(); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
vc.sent = true
|
||||||
|
return vc.writer.Write(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (vc *Conn) Read(b []byte) (int, error) {
|
||||||
|
if vc.received {
|
||||||
|
return vc.reader.Read(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := vc.recvResponse(); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
vc.received = true
|
||||||
|
return vc.reader.Read(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (vc *Conn) sendRequest() error {
|
||||||
|
timestamp := make([]byte, 8)
|
||||||
|
binary.BigEndian.PutUint64(timestamp, uint64(time.Now().UTC().Unix()))
|
||||||
|
|
||||||
|
h := hmac.New(md5.New, vc.id.UUID.Bytes())
|
||||||
|
h.Write(timestamp)
|
||||||
|
_, err := vc.Conn.Write(h.Sum(nil))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
buf := &bytes.Buffer{}
|
||||||
|
|
||||||
|
// Ver IV Key V Opt
|
||||||
|
buf.WriteByte(Version)
|
||||||
|
buf.Write(vc.reqBodyIV[:])
|
||||||
|
buf.Write(vc.reqBodyKey[:])
|
||||||
|
buf.WriteByte(vc.respV)
|
||||||
|
buf.WriteByte(OptionChunkStream)
|
||||||
|
|
||||||
|
p := rand.Intn(16)
|
||||||
|
// P Sec Reserve Cmd
|
||||||
|
buf.WriteByte(byte(p<<4) | byte(vc.security))
|
||||||
|
buf.WriteByte(0)
|
||||||
|
buf.WriteByte(CommandTCP)
|
||||||
|
|
||||||
|
// Port AddrType Addr
|
||||||
|
binary.Write(buf, binary.BigEndian, uint16(vc.dst.Port))
|
||||||
|
buf.WriteByte(vc.dst.AddrType)
|
||||||
|
buf.Write(vc.dst.Addr)
|
||||||
|
|
||||||
|
// padding
|
||||||
|
if p > 0 {
|
||||||
|
padding := make([]byte, p)
|
||||||
|
rand.Read(padding)
|
||||||
|
buf.Write(padding)
|
||||||
|
}
|
||||||
|
|
||||||
|
fnv1a := fnv.New32a()
|
||||||
|
fnv1a.Write(buf.Bytes())
|
||||||
|
buf.Write(fnv1a.Sum(nil))
|
||||||
|
|
||||||
|
block, err := aes.NewCipher(vc.id.CmdKey)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
stream := cipher.NewCFBEncrypter(block, hashTimestamp(time.Now().UTC()))
|
||||||
|
stream.XORKeyStream(buf.Bytes(), buf.Bytes())
|
||||||
|
_, err = vc.Conn.Write(buf.Bytes())
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (vc *Conn) recvResponse() error {
|
||||||
|
block, err := aes.NewCipher(vc.respBodyKey[:])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
stream := cipher.NewCFBDecrypter(block, vc.respBodyIV[:])
|
||||||
|
buf := make([]byte, 4)
|
||||||
|
_, err = io.ReadFull(vc.Conn, buf)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
stream.XORKeyStream(buf, buf)
|
||||||
|
|
||||||
|
if buf[0] != vc.respV {
|
||||||
|
return errors.New("unexpected response header")
|
||||||
|
}
|
||||||
|
|
||||||
|
if buf[2] != 0 {
|
||||||
|
return errors.New("dynamic port is not supported now")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func hashTimestamp(t time.Time) []byte {
|
||||||
|
md5hash := md5.New()
|
||||||
|
ts := make([]byte, 8)
|
||||||
|
binary.BigEndian.PutUint64(ts, uint64(t.UTC().Unix()))
|
||||||
|
md5hash.Write(ts)
|
||||||
|
md5hash.Write(ts)
|
||||||
|
md5hash.Write(ts)
|
||||||
|
md5hash.Write(ts)
|
||||||
|
return md5hash.Sum(nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// newConn return a Conn instance
|
||||||
|
func newConn(conn net.Conn, id *ID, dst *DstAddr, security Security) *Conn {
|
||||||
|
randBytes := make([]byte, 33)
|
||||||
|
rand.Read(randBytes)
|
||||||
|
reqBodyIV := make([]byte, 16)
|
||||||
|
reqBodyKey := make([]byte, 16)
|
||||||
|
copy(reqBodyIV[:], randBytes[:16])
|
||||||
|
copy(reqBodyKey[:], randBytes[16:32])
|
||||||
|
respV := randBytes[32]
|
||||||
|
|
||||||
|
respBodyKey := md5.Sum(reqBodyKey[:])
|
||||||
|
respBodyIV := md5.Sum(reqBodyIV[:])
|
||||||
|
|
||||||
|
var writer io.Writer
|
||||||
|
var reader io.Reader
|
||||||
|
switch security {
|
||||||
|
case SecurityNone:
|
||||||
|
reader = newChunkReader(conn)
|
||||||
|
writer = newChunkWriter(conn)
|
||||||
|
case SecurityAES128GCM:
|
||||||
|
block, _ := aes.NewCipher(reqBodyKey[:])
|
||||||
|
aead, _ := cipher.NewGCM(block)
|
||||||
|
writer = newAEADWriter(conn, aead, reqBodyIV[:])
|
||||||
|
|
||||||
|
block, _ = aes.NewCipher(respBodyKey[:])
|
||||||
|
aead, _ = cipher.NewGCM(block)
|
||||||
|
reader = newAEADReader(conn, aead, respBodyIV[:])
|
||||||
|
case SecurityCHACHA20POLY1305:
|
||||||
|
key := make([]byte, 32)
|
||||||
|
t := md5.Sum(reqBodyKey[:])
|
||||||
|
copy(key, t[:])
|
||||||
|
t = md5.Sum(key[:16])
|
||||||
|
copy(key[16:], t[:])
|
||||||
|
aead, _ := chacha20poly1305.New(key)
|
||||||
|
writer = newAEADWriter(conn, aead, reqBodyIV[:])
|
||||||
|
|
||||||
|
t = md5.Sum(respBodyKey[:])
|
||||||
|
copy(key, t[:])
|
||||||
|
t = md5.Sum(key[:16])
|
||||||
|
copy(key[16:], t[:])
|
||||||
|
aead, _ = chacha20poly1305.New(key)
|
||||||
|
reader = newAEADReader(conn, aead, respBodyIV[:])
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Conn{
|
||||||
|
Conn: conn,
|
||||||
|
id: id,
|
||||||
|
dst: dst,
|
||||||
|
reqBodyIV: reqBodyIV,
|
||||||
|
reqBodyKey: reqBodyKey,
|
||||||
|
respV: respV,
|
||||||
|
respBodyIV: respBodyIV[:],
|
||||||
|
respBodyKey: respBodyKey[:],
|
||||||
|
reader: reader,
|
||||||
|
writer: writer,
|
||||||
|
security: security,
|
||||||
|
}
|
||||||
|
}
|
54
common/vmess/user.go
Normal file
54
common/vmess/user.go
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
package vmess
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/md5"
|
||||||
|
|
||||||
|
"github.com/gofrs/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ID cmdKey length
|
||||||
|
const (
|
||||||
|
IDBytesLen = 16
|
||||||
|
)
|
||||||
|
|
||||||
|
// The ID of en entity, in the form of a UUID.
|
||||||
|
type ID struct {
|
||||||
|
UUID *uuid.UUID
|
||||||
|
CmdKey []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// newID returns an ID with given UUID.
|
||||||
|
func newID(uuid *uuid.UUID) *ID {
|
||||||
|
id := &ID{UUID: uuid, CmdKey: make([]byte, IDBytesLen)}
|
||||||
|
md5hash := md5.New()
|
||||||
|
md5hash.Write(uuid.Bytes())
|
||||||
|
md5hash.Write([]byte("c48619fe-8f02-49e0-b9e9-edf763e17e21"))
|
||||||
|
md5hash.Sum(id.CmdKey[:0])
|
||||||
|
return id
|
||||||
|
}
|
||||||
|
|
||||||
|
func nextID(u *uuid.UUID) *uuid.UUID {
|
||||||
|
md5hash := md5.New()
|
||||||
|
md5hash.Write(u.Bytes())
|
||||||
|
md5hash.Write([]byte("16167dc8-16b6-4e6d-b8bb-65dd68113a81"))
|
||||||
|
var newid uuid.UUID
|
||||||
|
for {
|
||||||
|
md5hash.Sum(newid[:0])
|
||||||
|
if !bytes.Equal(newid.Bytes(), u.Bytes()) {
|
||||||
|
return &newid
|
||||||
|
}
|
||||||
|
md5hash.Write([]byte("533eff8a-4113-4b10-b5ce-0f5d76b98cd2"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newAlterIDs(primary *ID, alterIDCount uint16) []*ID {
|
||||||
|
alterIDs := make([]*ID, alterIDCount)
|
||||||
|
prevID := primary.UUID
|
||||||
|
for idx := range alterIDs {
|
||||||
|
newid := nextID(prevID)
|
||||||
|
alterIDs[idx] = &ID{UUID: newid, CmdKey: primary.CmdKey[:]}
|
||||||
|
prevID = newid
|
||||||
|
}
|
||||||
|
return alterIDs
|
||||||
|
}
|
106
common/vmess/vmess.go
Normal file
106
common/vmess/vmess.go
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
package vmess
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"math/rand"
|
||||||
|
"net"
|
||||||
|
"runtime"
|
||||||
|
|
||||||
|
"github.com/gofrs/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Version of vmess
|
||||||
|
const Version byte = 1
|
||||||
|
|
||||||
|
// Request Options
|
||||||
|
const (
|
||||||
|
OptionChunkStream byte = 1
|
||||||
|
OptionChunkMasking byte = 4
|
||||||
|
)
|
||||||
|
|
||||||
|
// Security type vmess
|
||||||
|
type Security = byte
|
||||||
|
|
||||||
|
// Cipher types
|
||||||
|
const (
|
||||||
|
SecurityAES128GCM Security = 3
|
||||||
|
SecurityCHACHA20POLY1305 Security = 4
|
||||||
|
SecurityNone Security = 5
|
||||||
|
)
|
||||||
|
|
||||||
|
// CipherMapping return
|
||||||
|
var CipherMapping = map[string]byte{
|
||||||
|
"none": SecurityNone,
|
||||||
|
"aes-128-gcm": SecurityAES128GCM,
|
||||||
|
"chacha20-poly1305": SecurityCHACHA20POLY1305,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Command types
|
||||||
|
const (
|
||||||
|
CommandTCP byte = 1
|
||||||
|
CommandUDP byte = 2
|
||||||
|
)
|
||||||
|
|
||||||
|
// Addr types
|
||||||
|
const (
|
||||||
|
AtypIPv4 byte = 1
|
||||||
|
AtypDomainName byte = 2
|
||||||
|
AtypIPv6 byte = 3
|
||||||
|
)
|
||||||
|
|
||||||
|
// DstAddr store destination address
|
||||||
|
type DstAddr struct {
|
||||||
|
AddrType byte
|
||||||
|
Addr []byte
|
||||||
|
Port uint
|
||||||
|
}
|
||||||
|
|
||||||
|
// Client is vmess connection generator
|
||||||
|
type Client struct {
|
||||||
|
user []*ID
|
||||||
|
uuid *uuid.UUID
|
||||||
|
security Security
|
||||||
|
}
|
||||||
|
|
||||||
|
// Config of vmess
|
||||||
|
type Config struct {
|
||||||
|
UUID string
|
||||||
|
AlterID uint16
|
||||||
|
Security string
|
||||||
|
}
|
||||||
|
|
||||||
|
// New return a Conn with net.Conn and DstAddr
|
||||||
|
func (c *Client) New(conn net.Conn, dst *DstAddr) net.Conn {
|
||||||
|
r := rand.Intn(len(c.user))
|
||||||
|
return newConn(conn, c.user[r], dst, c.security)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewClient return Client instance
|
||||||
|
func NewClient(config Config) (*Client, error) {
|
||||||
|
uid, err := uuid.FromString(config.UUID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var security Security
|
||||||
|
switch config.Security {
|
||||||
|
case "aes-128-gcm":
|
||||||
|
security = SecurityAES128GCM
|
||||||
|
case "chacha20-poly1305":
|
||||||
|
security = SecurityCHACHA20POLY1305
|
||||||
|
case "none":
|
||||||
|
security = SecurityNone
|
||||||
|
case "auto":
|
||||||
|
security = SecurityCHACHA20POLY1305
|
||||||
|
if runtime.GOARCH == "amd64" || runtime.GOARCH == "s390x" || runtime.GOARCH == "arm64" {
|
||||||
|
security = SecurityAES128GCM
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("Unknown security type: %s", config.Security)
|
||||||
|
}
|
||||||
|
return &Client{
|
||||||
|
user: newAlterIDs(newID(&uid), config.AlterID),
|
||||||
|
uuid: &uid,
|
||||||
|
security: security,
|
||||||
|
}, nil
|
||||||
|
}
|
@ -236,6 +236,21 @@ func (c *Config) parseProxies(cfg *ini.File) error {
|
|||||||
addr := fmt.Sprintf("%s:%s", proxy[1], proxy[2])
|
addr := fmt.Sprintf("%s:%s", proxy[1], proxy[2])
|
||||||
socks5 := adapters.NewSocks5(key.Name(), addr)
|
socks5 := adapters.NewSocks5(key.Name(), addr)
|
||||||
proxies[key.Name()] = socks5
|
proxies[key.Name()] = socks5
|
||||||
|
// vmess, server, port, uuid, alterId, security
|
||||||
|
case "vmess":
|
||||||
|
if len(proxy) < 6 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
addr := fmt.Sprintf("%s:%s", proxy[1], proxy[2])
|
||||||
|
alterID, err := strconv.Atoi(proxy[4])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
vmess, err := adapters.NewVmess(key.Name(), addr, proxy[3], uint16(alterID), proxy[5])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
proxies[key.Name()] = vmess
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -12,6 +12,7 @@ const (
|
|||||||
Shadowsocks
|
Shadowsocks
|
||||||
Socks5
|
Socks5
|
||||||
URLTest
|
URLTest
|
||||||
|
Vmess
|
||||||
)
|
)
|
||||||
|
|
||||||
type ProxyAdapter interface {
|
type ProxyAdapter interface {
|
||||||
@ -47,6 +48,8 @@ func (at AdapterType) String() string {
|
|||||||
return "Socks5"
|
return "Socks5"
|
||||||
case URLTest:
|
case URLTest:
|
||||||
return "URLTest"
|
return "URLTest"
|
||||||
|
case Vmess:
|
||||||
|
return "Vmess"
|
||||||
default:
|
default:
|
||||||
return "Unknow"
|
return "Unknow"
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user