diff --git a/README.md b/README.md index 7019b8d..6f2782e 100644 --- a/README.md +++ b/README.md @@ -52,6 +52,7 @@ General: TODO: - [ ] Transparent UDP proxy (iptables tproxy) - [ ] DNS Cache +- [ ] Performance tuning - [ ] TUN/TAP device support - [ ] IPv6 support - [ ] SSH tunnel support diff --git a/common/socks/socks.go b/common/socks/socks.go index 7532402..d5a5ec1 100644 --- a/common/socks/socks.go +++ b/common/socks/socks.go @@ -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) } diff --git a/main.go b/main.go index d315207..d3a5a0d 100644 --- a/main.go +++ b/main.go @@ -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 . diff --git a/proxy/redir/redir_linux.go b/proxy/redir/redir_linux.go index d1da12b..1d202fc 100644 --- a/proxy/redir/redir_linux.go +++ b/proxy/redir/redir_linux.go @@ -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] diff --git a/proxy/socks5/socks5.go b/proxy/socks5/socks5.go index 95b0836..1f97793 100644 --- a/proxy/socks5/socks5.go +++ b/proxy/socks5/socks5.go @@ -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()) diff --git a/proxy/v2ray/v2ray.go b/proxy/v2ray/v2ray.go deleted file mode 100644 index f0f36b1..0000000 --- a/proxy/v2ray/v2ray.go +++ /dev/null @@ -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") -} diff --git a/proxy/vmess/addr.go b/proxy/vmess/addr.go new file mode 100644 index 0000000..d89f963 --- /dev/null +++ b/proxy/vmess/addr.go @@ -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 +} diff --git a/proxy/vmess/client.go b/proxy/vmess/client.go new file mode 100644 index 0000000..b22a89a --- /dev/null +++ b/proxy/vmess/client.go @@ -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) +} diff --git a/proxy/vmess/user.go b/proxy/vmess/user.go new file mode 100644 index 0000000..4fb4c4e --- /dev/null +++ b/proxy/vmess/user.go @@ -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 +} diff --git a/proxy/vmess/vmess.go b/proxy/vmess/vmess.go index aeea8d2..0a78ddd 100644 --- a/proxy/vmess/vmess.go +++ b/proxy/vmess/vmess.go @@ -15,12 +15,9 @@ type VMess struct { dialer proxy.Dialer addr string - uuid string - alertID uint32 - - outboundSecurity string - streamProtocol string - streamSecurity string + uuid string + alertID uint32 + 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) @@ -50,13 +57,11 @@ func NewVMess(s string, dialer proxy.Dialer) (*VMess, error) { } p := &VMess{ - dialer: dialer, - addr: addr, - uuid: uuid, - alertID: uint32(alertID), - outboundSecurity: "auto", - streamProtocol: "tcp", - streamSecurity: "tls", + dialer: dialer, + addr: addr, + uuid: uuid, + alertID: uint32(alertID), + 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.