mirror of
https://github.com/MetaCubeX/Clash.Meta.git
synced 2025-04-05 14:13:30 +03:00
Feature: add TCP TPROXY support (#1049)
This commit is contained in:
parent
87e4d94290
commit
83efe2ae57
11 changed files with 207 additions and 57 deletions
|
@ -38,6 +38,7 @@ type Inbound struct {
|
|||
Port int `json:"port"`
|
||||
SocksPort int `json:"socks-port"`
|
||||
RedirPort int `json:"redir-port"`
|
||||
TProxyPort int `json:"tproxy-port"`
|
||||
MixedPort int `json:"mixed-port"`
|
||||
Authentication []string `json:"authentication"`
|
||||
AllowLan bool `json:"allow-lan"`
|
||||
|
@ -111,6 +112,7 @@ type RawConfig struct {
|
|||
Port int `yaml:"port"`
|
||||
SocksPort int `yaml:"socks-port"`
|
||||
RedirPort int `yaml:"redir-port"`
|
||||
TProxyPort int `yaml:"tproxy-port"`
|
||||
MixedPort int `yaml:"mixed-port"`
|
||||
Authentication []string `yaml:"authentication"`
|
||||
AllowLan bool `yaml:"allow-lan"`
|
||||
|
@ -234,6 +236,7 @@ func parseGeneral(cfg *RawConfig) (*General, error) {
|
|||
Port: cfg.Port,
|
||||
SocksPort: cfg.SocksPort,
|
||||
RedirPort: cfg.RedirPort,
|
||||
TProxyPort: cfg.TProxyPort,
|
||||
MixedPort: cfg.MixedPort,
|
||||
AllowLan: cfg.AllowLan,
|
||||
BindAddress: cfg.BindAddress,
|
||||
|
|
|
@ -19,6 +19,7 @@ const (
|
|||
HTTPCONNECT
|
||||
SOCKS
|
||||
REDIR
|
||||
TPROXY
|
||||
)
|
||||
|
||||
type NetWork int
|
||||
|
@ -46,6 +47,8 @@ func (t Type) String() string {
|
|||
return "Socks5"
|
||||
case REDIR:
|
||||
return "Redir"
|
||||
case TPROXY:
|
||||
return "TProxy"
|
||||
default:
|
||||
return "Unknown"
|
||||
}
|
||||
|
|
|
@ -86,6 +86,7 @@ func GetGeneral() *config.General {
|
|||
Port: ports.Port,
|
||||
SocksPort: ports.SocksPort,
|
||||
RedirPort: ports.RedirPort,
|
||||
TProxyPort: ports.TProxyPort,
|
||||
MixedPort: ports.MixedPort,
|
||||
Authentication: authenticator,
|
||||
AllowLan: P.AllowLan(),
|
||||
|
@ -191,6 +192,10 @@ func updateGeneral(general *config.General, force bool) {
|
|||
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 {
|
||||
log.Errorln("Start Mixed(http and socks5) server error: %s", err.Error())
|
||||
}
|
||||
|
|
|
@ -26,6 +26,7 @@ type configSchema struct {
|
|||
Port *int `json:"port"`
|
||||
SocksPort *int `json:"socks-port"`
|
||||
RedirPort *int `json:"redir-port"`
|
||||
TProxyPort *int `json:"tproxy-port"`
|
||||
MixedPort *int `json:"mixed-port"`
|
||||
AllowLan *bool `json:"allow-lan"`
|
||||
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.ReCreateSocks(pointerOrDefault(general.SocksPort, ports.SocksPort))
|
||||
P.ReCreateRedir(pointerOrDefault(general.RedirPort, ports.RedirPort))
|
||||
P.ReCreateTProxy(pointerOrDefault(general.TProxyPort, ports.TProxyPort))
|
||||
P.ReCreateMixed(pointerOrDefault(general.MixedPort, ports.MixedPort))
|
||||
|
||||
if general.Mode != nil {
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue