Feature: add TCP TPROXY support (#1049)
This commit is contained in:
@ -17,26 +17,30 @@ var (
|
||||
allowLan = false
|
||||
bindAddress = "*"
|
||||
|
||||
socksListener *socks.SockListener
|
||||
socksUDPListener *socks.SockUDPListener
|
||||
httpListener *http.HttpListener
|
||||
redirListener *redir.RedirListener
|
||||
redirUDPListener *redir.RedirUDPListener
|
||||
mixedListener *mixed.MixedListener
|
||||
mixedUDPLister *socks.SockUDPListener
|
||||
socksListener *socks.SockListener
|
||||
socksUDPListener *socks.SockUDPListener
|
||||
httpListener *http.HttpListener
|
||||
redirListener *redir.RedirListener
|
||||
redirUDPListener *redir.RedirUDPListener
|
||||
tproxyListener *redir.TProxyListener
|
||||
tproxyUDPListener *redir.RedirUDPListener
|
||||
mixedListener *mixed.MixedListener
|
||||
mixedUDPLister *socks.SockUDPListener
|
||||
|
||||
// lock for recreate function
|
||||
socksMux sync.Mutex
|
||||
httpMux sync.Mutex
|
||||
redirMux sync.Mutex
|
||||
mixedMux sync.Mutex
|
||||
socksMux sync.Mutex
|
||||
httpMux sync.Mutex
|
||||
redirMux sync.Mutex
|
||||
tproxyMux sync.Mutex
|
||||
mixedMux sync.Mutex
|
||||
)
|
||||
|
||||
type Ports struct {
|
||||
Port int `json:"port"`
|
||||
SocksPort int `json:"socks-port"`
|
||||
RedirPort int `json:"redir-port"`
|
||||
MixedPort int `json:"mixed-port"`
|
||||
Port int `json:"port"`
|
||||
SocksPort int `json:"socks-port"`
|
||||
RedirPort int `json:"redir-port"`
|
||||
TProxyPort int `json:"tproxy-port"`
|
||||
MixedPort int `json:"mixed-port"`
|
||||
}
|
||||
|
||||
func AllowLan() bool {
|
||||
@ -174,6 +178,46 @@ func ReCreateRedir(port int) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func ReCreateTProxy(port int) error {
|
||||
tproxyMux.Lock()
|
||||
defer tproxyMux.Unlock()
|
||||
|
||||
addr := genAddr(bindAddress, port, allowLan)
|
||||
|
||||
if tproxyListener != nil {
|
||||
if tproxyListener.Address() == addr {
|
||||
return nil
|
||||
}
|
||||
tproxyListener.Close()
|
||||
tproxyListener = nil
|
||||
}
|
||||
|
||||
if tproxyUDPListener != nil {
|
||||
if tproxyUDPListener.Address() == addr {
|
||||
return nil
|
||||
}
|
||||
tproxyUDPListener.Close()
|
||||
tproxyUDPListener = nil
|
||||
}
|
||||
|
||||
if portIsZero(addr) {
|
||||
return nil
|
||||
}
|
||||
|
||||
var err error
|
||||
tproxyListener, err = redir.NewTProxy(addr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tproxyUDPListener, err = redir.NewRedirUDPProxy(addr)
|
||||
if err != nil {
|
||||
log.Warnln("Failed to start TProxy UDP Listener: %s", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func ReCreateMixed(port int) error {
|
||||
mixedMux.Lock()
|
||||
defer mixedMux.Unlock()
|
||||
@ -245,6 +289,12 @@ func GetPorts() *Ports {
|
||||
ports.RedirPort = port
|
||||
}
|
||||
|
||||
if tproxyListener != nil {
|
||||
_, portStr, _ := net.SplitHostPort(tproxyListener.Address())
|
||||
port, _ := strconv.Atoi(portStr)
|
||||
ports.TProxyPort = port
|
||||
}
|
||||
|
||||
if mixedListener != nil {
|
||||
_, portStr, _ := net.SplitHostPort(mixedListener.Address())
|
||||
port, _ := strconv.Atoi(portStr)
|
||||
|
71
proxy/redir/tproxy.go
Normal file
71
proxy/redir/tproxy.go
Normal file
@ -0,0 +1,71 @@
|
||||
package redir
|
||||
|
||||
import (
|
||||
"net"
|
||||
|
||||
"github.com/Dreamacro/clash/adapters/inbound"
|
||||
"github.com/Dreamacro/clash/component/socks5"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/log"
|
||||
"github.com/Dreamacro/clash/tunnel"
|
||||
)
|
||||
|
||||
type TProxyListener struct {
|
||||
net.Listener
|
||||
address string
|
||||
closed bool
|
||||
}
|
||||
|
||||
func NewTProxy(addr string) (*TProxyListener, error) {
|
||||
l, err := net.Listen("tcp", addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tl := l.(*net.TCPListener)
|
||||
rc, err := tl.SyscallConn()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = setsockopt(rc, addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rl := &TProxyListener{
|
||||
Listener: l,
|
||||
address: addr,
|
||||
}
|
||||
|
||||
go func() {
|
||||
log.Infoln("TProxy server listening at: %s", addr)
|
||||
for {
|
||||
c, err := l.Accept()
|
||||
if err != nil {
|
||||
if rl.closed {
|
||||
break
|
||||
}
|
||||
continue
|
||||
}
|
||||
go rl.handleRedir(c)
|
||||
}
|
||||
}()
|
||||
|
||||
return rl, nil
|
||||
}
|
||||
|
||||
func (l *TProxyListener) Close() {
|
||||
l.closed = true
|
||||
l.Listener.Close()
|
||||
}
|
||||
|
||||
func (l *TProxyListener) Address() string {
|
||||
return l.address
|
||||
}
|
||||
|
||||
func (l *TProxyListener) handleRedir(conn net.Conn) {
|
||||
target := socks5.ParseAddrToSocksAddr(conn.LocalAddr())
|
||||
conn.(*net.TCPConn).SetKeepAlive(true)
|
||||
tunnel.Add(inbound.NewSocket(target, conn, C.TPROXY))
|
||||
}
|
40
proxy/redir/tproxy_linux.go
Normal file
40
proxy/redir/tproxy_linux.go
Normal file
@ -0,0 +1,40 @@
|
||||
// +build linux
|
||||
|
||||
package redir
|
||||
|
||||
import (
|
||||
"net"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
func setsockopt(rc syscall.RawConn, addr string) error {
|
||||
isIPv6 := true
|
||||
host, _, err := net.SplitHostPort(addr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ip := net.ParseIP(host)
|
||||
if ip != nil && ip.To4() != nil {
|
||||
isIPv6 = false
|
||||
}
|
||||
|
||||
rc.Control(func(fd uintptr) {
|
||||
err = syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, syscall.SO_REUSEADDR, 1)
|
||||
|
||||
if err == nil {
|
||||
err = syscall.SetsockoptInt(int(fd), syscall.SOL_IP, syscall.IP_TRANSPARENT, 1)
|
||||
}
|
||||
if err == nil && isIPv6 {
|
||||
err = syscall.SetsockoptInt(int(fd), syscall.SOL_IPV6, IPV6_TRANSPARENT, 1)
|
||||
}
|
||||
|
||||
if err == nil {
|
||||
err = syscall.SetsockoptInt(int(fd), syscall.SOL_IP, syscall.IP_RECVORIGDSTADDR, 1)
|
||||
}
|
||||
if err == nil && isIPv6 {
|
||||
err = syscall.SetsockoptInt(int(fd), syscall.SOL_IPV6, IPV6_RECVORIGDSTADDR, 1)
|
||||
}
|
||||
})
|
||||
|
||||
return err
|
||||
}
|
12
proxy/redir/tproxy_other.go
Normal file
12
proxy/redir/tproxy_other.go
Normal file
@ -0,0 +1,12 @@
|
||||
// +build !linux
|
||||
|
||||
package redir
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
func setsockopt(rc syscall.RawConn, addr string) error {
|
||||
return errors.New("Not supported on current platform")
|
||||
}
|
@ -26,7 +26,12 @@ func NewRedirUDPProxy(addr string) (*RedirUDPListener, error) {
|
||||
|
||||
c := l.(*net.UDPConn)
|
||||
|
||||
err = setsockopt(c, addr)
|
||||
rc, err := c.SyscallConn()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = setsockopt(rc, addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -14,43 +14,6 @@ const (
|
||||
IPV6_RECVORIGDSTADDR = 0x4a
|
||||
)
|
||||
|
||||
func setsockopt(c *net.UDPConn, addr string) error {
|
||||
isIPv6 := true
|
||||
host, _, err := net.SplitHostPort(addr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ip := net.ParseIP(host)
|
||||
if ip != nil && ip.To4() != nil {
|
||||
isIPv6 = false
|
||||
}
|
||||
|
||||
rc, err := c.SyscallConn()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
rc.Control(func(fd uintptr) {
|
||||
err = syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, syscall.SO_REUSEADDR, 1)
|
||||
|
||||
if err == nil {
|
||||
err = syscall.SetsockoptInt(int(fd), syscall.SOL_IP, syscall.IP_TRANSPARENT, 1)
|
||||
}
|
||||
if err == nil && isIPv6 {
|
||||
err = syscall.SetsockoptInt(int(fd), syscall.SOL_IPV6, IPV6_TRANSPARENT, 1)
|
||||
}
|
||||
|
||||
if err == nil {
|
||||
err = syscall.SetsockoptInt(int(fd), syscall.SOL_IP, syscall.IP_RECVORIGDSTADDR, 1)
|
||||
}
|
||||
if err == nil && isIPv6 {
|
||||
err = syscall.SetsockoptInt(int(fd), syscall.SOL_IPV6, IPV6_RECVORIGDSTADDR, 1)
|
||||
}
|
||||
})
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func getOrigDst(oob []byte, oobn int) (*net.UDPAddr, error) {
|
||||
msgs, err := syscall.ParseSocketControlMessage(oob[:oobn])
|
||||
if err != nil {
|
||||
|
@ -7,10 +7,6 @@ import (
|
||||
"net"
|
||||
)
|
||||
|
||||
func setsockopt(c *net.UDPConn, addr string) error {
|
||||
return errors.New("UDP redir not supported on current platform")
|
||||
}
|
||||
|
||||
func getOrigDst(oob []byte, oobn int) (*net.UDPAddr, error) {
|
||||
return nil, errors.New("UDP redir not supported on current platform")
|
||||
}
|
||||
|
Reference in New Issue
Block a user