Fix: some UDP issues (#265)
This commit is contained in:
@ -86,11 +86,14 @@ func (t *Tunnel) handleUDPToRemote(conn net.Conn, pc net.PacketConn, addr net.Ad
|
||||
t.traffic.Up() <- int64(n)
|
||||
}
|
||||
|
||||
func (t *Tunnel) handleUDPToLocal(conn net.Conn, pc net.PacketConn) {
|
||||
func (t *Tunnel) handleUDPToLocal(conn net.Conn, pc net.PacketConn, key string, timeout time.Duration) {
|
||||
buf := pool.BufPool.Get().([]byte)
|
||||
defer pool.BufPool.Put(buf[:cap(buf)])
|
||||
defer t.natTable.Delete(key)
|
||||
defer pc.Close()
|
||||
|
||||
for {
|
||||
pc.SetReadDeadline(time.Now().Add(timeout))
|
||||
n, _, err := pc.ReadFrom(buf)
|
||||
if err != nil {
|
||||
return
|
||||
|
@ -1,22 +0,0 @@
|
||||
package tunnel
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
nat "github.com/Dreamacro/clash/component/nat-table"
|
||||
)
|
||||
|
||||
var (
|
||||
natTable *nat.Table
|
||||
natOnce sync.Once
|
||||
|
||||
natTimeout = 120 * time.Second
|
||||
)
|
||||
|
||||
func NATInstance() *nat.Table {
|
||||
natOnce.Do(func() {
|
||||
natTable = nat.New(natTimeout)
|
||||
})
|
||||
return natTable
|
||||
}
|
157
tunnel/tunnel.go
157
tunnel/tunnel.go
@ -7,6 +7,7 @@ import (
|
||||
"time"
|
||||
|
||||
InboundAdapter "github.com/Dreamacro/clash/adapters/inbound"
|
||||
"github.com/Dreamacro/clash/component/nat"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/dns"
|
||||
"github.com/Dreamacro/clash/log"
|
||||
@ -17,11 +18,16 @@ import (
|
||||
var (
|
||||
tunnel *Tunnel
|
||||
once sync.Once
|
||||
|
||||
// default timeout for UDP session
|
||||
udpTimeout = 60 * time.Second
|
||||
)
|
||||
|
||||
// Tunnel handle relay inbound proxy and outbound proxy
|
||||
type Tunnel struct {
|
||||
queue *channels.InfiniteChannel
|
||||
tcpQueue *channels.InfiniteChannel
|
||||
udpQueue *channels.InfiniteChannel
|
||||
natTable *nat.Table
|
||||
rules []C.Rule
|
||||
proxies map[string]C.Proxy
|
||||
configMux *sync.RWMutex
|
||||
@ -36,7 +42,12 @@ type Tunnel struct {
|
||||
|
||||
// Add request to queue
|
||||
func (t *Tunnel) Add(req C.ServerAdapter) {
|
||||
t.queue.In() <- req
|
||||
switch req.Metadata().NetWork {
|
||||
case C.TCP:
|
||||
t.tcpQueue.In() <- req
|
||||
case C.UDP:
|
||||
t.udpQueue.In() <- req
|
||||
}
|
||||
}
|
||||
|
||||
// Traffic return traffic of all connections
|
||||
@ -86,11 +97,18 @@ func (t *Tunnel) SetMode(mode Mode) {
|
||||
}
|
||||
|
||||
func (t *Tunnel) process() {
|
||||
queue := t.queue.Out()
|
||||
for {
|
||||
elm := <-queue
|
||||
go func() {
|
||||
queue := t.udpQueue.Out()
|
||||
for elm := range queue {
|
||||
conn := elm.(C.ServerAdapter)
|
||||
t.handleUDPConn(conn)
|
||||
}
|
||||
}()
|
||||
|
||||
queue := t.tcpQueue.Out()
|
||||
for elm := range queue {
|
||||
conn := elm.(C.ServerAdapter)
|
||||
go t.handleConn(conn)
|
||||
go t.handleTCPConn(conn)
|
||||
}
|
||||
}
|
||||
|
||||
@ -102,26 +120,7 @@ func (t *Tunnel) needLookupIP(metadata *C.Metadata) bool {
|
||||
return dns.DefaultResolver != nil && (dns.DefaultResolver.IsMapping() || dns.DefaultResolver.IsFakeIP()) && metadata.Host == "" && metadata.DstIP != nil
|
||||
}
|
||||
|
||||
func (t *Tunnel) handleConn(localConn C.ServerAdapter) {
|
||||
defer func() {
|
||||
var conn net.Conn
|
||||
switch adapter := localConn.(type) {
|
||||
case *InboundAdapter.HTTPAdapter:
|
||||
conn = adapter.Conn
|
||||
case *InboundAdapter.SocketAdapter:
|
||||
conn = adapter.Conn
|
||||
}
|
||||
if _, ok := conn.(*net.TCPConn); ok {
|
||||
localConn.Close()
|
||||
}
|
||||
}()
|
||||
|
||||
metadata := localConn.Metadata()
|
||||
if !metadata.Valid() {
|
||||
log.Warnln("[Metadata] not valid: %#v", metadata)
|
||||
return
|
||||
}
|
||||
|
||||
func (t *Tunnel) resolveMetadata(metadata *C.Metadata) (C.Proxy, C.Rule, error) {
|
||||
// preprocess enhanced-mode metadata
|
||||
if t.needLookupIP(metadata) {
|
||||
host, exist := dns.DefaultResolver.IPToHost(*metadata.DstIP)
|
||||
@ -146,43 +145,87 @@ func (t *Tunnel) handleConn(localConn C.ServerAdapter) {
|
||||
var err error
|
||||
proxy, rule, err = t.match(metadata)
|
||||
if err != nil {
|
||||
return
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
|
||||
switch metadata.NetWork {
|
||||
case C.TCP:
|
||||
t.handleTCPConn(localConn, metadata, proxy, rule)
|
||||
case C.UDP:
|
||||
t.handleUDPConn(localConn, metadata, proxy, rule)
|
||||
}
|
||||
return proxy, rule, nil
|
||||
}
|
||||
|
||||
func (t *Tunnel) handleUDPConn(localConn C.ServerAdapter, metadata *C.Metadata, proxy C.Proxy, rule C.Rule) {
|
||||
pc, addr := natTable.Get(localConn.RemoteAddr())
|
||||
if pc == nil {
|
||||
rawPc, nAddr, err := proxy.DialUDP(metadata)
|
||||
addr = nAddr
|
||||
pc = rawPc
|
||||
if err != nil {
|
||||
log.Warnln("dial %s error: %s", proxy.Name(), err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if rule != nil {
|
||||
log.Infoln("%s --> %v match %s using %s", metadata.SrcIP.String(), metadata.String(), rule.RuleType().String(), rawPc.Chains().String())
|
||||
} else {
|
||||
log.Infoln("%s --> %v doesn't match any rule using DIRECT", metadata.SrcIP.String(), metadata.String())
|
||||
}
|
||||
|
||||
natTable.Set(localConn.RemoteAddr(), pc, addr)
|
||||
go t.handleUDPToLocal(localConn, pc)
|
||||
func (t *Tunnel) handleUDPConn(localConn C.ServerAdapter) {
|
||||
metadata := localConn.Metadata()
|
||||
if !metadata.Valid() {
|
||||
log.Warnln("[Metadata] not valid: %#v", metadata)
|
||||
return
|
||||
}
|
||||
|
||||
t.handleUDPToRemote(localConn, pc, addr)
|
||||
src := localConn.RemoteAddr().String()
|
||||
dst := metadata.RemoteAddress()
|
||||
key := src + "-" + dst
|
||||
|
||||
pc, addr := t.natTable.Get(key)
|
||||
if pc != nil {
|
||||
t.handleUDPToRemote(localConn, pc, addr)
|
||||
return
|
||||
}
|
||||
|
||||
lockKey := key + "-lock"
|
||||
wg, loaded := t.natTable.GetOrCreateLock(lockKey)
|
||||
go func() {
|
||||
if !loaded {
|
||||
wg.Add(1)
|
||||
proxy, rule, err := t.resolveMetadata(metadata)
|
||||
if err != nil {
|
||||
log.Warnln("Parse metadata failed: %s", err.Error())
|
||||
t.natTable.Delete(lockKey)
|
||||
wg.Done()
|
||||
return
|
||||
}
|
||||
|
||||
rawPc, nAddr, err := proxy.DialUDP(metadata)
|
||||
if err != nil {
|
||||
log.Warnln("dial %s error: %s", proxy.Name(), err.Error())
|
||||
t.natTable.Delete(lockKey)
|
||||
wg.Done()
|
||||
return
|
||||
}
|
||||
pc = rawPc
|
||||
addr = nAddr
|
||||
|
||||
if rule != nil {
|
||||
log.Infoln("%s --> %v match %s using %s", metadata.SrcIP.String(), metadata.String(), rule.RuleType().String(), rawPc.Chains().String())
|
||||
} else {
|
||||
log.Infoln("%s --> %v doesn't match any rule using DIRECT", metadata.SrcIP.String(), metadata.String())
|
||||
}
|
||||
|
||||
t.natTable.Set(key, pc, addr)
|
||||
t.natTable.Delete(lockKey)
|
||||
wg.Done()
|
||||
go t.handleUDPToLocal(localConn, pc, key, udpTimeout)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
pc, addr := t.natTable.Get(key)
|
||||
if pc != nil {
|
||||
t.handleUDPToRemote(localConn, pc, addr)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func (t *Tunnel) handleTCPConn(localConn C.ServerAdapter, metadata *C.Metadata, proxy C.Proxy, rule C.Rule) {
|
||||
func (t *Tunnel) handleTCPConn(localConn C.ServerAdapter) {
|
||||
defer localConn.Close()
|
||||
|
||||
metadata := localConn.Metadata()
|
||||
if !metadata.Valid() {
|
||||
log.Warnln("[Metadata] not valid: %#v", metadata)
|
||||
return
|
||||
}
|
||||
|
||||
proxy, rule, err := t.resolveMetadata(metadata)
|
||||
if err != nil {
|
||||
log.Warnln("Parse metadata failed: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
remoteConn, err := proxy.Dial(metadata)
|
||||
if err != nil {
|
||||
log.Warnln("dial %s error: %s", proxy.Name(), err.Error())
|
||||
@ -253,7 +296,9 @@ func (t *Tunnel) match(metadata *C.Metadata) (C.Proxy, C.Rule, error) {
|
||||
|
||||
func newTunnel() *Tunnel {
|
||||
return &Tunnel{
|
||||
queue: channels.NewInfiniteChannel(),
|
||||
tcpQueue: channels.NewInfiniteChannel(),
|
||||
udpQueue: channels.NewInfiniteChannel(),
|
||||
natTable: nat.New(),
|
||||
proxies: make(map[string]C.Proxy),
|
||||
configMux: &sync.RWMutex{},
|
||||
traffic: C.NewTraffic(time.Second),
|
||||
|
Reference in New Issue
Block a user