New: redir proxy

This commit is contained in:
gVisor bot 2018-08-12 04:00:34 +08:00
parent a0dd4ffddc
commit 13cf2bb270
8 changed files with 254 additions and 0 deletions

View file

@ -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
} }
} }
} }

View file

@ -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
View 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
View 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
View 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
}

View 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
}

View 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
}

View 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")
}