diff --git a/README.md b/README.md index 8ac9181..f0f1c43 100644 --- a/README.md +++ b/README.md @@ -37,7 +37,7 @@ we can set up local listeners as proxy servers, and forward requests to internet - Periodical availability checking for forwarders - Send requests from specific local ip/interface - Services: - - dhcpd: a simple dhcp server + - dhcpd: a simple dhcp server that can detect existing dhcp server and avoid conflicts ## Protocols
@@ -54,6 +54,7 @@ we can set up local listeners as proxy servers, and forward requests to internet |ssh | | |√| |client only |trojan | | |√|√|client only |vmess | | |√| |client only +|vless | | |√| |client only |redir |√| | | |linux only |redir6 |√| | | |linux only(ipv6) |tls |√| |√| |transport client & server @@ -145,7 +146,7 @@ glider -h Available schemes: listen: mixed ss socks5 http redir redir6 tcptun udptun uottun tls unix kcp - forward: reject ss socks4 socks5 http ssr ssh vmess trojan tls ws unix kcp simple-obfs + forward: reject ss socks4 socks5 http ssr ssh vmess vless trojan tls ws unix kcp simple-obfs SS scheme: ss://method:pass@host:port @@ -168,6 +169,9 @@ SSH scheme: VMess scheme: vmess://[security:]uuid@host:port?alterID=num +VLESS scheme: + vless://uuid@host:port + Trojan scheme: trojan://pass@host:port[?skipVerify=true] @@ -361,9 +365,11 @@ glider -config CONFIGPATH -listen :8080 -verbose
-## Builtin Service +## Service + +Scheme: +service=SERVICE_NAME[,SERVICE_CONFIG] -scheme: service=SERVICE_NAME[,SERVICE_CONFIG] - dhcpd(from v0.11.0): - service=dhcpd,INTERFACE,START_IP,END_IP - e.g., service=dhcpd,en0,192.168.254.100,192.168.254.199 diff --git a/config.go b/config.go index c4bb092..45fda0a 100644 --- a/config.go +++ b/config.go @@ -127,7 +127,7 @@ func usage() { fmt.Fprintf(w, "Available schemes:\n") fmt.Fprintf(w, " listen: mixed ss socks5 http redir redir6 tcptun udptun uottun tls unix kcp\n") - fmt.Fprintf(w, " forward: reject ss socks4 socks5 http ssr ssh vmess trojan tls ws unix kcp simple-obfs\n") + fmt.Fprintf(w, " forward: reject ss socks4 socks5 http ssr ssh vmess vless trojan tls ws unix kcp simple-obfs\n") fmt.Fprintf(w, "\n") fmt.Fprintf(w, "SS scheme:\n") @@ -156,6 +156,10 @@ func usage() { fmt.Fprintf(w, " vmess://[security:]uuid@host:port?alterID=num\n") fmt.Fprintf(w, "\n") + fmt.Fprintf(w, "VLESS scheme:\n") + fmt.Fprintf(w, " vless://uuid@host:port\n") + fmt.Fprintf(w, "\n") + fmt.Fprintf(w, "Trojan scheme:\n") fmt.Fprintf(w, " trojan://pass@host:port[?skipVerify=true]\n") fmt.Fprintf(w, "\n") diff --git a/config/glider.conf.example b/config/glider.conf.example index c2a7032..16e7de0 100644 --- a/config/glider.conf.example +++ b/config/glider.conf.example @@ -105,6 +105,9 @@ listen=socks5://:1080 # trojan as forwarder # forward=trojan://PASSWORD@1.1.1.1:8080[?skipVerify=true] +# vless forwarder +# forward=vless://5a146038-0b56-4e95-b1dc-5c6f5a32cd98@1.1.1.1:443 + # vmess with none security # forward=vmess://5a146038-0b56-4e95-b1dc-5c6f5a32cd98@1.1.1.1:443?alterID=2 diff --git a/feature.go b/feature.go index 99e0c7c..c14fda5 100644 --- a/feature.go +++ b/feature.go @@ -20,6 +20,7 @@ import ( _ "github.com/nadoo/glider/proxy/trojan" _ "github.com/nadoo/glider/proxy/udptun" _ "github.com/nadoo/glider/proxy/uottun" + _ "github.com/nadoo/glider/proxy/vless" _ "github.com/nadoo/glider/proxy/vmess" _ "github.com/nadoo/glider/proxy/ws" ) diff --git a/proxy/vless/addr.go b/proxy/vless/addr.go new file mode 100644 index 0000000..5ecd683 --- /dev/null +++ b/proxy/vless/addr.go @@ -0,0 +1,61 @@ +package vless + +import ( + "net" + "strconv" +) + +// Atyp is vless addr type. +type Atyp byte + +// Atyp +const ( + AtypErr Atyp = 0 + AtypIP4 Atyp = 1 + AtypDomain Atyp = 2 + AtypIP6 Atyp = 3 +) + +// Addr is vless addr. +type Addr []byte + +// Port is vless addr port. +type Port uint16 + +// ParseAddr parses the address in string s. +func ParseAddr(s string) (Atyp, Addr, Port, error) { + var atyp Atyp + 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) + atyp = AtypIP4 + copy(addr[:], ip4) + } else { + addr = make([]byte, net.IPv6len) + atyp = AtypIP6 + copy(addr[:], ip) + } + } else { + if len(host) > 255 { + return 0, nil, 0, err + } + addr = make([]byte, 1+len(host)) + atyp = AtypDomain + 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 atyp, addr, Port(portnum), err +} diff --git a/proxy/vless/client.go b/proxy/vless/client.go new file mode 100644 index 0000000..c1488df --- /dev/null +++ b/proxy/vless/client.go @@ -0,0 +1,88 @@ +package vless + +import ( + "encoding/binary" + "encoding/hex" + "errors" + "io" + "io/ioutil" + "net" + "strings" + + "github.com/nadoo/glider/common/pool" +) + +const Version byte = 0 + +// CMD types. +const ( + CmdTCP byte = 1 + CmdUDP byte = 2 +) + +// Conn is a vless client connection. +type Conn struct { + net.Conn + rcved bool +} + +// NewConn returns a new vless client conn. +func NewConn(c net.Conn, uuid [16]byte, target string) (*Conn, error) { + atyp, addr, port, err := ParseAddr(target) + if err != nil { + return nil, err + } + + buf := pool.GetWriteBuffer() + defer pool.PutWriteBuffer(buf) + + buf.WriteByte(Version) // ver + buf.Write(uuid[:]) // uuid + buf.WriteByte(0) // addinfo + buf.WriteByte(CmdTCP) // cmd + + // target + err = binary.Write(buf, binary.BigEndian, uint16(port)) // port + if err != nil { + return nil, err + } + buf.WriteByte(byte(atyp)) // atyp + buf.Write(addr) //addr + + _, err = c.Write(buf.Bytes()) + return &Conn{Conn: c}, err +} + +func (c *Conn) Read(b []byte) (n int, err error) { + if !c.rcved { + buf := pool.GetBuffer(2) + defer pool.PutBuffer(buf) + + n, err = io.ReadFull(c.Conn, buf) + if err != nil { + return + } + + if buf[0] != Version { + return n, errors.New("version not supported") + } + + if addLen := int64(buf[1]); addLen != 0 { + io.CopyN(ioutil.Discard, c.Conn, addLen) + } + c.rcved = true + } + + return c.Conn.Read(b) +} + +// 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/vless/vless.go b/proxy/vless/vless.go new file mode 100644 index 0000000..972eb95 --- /dev/null +++ b/proxy/vless/vless.go @@ -0,0 +1,69 @@ +package vless + +import ( + "errors" + "net" + "net/url" + + "github.com/nadoo/glider/proxy" +) + +// VLess struct. +type VLess struct { + dialer proxy.Dialer + addr string + uuid [16]byte +} + +func init() { + proxy.RegisterDialer("vless", NewVLessDialer) +} + +// NewVLess returns a vless proxy. +func NewVLess(s string, d proxy.Dialer) (*VLess, error) { + u, err := url.Parse(s) + if err != nil { + return nil, err + } + + addr := u.Host + uuid, err := StrToUUID(u.User.Username()) + if err != nil { + return nil, err + } + + p := &VLess{ + dialer: d, + addr: addr, + uuid: uuid, + } + + return p, nil +} + +// NewVLessDialer returns a vless proxy dialer. +func NewVLessDialer(s string, dialer proxy.Dialer) (proxy.Dialer, error) { + return NewVLess(s, dialer) +} + +// Addr returns forwarder's address. +func (s *VLess) Addr() string { + if s.addr == "" { + return s.dialer.Addr() + } + return s.addr +} + +// Dial connects to the address addr on the network net via the proxy. +func (s *VLess) Dial(network, addr string) (net.Conn, error) { + rc, err := s.dialer.Dial("tcp", s.addr) + if err != nil { + return nil, err + } + return NewConn(rc, s.uuid, addr) +} + +// DialUDP connects to the given address via the proxy. +func (s *VLess) DialUDP(network, addr string) (net.PacketConn, net.Addr, error) { + return nil, nil, errors.New("vless client does not support udp now") +}