Feature: add tunnels

This commit is contained in:
Dreamacro
2022-11-18 22:57:33 +08:00
parent de264c42a8
commit 5b07d7b776
10 changed files with 416 additions and 35 deletions

View File

@ -1,34 +1,41 @@
package proxy
package listener
import (
"fmt"
"net"
"strconv"
"strings"
"sync"
"github.com/Dreamacro/clash/adapter/inbound"
"github.com/Dreamacro/clash/config"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/listener/http"
"github.com/Dreamacro/clash/listener/mixed"
"github.com/Dreamacro/clash/listener/redir"
"github.com/Dreamacro/clash/listener/socks"
"github.com/Dreamacro/clash/listener/tproxy"
"github.com/Dreamacro/clash/listener/tunnel"
"github.com/Dreamacro/clash/log"
"github.com/samber/lo"
)
var (
allowLan = false
bindAddress = "*"
socksListener *socks.Listener
socksUDPListener *socks.UDPListener
httpListener *http.Listener
redirListener *redir.Listener
redirUDPListener *tproxy.UDPListener
tproxyListener *tproxy.Listener
tproxyUDPListener *tproxy.UDPListener
mixedListener *mixed.Listener
mixedUDPLister *socks.UDPListener
socksListener *socks.Listener
socksUDPListener *socks.UDPListener
httpListener *http.Listener
redirListener *redir.Listener
redirUDPListener *tproxy.UDPListener
tproxyListener *tproxy.Listener
tproxyUDPListener *tproxy.UDPListener
mixedListener *mixed.Listener
mixedUDPLister *socks.UDPListener
tunnelTCPListeners = map[string]*tunnel.Listener{}
tunnelUDPListeners = map[string]*tunnel.PacketConn{}
// lock for recreate function
socksMux sync.Mutex
@ -36,6 +43,7 @@ var (
redirMux sync.Mutex
tproxyMux sync.Mutex
mixedMux sync.Mutex
tunnelMux sync.Mutex
)
type Ports struct {
@ -301,6 +309,95 @@ func ReCreateMixed(port int, tcpIn chan<- C.ConnContext, udpIn chan<- *inbound.P
log.Infoln("Mixed(http+socks) proxy listening at: %s", mixedListener.Address())
}
func PatchTunnel(tunnels []config.Tunnel, tcpIn chan<- C.ConnContext, udpIn chan<- *inbound.PacketAdapter) {
tunnelMux.Lock()
defer tunnelMux.Unlock()
type addrProxy struct {
network string
addr string
target string
proxy string
}
tcpOld := lo.Map(
lo.Keys(tunnelTCPListeners),
func(key string, _ int) addrProxy {
parts := strings.Split(key, "/")
return addrProxy{
network: "tcp",
addr: parts[0],
target: parts[1],
proxy: parts[2],
}
},
)
udpOld := lo.Map(
lo.Keys(tunnelUDPListeners),
func(key string, _ int) addrProxy {
parts := strings.Split(key, "/")
return addrProxy{
network: "udp",
addr: parts[0],
target: parts[1],
proxy: parts[2],
}
},
)
oldElm := lo.Union(tcpOld, udpOld)
newElm := lo.FlatMap(
tunnels,
func(tunnel config.Tunnel, _ int) []addrProxy {
return lo.Map(
tunnel.Network,
func(network string, _ int) addrProxy {
return addrProxy{
network: network,
addr: tunnel.Address,
target: tunnel.Target,
proxy: tunnel.Proxy,
}
},
)
},
)
needClose, needCreate := lo.Difference(oldElm, newElm)
for _, elm := range needClose {
key := fmt.Sprintf("%s/%s/%s", elm.addr, elm.target, elm.proxy)
if elm.network == "tcp" {
tunnelTCPListeners[key].Close()
delete(tunnelTCPListeners, key)
} else {
tunnelUDPListeners[key].Close()
delete(tunnelUDPListeners, key)
}
}
for _, elm := range needCreate {
key := fmt.Sprintf("%s/%s/%s", elm.addr, elm.target, elm.proxy)
if elm.network == "tcp" {
l, err := tunnel.New(elm.addr, elm.target, elm.proxy, tcpIn)
if err != nil {
log.Errorln("Start tunnel %s error: %w", elm.target, err)
continue
}
tunnelTCPListeners[key] = l
log.Infoln("Tunnel(tcp/%s) proxy %s listening at: %s", elm.target, elm.proxy, tunnelTCPListeners[key].Address())
} else {
l, err := tunnel.NewUDP(elm.addr, elm.target, elm.proxy, udpIn)
if err != nil {
log.Errorln("Start tunnel %s error: %w", elm.target, err)
continue
}
tunnelUDPListeners[key] = l
log.Infoln("Tunnel(udp/%s) proxy %s listening at: %s", elm.target, elm.proxy, tunnelUDPListeners[key].Address())
}
}
}
// GetPorts return the ports of proxy servers
func GetPorts() *Ports {
ports := &Ports{}

31
listener/tunnel/packet.go Normal file
View File

@ -0,0 +1,31 @@
package tunnel
import (
"net"
"github.com/Dreamacro/clash/common/pool"
)
type packet struct {
pc net.PacketConn
rAddr net.Addr
payload []byte
}
func (c *packet) Data() []byte {
return c.payload
}
// WriteBack write UDP packet with source(ip, port) = `addr`
func (c *packet) WriteBack(b []byte, addr net.Addr) (n int, err error) {
return c.pc.WriteTo(b, c.rAddr)
}
// LocalAddr returns the source IP/Port of UDP Packet
func (c *packet) LocalAddr() net.Addr {
return c.rAddr
}
func (c *packet) Drop() {
pool.Put(c.payload)
}

75
listener/tunnel/tcp.go Normal file
View File

@ -0,0 +1,75 @@
package tunnel
import (
"fmt"
"net"
"github.com/Dreamacro/clash/adapter/inbound"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/transport/socks5"
)
type Listener struct {
listener net.Listener
addr string
target socks5.Addr
proxy string
closed bool
}
// RawAddress implements C.Listener
func (l *Listener) RawAddress() string {
return l.addr
}
// Address implements C.Listener
func (l *Listener) Address() string {
return l.listener.Addr().String()
}
// Close implements C.Listener
func (l *Listener) Close() error {
l.closed = true
return l.listener.Close()
}
func (l *Listener) handleTCP(conn net.Conn, in chan<- C.ConnContext) {
conn.(*net.TCPConn).SetKeepAlive(true)
ctx := inbound.NewSocket(l.target, conn, C.TUNNEL)
ctx.Metadata().SpecialProxy = l.proxy
in <- ctx
}
func New(addr, target, proxy string, in chan<- C.ConnContext) (*Listener, error) {
l, err := net.Listen("tcp", addr)
if err != nil {
return nil, err
}
targetAddr := socks5.ParseAddr(target)
if targetAddr == nil {
return nil, fmt.Errorf("invalid target address %s", target)
}
rl := &Listener{
listener: l,
target: targetAddr,
proxy: proxy,
addr: addr,
}
go func() {
for {
c, err := l.Accept()
if err != nil {
if rl.closed {
break
}
continue
}
go rl.handleTCP(c, in)
}
}()
return rl, nil
}

85
listener/tunnel/udp.go Normal file
View File

@ -0,0 +1,85 @@
package tunnel
import (
"fmt"
"net"
"github.com/Dreamacro/clash/adapter/inbound"
"github.com/Dreamacro/clash/common/pool"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/transport/socks5"
)
type PacketConn struct {
conn net.PacketConn
addr string
target socks5.Addr
proxy string
closed bool
}
// RawAddress implements C.Listener
func (l *PacketConn) RawAddress() string {
return l.addr
}
// Address implements C.Listener
func (l *PacketConn) Address() string {
return l.conn.LocalAddr().String()
}
// Close implements C.Listener
func (l *PacketConn) Close() error {
l.closed = true
return l.conn.Close()
}
func NewUDP(addr, target, proxy string, in chan<- *inbound.PacketAdapter) (*PacketConn, error) {
l, err := net.ListenPacket("udp", addr)
if err != nil {
return nil, err
}
targetAddr := socks5.ParseAddr(target)
if targetAddr == nil {
return nil, fmt.Errorf("invalid target address %s", target)
}
sl := &PacketConn{
conn: l,
target: targetAddr,
proxy: proxy,
addr: addr,
}
go func() {
for {
buf := pool.Get(pool.UDPBufferSize)
n, remoteAddr, err := l.ReadFrom(buf)
if err != nil {
pool.Put(buf)
if sl.closed {
break
}
continue
}
sl.handleUDP(l, in, buf[:n], remoteAddr)
}
}()
return sl, nil
}
func (l *PacketConn) handleUDP(pc net.PacketConn, in chan<- *inbound.PacketAdapter, buf []byte, addr net.Addr) {
packet := &packet{
pc: pc,
rAddr: addr,
payload: buf,
}
ctx := inbound.NewPacket(l.target, packet, C.TUNNEL)
ctx.Metadata().SpecialProxy = l.proxy
select {
case in <- ctx:
default:
}
}