Feature: socks5 udp associate

This commit is contained in:
gVisor bot 2019-04-23 23:29:36 +08:00
parent 4aade6a0e6
commit c02b16b446
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
# 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
- { 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
- name: "ss2"
@ -226,5 +226,5 @@ https://clash.gitbook.io/
- [x] Complementing the necessary rule operators
- [x] Redir proxy
- [ ] UDP support
- [ ] UDP support (vmess, outbound socks5)
- [ ] Connection manager

View File

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

View File

@ -9,33 +9,24 @@ import (
// SocketAdapter is a adapter for socks and redir connection
type SocketAdapter struct {
conn net.Conn
net.Conn
metadata *C.Metadata
}
// Close socks and redir connection
func (s *SocketAdapter) Close() {
s.conn.Close()
}
// Metadata return destination metadata
func (s *SocketAdapter) Metadata() *C.Metadata {
return s.metadata
}
// Conn return raw net.Conn
func (s *SocketAdapter) Conn() net.Conn {
return s.conn
}
// 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.NetWork = netType
metadata.Source = source
metadata.SourceIP = parseSourceIP(conn)
return &SocketAdapter{
conn: conn,
Conn: conn,
metadata: metadata,
}
}

View File

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

View File

@ -2,6 +2,7 @@ package adapters
import (
"encoding/json"
"errors"
"net"
"net/http"
"time"
@ -13,6 +14,7 @@ import (
type Base struct {
name string
tp C.AdapterType
udp bool
}
func (b *Base) Name() string {
@ -23,6 +25,14 @@ func (b *Base) Type() C.AdapterType {
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) MarshalJSON() ([]byte, error) {

View File

@ -24,11 +24,22 @@ func (d *Direct) Dial(metadata *C.Metadata) (net.Conn, error) {
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 {
return &Direct{
Base: &Base{
name: "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)
}
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) {
var all []string
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)
}
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() {
lb.done <- struct{}{}
}

View File

@ -24,6 +24,14 @@ func (s *Selector) Dial(metadata *C.Metadata) (net.Conn, error) {
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) {
var all []string
for k := range s.proxies {

View File

@ -8,6 +8,7 @@ import (
"net"
"strconv"
"github.com/Dreamacro/clash/common/pool"
"github.com/Dreamacro/clash/common/structure"
obfs "github.com/Dreamacro/clash/component/simple-obfs"
v2rayObfs "github.com/Dreamacro/clash/component/v2ray-plugin"
@ -34,6 +35,7 @@ type ShadowSocksOption struct {
Port int `proxy:"port"`
Password string `proxy:"password"`
Cipher string `proxy:"cipher"`
UDP bool `proxy:"udp,omitempty"`
Plugin string `proxy:"plugin,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
}
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) {
return json.Marshal(map[string]string{
"type": ss.Type().String(),
@ -144,6 +159,7 @@ func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) {
Base: &Base{
name: option.Name,
tp: C.Shadowsocks,
udp: option.UDP,
},
server: server,
cipher: ciph,
@ -173,3 +189,24 @@ func serializesSocksAddr(metadata *C.Metadata) []byte {
}
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"`
Password string `proxy:"password,omitempty"`
TLS bool `proxy:"tls,omitempty"`
UDP bool `proxy:"udp,omitempty"`
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
}
@ -126,6 +127,7 @@ func NewSocks5(option Socks5Option) *Socks5 {
Base: &Base{
name: option.Name,
tp: C.Socks5,
udp: option.UDP,
},
addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)),
user: option.UserName,

View File

@ -43,6 +43,14 @@ func (u *URLTest) Dial(metadata *C.Metadata) (net.Conn, error) {
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) {
var all []string
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"
"net"
"net/http"
"github.com/Dreamacro/clash/common/pool"
)
// HTTPObfs is shadowsocks http simple-obfs implementation
@ -32,15 +34,15 @@ func (ho *HTTPObfs) Read(b []byte) (int, error) {
}
if ho.firstResponse {
buf := bufPool.Get().([]byte)
buf := pool.BufPool.Get().([]byte)
n, err := ho.Conn.Read(buf)
if err != nil {
bufPool.Put(buf[:cap(buf)])
pool.BufPool.Put(buf[:cap(buf)])
return 0, err
}
idx := bytes.Index(buf[:n], []byte("\r\n\r\n"))
if idx == -1 {
bufPool.Put(buf[:cap(buf)])
pool.BufPool.Put(buf[:cap(buf)])
return 0, io.EOF
}
ho.firstResponse = false
@ -50,7 +52,7 @@ func (ho *HTTPObfs) Read(b []byte) (int, error) {
ho.buf = buf[:idx+4+length]
ho.offset = idx + 4 + n
} else {
bufPool.Put(buf[:cap(buf)])
pool.BufPool.Put(buf[:cap(buf)])
}
return n, nil
}

View File

@ -6,8 +6,9 @@ import (
"io"
"math/rand"
"net"
"sync"
"time"
"github.com/Dreamacro/clash/common/pool"
)
func init() {
@ -18,8 +19,6 @@ const (
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
type TLSObfs struct {
net.Conn
@ -30,12 +29,12 @@ type TLSObfs struct {
}
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])
if err != nil {
return 0, err
}
bufPool.Put(buf[:cap(buf)])
pool.BufPool.Put(buf[:cap(buf)])
sizeBuf := make([]byte, 2)
_, err = io.ReadFull(to.Conn, sizeBuf)
@ -103,7 +102,7 @@ func (to *TLSObfs) write(b []byte) (int, error) {
return len(b), err
}
size := bufPool.Get().([]byte)
size := pool.BufPool.Get().([]byte)
binary.BigEndian.PutUint16(size[:2], uint16(len(b)))
buf := &bytes.Buffer{}
@ -111,7 +110,7 @@ func (to *TLSObfs) write(b []byte) (int, error) {
buf.Write(size[:2])
buf.Write(b)
_, err := to.Conn.Write(buf.Bytes())
bufPool.Put(size[:cap(size)])
pool.BufPool.Put(size[:cap(size)])
return len(b), err
}

View File

@ -5,6 +5,8 @@ import (
"encoding/binary"
"errors"
"io"
"github.com/Dreamacro/clash/common/pool"
)
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) {
buf := bufPool.Get().([]byte)
defer bufPool.Put(buf[:cap(buf)])
buf := pool.BufPool.Get().([]byte)
defer pool.BufPool.Put(buf[:cap(buf)])
length := len(b)
for {
if length == 0 {
@ -71,7 +73,7 @@ func (r *aeadReader) Read(b []byte) (int, error) {
n := copy(b, r.buf[r.offset:])
r.offset += n
if r.offset == len(r.buf) {
bufPool.Put(r.buf[:cap(r.buf)])
pool.BufPool.Put(r.buf[:cap(r.buf)])
r.buf = 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")
}
buf := bufPool.Get().([]byte)
buf := pool.BufPool.Get().([]byte)
_, err = io.ReadFull(r.Reader, buf[:size])
if err != nil {
bufPool.Put(buf[:cap(buf)])
pool.BufPool.Put(buf[:cap(buf)])
return 0, err
}
@ -105,7 +107,7 @@ func (r *aeadReader) Read(b []byte) (int, error) {
realLen := size - r.Overhead()
n := copy(b, buf[:realLen])
if len(b) >= realLen {
bufPool.Put(buf[:cap(buf)])
pool.BufPool.Put(buf[:cap(buf)])
return n, nil
}

View File

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

View File

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

2
go.mod
View File

@ -1,7 +1,7 @@
module github.com/Dreamacro/clash
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/go-chi/chi v4.0.1+incompatible
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.20190202135136-da4602d8f112/go.mod h1:giIuN+TuUudTxHc1jjTOyyQYiJ3VXp1pWOHdJbSCAPo=
github.com/Dreamacro/go-shadowsocks2 v0.1.3-0.20190406142755-9128a199439f h1:nlImrmI6I2AVjJ2AvE3w3f7fi8rhLQAhZO1Gs31+/nE=
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/go.mod h1:eHEWzANqSiWQsof+nXEI9bUVUyV6F53Fp89EuCh2EAA=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=

View File

@ -59,5 +59,5 @@ func handleRedir(conn net.Conn) {
return
}
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
import (
"io"
"net"
"strconv"
"github.com/Dreamacro/clash/adapters/inbound"
adapters "github.com/Dreamacro/clash/adapters/inbound"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/log"
"github.com/Dreamacro/clash/tunnel"
@ -15,6 +17,41 @@ var (
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 {
net.Listener
address string
@ -55,11 +92,78 @@ func (l *SockListener) Address() string {
}
func handleSocks(conn net.Conn) {
target, err := socks.Handshake(conn)
target, command, err := handshake(conn)
if err != nil {
conn.Close()
return
}
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/http"
"strings"
"sync"
"time"
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) {
conn := newTrafficTrack(outbound, t.traffic)
req := request.R
@ -50,7 +41,7 @@ func (t *Tunnel) handleHTTP(request *adapters.HTTPAdapter, outbound net.Conn) {
} else {
resp.Close = true
}
err = resp.Write(request.Conn())
err = resp.Write(request)
if err != nil || resp.Close {
break
}
@ -59,7 +50,7 @@ func (t *Tunnel) handleHTTP(request *adapters.HTTPAdapter, outbound net.Conn) {
break
}
req, err = http.ReadRequest(bufio.NewReader(request.Conn()))
req, err = http.ReadRequest(bufio.NewReader(request))
if err != nil {
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)
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.
@ -82,16 +116,16 @@ func relay(leftConn, rightConn net.Conn) {
ch := make(chan error)
go func() {
buf := bufPool.Get().([]byte)
buf := pool.BufPool.Get().([]byte)
_, err := io.CopyBuffer(leftConn, rightConn, buf)
bufPool.Put(buf[:cap(buf)])
pool.BufPool.Put(buf[:cap(buf)])
leftConn.SetReadDeadline(time.Now())
ch <- err
}()
buf := bufPool.Get().([]byte)
buf := pool.BufPool.Get().([]byte)
io.CopyBuffer(rightConn, leftConn, buf)
bufPool.Put(buf[:cap(buf)])
pool.BufPool.Put(buf[:cap(buf)])
rightConn.SetReadDeadline(time.Now())
<-ch
}

View File

@ -19,7 +19,7 @@ var (
once sync.Once
)
// Tunnel handle proxy socket and HTTP/SOCKS socket
// Tunnel handle relay inbound proxy and outbound proxy
type Tunnel struct {
queue *channels.InfiniteChannel
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)
if err != nil {
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:
t.handleHTTP(adapter, remoConn)
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 a, ok := t.proxies[rule.Adapter()]; ok {
log.Infoln("%s --> %v match %s using %s", metadata.SourceIP.String(), metadata.String(), rule.RuleType().String(), rule.Adapter())
return a, nil
adapter, ok := t.proxies[rule.Adapter()]
if !ok {
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())