mirror of
https://github.com/nadoo/glider.git
synced 2025-02-23 17:35:40 +08:00
vmess: worked in SecTypeNone mode
This commit is contained in:
parent
4a72e79002
commit
385f5d7790
@ -52,6 +52,7 @@ General:
|
|||||||
TODO:
|
TODO:
|
||||||
- [ ] Transparent UDP proxy (iptables tproxy)
|
- [ ] Transparent UDP proxy (iptables tproxy)
|
||||||
- [ ] DNS Cache
|
- [ ] DNS Cache
|
||||||
|
- [ ] Performance tuning
|
||||||
- [ ] TUN/TAP device support
|
- [ ] TUN/TAP device support
|
||||||
- [ ] IPv6 support
|
- [ ] IPv6 support
|
||||||
- [ ] SSH tunnel support
|
- [ ] SSH tunnel support
|
||||||
|
@ -22,9 +22,9 @@ const (
|
|||||||
|
|
||||||
// SOCKS address types as defined in RFC 1928 section 5.
|
// SOCKS address types as defined in RFC 1928 section 5.
|
||||||
const (
|
const (
|
||||||
ATypeIP4 = 1
|
ATypIP4 = 1
|
||||||
ATypeDomain = 3
|
ATypDomain = 3
|
||||||
ATypeIP6 = 4
|
ATypIP6 = 4
|
||||||
)
|
)
|
||||||
|
|
||||||
// MaxAddrLen is the maximum size of SOCKS address in bytes.
|
// MaxAddrLen is the maximum size of SOCKS address in bytes.
|
||||||
@ -52,13 +52,13 @@ func (a Addr) String() string {
|
|||||||
var host, port string
|
var host, port string
|
||||||
|
|
||||||
switch ATYP(a[0]) { // address type
|
switch ATYP(a[0]) { // address type
|
||||||
case ATypeDomain:
|
case ATypDomain:
|
||||||
host = string(a[2 : 2+int(a[1])])
|
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]))
|
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()
|
host = net.IP(a[1 : 1+net.IPv4len]).String()
|
||||||
port = strconv.Itoa((int(a[1+net.IPv4len]) << 8) | int(a[1+net.IPv4len+1]))
|
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()
|
host = net.IP(a[1 : 1+net.IPv6len]).String()
|
||||||
port = strconv.Itoa((int(a[1+net.IPv6len]) << 8) | int(a[1+net.IPv6len+1]))
|
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)
|
return net.JoinHostPort(host, port)
|
||||||
}
|
}
|
||||||
|
|
||||||
// UoT udp over tcp
|
// UoT returns whether it is udp over tcp
|
||||||
func UoT(b byte) bool {
|
func UoT(b byte) bool {
|
||||||
return b&0x8 == 0x8
|
return b&0x8 == 0x8
|
||||||
}
|
}
|
||||||
|
|
||||||
// ATYP return the address type
|
// ATYP returns the address type
|
||||||
func ATYP(b byte) int {
|
func ATYP(b byte) int {
|
||||||
return int(b &^ 0x8)
|
return int(b &^ 0x8)
|
||||||
}
|
}
|
||||||
@ -87,17 +87,17 @@ func ReadAddrBuf(r io.Reader, b []byte) (Addr, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
switch ATYP(b[0]) {
|
switch ATYP(b[0]) {
|
||||||
case ATypeDomain:
|
case ATypDomain:
|
||||||
_, err = io.ReadFull(r, b[1:2]) // read 2nd byte for domain length
|
_, err = io.ReadFull(r, b[1:2]) // read 2nd byte for domain length
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
_, err = io.ReadFull(r, b[2:2+int(b[1])+2])
|
_, err = io.ReadFull(r, b[2:2+int(b[1])+2])
|
||||||
return b[:1+1+int(b[1])+2], err
|
return b[:1+1+int(b[1])+2], err
|
||||||
case ATypeIP4:
|
case ATypIP4:
|
||||||
_, err = io.ReadFull(r, b[1:1+net.IPv4len+2])
|
_, err = io.ReadFull(r, b[1:1+net.IPv4len+2])
|
||||||
return b[:1+net.IPv4len+2], err
|
return b[:1+net.IPv4len+2], err
|
||||||
case ATypeIP6:
|
case ATypIP6:
|
||||||
_, err = io.ReadFull(r, b[1:1+net.IPv6len+2])
|
_, err = io.ReadFull(r, b[1:1+net.IPv6len+2])
|
||||||
return b[:1+net.IPv6len+2], err
|
return b[:1+net.IPv6len+2], err
|
||||||
}
|
}
|
||||||
@ -118,14 +118,14 @@ func SplitAddr(b []byte) Addr {
|
|||||||
}
|
}
|
||||||
|
|
||||||
switch ATYP(b[0]) {
|
switch ATYP(b[0]) {
|
||||||
case ATypeDomain:
|
case ATypDomain:
|
||||||
if len(b) < 2 {
|
if len(b) < 2 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
addrLen = 1 + 1 + int(b[1]) + 2
|
addrLen = 1 + 1 + int(b[1]) + 2
|
||||||
case ATypeIP4:
|
case ATypIP4:
|
||||||
addrLen = 1 + net.IPv4len + 2
|
addrLen = 1 + net.IPv4len + 2
|
||||||
case ATypeIP6:
|
case ATypIP6:
|
||||||
addrLen = 1 + net.IPv6len + 2
|
addrLen = 1 + net.IPv6len + 2
|
||||||
default:
|
default:
|
||||||
return nil
|
return nil
|
||||||
@ -149,11 +149,11 @@ func ParseAddr(s string) Addr {
|
|||||||
if ip := net.ParseIP(host); ip != nil {
|
if ip := net.ParseIP(host); ip != nil {
|
||||||
if ip4 := ip.To4(); ip4 != nil {
|
if ip4 := ip.To4(); ip4 != nil {
|
||||||
addr = make([]byte, 1+net.IPv4len+2)
|
addr = make([]byte, 1+net.IPv4len+2)
|
||||||
addr[0] = ATypeIP4
|
addr[0] = ATypIP4
|
||||||
copy(addr[1:], ip4)
|
copy(addr[1:], ip4)
|
||||||
} else {
|
} else {
|
||||||
addr = make([]byte, 1+net.IPv6len+2)
|
addr = make([]byte, 1+net.IPv6len+2)
|
||||||
addr[0] = ATypeIP6
|
addr[0] = ATypIP6
|
||||||
copy(addr[1:], ip)
|
copy(addr[1:], ip)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -161,7 +161,7 @@ func ParseAddr(s string) Addr {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
addr = make([]byte, 1+1+len(host)+2)
|
addr = make([]byte, 1+1+len(host)+2)
|
||||||
addr[0] = ATypeDomain
|
addr[0] = ATypDomain
|
||||||
addr[1] = byte(len(host))
|
addr[1] = byte(len(host))
|
||||||
copy(addr[2:], host)
|
copy(addr[2:], host)
|
||||||
}
|
}
|
||||||
|
3
main.go
3
main.go
@ -21,8 +21,7 @@ import (
|
|||||||
_ "github.com/nadoo/glider/proxy/tls"
|
_ "github.com/nadoo/glider/proxy/tls"
|
||||||
_ "github.com/nadoo/glider/proxy/udptun"
|
_ "github.com/nadoo/glider/proxy/udptun"
|
||||||
_ "github.com/nadoo/glider/proxy/uottun"
|
_ "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 .
|
// VERSION .
|
||||||
|
@ -143,7 +143,7 @@ func getorigdst(fd uintptr) (socks.Addr, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
addr := make([]byte, 1+net.IPv4len+2)
|
addr := make([]byte, 1+net.IPv4len+2)
|
||||||
addr[0] = socks.ATypeIP4
|
addr[0] = socks.ATypIP4
|
||||||
copy(addr[1:1+net.IPv4len], raw.Addr[:])
|
copy(addr[1:1+net.IPv4len], raw.Addr[:])
|
||||||
port := (*[2]byte)(unsafe.Pointer(&raw.Port)) // big-endian
|
port := (*[2]byte)(unsafe.Pointer(&raw.Port)) // big-endian
|
||||||
addr[1+net.IPv4len], addr[1+net.IPv4len+1] = port[0], port[1]
|
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 := make([]byte, 1+net.IPv6len+2)
|
||||||
addr[0] = socks.ATypeIP6
|
addr[0] = socks.ATypIP6
|
||||||
copy(addr[1:1+net.IPv6len], raw.Addr[:])
|
copy(addr[1:1+net.IPv6len], raw.Addr[:])
|
||||||
port := (*[2]byte)(unsafe.Pointer(&raw.Port)) // big-endian
|
port := (*[2]byte)(unsafe.Pointer(&raw.Port)) // big-endian
|
||||||
addr[1+net.IPv6len], addr[1+net.IPv6len+1] = port[0], port[1]
|
addr[1+net.IPv6len], addr[1+net.IPv6len+1] = port[0], port[1]
|
||||||
|
@ -362,17 +362,17 @@ func (s *SOCKS5) connect(conn net.Conn, target string) error {
|
|||||||
|
|
||||||
if ip := net.ParseIP(host); ip != nil {
|
if ip := net.ParseIP(host); ip != nil {
|
||||||
if ip4 := ip.To4(); ip4 != nil {
|
if ip4 := ip.To4(); ip4 != nil {
|
||||||
buf = append(buf, socks.ATypeIP4)
|
buf = append(buf, socks.ATypIP4)
|
||||||
ip = ip4
|
ip = ip4
|
||||||
} else {
|
} else {
|
||||||
buf = append(buf, socks.ATypeIP6)
|
buf = append(buf, socks.ATypIP6)
|
||||||
}
|
}
|
||||||
buf = append(buf, ip...)
|
buf = append(buf, ip...)
|
||||||
} else {
|
} else {
|
||||||
if len(host) > 255 {
|
if len(host) > 255 {
|
||||||
return errors.New("proxy: destination hostname too long: " + host)
|
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, byte(len(host)))
|
||||||
buf = append(buf, host...)
|
buf = append(buf, host...)
|
||||||
}
|
}
|
||||||
@ -397,11 +397,11 @@ func (s *SOCKS5) connect(conn net.Conn, target string) error {
|
|||||||
|
|
||||||
bytesToDiscard := 0
|
bytesToDiscard := 0
|
||||||
switch buf[3] {
|
switch buf[3] {
|
||||||
case socks.ATypeIP4:
|
case socks.ATypIP4:
|
||||||
bytesToDiscard = net.IPv4len
|
bytesToDiscard = net.IPv4len
|
||||||
case socks.ATypeIP6:
|
case socks.ATypIP6:
|
||||||
bytesToDiscard = net.IPv6len
|
bytesToDiscard = net.IPv6len
|
||||||
case socks.ATypeDomain:
|
case socks.ATypDomain:
|
||||||
_, err := io.ReadFull(conn, buf[:1])
|
_, err := io.ReadFull(conn, buf[:1])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.New("proxy: failed to read domain length from SOCKS5 proxy at " + s.addr + ": " + err.Error())
|
return errors.New("proxy: failed to read domain length from SOCKS5 proxy at " + s.addr + ": " + err.Error())
|
||||||
|
@ -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
61
proxy/vmess/addr.go
Normal 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
214
proxy/vmess/client.go
Normal 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:
|
||||||
|
// Key:MD5(UUID + []byte('c48619fe-8f02-49e0-b9e9-edf763e17e21'))
|
||||||
|
// IV:MD5(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
37
proxy/vmess/user.go
Normal 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
|
||||||
|
}
|
@ -15,12 +15,9 @@ type VMess struct {
|
|||||||
dialer proxy.Dialer
|
dialer proxy.Dialer
|
||||||
addr string
|
addr string
|
||||||
|
|
||||||
uuid string
|
uuid string
|
||||||
alertID uint32
|
alertID uint32
|
||||||
|
security string
|
||||||
outboundSecurity string
|
|
||||||
streamProtocol string
|
|
||||||
streamSecurity string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@ -37,10 +34,20 @@ func NewVMess(s string, dialer proxy.Dialer) (*VMess, error) {
|
|||||||
|
|
||||||
addr := u.Host
|
addr := u.Host
|
||||||
|
|
||||||
var uuid, aid string
|
var uuid, security string
|
||||||
if u.User != nil {
|
if u.User != nil {
|
||||||
uuid = u.User.Username()
|
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)
|
alertID, err := strconv.ParseUint(aid, 10, 32)
|
||||||
@ -50,13 +57,11 @@ func NewVMess(s string, dialer proxy.Dialer) (*VMess, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
p := &VMess{
|
p := &VMess{
|
||||||
dialer: dialer,
|
dialer: dialer,
|
||||||
addr: addr,
|
addr: addr,
|
||||||
uuid: uuid,
|
uuid: uuid,
|
||||||
alertID: uint32(alertID),
|
alertID: uint32(alertID),
|
||||||
outboundSecurity: "auto",
|
security: security,
|
||||||
streamProtocol: "tcp",
|
|
||||||
streamSecurity: "tls",
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return p, nil
|
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.
|
// Dial connects to the address addr on the network net via the proxy.
|
||||||
func (s *VMess) Dial(network, addr string) (net.Conn, error) {
|
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.
|
// DialUDP connects to the given address via the proxy.
|
||||||
|
Loading…
Reference in New Issue
Block a user