New: redir proxy
This commit is contained in:
parent
a0dd4ffddc
commit
13cf2bb270
8 changed files with 254 additions and 0 deletions
|
@ -13,6 +13,7 @@ import (
|
||||||
"github.com/Dreamacro/clash/observable"
|
"github.com/Dreamacro/clash/observable"
|
||||||
R "github.com/Dreamacro/clash/rules"
|
R "github.com/Dreamacro/clash/rules"
|
||||||
|
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
"gopkg.in/ini.v1"
|
"gopkg.in/ini.v1"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -25,6 +26,7 @@ var (
|
||||||
type General struct {
|
type General struct {
|
||||||
Port int
|
Port int
|
||||||
SocksPort int
|
SocksPort int
|
||||||
|
RedirPort int
|
||||||
AllowLan bool
|
AllowLan bool
|
||||||
Mode Mode
|
Mode Mode
|
||||||
LogLevel C.LogLevel
|
LogLevel C.LogLevel
|
||||||
|
@ -34,6 +36,7 @@ type General struct {
|
||||||
type ProxyConfig struct {
|
type ProxyConfig struct {
|
||||||
Port *int
|
Port *int
|
||||||
SocksPort *int
|
SocksPort *int
|
||||||
|
RedirPort *int
|
||||||
AllowLan *bool
|
AllowLan *bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -136,6 +139,7 @@ func (c *Config) parseGeneral(cfg *ini.File) error {
|
||||||
|
|
||||||
port := general.Key("port").RangeInt(0, 1, 65535)
|
port := general.Key("port").RangeInt(0, 1, 65535)
|
||||||
socksPort := general.Key("socks-port").RangeInt(0, 1, 65535)
|
socksPort := general.Key("socks-port").RangeInt(0, 1, 65535)
|
||||||
|
redirPort := general.Key("redir-port").RangeInt(0, 1, 65535)
|
||||||
allowLan := general.Key("allow-lan").MustBool()
|
allowLan := general.Key("allow-lan").MustBool()
|
||||||
logLevelString := general.Key("log-level").MustString(C.INFO.String())
|
logLevelString := general.Key("log-level").MustString(C.INFO.String())
|
||||||
modeString := general.Key("mode").MustString(Rule.String())
|
modeString := general.Key("mode").MustString(Rule.String())
|
||||||
|
@ -153,6 +157,7 @@ func (c *Config) parseGeneral(cfg *ini.File) error {
|
||||||
c.general = &General{
|
c.general = &General{
|
||||||
Port: port,
|
Port: port,
|
||||||
SocksPort: socksPort,
|
SocksPort: socksPort,
|
||||||
|
RedirPort: redirPort,
|
||||||
AllowLan: allowLan,
|
AllowLan: allowLan,
|
||||||
Mode: mode,
|
Mode: mode,
|
||||||
LogLevel: logLevel,
|
LogLevel: logLevel,
|
||||||
|
@ -171,6 +176,7 @@ func (c *Config) UpdateGeneral(general General) {
|
||||||
c.UpdateProxy(ProxyConfig{
|
c.UpdateProxy(ProxyConfig{
|
||||||
Port: &general.Port,
|
Port: &general.Port,
|
||||||
SocksPort: &general.SocksPort,
|
SocksPort: &general.SocksPort,
|
||||||
|
RedirPort: &general.RedirPort,
|
||||||
AllowLan: &general.AllowLan,
|
AllowLan: &general.AllowLan,
|
||||||
})
|
})
|
||||||
c.event <- &Event{Type: "mode", Payload: general.Mode}
|
c.event <- &Event{Type: "mode", Payload: general.Mode}
|
||||||
|
@ -192,6 +198,11 @@ func (c *Config) UpdateProxy(pc ProxyConfig) {
|
||||||
c.general.SocksPort = *pc.SocksPort
|
c.general.SocksPort = *pc.SocksPort
|
||||||
c.event <- &Event{Type: "socks-addr", Payload: genAddr(*pc.SocksPort, c.general.AllowLan)}
|
c.event <- &Event{Type: "socks-addr", Payload: genAddr(*pc.SocksPort, c.general.AllowLan)}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (pc.AllowLan != nil || pc.RedirPort != nil) && *pc.RedirPort != 0 {
|
||||||
|
c.general.RedirPort = *pc.RedirPort
|
||||||
|
c.event <- &Event{Type: "redir-addr", Payload: genAddr(*pc.RedirPort, c.general.AllowLan)}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Config) parseProxies(cfg *ini.File) error {
|
func (c *Config) parseProxies(cfg *ini.File) error {
|
||||||
|
@ -311,14 +322,22 @@ func (c *Config) handleResponseMessage() {
|
||||||
switch event.Type {
|
switch event.Type {
|
||||||
case "http-addr":
|
case "http-addr":
|
||||||
if event.Payload.(bool) == false {
|
if event.Payload.(bool) == false {
|
||||||
|
log.Errorf("Listening HTTP proxy at %s error", c.general.Port)
|
||||||
c.general.Port = 0
|
c.general.Port = 0
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
case "socks-addr":
|
case "socks-addr":
|
||||||
if event.Payload.(bool) == false {
|
if event.Payload.(bool) == false {
|
||||||
|
log.Errorf("Listening SOCKS proxy at %s error", c.general.SocksPort)
|
||||||
c.general.SocksPort = 0
|
c.general.SocksPort = 0
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
|
case "redir-addr":
|
||||||
|
if event.Payload.(bool) == false {
|
||||||
|
log.Errorf("Listening Redir proxy at %s error", c.general.RedirPort)
|
||||||
|
c.general.RedirPort = 0
|
||||||
|
}
|
||||||
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"github.com/Dreamacro/clash/config"
|
"github.com/Dreamacro/clash/config"
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
"github.com/Dreamacro/clash/proxy/http"
|
"github.com/Dreamacro/clash/proxy/http"
|
||||||
|
"github.com/Dreamacro/clash/proxy/redir"
|
||||||
"github.com/Dreamacro/clash/proxy/socks"
|
"github.com/Dreamacro/clash/proxy/socks"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -18,6 +19,7 @@ type Listener struct {
|
||||||
// signal for update
|
// signal for update
|
||||||
httpSignal *C.ProxySignal
|
httpSignal *C.ProxySignal
|
||||||
socksSignal *C.ProxySignal
|
socksSignal *C.ProxySignal
|
||||||
|
redirSignal *C.ProxySignal
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *Listener) updateHTTP(addr string) error {
|
func (l *Listener) updateHTTP(addr string) error {
|
||||||
|
@ -54,6 +56,23 @@ func (l *Listener) updateSocks(addr string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (l *Listener) updateRedir(addr string) error {
|
||||||
|
if l.redirSignal != nil {
|
||||||
|
signal := l.redirSignal
|
||||||
|
signal.Done <- struct{}{}
|
||||||
|
<-signal.Closed
|
||||||
|
l.redirSignal = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
signal, err := redir.NewRedirProxy(addr)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
l.redirSignal = signal
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (l *Listener) process(signal chan<- struct{}) {
|
func (l *Listener) process(signal chan<- struct{}) {
|
||||||
sub := config.Instance().Subscribe()
|
sub := config.Instance().Subscribe()
|
||||||
signal <- struct{}{}
|
signal <- struct{}{}
|
||||||
|
@ -71,6 +90,11 @@ func (l *Listener) process(signal chan<- struct{}) {
|
||||||
err := l.updateSocks(addr)
|
err := l.updateSocks(addr)
|
||||||
reportCH <- &config.Event{Type: "socks-addr", Payload: err == nil}
|
reportCH <- &config.Event{Type: "socks-addr", Payload: err == nil}
|
||||||
break
|
break
|
||||||
|
case "redir-addr":
|
||||||
|
addr := event.Payload.(string)
|
||||||
|
err := l.updateRedir(addr)
|
||||||
|
reportCH <- &config.Event{Type: "redir-addr", Payload: err == nil}
|
||||||
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
62
proxy/redir/tcp.go
Normal file
62
proxy/redir/tcp.go
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
package redir
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
|
||||||
|
"github.com/Dreamacro/clash/adapters/local"
|
||||||
|
C "github.com/Dreamacro/clash/constant"
|
||||||
|
"github.com/Dreamacro/clash/tunnel"
|
||||||
|
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
tun = tunnel.Instance()
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewRedirProxy(addr string) (*C.ProxySignal, error) {
|
||||||
|
l, err := net.Listen("tcp", addr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
done := make(chan struct{})
|
||||||
|
closed := make(chan struct{})
|
||||||
|
signal := &C.ProxySignal{
|
||||||
|
Done: done,
|
||||||
|
Closed: closed,
|
||||||
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
log.Infof("Redir proxy listening at: %s", addr)
|
||||||
|
for {
|
||||||
|
c, err := l.Accept()
|
||||||
|
if err != nil {
|
||||||
|
if _, open := <-done; !open {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
go handleRedir(c)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
<-done
|
||||||
|
close(done)
|
||||||
|
l.Close()
|
||||||
|
closed <- struct{}{}
|
||||||
|
}()
|
||||||
|
|
||||||
|
return signal, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleRedir(conn net.Conn) {
|
||||||
|
target, err := parserPacket(conn)
|
||||||
|
if err != nil {
|
||||||
|
conn.Close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
conn.(*net.TCPConn).SetKeepAlive(true)
|
||||||
|
tun.Add(adapters.NewSocks(target, conn))
|
||||||
|
}
|
58
proxy/redir/tcp_darwin.go
Normal file
58
proxy/redir/tcp_darwin.go
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
package redir
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"github.com/riobard/go-shadowsocks2/socks"
|
||||||
|
)
|
||||||
|
|
||||||
|
func parserPacket(c net.Conn) (socks.Addr, error) {
|
||||||
|
const (
|
||||||
|
PfInout = 0
|
||||||
|
PfIn = 1
|
||||||
|
PfOut = 2
|
||||||
|
IOCOut = 0x40000000
|
||||||
|
IOCIn = 0x80000000
|
||||||
|
IOCInOut = IOCIn | IOCOut
|
||||||
|
IOCPARMMask = 0x1FFF
|
||||||
|
LEN = 4*16 + 4*4 + 4*1
|
||||||
|
// #define _IOC(inout,group,num,len) (inout | ((len & IOCPARMMask) << 16) | ((group) << 8) | (num))
|
||||||
|
// #define _IOWR(g,n,t) _IOC(IOCInOut, (g), (n), sizeof(t))
|
||||||
|
// #define DIOCNATLOOK _IOWR('D', 23, struct pfioc_natlook)
|
||||||
|
DIOCNATLOOK = IOCInOut | ((LEN & IOCPARMMask) << 16) | ('D' << 8) | 23
|
||||||
|
)
|
||||||
|
|
||||||
|
fd, err := syscall.Open("/dev/pf", 0, syscall.O_RDONLY)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer syscall.Close(fd)
|
||||||
|
|
||||||
|
nl := struct { // struct pfioc_natlook
|
||||||
|
saddr, daddr, rsaddr, rdaddr [16]byte
|
||||||
|
sxport, dxport, rsxport, rdxport [4]byte
|
||||||
|
af, proto, protoVariant, direction uint8
|
||||||
|
}{
|
||||||
|
af: syscall.AF_INET,
|
||||||
|
proto: syscall.IPPROTO_TCP,
|
||||||
|
direction: PfOut,
|
||||||
|
}
|
||||||
|
saddr := c.RemoteAddr().(*net.TCPAddr)
|
||||||
|
daddr := c.LocalAddr().(*net.TCPAddr)
|
||||||
|
copy(nl.saddr[:], saddr.IP)
|
||||||
|
copy(nl.daddr[:], daddr.IP)
|
||||||
|
nl.sxport[0], nl.sxport[1] = byte(saddr.Port>>8), byte(saddr.Port)
|
||||||
|
nl.dxport[0], nl.dxport[1] = byte(daddr.Port>>8), byte(daddr.Port)
|
||||||
|
|
||||||
|
if _, _, errno := syscall.Syscall(syscall.SYS_IOCTL, uintptr(fd), DIOCNATLOOK, uintptr(unsafe.Pointer(&nl))); errno != 0 {
|
||||||
|
return nil, errno
|
||||||
|
}
|
||||||
|
|
||||||
|
addr := make([]byte, 1+net.IPv4len+2)
|
||||||
|
addr[0] = socks.AtypIPv4
|
||||||
|
copy(addr[1:1+net.IPv4len], nl.rdaddr[:4])
|
||||||
|
copy(addr[1+net.IPv4len:], nl.rdxport[:2])
|
||||||
|
return addr, nil
|
||||||
|
}
|
51
proxy/redir/tcp_linux.go
Normal file
51
proxy/redir/tcp_linux.go
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
package redir
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"net"
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"github.com/riobard/go-shadowsocks2/socks"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
SO_ORIGINAL_DST = 80 // from linux/include/uapi/linux/netfilter_ipv4.h
|
||||||
|
IP6T_SO_ORIGINAL_DST = 80 // from linux/include/uapi/linux/netfilter_ipv6/ip6_tables.h
|
||||||
|
)
|
||||||
|
|
||||||
|
func parserPacket(conn net.Conn) (socks.Addr, error) {
|
||||||
|
c, ok := conn.(*net.TCPConn)
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.New("only work with TCP connection")
|
||||||
|
}
|
||||||
|
|
||||||
|
rc, err := c.SyscallConn()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var addr socks.Addr
|
||||||
|
|
||||||
|
rc.Control(func(fd uintptr) {
|
||||||
|
addr, err = getorigdst(fd)
|
||||||
|
})
|
||||||
|
|
||||||
|
return addr, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call getorigdst() from linux/net/ipv4/netfilter/nf_conntrack_l3proto_ipv4.c
|
||||||
|
func getorigdst(fd uintptr) (socks.Addr, error) {
|
||||||
|
raw := syscall.RawSockaddrInet4{}
|
||||||
|
siz := unsafe.Sizeof(raw)
|
||||||
|
if err := socketcall(GETSOCKOPT, fd, syscall.IPPROTO_IP, SO_ORIGINAL_DST, uintptr(unsafe.Pointer(&raw)), uintptr(unsafe.Pointer(&siz)), 0); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
addr := make([]byte, 1+net.IPv4len+2)
|
||||||
|
addr[0] = socks.AtypIPv4
|
||||||
|
copy(addr[1:1+net.IPv4len], raw.Addr[:])
|
||||||
|
port := (*[2]byte)(unsafe.Pointer(&raw.Port)) // big-endian
|
||||||
|
addr[1+net.IPv4len], addr[1+net.IPv4len+1] = port[0], port[1]
|
||||||
|
return addr, nil
|
||||||
|
}
|
17
proxy/redir/tcp_linux_386.go
Normal file
17
proxy/redir/tcp_linux_386.go
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
package redir
|
||||||
|
|
||||||
|
import (
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
const GETSOCKOPT = 15 // https://golang.org/src/syscall/syscall_linux_386.go#L183
|
||||||
|
|
||||||
|
func socketcall(call, a0, a1, a2, a3, a4, a5 uintptr) error {
|
||||||
|
var a [6]uintptr
|
||||||
|
a[0], a[1], a[2], a[3], a[4], a[5] = a0, a1, a2, a3, a4, a5
|
||||||
|
if _, _, errno := syscall.Syscall6(syscall.SYS_SOCKETCALL, call, uintptr(unsafe.Pointer(&a)), 0, 0, 0, 0); errno != 0 {
|
||||||
|
return errno
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
14
proxy/redir/tcp_linux_other.go
Normal file
14
proxy/redir/tcp_linux_other.go
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
// +build linux,!386
|
||||||
|
|
||||||
|
package redir
|
||||||
|
|
||||||
|
import "syscall"
|
||||||
|
|
||||||
|
const GETSOCKOPT = syscall.SYS_GETSOCKOPT
|
||||||
|
|
||||||
|
func socketcall(call, a0, a1, a2, a3, a4, a5 uintptr) error {
|
||||||
|
if _, _, errno := syscall.Syscall6(call, a0, a1, a2, a3, a4, a5); errno != 0 {
|
||||||
|
return errno
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
9
proxy/redir/tcp_windows.go
Normal file
9
proxy/redir/tcp_windows.go
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
package redir
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
func parserPacket(conn net.Conn) (socks.Addr, error) {
|
||||||
|
return nil, errors.New("Windows not support yet")
|
||||||
|
}
|
Loading…
Reference in a new issue