vmess: worked in SecTypeNone mode

This commit is contained in:
nadoo 2018-07-03 00:31:43 +08:00
parent 4a72e79002
commit 385f5d7790
10 changed files with 372 additions and 228 deletions

View File

@ -52,6 +52,7 @@ General:
TODO:
- [ ] Transparent UDP proxy (iptables tproxy)
- [ ] DNS Cache
- [ ] Performance tuning
- [ ] TUN/TAP device support
- [ ] IPv6 support
- [ ] SSH tunnel support

View File

@ -22,9 +22,9 @@ const (
// SOCKS address types as defined in RFC 1928 section 5.
const (
ATypeIP4 = 1
ATypeDomain = 3
ATypeIP6 = 4
ATypIP4 = 1
ATypDomain = 3
ATypIP6 = 4
)
// MaxAddrLen is the maximum size of SOCKS address in bytes.
@ -52,13 +52,13 @@ func (a Addr) String() string {
var host, port string
switch ATYP(a[0]) { // address type
case ATypeDomain:
case ATypDomain:
host = string(a[2 : 2+int(a[1])])
port = strconv.Itoa((int(a[2+int(a[1])]) << 8) | int(a[2+int(a[1])+1]))
case ATypeIP4:
case ATypIP4:
host = net.IP(a[1 : 1+net.IPv4len]).String()
port = strconv.Itoa((int(a[1+net.IPv4len]) << 8) | int(a[1+net.IPv4len+1]))
case ATypeIP6:
case ATypIP6:
host = net.IP(a[1 : 1+net.IPv6len]).String()
port = strconv.Itoa((int(a[1+net.IPv6len]) << 8) | int(a[1+net.IPv6len+1]))
}
@ -66,12 +66,12 @@ func (a Addr) String() string {
return net.JoinHostPort(host, port)
}
// UoT udp over tcp
// UoT returns whether it is udp over tcp
func UoT(b byte) bool {
return b&0x8 == 0x8
}
// ATYP return the address type
// ATYP returns the address type
func ATYP(b byte) int {
return int(b &^ 0x8)
}
@ -87,17 +87,17 @@ func ReadAddrBuf(r io.Reader, b []byte) (Addr, error) {
}
switch ATYP(b[0]) {
case ATypeDomain:
case ATypDomain:
_, err = io.ReadFull(r, b[1:2]) // read 2nd byte for domain length
if err != nil {
return nil, err
}
_, err = io.ReadFull(r, b[2:2+int(b[1])+2])
return b[:1+1+int(b[1])+2], err
case ATypeIP4:
case ATypIP4:
_, err = io.ReadFull(r, b[1:1+net.IPv4len+2])
return b[:1+net.IPv4len+2], err
case ATypeIP6:
case ATypIP6:
_, err = io.ReadFull(r, b[1:1+net.IPv6len+2])
return b[:1+net.IPv6len+2], err
}
@ -118,14 +118,14 @@ func SplitAddr(b []byte) Addr {
}
switch ATYP(b[0]) {
case ATypeDomain:
case ATypDomain:
if len(b) < 2 {
return nil
}
addrLen = 1 + 1 + int(b[1]) + 2
case ATypeIP4:
case ATypIP4:
addrLen = 1 + net.IPv4len + 2
case ATypeIP6:
case ATypIP6:
addrLen = 1 + net.IPv6len + 2
default:
return nil
@ -149,11 +149,11 @@ func ParseAddr(s string) Addr {
if ip := net.ParseIP(host); ip != nil {
if ip4 := ip.To4(); ip4 != nil {
addr = make([]byte, 1+net.IPv4len+2)
addr[0] = ATypeIP4
addr[0] = ATypIP4
copy(addr[1:], ip4)
} else {
addr = make([]byte, 1+net.IPv6len+2)
addr[0] = ATypeIP6
addr[0] = ATypIP6
copy(addr[1:], ip)
}
} else {
@ -161,7 +161,7 @@ func ParseAddr(s string) Addr {
return nil
}
addr = make([]byte, 1+1+len(host)+2)
addr[0] = ATypeDomain
addr[0] = ATypDomain
addr[1] = byte(len(host))
copy(addr[2:], host)
}

View File

@ -21,8 +21,7 @@ import (
_ "github.com/nadoo/glider/proxy/tls"
_ "github.com/nadoo/glider/proxy/udptun"
_ "github.com/nadoo/glider/proxy/uottun"
// _ "github.com/nadoo/glider/proxy/v2ray"
// _ "github.com/nadoo/glider/proxy/vmess"
_ "github.com/nadoo/glider/proxy/vmess"
)
// VERSION .

View File

@ -143,7 +143,7 @@ func getorigdst(fd uintptr) (socks.Addr, error) {
}
addr := make([]byte, 1+net.IPv4len+2)
addr[0] = socks.ATypeIP4
addr[0] = socks.ATypIP4
copy(addr[1:1+net.IPv4len], raw.Addr[:])
port := (*[2]byte)(unsafe.Pointer(&raw.Port)) // big-endian
addr[1+net.IPv4len], addr[1+net.IPv4len+1] = port[0], port[1]
@ -160,7 +160,7 @@ func getorigdstIPv6(fd uintptr) (socks.Addr, error) {
}
addr := make([]byte, 1+net.IPv6len+2)
addr[0] = socks.ATypeIP6
addr[0] = socks.ATypIP6
copy(addr[1:1+net.IPv6len], raw.Addr[:])
port := (*[2]byte)(unsafe.Pointer(&raw.Port)) // big-endian
addr[1+net.IPv6len], addr[1+net.IPv6len+1] = port[0], port[1]

View File

@ -362,17 +362,17 @@ func (s *SOCKS5) connect(conn net.Conn, target string) error {
if ip := net.ParseIP(host); ip != nil {
if ip4 := ip.To4(); ip4 != nil {
buf = append(buf, socks.ATypeIP4)
buf = append(buf, socks.ATypIP4)
ip = ip4
} else {
buf = append(buf, socks.ATypeIP6)
buf = append(buf, socks.ATypIP6)
}
buf = append(buf, ip...)
} else {
if len(host) > 255 {
return errors.New("proxy: destination hostname too long: " + host)
}
buf = append(buf, socks.ATypeDomain)
buf = append(buf, socks.ATypDomain)
buf = append(buf, byte(len(host)))
buf = append(buf, host...)
}
@ -397,11 +397,11 @@ func (s *SOCKS5) connect(conn net.Conn, target string) error {
bytesToDiscard := 0
switch buf[3] {
case socks.ATypeIP4:
case socks.ATypIP4:
bytesToDiscard = net.IPv4len
case socks.ATypeIP6:
case socks.ATypIP6:
bytesToDiscard = net.IPv6len
case socks.ATypeDomain:
case socks.ATypDomain:
_, err := io.ReadFull(conn, buf[:1])
if err != nil {
return errors.New("proxy: failed to read domain length from SOCKS5 proxy at " + s.addr + ": " + err.Error())

View File

@ -1,185 +0,0 @@
package v2ray
import (
"context"
"errors"
"net"
"net/url"
"strconv"
"strings"
"github.com/nadoo/glider/common/log"
"github.com/nadoo/glider/proxy"
"v2ray.com/core"
"v2ray.com/core/app/dispatcher"
"v2ray.com/core/app/proxyman"
v2net "v2ray.com/core/common/net"
"v2ray.com/core/common/protocol"
"v2ray.com/core/common/serial"
"v2ray.com/core/proxy/vmess"
"v2ray.com/core/proxy/vmess/outbound"
"v2ray.com/core/transport/internet"
"v2ray.com/core/transport/internet/tls"
// needed
_ "v2ray.com/core/app/proxyman/outbound"
_ "v2ray.com/core/transport/internet/tcp"
)
// V2Ray .
type V2Ray struct {
dialer proxy.Dialer
addr string
uuid string
alertID uint32
outboundSecurity string
streamProtocol string
streamSecurity string
config *core.Config
instance *core.Instance
}
func init() {
proxy.RegisterDialer("v2ray", NewV2RayDialer)
}
// NewV2Ray returns a v2ray proxy.
func NewV2Ray(s string, dialer proxy.Dialer) (*V2Ray, error) {
u, err := url.Parse(s)
if err != nil {
log.F("parse url err: %s", err)
return nil, err
}
addr := u.Host
host := u.Hostname()
port, err := strconv.ParseUint(u.Port(), 10, 32)
if err != nil {
log.F("parse port err: %s", err)
return nil, err
}
var uuid, aid string
if u.User != nil {
uuid = u.User.Username()
aid, _ = u.User.Password()
}
alertID, err := strconv.ParseUint(aid, 10, 32)
if err != nil {
log.F("parse alertID err: %s", err)
return nil, err
}
config := &core.Config{
App: []*serial.TypedMessage{
serial.ToTypedMessage(&dispatcher.Config{}),
serial.ToTypedMessage(&proxyman.OutboundConfig{}),
},
Outbound: []*core.OutboundHandlerConfig{{
ProxySettings: serial.ToTypedMessage(&outbound.Config{
Receiver: []*protocol.ServerEndpoint{
{
Address: v2net.NewIPOrDomain(v2net.ParseAddress(host)),
Port: uint32(port),
User: []*protocol.User{
{
Account: serial.ToTypedMessage(&vmess.Account{
Id: uuid,
AlterId: uint32(alertID),
SecuritySettings: &protocol.SecurityConfig{
Type: protocol.SecurityType_NONE,
},
}),
},
},
},
},
}),
SenderSettings: serial.ToTypedMessage(&proxyman.SenderConfig{
StreamSettings: &internet.StreamConfig{
Protocol: internet.TransportProtocol_TCP,
SecurityType: serial.GetMessageType(&tls.Config{}),
SecuritySettings: []*serial.TypedMessage{
serial.ToTypedMessage(&tls.Config{
AllowInsecure: true,
}),
},
},
})},
},
}
v, err := core.New(config)
if err != nil {
log.Fatal("Failed to create V: ", err.Error())
}
p := &V2Ray{
dialer: dialer,
addr: addr,
uuid: uuid,
alertID: uint32(alertID),
outboundSecurity: "auto",
streamProtocol: "tcp",
streamSecurity: "tls",
config: config,
instance: v,
}
return p, nil
}
// NewV2RayDialer returns a v2ray proxy dialer.
func NewV2RayDialer(s string, dialer proxy.Dialer) (proxy.Dialer, error) {
return NewV2Ray(s, dialer)
}
// Addr returns forwarder's address
func (s *V2Ray) Addr() string { return s.addr }
// NextDialer returns the next dialer
func (s *V2Ray) NextDialer(dstAddr string) proxy.Dialer { return s.dialer.NextDialer(dstAddr) }
// Dial connects to the address addr on the network net via the proxy.
func (s *V2Ray) Dial(network, addr string) (net.Conn, error) {
// c, err := s.dialer.Dial("tcp", s.addr)
d := strings.Split(addr, ":")
host, portStr := d[0], d[1]
port, err := strconv.ParseUint(portStr, 10, 32)
if err != nil {
log.F("parse portStr err: %s", err)
return nil, err
}
// TODO: does not support upstream dialer now
c, err := core.Dial(context.Background(),
s.instance,
v2net.TCPDestination(v2net.ParseAddress(host), v2net.Port(port)))
if err != nil {
log.F("[v2ray] dial to %s error: %s", s.addr, err)
return nil, err
}
if c, ok := c.(*net.TCPConn); ok {
c.SetKeepAlive(true)
}
return c, err
}
// DialUDP connects to the given address via the proxy.
func (s *V2Ray) DialUDP(network, addr string) (net.PacketConn, net.Addr, error) {
return nil, nil, errors.New("v2ray client does not support udp now")
}

61
proxy/vmess/addr.go Normal file
View File

@ -0,0 +1,61 @@
package vmess
import (
"net"
"strconv"
)
// AType is vmess addr type
type AType byte
// Atyp
const (
ATypeErr AType = 0
ATypeIP4 AType = 1
ATypeDomain AType = 2
ATypeIP6 AType = 3
)
// Addr is vmess addr
type Addr []byte
// Port is vmess addr port
type Port uint16
// ParseAddr parses the address in string s. return AType = 0 if error.
func ParseAddr(s string) (AType, Addr, Port, error) {
var atype AType
var addr Addr
host, port, err := net.SplitHostPort(s)
if err != nil {
return 0, nil, 0, err
}
if ip := net.ParseIP(host); ip != nil {
if ip4 := ip.To4(); ip4 != nil {
addr = make([]byte, net.IPv4len)
atype = ATypeIP4
copy(addr[:], ip4)
} else {
addr = make([]byte, net.IPv6len)
atype = ATypeIP6
copy(addr[:], ip)
}
} else {
if len(host) > 255 {
return 0, nil, 0, err
}
addr = make([]byte, 1+len(host))
atype = ATypeDomain
addr[0] = byte(len(host))
copy(addr[1:], host)
}
portnum, err := strconv.ParseUint(port, 10, 16)
if err != nil {
return 0, nil, 0, err
}
return atype, addr, Port(portnum), err
}

214
proxy/vmess/client.go Normal file
View File

@ -0,0 +1,214 @@
package vmess
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"crypto/hmac"
"crypto/md5"
"encoding/binary"
"errors"
"hash/fnv"
"io"
"math/rand"
"net"
"time"
)
// Request Options
const (
OptChunkStream byte = 1
OptReuseTCPConnection byte = 2
OptMetadataObfuscate byte = 4
)
// SEC types
const (
SecTypeUnknown byte = 0
SecTypeLegacy byte = 1
SecTypeAuto byte = 2
SecTypeAES128GCM byte = 3
SecTypeChacha20Poly1305 byte = 4
SecTypeNone byte = 5
)
// CMD types
const (
CmdTCP byte = 1
CmdUDP byte = 2
)
// Client vmess client
type Client struct {
user *User
atype AType
addr Addr
port Port
reqBodyIV [16]byte
reqBodyKey [16]byte
reqRespV byte
respBodyKey [16]byte
respBodyIV [16]byte
net.Conn
connected bool
}
// NewClient .
func NewClient(uuid, target string) (*Client, error) {
user, err := NewUser(uuid)
if err != nil {
return nil, err
}
c := &Client{user: user}
c.atype, c.addr, c.port, err = ParseAddr(target)
if err != nil {
return nil, err
}
randBytes := make([]byte, 33)
rand.Read(randBytes)
copy(c.reqBodyIV[:], randBytes[:16])
copy(c.reqBodyKey[:], randBytes[16:32])
c.reqRespV = randBytes[32]
c.respBodyIV = md5.Sum(c.reqBodyIV[:])
c.respBodyKey = md5.Sum(c.reqBodyKey[:])
return c, nil
}
// EncodeAuthInfo returns HMAC("md5", UUID, UTC) result
func (c *Client) EncodeAuthInfo() []byte {
ts := make([]byte, 8)
binary.BigEndian.PutUint64(ts, uint64(time.Now().UTC().Unix()))
h := hmac.New(md5.New, c.user.UUID[:])
h.Write(ts)
return h.Sum(nil)
}
// EncodeRequest encodes requests to network bytes
func (c *Client) EncodeRequest() ([]byte, error) {
buf := new(bytes.Buffer)
// Request
buf.WriteByte(1) // Ver
buf.Write(c.reqBodyIV[:]) // IV
buf.Write(c.reqBodyKey[:]) // Key
buf.WriteByte(c.reqRespV) // V
buf.WriteByte(0) // Opt
// pLen and Sec
paddingLen := rand.Intn(16)
pSec := byte(paddingLen<<4) | SecTypeNone // P(4bit) and Sec(4bit)
buf.WriteByte(pSec)
buf.WriteByte(0) // reserved
buf.WriteByte(CmdTCP) // cmd
// target
binary.Write(buf, binary.BigEndian, uint16(c.port)) // port
buf.WriteByte(byte(c.atype)) // atype
buf.Write(c.addr) // addr
// padding
if paddingLen > 0 {
padding := make([]byte, paddingLen)
rand.Read(padding)
buf.Write(padding)
}
// F
fnv1a := fnv.New32a()
fnv1a.Write(buf.Bytes())
buf.Write(fnv1a.Sum(nil))
// AES-128-CFB crypt the request
// KeyMD5(UUID + []byte('c48619fe-8f02-49e0-b9e9-edf763e17e21'))
// IVMD5(X + X + X + X)X = []byte(timestamp.now) (8 bytes, Big Endian)
md5hash := md5.New()
md5hash.Write(c.user.UUID[:])
md5hash.Write([]byte("c48619fe-8f02-49e0-b9e9-edf763e17e21"))
key := md5hash.Sum(nil)
md5hash.Reset()
ts := make([]byte, 8)
binary.BigEndian.PutUint64(ts, uint64(time.Now().UTC().Unix()))
md5hash.Write(ts)
md5hash.Write(ts)
md5hash.Write(ts)
md5hash.Write(ts)
iv := md5hash.Sum(nil)
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
stream := cipher.NewCFBEncrypter(block, iv)
stream.XORKeyStream(buf.Bytes(), buf.Bytes())
return buf.Bytes(), nil
}
// DecodeRespHeader .
func (c *Client) DecodeRespHeader() error {
block, err := aes.NewCipher(c.respBodyKey[:])
if err != nil {
return err
}
stream := cipher.NewCFBDecrypter(block, c.respBodyIV[:])
buf := make([]byte, 4)
io.ReadFull(c.Conn, buf)
stream.XORKeyStream(buf, buf)
if buf[0] != c.reqRespV {
return errors.New("unexpected response header")
}
// TODO: Dynamic port supported
// if buf[2] != 0 {
// cmd := buf[2]
// dataLen := int32(buf[3])
// }
c.connected = true
return nil
}
// NewConn wraps a net.Conn to VMessConn
func (c *Client) NewConn(rc net.Conn) (net.Conn, error) {
// AuthInfo
rc.Write(c.EncodeAuthInfo())
// Request
req, err := c.EncodeRequest()
if err != nil {
return nil, err
}
rc.Write(req)
c.Conn = rc
return c, err
}
func (c *Client) Read(b []byte) (n int, err error) {
if !c.connected {
c.DecodeRespHeader()
}
return c.Conn.Read(b)
}
func (c *Client) Write(b []byte) (n int, err error) {
return c.Conn.Write(b)
}

37
proxy/vmess/user.go Normal file
View File

@ -0,0 +1,37 @@
package vmess
import (
"encoding/hex"
"errors"
"strings"
)
// User of vmess client
type User struct {
UUID [16]byte
}
// NewUser .
func NewUser(uuidStr string) (*User, error) {
uuid, err := StrToUUID(uuidStr)
if err != nil {
return nil, err
}
u := &User{
UUID: uuid,
}
return u, nil
}
// StrToUUID converts string to uuid.
// s fomat: "6ba7b810-9dad-11d1-80b4-00c04fd430c8"
func StrToUUID(s string) (uuid [16]byte, err error) {
b := []byte(strings.Replace(s, "-", "", -1))
if len(b) != 32 {
return uuid, errors.New("invalid UUID: " + s)
}
_, err = hex.Decode(uuid[:], b)
return
}

View File

@ -17,10 +17,7 @@ type VMess struct {
uuid string
alertID uint32
outboundSecurity string
streamProtocol string
streamSecurity string
security string
}
func init() {
@ -37,10 +34,20 @@ func NewVMess(s string, dialer proxy.Dialer) (*VMess, error) {
addr := u.Host
var uuid, aid string
var uuid, security string
if u.User != nil {
uuid = u.User.Username()
aid, _ = u.User.Password()
security, _ = u.User.Password()
}
if security == "" {
security = "NONE"
}
aid := "0"
params, _ := url.ParseQuery(u.RawQuery)
if v, ok := params["alertId"]; ok {
aid = v[0]
}
alertID, err := strconv.ParseUint(aid, 10, 32)
@ -54,9 +61,7 @@ func NewVMess(s string, dialer proxy.Dialer) (*VMess, error) {
addr: addr,
uuid: uuid,
alertID: uint32(alertID),
outboundSecurity: "auto",
streamProtocol: "tcp",
streamSecurity: "tls",
security: security,
}
return p, nil
@ -75,7 +80,19 @@ func (s *VMess) NextDialer(dstAddr string) proxy.Dialer { return s.dialer.NextDi
// Dial connects to the address addr on the network net via the proxy.
func (s *VMess) Dial(network, addr string) (net.Conn, error) {
return nil, nil
rc, err := s.dialer.Dial("tcp", s.addr)
if err != nil {
return nil, err
}
client, err := NewClient(s.uuid, addr)
if err != nil {
return nil, err
}
rc, err = client.NewConn(rc)
return rc, nil
}
// DialUDP connects to the given address via the proxy.