Feature: socks5 udp associate

This commit is contained in:
Dreamacro 2019-04-23 23:29:36 +08:00
parent 49f8902961
commit c92cda6980
25 changed files with 339 additions and 85 deletions

View File

@ -122,7 +122,7 @@ Proxy:
# The types of cipher are consistent with go-shadowsocks2 # The types of cipher are consistent with go-shadowsocks2
# support AEAD_AES_128_GCM AEAD_AES_192_GCM AEAD_AES_256_GCM AEAD_CHACHA20_POLY1305 AES-128-CTR AES-192-CTR AES-256-CTR AES-128-CFB AES-192-CFB AES-256-CFB CHACHA20-IETF XCHACHA20 # support AEAD_AES_128_GCM AEAD_AES_192_GCM AEAD_AES_256_GCM AEAD_CHACHA20_POLY1305 AES-128-CTR AES-192-CTR AES-256-CTR AES-128-CFB AES-192-CFB AES-256-CFB CHACHA20-IETF XCHACHA20
# In addition to what go-shadowsocks2 supports, it also supports chacha20 rc4-md5 xchacha20-ietf-poly1305 # In addition to what go-shadowsocks2 supports, it also supports chacha20 rc4-md5 xchacha20-ietf-poly1305
- { name: "ss1", type: ss, server: server, port: 443, cipher: AEAD_CHACHA20_POLY1305, password: "password" } - { name: "ss1", type: ss, server: server, port: 443, cipher: AEAD_CHACHA20_POLY1305, password: "password", udp: true }
# old obfs configuration remove after prerelease # old obfs configuration remove after prerelease
- name: "ss2" - name: "ss2"
@ -226,5 +226,5 @@ https://clash.gitbook.io/
- [x] Complementing the necessary rule operators - [x] Complementing the necessary rule operators
- [x] Redir proxy - [x] Redir proxy
- [ ] UDP support - [ ] UDP support (vmess, outbound socks5)
- [ ] Connection manager - [ ] Connection manager

View File

@ -10,26 +10,16 @@ import (
// HTTPAdapter is a adapter for HTTP connection // HTTPAdapter is a adapter for HTTP connection
type HTTPAdapter struct { type HTTPAdapter struct {
net.Conn
metadata *C.Metadata metadata *C.Metadata
conn net.Conn
R *http.Request R *http.Request
} }
// Close HTTP connection
func (h *HTTPAdapter) Close() {
h.conn.Close()
}
// Metadata return destination metadata // Metadata return destination metadata
func (h *HTTPAdapter) Metadata() *C.Metadata { func (h *HTTPAdapter) Metadata() *C.Metadata {
return h.metadata return h.metadata
} }
// Conn return raw net.Conn of HTTP
func (h *HTTPAdapter) Conn() net.Conn {
return h.conn
}
// NewHTTP is HTTPAdapter generator // NewHTTP is HTTPAdapter generator
func NewHTTP(request *http.Request, conn net.Conn) *HTTPAdapter { func NewHTTP(request *http.Request, conn net.Conn) *HTTPAdapter {
metadata := parseHTTPAddr(request) metadata := parseHTTPAddr(request)
@ -37,7 +27,7 @@ func NewHTTP(request *http.Request, conn net.Conn) *HTTPAdapter {
return &HTTPAdapter{ return &HTTPAdapter{
metadata: metadata, metadata: metadata,
R: request, R: request,
conn: conn, Conn: conn,
} }
} }

View File

@ -11,6 +11,6 @@ func NewHTTPS(request *http.Request, conn net.Conn) *SocketAdapter {
metadata.SourceIP = parseSourceIP(conn) metadata.SourceIP = parseSourceIP(conn)
return &SocketAdapter{ return &SocketAdapter{
metadata: metadata, metadata: metadata,
conn: conn, Conn: conn,
} }
} }

View File

@ -9,33 +9,24 @@ import (
// SocketAdapter is a adapter for socks and redir connection // SocketAdapter is a adapter for socks and redir connection
type SocketAdapter struct { type SocketAdapter struct {
conn net.Conn net.Conn
metadata *C.Metadata metadata *C.Metadata
} }
// Close socks and redir connection
func (s *SocketAdapter) Close() {
s.conn.Close()
}
// Metadata return destination metadata // Metadata return destination metadata
func (s *SocketAdapter) Metadata() *C.Metadata { func (s *SocketAdapter) Metadata() *C.Metadata {
return s.metadata return s.metadata
} }
// Conn return raw net.Conn
func (s *SocketAdapter) Conn() net.Conn {
return s.conn
}
// NewSocket is SocketAdapter generator // NewSocket is SocketAdapter generator
func NewSocket(target socks.Addr, conn net.Conn, source C.SourceType) *SocketAdapter { func NewSocket(target socks.Addr, conn net.Conn, source C.SourceType, netType C.NetWork) *SocketAdapter {
metadata := parseSocksAddr(target) metadata := parseSocksAddr(target)
metadata.NetWork = netType
metadata.Source = source metadata.Source = source
metadata.SourceIP = parseSourceIP(conn) metadata.SourceIP = parseSourceIP(conn)
return &SocketAdapter{ return &SocketAdapter{
conn: conn, Conn: conn,
metadata: metadata, metadata: metadata,
} }
} }

View File

@ -11,7 +11,6 @@ import (
func parseSocksAddr(target socks.Addr) *C.Metadata { func parseSocksAddr(target socks.Addr) *C.Metadata {
metadata := &C.Metadata{ metadata := &C.Metadata{
NetWork: C.TCP,
AddrType: int(target[0]), AddrType: int(target[0]),
} }

View File

@ -2,6 +2,7 @@ package adapters
import ( import (
"encoding/json" "encoding/json"
"errors"
"net" "net"
"net/http" "net/http"
"time" "time"
@ -13,6 +14,7 @@ import (
type Base struct { type Base struct {
name string name string
tp C.AdapterType tp C.AdapterType
udp bool
} }
func (b *Base) Name() string { func (b *Base) Name() string {
@ -23,6 +25,14 @@ func (b *Base) Type() C.AdapterType {
return b.tp return b.tp
} }
func (b *Base) DialUDP(metadata *C.Metadata) (net.PacketConn, net.Addr, error) {
return nil, nil, errors.New("no support")
}
func (b *Base) SupportUDP() bool {
return b.udp
}
func (b *Base) Destroy() {} func (b *Base) Destroy() {}
func (b *Base) MarshalJSON() ([]byte, error) { func (b *Base) MarshalJSON() ([]byte, error) {

View File

@ -24,11 +24,22 @@ func (d *Direct) Dial(metadata *C.Metadata) (net.Conn, error) {
return c, nil return c, nil
} }
func (d *Direct) DialUDP(metadata *C.Metadata) (net.PacketConn, net.Addr, error) {
pc, err := net.ListenPacket("udp", "")
if err != nil {
return nil, nil, err
}
addr, _ := net.ResolveUDPAddr("udp", net.JoinHostPort(metadata.String(), metadata.Port))
return pc, addr, nil
}
func NewDirect() *Direct { func NewDirect() *Direct {
return &Direct{ return &Direct{
Base: &Base{ Base: &Base{
name: "DIRECT", name: "DIRECT",
tp: C.Direct, tp: C.Direct,
udp: true,
}, },
} }
} }

View File

@ -35,6 +35,16 @@ func (f *Fallback) Dial(metadata *C.Metadata) (net.Conn, error) {
return proxy.Dial(metadata) return proxy.Dial(metadata)
} }
func (f *Fallback) DialUDP(metadata *C.Metadata) (net.PacketConn, net.Addr, error) {
proxy := f.findAliveProxy()
return proxy.DialUDP(metadata)
}
func (f *Fallback) SupportUDP() bool {
proxy := f.findAliveProxy()
return proxy.SupportUDP()
}
func (f *Fallback) MarshalJSON() ([]byte, error) { func (f *Fallback) MarshalJSON() ([]byte, error) {
var all []string var all []string
for _, proxy := range f.proxies { for _, proxy := range f.proxies {

View File

@ -67,6 +67,24 @@ func (lb *LoadBalance) Dial(metadata *C.Metadata) (net.Conn, error) {
return lb.proxies[0].Dial(metadata) return lb.proxies[0].Dial(metadata)
} }
func (lb *LoadBalance) DialUDP(metadata *C.Metadata) (net.PacketConn, net.Addr, error) {
key := uint64(murmur3.Sum32([]byte(getKey(metadata))))
buckets := int32(len(lb.proxies))
for i := 0; i < lb.maxRetry; i, key = i+1, key+1 {
idx := jumpHash(key, buckets)
proxy := lb.proxies[idx]
if proxy.Alive() {
return proxy.DialUDP(metadata)
}
}
return lb.proxies[0].DialUDP(metadata)
}
func (lb *LoadBalance) SupportUDP() bool {
return true
}
func (lb *LoadBalance) Destroy() { func (lb *LoadBalance) Destroy() {
lb.done <- struct{}{} lb.done <- struct{}{}
} }

View File

@ -24,6 +24,14 @@ func (s *Selector) Dial(metadata *C.Metadata) (net.Conn, error) {
return s.selected.Dial(metadata) return s.selected.Dial(metadata)
} }
func (s *Selector) DialUDP(metadata *C.Metadata) (net.PacketConn, net.Addr, error) {
return s.selected.DialUDP(metadata)
}
func (s *Selector) SupportUDP() bool {
return s.selected.SupportUDP()
}
func (s *Selector) MarshalJSON() ([]byte, error) { func (s *Selector) MarshalJSON() ([]byte, error) {
var all []string var all []string
for k := range s.proxies { for k := range s.proxies {

View File

@ -8,6 +8,7 @@ import (
"net" "net"
"strconv" "strconv"
"github.com/Dreamacro/clash/common/pool"
"github.com/Dreamacro/clash/common/structure" "github.com/Dreamacro/clash/common/structure"
obfs "github.com/Dreamacro/clash/component/simple-obfs" obfs "github.com/Dreamacro/clash/component/simple-obfs"
v2rayObfs "github.com/Dreamacro/clash/component/v2ray-plugin" v2rayObfs "github.com/Dreamacro/clash/component/v2ray-plugin"
@ -34,6 +35,7 @@ type ShadowSocksOption struct {
Port int `proxy:"port"` Port int `proxy:"port"`
Password string `proxy:"password"` Password string `proxy:"password"`
Cipher string `proxy:"cipher"` Cipher string `proxy:"cipher"`
UDP bool `proxy:"udp,omitempty"`
Plugin string `proxy:"plugin,omitempty"` Plugin string `proxy:"plugin,omitempty"`
PluginOpts map[string]interface{} `proxy:"plugin-opts,omitempty"` PluginOpts map[string]interface{} `proxy:"plugin-opts,omitempty"`
@ -80,6 +82,19 @@ func (ss *ShadowSocks) Dial(metadata *C.Metadata) (net.Conn, error) {
return c, err return c, err
} }
func (ss *ShadowSocks) DialUDP(metadata *C.Metadata) (net.PacketConn, net.Addr, error) {
pc, err := net.ListenPacket("udp", "")
if err != nil {
return nil, nil, err
}
addr, _ := net.ResolveUDPAddr("udp", ss.server)
remoteAddr, _ := net.ResolveUDPAddr("udp", net.JoinHostPort(metadata.String(), metadata.Port))
pc = ss.cipher.PacketConn(pc)
return &ssUDPConn{PacketConn: pc, rAddr: remoteAddr}, addr, nil
}
func (ss *ShadowSocks) MarshalJSON() ([]byte, error) { func (ss *ShadowSocks) MarshalJSON() ([]byte, error) {
return json.Marshal(map[string]string{ return json.Marshal(map[string]string{
"type": ss.Type().String(), "type": ss.Type().String(),
@ -144,6 +159,7 @@ func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) {
Base: &Base{ Base: &Base{
name: option.Name, name: option.Name,
tp: C.Shadowsocks, tp: C.Shadowsocks,
udp: option.UDP,
}, },
server: server, server: server,
cipher: ciph, cipher: ciph,
@ -173,3 +189,24 @@ func serializesSocksAddr(metadata *C.Metadata) []byte {
} }
return bytes.Join(buf, nil) return bytes.Join(buf, nil)
} }
type ssUDPConn struct {
net.PacketConn
rAddr net.Addr
}
func (uc *ssUDPConn) WriteTo(b []byte, addr net.Addr) (int, error) {
buf := pool.BufPool.Get().([]byte)
defer pool.BufPool.Put(buf[:cap(buf)])
rAddr := socks.ParseAddr(uc.rAddr.String())
copy(buf[len(rAddr):], b)
copy(buf, rAddr)
return uc.PacketConn.WriteTo(buf[:len(rAddr)+len(b)], addr)
}
func (uc *ssUDPConn) ReadFrom(b []byte) (int, net.Addr, error) {
n, a, e := uc.PacketConn.ReadFrom(b)
addr := socks.SplitAddr(b[:n])
copy(b, b[len(addr):])
return n - len(addr), a, e
}

View File

@ -31,6 +31,7 @@ type Socks5Option struct {
UserName string `proxy:"username,omitempty"` UserName string `proxy:"username,omitempty"`
Password string `proxy:"password,omitempty"` Password string `proxy:"password,omitempty"`
TLS bool `proxy:"tls,omitempty"` TLS bool `proxy:"tls,omitempty"`
UDP bool `proxy:"udp,omitempty"`
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"` SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
} }
@ -126,6 +127,7 @@ func NewSocks5(option Socks5Option) *Socks5 {
Base: &Base{ Base: &Base{
name: option.Name, name: option.Name,
tp: C.Socks5, tp: C.Socks5,
udp: option.UDP,
}, },
addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)), addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)),
user: option.UserName, user: option.UserName,

View File

@ -43,6 +43,14 @@ func (u *URLTest) Dial(metadata *C.Metadata) (net.Conn, error) {
return a, err return a, err
} }
func (u *URLTest) DialUDP(metadata *C.Metadata) (net.PacketConn, net.Addr, error) {
return u.fast.DialUDP(metadata)
}
func (u *URLTest) SupportUDP() bool {
return u.fast.SupportUDP()
}
func (u *URLTest) MarshalJSON() ([]byte, error) { func (u *URLTest) MarshalJSON() ([]byte, error) {
var all []string var all []string
for _, proxy := range u.proxies { for _, proxy := range u.proxies {

15
common/pool/pool.go Normal file
View File

@ -0,0 +1,15 @@
package pool
import (
"sync"
)
const (
// io.Copy default buffer size is 32 KiB
// but the maximum packet size of vmess/shadowsocks is about 16 KiB
// so define a buffer of 20 KiB to reduce the memory of each TCP relay
bufferSize = 20 * 1024
)
// BufPool provide buffer for relay
var BufPool = sync.Pool{New: func() interface{} { return make([]byte, bufferSize) }}

View File

@ -8,6 +8,8 @@ import (
"math/rand" "math/rand"
"net" "net"
"net/http" "net/http"
"github.com/Dreamacro/clash/common/pool"
) )
// HTTPObfs is shadowsocks http simple-obfs implementation // HTTPObfs is shadowsocks http simple-obfs implementation
@ -32,15 +34,15 @@ func (ho *HTTPObfs) Read(b []byte) (int, error) {
} }
if ho.firstResponse { if ho.firstResponse {
buf := bufPool.Get().([]byte) buf := pool.BufPool.Get().([]byte)
n, err := ho.Conn.Read(buf) n, err := ho.Conn.Read(buf)
if err != nil { if err != nil {
bufPool.Put(buf[:cap(buf)]) pool.BufPool.Put(buf[:cap(buf)])
return 0, err return 0, err
} }
idx := bytes.Index(buf[:n], []byte("\r\n\r\n")) idx := bytes.Index(buf[:n], []byte("\r\n\r\n"))
if idx == -1 { if idx == -1 {
bufPool.Put(buf[:cap(buf)]) pool.BufPool.Put(buf[:cap(buf)])
return 0, io.EOF return 0, io.EOF
} }
ho.firstResponse = false ho.firstResponse = false
@ -50,7 +52,7 @@ func (ho *HTTPObfs) Read(b []byte) (int, error) {
ho.buf = buf[:idx+4+length] ho.buf = buf[:idx+4+length]
ho.offset = idx + 4 + n ho.offset = idx + 4 + n
} else { } else {
bufPool.Put(buf[:cap(buf)]) pool.BufPool.Put(buf[:cap(buf)])
} }
return n, nil return n, nil
} }

View File

@ -6,8 +6,9 @@ import (
"io" "io"
"math/rand" "math/rand"
"net" "net"
"sync"
"time" "time"
"github.com/Dreamacro/clash/common/pool"
) )
func init() { func init() {
@ -18,8 +19,6 @@ const (
chunkSize = 1 << 14 // 2 ** 14 == 16 * 1024 chunkSize = 1 << 14 // 2 ** 14 == 16 * 1024
) )
var bufPool = sync.Pool{New: func() interface{} { return make([]byte, 2048) }}
// TLSObfs is shadowsocks tls simple-obfs implementation // TLSObfs is shadowsocks tls simple-obfs implementation
type TLSObfs struct { type TLSObfs struct {
net.Conn net.Conn
@ -30,12 +29,12 @@ type TLSObfs struct {
} }
func (to *TLSObfs) read(b []byte, discardN int) (int, error) { func (to *TLSObfs) read(b []byte, discardN int) (int, error) {
buf := bufPool.Get().([]byte) buf := pool.BufPool.Get().([]byte)
_, err := io.ReadFull(to.Conn, buf[:discardN]) _, err := io.ReadFull(to.Conn, buf[:discardN])
if err != nil { if err != nil {
return 0, err return 0, err
} }
bufPool.Put(buf[:cap(buf)]) pool.BufPool.Put(buf[:cap(buf)])
sizeBuf := make([]byte, 2) sizeBuf := make([]byte, 2)
_, err = io.ReadFull(to.Conn, sizeBuf) _, err = io.ReadFull(to.Conn, sizeBuf)
@ -103,7 +102,7 @@ func (to *TLSObfs) write(b []byte) (int, error) {
return len(b), err return len(b), err
} }
size := bufPool.Get().([]byte) size := pool.BufPool.Get().([]byte)
binary.BigEndian.PutUint16(size[:2], uint16(len(b))) binary.BigEndian.PutUint16(size[:2], uint16(len(b)))
buf := &bytes.Buffer{} buf := &bytes.Buffer{}
@ -111,7 +110,7 @@ func (to *TLSObfs) write(b []byte) (int, error) {
buf.Write(size[:2]) buf.Write(size[:2])
buf.Write(b) buf.Write(b)
_, err := to.Conn.Write(buf.Bytes()) _, err := to.Conn.Write(buf.Bytes())
bufPool.Put(size[:cap(size)]) pool.BufPool.Put(size[:cap(size)])
return len(b), err return len(b), err
} }

View File

@ -5,6 +5,8 @@ import (
"encoding/binary" "encoding/binary"
"errors" "errors"
"io" "io"
"github.com/Dreamacro/clash/common/pool"
) )
type aeadWriter struct { type aeadWriter struct {
@ -20,8 +22,8 @@ func newAEADWriter(w io.Writer, aead cipher.AEAD, iv []byte) *aeadWriter {
} }
func (w *aeadWriter) Write(b []byte) (n int, err error) { func (w *aeadWriter) Write(b []byte) (n int, err error) {
buf := bufPool.Get().([]byte) buf := pool.BufPool.Get().([]byte)
defer bufPool.Put(buf[:cap(buf)]) defer pool.BufPool.Put(buf[:cap(buf)])
length := len(b) length := len(b)
for { for {
if length == 0 { if length == 0 {
@ -71,7 +73,7 @@ func (r *aeadReader) Read(b []byte) (int, error) {
n := copy(b, r.buf[r.offset:]) n := copy(b, r.buf[r.offset:])
r.offset += n r.offset += n
if r.offset == len(r.buf) { if r.offset == len(r.buf) {
bufPool.Put(r.buf[:cap(r.buf)]) pool.BufPool.Put(r.buf[:cap(r.buf)])
r.buf = nil r.buf = nil
} }
return n, nil return n, nil
@ -87,10 +89,10 @@ func (r *aeadReader) Read(b []byte) (int, error) {
return 0, errors.New("Buffer is larger than standard") return 0, errors.New("Buffer is larger than standard")
} }
buf := bufPool.Get().([]byte) buf := pool.BufPool.Get().([]byte)
_, err = io.ReadFull(r.Reader, buf[:size]) _, err = io.ReadFull(r.Reader, buf[:size])
if err != nil { if err != nil {
bufPool.Put(buf[:cap(buf)]) pool.BufPool.Put(buf[:cap(buf)])
return 0, err return 0, err
} }
@ -105,7 +107,7 @@ func (r *aeadReader) Read(b []byte) (int, error) {
realLen := size - r.Overhead() realLen := size - r.Overhead()
n := copy(b, buf[:realLen]) n := copy(b, buf[:realLen])
if len(b) >= realLen { if len(b) >= realLen {
bufPool.Put(buf[:cap(buf)]) pool.BufPool.Put(buf[:cap(buf)])
return n, nil return n, nil
} }

View File

@ -4,7 +4,8 @@ import (
"encoding/binary" "encoding/binary"
"errors" "errors"
"io" "io"
"sync"
"github.com/Dreamacro/clash/common/pool"
) )
const ( const (
@ -13,8 +14,6 @@ const (
maxSize = 17 * 1024 // 2 + chunkSize + aead.Overhead() maxSize = 17 * 1024 // 2 + chunkSize + aead.Overhead()
) )
var bufPool = sync.Pool{New: func() interface{} { return make([]byte, maxSize) }}
type chunkReader struct { type chunkReader struct {
io.Reader io.Reader
buf []byte buf []byte
@ -35,7 +34,7 @@ func (cr *chunkReader) Read(b []byte) (int, error) {
n := copy(b, cr.buf[cr.offset:]) n := copy(b, cr.buf[cr.offset:])
cr.offset += n cr.offset += n
if cr.offset == len(cr.buf) { if cr.offset == len(cr.buf) {
bufPool.Put(cr.buf[:cap(cr.buf)]) pool.BufPool.Put(cr.buf[:cap(cr.buf)])
cr.buf = nil cr.buf = nil
} }
return n, nil return n, nil
@ -60,10 +59,10 @@ func (cr *chunkReader) Read(b []byte) (int, error) {
return size, nil return size, nil
} }
buf := bufPool.Get().([]byte) buf := pool.BufPool.Get().([]byte)
_, err = io.ReadFull(cr.Reader, buf[:size]) _, err = io.ReadFull(cr.Reader, buf[:size])
if err != nil { if err != nil {
bufPool.Put(buf[:cap(buf)]) pool.BufPool.Put(buf[:cap(buf)])
return 0, err return 0, err
} }
n := copy(b, cr.buf[:]) n := copy(b, cr.buf[:])
@ -77,8 +76,8 @@ type chunkWriter struct {
} }
func (cw *chunkWriter) Write(b []byte) (n int, err error) { func (cw *chunkWriter) Write(b []byte) (n int, err error) {
buf := bufPool.Get().([]byte) buf := pool.BufPool.Get().([]byte)
defer bufPool.Put(buf[:cap(buf)]) defer pool.BufPool.Put(buf[:cap(buf)])
length := len(b) length := len(b)
for { for {
if length == 0 { if length == 0 {

View File

@ -20,14 +20,16 @@ const (
) )
type ServerAdapter interface { type ServerAdapter interface {
net.Conn
Metadata() *Metadata Metadata() *Metadata
Close()
} }
type ProxyAdapter interface { type ProxyAdapter interface {
Name() string Name() string
Type() AdapterType Type() AdapterType
Dial(metadata *Metadata) (net.Conn, error) Dial(metadata *Metadata) (net.Conn, error)
DialUDP(metadata *Metadata) (net.PacketConn, net.Addr, error)
SupportUDP() bool
Destroy() Destroy()
MarshalJSON() ([]byte, error) MarshalJSON() ([]byte, error)
} }

2
go.mod
View File

@ -1,7 +1,7 @@
module github.com/Dreamacro/clash module github.com/Dreamacro/clash
require ( require (
github.com/Dreamacro/go-shadowsocks2 v0.1.3-0.20190202135136-da4602d8f112 github.com/Dreamacro/go-shadowsocks2 v0.1.3-0.20190406142755-9128a199439f
github.com/eapache/queue v1.1.0 // indirect github.com/eapache/queue v1.1.0 // indirect
github.com/go-chi/chi v4.0.1+incompatible github.com/go-chi/chi v4.0.1+incompatible
github.com/go-chi/cors v1.0.0 github.com/go-chi/cors v1.0.0

4
go.sum
View File

@ -1,5 +1,5 @@
github.com/Dreamacro/go-shadowsocks2 v0.1.3-0.20190202135136-da4602d8f112 h1:1axYxE0ZLJy40+ulq46XQt7MaJDJr4iGer1NQz7jmKw= github.com/Dreamacro/go-shadowsocks2 v0.1.3-0.20190406142755-9128a199439f h1:nlImrmI6I2AVjJ2AvE3w3f7fi8rhLQAhZO1Gs31+/nE=
github.com/Dreamacro/go-shadowsocks2 v0.1.3-0.20190202135136-da4602d8f112/go.mod h1:giIuN+TuUudTxHc1jjTOyyQYiJ3VXp1pWOHdJbSCAPo= github.com/Dreamacro/go-shadowsocks2 v0.1.3-0.20190406142755-9128a199439f/go.mod h1:giIuN+TuUudTxHc1jjTOyyQYiJ3VXp1pWOHdJbSCAPo=
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da h1:KjTM2ks9d14ZYCvmHS9iAKVt9AyzRSqNU1qabPih5BY= github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da h1:KjTM2ks9d14ZYCvmHS9iAKVt9AyzRSqNU1qabPih5BY=
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da/go.mod h1:eHEWzANqSiWQsof+nXEI9bUVUyV6F53Fp89EuCh2EAA= github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da/go.mod h1:eHEWzANqSiWQsof+nXEI9bUVUyV6F53Fp89EuCh2EAA=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=

View File

@ -59,5 +59,5 @@ func handleRedir(conn net.Conn) {
return return
} }
conn.(*net.TCPConn).SetKeepAlive(true) conn.(*net.TCPConn).SetKeepAlive(true)
tun.Add(adapters.NewSocket(target, conn, C.REDIR)) tun.Add(adapters.NewSocket(target, conn, C.REDIR, C.TCP))
} }

View File

@ -1,9 +1,11 @@
package socks package socks
import ( import (
"io"
"net" "net"
"strconv"
"github.com/Dreamacro/clash/adapters/inbound" adapters "github.com/Dreamacro/clash/adapters/inbound"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/log" "github.com/Dreamacro/clash/log"
"github.com/Dreamacro/clash/tunnel" "github.com/Dreamacro/clash/tunnel"
@ -15,6 +17,41 @@ var (
tun = tunnel.Instance() tun = tunnel.Instance()
) )
// Error represents a SOCKS error
type Error byte
func (err Error) Error() string {
return "SOCKS error: " + strconv.Itoa(int(err))
}
// SOCKS request commands as defined in RFC 1928 section 4.
const (
CmdConnect = 1
CmdBind = 2
CmdUDPAssociate = 3
)
// SOCKS address types as defined in RFC 1928 section 5.
const (
AtypIPv4 = 1
AtypDomainName = 3
AtypIPv6 = 4
)
const MaxAddrLen = 1 + 1 + 255 + 2
// SOCKS errors as defined in RFC 1928 section 6.
const (
ErrGeneralFailure = Error(1)
ErrConnectionNotAllowed = Error(2)
ErrNetworkUnreachable = Error(3)
ErrHostUnreachable = Error(4)
ErrConnectionRefused = Error(5)
ErrTTLExpired = Error(6)
ErrCommandNotSupported = Error(7)
ErrAddressNotSupported = Error(8)
)
type SockListener struct { type SockListener struct {
net.Listener net.Listener
address string address string
@ -55,11 +92,78 @@ func (l *SockListener) Address() string {
} }
func handleSocks(conn net.Conn) { func handleSocks(conn net.Conn) {
target, err := socks.Handshake(conn) target, command, err := handshake(conn)
if err != nil { if err != nil {
conn.Close() conn.Close()
return return
} }
conn.(*net.TCPConn).SetKeepAlive(true) conn.(*net.TCPConn).SetKeepAlive(true)
tun.Add(adapters.NewSocket(target, conn, C.SOCKS)) if command == CmdUDPAssociate {
tun.Add(adapters.NewSocket(target, conn, C.SOCKS, C.UDP))
return
}
tun.Add(adapters.NewSocket(target, conn, C.SOCKS, C.TCP))
}
// handshake fast-tracks SOCKS initialization to get target address to connect.
func handshake(rw io.ReadWriter) (addr socks.Addr, command int, err error) {
// Read RFC 1928 for request and reply structure and sizes.
buf := make([]byte, MaxAddrLen)
// read VER, NMETHODS, METHODS
if _, err = io.ReadFull(rw, buf[:2]); err != nil {
return
}
nmethods := buf[1]
if _, err = io.ReadFull(rw, buf[:nmethods]); err != nil {
return
}
// write VER METHOD
if _, err = rw.Write([]byte{5, 0}); err != nil {
return
}
// read VER CMD RSV ATYP DST.ADDR DST.PORT
if _, err = io.ReadFull(rw, buf[:3]); err != nil {
return
}
if buf[1] != CmdConnect && buf[1] != CmdUDPAssociate {
err = ErrCommandNotSupported
return
}
command = int(buf[1])
addr, err = readAddr(rw, buf)
if err != nil {
return
}
// write VER REP RSV ATYP BND.ADDR BND.PORT
_, err = rw.Write([]byte{5, 0, 0, 1, 0, 0, 0, 0, 0, 0})
return
}
func readAddr(r io.Reader, b []byte) (socks.Addr, error) {
if len(b) < MaxAddrLen {
return nil, io.ErrShortBuffer
}
_, err := io.ReadFull(r, b[:1]) // read 1st byte for address type
if err != nil {
return nil, err
}
switch b[0] {
case AtypDomainName:
_, 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+b[1]+2])
return b[:1+1+b[1]+2], err
case AtypIPv4:
_, err = io.ReadFull(r, b[1:1+net.IPv4len+2])
return b[:1+net.IPv4len+2], err
case AtypIPv6:
_, err = io.ReadFull(r, b[1:1+net.IPv6len+2])
return b[:1+net.IPv6len+2], err
}
return nil, ErrAddressNotSupported
} }

View File

@ -6,21 +6,12 @@ import (
"net" "net"
"net/http" "net/http"
"strings" "strings"
"sync"
"time" "time"
adapters "github.com/Dreamacro/clash/adapters/inbound" adapters "github.com/Dreamacro/clash/adapters/inbound"
"github.com/Dreamacro/clash/common/pool"
) )
const (
// io.Copy default buffer size is 32 KiB
// but the maximum packet size of vmess/shadowsocks is about 16 KiB
// so define a buffer of 20 KiB to reduce the memory of each TCP relay
bufferSize = 20 * 1024
)
var bufPool = sync.Pool{New: func() interface{} { return make([]byte, bufferSize) }}
func (t *Tunnel) handleHTTP(request *adapters.HTTPAdapter, outbound net.Conn) { func (t *Tunnel) handleHTTP(request *adapters.HTTPAdapter, outbound net.Conn) {
conn := newTrafficTrack(outbound, t.traffic) conn := newTrafficTrack(outbound, t.traffic)
req := request.R req := request.R
@ -50,7 +41,7 @@ func (t *Tunnel) handleHTTP(request *adapters.HTTPAdapter, outbound net.Conn) {
} else { } else {
resp.Close = true resp.Close = true
} }
err = resp.Write(request.Conn()) err = resp.Write(request)
if err != nil || resp.Close { if err != nil || resp.Close {
break break
} }
@ -59,7 +50,7 @@ func (t *Tunnel) handleHTTP(request *adapters.HTTPAdapter, outbound net.Conn) {
break break
} }
req, err = http.ReadRequest(bufio.NewReader(request.Conn())) req, err = http.ReadRequest(bufio.NewReader(request))
if err != nil { if err != nil {
break break
} }
@ -72,9 +63,52 @@ func (t *Tunnel) handleHTTP(request *adapters.HTTPAdapter, outbound net.Conn) {
} }
} }
func (t *Tunnel) handleSOCKS(request *adapters.SocketAdapter, outbound net.Conn) { func (t *Tunnel) handleSocket(request *adapters.SocketAdapter, outbound net.Conn) {
conn := newTrafficTrack(outbound, t.traffic) conn := newTrafficTrack(outbound, t.traffic)
relay(request.Conn(), conn) relay(request, conn)
}
func (t *Tunnel) handleUDPOverTCP(conn net.Conn, pc net.PacketConn, addr net.Addr) error {
ch := make(chan error, 1)
go func() {
buf := pool.BufPool.Get().([]byte)
defer pool.BufPool.Put(buf)
for {
n, err := conn.Read(buf)
if err != nil {
ch <- err
return
}
pc.SetReadDeadline(time.Now().Add(120 * time.Second))
if _, err = pc.WriteTo(buf[:n], addr); err != nil {
ch <- err
return
}
t.traffic.Up() <- int64(n)
ch <- nil
}
}()
buf := pool.BufPool.Get().([]byte)
defer pool.BufPool.Put(buf)
for {
pc.SetReadDeadline(time.Now().Add(120 * time.Second))
n, _, err := pc.ReadFrom(buf)
if err != nil {
break
}
if _, err := conn.Write(buf[:n]); err != nil {
break
}
t.traffic.Down() <- int64(n)
}
<-ch
return nil
} }
// relay copies between left and right bidirectionally. // relay copies between left and right bidirectionally.
@ -82,16 +116,16 @@ func relay(leftConn, rightConn net.Conn) {
ch := make(chan error) ch := make(chan error)
go func() { go func() {
buf := bufPool.Get().([]byte) buf := pool.BufPool.Get().([]byte)
_, err := io.CopyBuffer(leftConn, rightConn, buf) _, err := io.CopyBuffer(leftConn, rightConn, buf)
bufPool.Put(buf[:cap(buf)]) pool.BufPool.Put(buf[:cap(buf)])
leftConn.SetReadDeadline(time.Now()) leftConn.SetReadDeadline(time.Now())
ch <- err ch <- err
}() }()
buf := bufPool.Get().([]byte) buf := pool.BufPool.Get().([]byte)
io.CopyBuffer(rightConn, leftConn, buf) io.CopyBuffer(rightConn, leftConn, buf)
bufPool.Put(buf[:cap(buf)]) pool.BufPool.Put(buf[:cap(buf)])
rightConn.SetReadDeadline(time.Now()) rightConn.SetReadDeadline(time.Now())
<-ch <-ch
} }

View File

@ -19,7 +19,7 @@ var (
once sync.Once once sync.Once
) )
// Tunnel handle proxy socket and HTTP/SOCKS socket // Tunnel handle relay inbound proxy and outbound proxy
type Tunnel struct { type Tunnel struct {
queue *channels.InfiniteChannel queue *channels.InfiniteChannel
rules []C.Rule rules []C.Rule
@ -143,6 +143,12 @@ func (t *Tunnel) handleConn(localConn C.ServerAdapter) {
} }
} }
if metadata.NetWork == C.UDP {
pc, addr, _ := proxy.DialUDP(metadata)
t.handleUDPOverTCP(localConn, pc, addr)
return
}
remoConn, err := proxy.Dial(metadata) remoConn, err := proxy.Dial(metadata)
if err != nil { if err != nil {
log.Warnln("Proxy[%s] connect [%s --> %s] error: %s", proxy.Name(), metadata.SourceIP.String(), metadata.String(), err.Error()) log.Warnln("Proxy[%s] connect [%s --> %s] error: %s", proxy.Name(), metadata.SourceIP.String(), metadata.String(), err.Error())
@ -154,7 +160,7 @@ func (t *Tunnel) handleConn(localConn C.ServerAdapter) {
case *InboundAdapter.HTTPAdapter: case *InboundAdapter.HTTPAdapter:
t.handleHTTP(adapter, remoConn) t.handleHTTP(adapter, remoConn)
case *InboundAdapter.SocketAdapter: case *InboundAdapter.SocketAdapter:
t.handleSOCKS(adapter, remoConn) t.handleSocket(adapter, remoConn)
} }
} }
@ -177,10 +183,17 @@ func (t *Tunnel) match(metadata *C.Metadata) (C.Proxy, error) {
} }
if rule.IsMatch(metadata) { if rule.IsMatch(metadata) {
if a, ok := t.proxies[rule.Adapter()]; ok { adapter, ok := t.proxies[rule.Adapter()]
log.Infoln("%s --> %v match %s using %s", metadata.SourceIP.String(), metadata.String(), rule.RuleType().String(), rule.Adapter()) if !ok {
return a, nil continue
} }
if metadata.NetWork == C.UDP && !adapter.SupportUDP() {
continue
}
log.Infoln("%s --> %v match %s using %s", metadata.SourceIP.String(), metadata.String(), rule.RuleType().String(), rule.Adapter())
return adapter, nil
} }
} }
log.Infoln("%s --> %v doesn't match any rule using DIRECT", metadata.SourceIP.String(), metadata.String()) log.Infoln("%s --> %v doesn't match any rule using DIRECT", metadata.SourceIP.String(), metadata.String())