diff --git a/config/config.go b/config/config.go index f0a127a3a..e1bf695b0 100644 --- a/config/config.go +++ b/config/config.go @@ -13,6 +13,7 @@ import ( "github.com/Dreamacro/clash/observable" R "github.com/Dreamacro/clash/rules" + log "github.com/sirupsen/logrus" "gopkg.in/ini.v1" ) @@ -25,6 +26,7 @@ var ( type General struct { Port int SocksPort int + RedirPort int AllowLan bool Mode Mode LogLevel C.LogLevel @@ -34,6 +36,7 @@ type General struct { type ProxyConfig struct { Port *int SocksPort *int + RedirPort *int AllowLan *bool } @@ -136,6 +139,7 @@ func (c *Config) parseGeneral(cfg *ini.File) error { port := general.Key("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() logLevelString := general.Key("log-level").MustString(C.INFO.String()) modeString := general.Key("mode").MustString(Rule.String()) @@ -153,6 +157,7 @@ func (c *Config) parseGeneral(cfg *ini.File) error { c.general = &General{ Port: port, SocksPort: socksPort, + RedirPort: redirPort, AllowLan: allowLan, Mode: mode, LogLevel: logLevel, @@ -171,6 +176,7 @@ func (c *Config) UpdateGeneral(general General) { c.UpdateProxy(ProxyConfig{ Port: &general.Port, SocksPort: &general.SocksPort, + RedirPort: &general.RedirPort, AllowLan: &general.AllowLan, }) c.event <- &Event{Type: "mode", Payload: general.Mode} @@ -192,6 +198,11 @@ func (c *Config) UpdateProxy(pc ProxyConfig) { c.general.SocksPort = *pc.SocksPort 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 { @@ -311,14 +322,22 @@ func (c *Config) handleResponseMessage() { switch event.Type { case "http-addr": if event.Payload.(bool) == false { + log.Errorf("Listening HTTP proxy at %s error", c.general.Port) c.general.Port = 0 } break case "socks-addr": if event.Payload.(bool) == false { + log.Errorf("Listening SOCKS proxy at %s error", c.general.SocksPort) c.general.SocksPort = 0 } 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 } } } diff --git a/proxy/listener.go b/proxy/listener.go index 91c1c9285..e3f6863c4 100644 --- a/proxy/listener.go +++ b/proxy/listener.go @@ -6,6 +6,7 @@ import ( "github.com/Dreamacro/clash/config" C "github.com/Dreamacro/clash/constant" "github.com/Dreamacro/clash/proxy/http" + "github.com/Dreamacro/clash/proxy/redir" "github.com/Dreamacro/clash/proxy/socks" ) @@ -18,6 +19,7 @@ type Listener struct { // signal for update httpSignal *C.ProxySignal socksSignal *C.ProxySignal + redirSignal *C.ProxySignal } func (l *Listener) updateHTTP(addr string) error { @@ -54,6 +56,23 @@ func (l *Listener) updateSocks(addr string) error { 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{}) { sub := config.Instance().Subscribe() signal <- struct{}{} @@ -71,6 +90,11 @@ func (l *Listener) process(signal chan<- struct{}) { err := l.updateSocks(addr) reportCH <- &config.Event{Type: "socks-addr", Payload: err == nil} break + case "redir-addr": + addr := event.Payload.(string) + err := l.updateRedir(addr) + reportCH <- &config.Event{Type: "redir-addr", Payload: err == nil} + break } } } diff --git a/proxy/redir/tcp.go b/proxy/redir/tcp.go new file mode 100644 index 000000000..16ccb248a --- /dev/null +++ b/proxy/redir/tcp.go @@ -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)) +} diff --git a/proxy/redir/tcp_darwin.go b/proxy/redir/tcp_darwin.go new file mode 100644 index 000000000..3fe7803f5 --- /dev/null +++ b/proxy/redir/tcp_darwin.go @@ -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 +} diff --git a/proxy/redir/tcp_linux.go b/proxy/redir/tcp_linux.go new file mode 100644 index 000000000..836b2e5d4 --- /dev/null +++ b/proxy/redir/tcp_linux.go @@ -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 +} diff --git a/proxy/redir/tcp_linux_386.go b/proxy/redir/tcp_linux_386.go new file mode 100644 index 000000000..32f692df8 --- /dev/null +++ b/proxy/redir/tcp_linux_386.go @@ -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 +} diff --git a/proxy/redir/tcp_linux_other.go b/proxy/redir/tcp_linux_other.go new file mode 100644 index 000000000..95472823c --- /dev/null +++ b/proxy/redir/tcp_linux_other.go @@ -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 +} diff --git a/proxy/redir/tcp_windows.go b/proxy/redir/tcp_windows.go new file mode 100644 index 000000000..075e5f27d --- /dev/null +++ b/proxy/redir/tcp_windows.go @@ -0,0 +1,9 @@ +package redir + +import ( + "errors" +) + +func parserPacket(conn net.Conn) (socks.Addr, error) { + return nil, errors.New("Windows not support yet") +}