Feature: add TCP TPROXY support (#1049)
This commit is contained in:
parent
87e4d94290
commit
83efe2ae57
@ -38,6 +38,7 @@ type Inbound struct {
|
|||||||
Port int `json:"port"`
|
Port int `json:"port"`
|
||||||
SocksPort int `json:"socks-port"`
|
SocksPort int `json:"socks-port"`
|
||||||
RedirPort int `json:"redir-port"`
|
RedirPort int `json:"redir-port"`
|
||||||
|
TProxyPort int `json:"tproxy-port"`
|
||||||
MixedPort int `json:"mixed-port"`
|
MixedPort int `json:"mixed-port"`
|
||||||
Authentication []string `json:"authentication"`
|
Authentication []string `json:"authentication"`
|
||||||
AllowLan bool `json:"allow-lan"`
|
AllowLan bool `json:"allow-lan"`
|
||||||
@ -111,6 +112,7 @@ type RawConfig struct {
|
|||||||
Port int `yaml:"port"`
|
Port int `yaml:"port"`
|
||||||
SocksPort int `yaml:"socks-port"`
|
SocksPort int `yaml:"socks-port"`
|
||||||
RedirPort int `yaml:"redir-port"`
|
RedirPort int `yaml:"redir-port"`
|
||||||
|
TProxyPort int `yaml:"tproxy-port"`
|
||||||
MixedPort int `yaml:"mixed-port"`
|
MixedPort int `yaml:"mixed-port"`
|
||||||
Authentication []string `yaml:"authentication"`
|
Authentication []string `yaml:"authentication"`
|
||||||
AllowLan bool `yaml:"allow-lan"`
|
AllowLan bool `yaml:"allow-lan"`
|
||||||
@ -234,6 +236,7 @@ func parseGeneral(cfg *RawConfig) (*General, error) {
|
|||||||
Port: cfg.Port,
|
Port: cfg.Port,
|
||||||
SocksPort: cfg.SocksPort,
|
SocksPort: cfg.SocksPort,
|
||||||
RedirPort: cfg.RedirPort,
|
RedirPort: cfg.RedirPort,
|
||||||
|
TProxyPort: cfg.TProxyPort,
|
||||||
MixedPort: cfg.MixedPort,
|
MixedPort: cfg.MixedPort,
|
||||||
AllowLan: cfg.AllowLan,
|
AllowLan: cfg.AllowLan,
|
||||||
BindAddress: cfg.BindAddress,
|
BindAddress: cfg.BindAddress,
|
||||||
|
@ -19,6 +19,7 @@ const (
|
|||||||
HTTPCONNECT
|
HTTPCONNECT
|
||||||
SOCKS
|
SOCKS
|
||||||
REDIR
|
REDIR
|
||||||
|
TPROXY
|
||||||
)
|
)
|
||||||
|
|
||||||
type NetWork int
|
type NetWork int
|
||||||
@ -46,6 +47,8 @@ func (t Type) String() string {
|
|||||||
return "Socks5"
|
return "Socks5"
|
||||||
case REDIR:
|
case REDIR:
|
||||||
return "Redir"
|
return "Redir"
|
||||||
|
case TPROXY:
|
||||||
|
return "TProxy"
|
||||||
default:
|
default:
|
||||||
return "Unknown"
|
return "Unknown"
|
||||||
}
|
}
|
||||||
|
@ -86,6 +86,7 @@ func GetGeneral() *config.General {
|
|||||||
Port: ports.Port,
|
Port: ports.Port,
|
||||||
SocksPort: ports.SocksPort,
|
SocksPort: ports.SocksPort,
|
||||||
RedirPort: ports.RedirPort,
|
RedirPort: ports.RedirPort,
|
||||||
|
TProxyPort: ports.TProxyPort,
|
||||||
MixedPort: ports.MixedPort,
|
MixedPort: ports.MixedPort,
|
||||||
Authentication: authenticator,
|
Authentication: authenticator,
|
||||||
AllowLan: P.AllowLan(),
|
AllowLan: P.AllowLan(),
|
||||||
@ -191,6 +192,10 @@ func updateGeneral(general *config.General, force bool) {
|
|||||||
log.Errorln("Start Redir server error: %s", err.Error())
|
log.Errorln("Start Redir server error: %s", err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := P.ReCreateTProxy(general.TProxyPort); err != nil {
|
||||||
|
log.Errorln("Start TProxy server error: %s", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
if err := P.ReCreateMixed(general.MixedPort); err != nil {
|
if err := P.ReCreateMixed(general.MixedPort); err != nil {
|
||||||
log.Errorln("Start Mixed(http and socks5) server error: %s", err.Error())
|
log.Errorln("Start Mixed(http and socks5) server error: %s", err.Error())
|
||||||
}
|
}
|
||||||
|
@ -26,6 +26,7 @@ type configSchema struct {
|
|||||||
Port *int `json:"port"`
|
Port *int `json:"port"`
|
||||||
SocksPort *int `json:"socks-port"`
|
SocksPort *int `json:"socks-port"`
|
||||||
RedirPort *int `json:"redir-port"`
|
RedirPort *int `json:"redir-port"`
|
||||||
|
TProxyPort *int `json:"tproxy-port"`
|
||||||
MixedPort *int `json:"mixed-port"`
|
MixedPort *int `json:"mixed-port"`
|
||||||
AllowLan *bool `json:"allow-lan"`
|
AllowLan *bool `json:"allow-lan"`
|
||||||
BindAddress *string `json:"bind-address"`
|
BindAddress *string `json:"bind-address"`
|
||||||
@ -66,6 +67,7 @@ func patchConfigs(w http.ResponseWriter, r *http.Request) {
|
|||||||
P.ReCreateHTTP(pointerOrDefault(general.Port, ports.Port))
|
P.ReCreateHTTP(pointerOrDefault(general.Port, ports.Port))
|
||||||
P.ReCreateSocks(pointerOrDefault(general.SocksPort, ports.SocksPort))
|
P.ReCreateSocks(pointerOrDefault(general.SocksPort, ports.SocksPort))
|
||||||
P.ReCreateRedir(pointerOrDefault(general.RedirPort, ports.RedirPort))
|
P.ReCreateRedir(pointerOrDefault(general.RedirPort, ports.RedirPort))
|
||||||
|
P.ReCreateTProxy(pointerOrDefault(general.TProxyPort, ports.TProxyPort))
|
||||||
P.ReCreateMixed(pointerOrDefault(general.MixedPort, ports.MixedPort))
|
P.ReCreateMixed(pointerOrDefault(general.MixedPort, ports.MixedPort))
|
||||||
|
|
||||||
if general.Mode != nil {
|
if general.Mode != nil {
|
||||||
|
@ -22,6 +22,8 @@ var (
|
|||||||
httpListener *http.HttpListener
|
httpListener *http.HttpListener
|
||||||
redirListener *redir.RedirListener
|
redirListener *redir.RedirListener
|
||||||
redirUDPListener *redir.RedirUDPListener
|
redirUDPListener *redir.RedirUDPListener
|
||||||
|
tproxyListener *redir.TProxyListener
|
||||||
|
tproxyUDPListener *redir.RedirUDPListener
|
||||||
mixedListener *mixed.MixedListener
|
mixedListener *mixed.MixedListener
|
||||||
mixedUDPLister *socks.SockUDPListener
|
mixedUDPLister *socks.SockUDPListener
|
||||||
|
|
||||||
@ -29,6 +31,7 @@ var (
|
|||||||
socksMux sync.Mutex
|
socksMux sync.Mutex
|
||||||
httpMux sync.Mutex
|
httpMux sync.Mutex
|
||||||
redirMux sync.Mutex
|
redirMux sync.Mutex
|
||||||
|
tproxyMux sync.Mutex
|
||||||
mixedMux sync.Mutex
|
mixedMux sync.Mutex
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -36,6 +39,7 @@ type Ports struct {
|
|||||||
Port int `json:"port"`
|
Port int `json:"port"`
|
||||||
SocksPort int `json:"socks-port"`
|
SocksPort int `json:"socks-port"`
|
||||||
RedirPort int `json:"redir-port"`
|
RedirPort int `json:"redir-port"`
|
||||||
|
TProxyPort int `json:"tproxy-port"`
|
||||||
MixedPort int `json:"mixed-port"`
|
MixedPort int `json:"mixed-port"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -174,6 +178,46 @@ func ReCreateRedir(port int) error {
|
|||||||
return nil
|
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 {
|
func ReCreateMixed(port int) error {
|
||||||
mixedMux.Lock()
|
mixedMux.Lock()
|
||||||
defer mixedMux.Unlock()
|
defer mixedMux.Unlock()
|
||||||
@ -245,6 +289,12 @@ func GetPorts() *Ports {
|
|||||||
ports.RedirPort = port
|
ports.RedirPort = port
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if tproxyListener != nil {
|
||||||
|
_, portStr, _ := net.SplitHostPort(tproxyListener.Address())
|
||||||
|
port, _ := strconv.Atoi(portStr)
|
||||||
|
ports.TProxyPort = port
|
||||||
|
}
|
||||||
|
|
||||||
if mixedListener != nil {
|
if mixedListener != nil {
|
||||||
_, portStr, _ := net.SplitHostPort(mixedListener.Address())
|
_, portStr, _ := net.SplitHostPort(mixedListener.Address())
|
||||||
port, _ := strconv.Atoi(portStr)
|
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)
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -14,43 +14,6 @@ const (
|
|||||||
IPV6_RECVORIGDSTADDR = 0x4a
|
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) {
|
func getOrigDst(oob []byte, oobn int) (*net.UDPAddr, error) {
|
||||||
msgs, err := syscall.ParseSocketControlMessage(oob[:oobn])
|
msgs, err := syscall.ParseSocketControlMessage(oob[:oobn])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -7,10 +7,6 @@ import (
|
|||||||
"net"
|
"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) {
|
func getOrigDst(oob []byte, oobn int) (*net.UDPAddr, error) {
|
||||||
return nil, errors.New("UDP redir not supported on current platform")
|
return nil, errors.New("UDP redir not supported on current platform")
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user