diff --git a/cmd/tun2socks/main.go b/cmd/tun2socks/main.go index 79533ea8..9a4b668c 100644 --- a/cmd/tun2socks/main.go +++ b/cmd/tun2socks/main.go @@ -33,30 +33,32 @@ func addPostFlagsInitFn(fn func()) { } type CmdArgs struct { - Version *bool - TunName *string - TunAddr *string - TunGw *string - TunMask *string - TunDns *string - ProxyType *string - VConfig *string - Gateway *string - SniffingType *string - ProxyServer *string - ProxyHost *string - ProxyPort *uint16 - ProxyCipher *string - ProxyPassword *string - DelayICMP *int - UdpTimeout *time.Duration - Applog *bool - DisableDnsCache *bool - DnsFallback *bool - LogLevel *string - EnableFakeDns *bool - FakeDnsMinIP *string - FakeDnsMaxIP *string + Version *bool + TunName *string + TunAddr *string + TunGw *string + TunMask *string + TunDns *string + ProxyType *string + VConfig *string + Gateway *string + SniffingType *string + ProxyServer *string + ProxyHost *string + ProxyPort *uint16 + ProxyCipher *string + ProxyPassword *string + DelayICMP *int + UdpTimeout *time.Duration + Applog *bool + DisableDnsCache *bool + DnsFallback *bool + LogLevel *string + EnableFakeDns *bool + FakeDnsMinIP *string + FakeDnsMaxIP *string + ExceptionApps *string + ExceptionSendThrough *string } type cmdFlag uint diff --git a/cmd/tun2socks/main_d.go b/cmd/tun2socks/main_d.go new file mode 100644 index 00000000..062efab1 --- /dev/null +++ b/cmd/tun2socks/main_d.go @@ -0,0 +1,47 @@ +// +build d + +package main + +import ( + "flag" + "net" + "strings" + + "github.com/eycorsican/go-tun2socks/common/log" + "github.com/eycorsican/go-tun2socks/core" + "github.com/eycorsican/go-tun2socks/proxy/d" + "github.com/eycorsican/go-tun2socks/proxy/socks" +) + +func init() { + args.addFlag(fProxyServer) + args.addFlag(fUdpTimeout) + args.addFlag(fApplog) + + args.ExceptionApps = flag.String("exceptionApps", "", "Exception app list separated by commas") + args.ExceptionSendThrough = flag.String("exceptionSendThrough", "192.168.1.101:0", "Exception send through address") + + registerHandlerCreater("d", func() { + // Verify proxy server address. + proxyAddr, err := net.ResolveTCPAddr("tcp", *args.ProxyServer) + if err != nil { + log.Fatalf("invalid proxy server address: %v", err) + } + proxyHost := proxyAddr.IP.String() + proxyPort := uint16(proxyAddr.Port) + + proxyTCPHandler := socks.NewTCPHandler(proxyHost, proxyPort, fakeDns) + proxyUDPHandler := socks.NewUDPHandler(proxyHost, proxyPort, *args.UdpTimeout, dnsCache, fakeDns) + + sendThrough, err := net.ResolveTCPAddr("tcp", *args.ExceptionSendThrough) + if err != nil { + log.Fatalf("invalid exception send through address: %v", err) + } + apps := strings.Split(*args.ExceptionApps, ",") + tcpHandler := d.NewTCPHandler(proxyTCPHandler, apps, sendThrough) + udpHandler := d.NewUDPHandler(proxyUDPHandler, apps, sendThrough, *args.UdpTimeout) + + core.RegisterTCPConnHandler(tcpHandler) + core.RegisterUDPConnHandler(udpHandler) + }) +} diff --git a/common/log/log.go b/common/log/log.go index ffb0e10a..c71b6b02 100644 --- a/common/log/log.go +++ b/common/log/log.go @@ -49,12 +49,12 @@ func Fatalf(msg string, args ...interface{}) { } } -func Access(network, localAddr, target string) { +func Access(outbound, network, localAddr, target string) { localHost, localPortStr, _ := net.SplitHostPort(localAddr) localPortInt, _ := strconv.Atoi(localPortStr) cmd, err := lsof.GetCommandNameBySocket(network, localHost, uint16(localPortInt)) if err != nil { cmd = "unknown process" } - Infof("[proxy] [%v] [%v] %s", network, cmd, target) + Infof("[%v] [%v] [%v] %s", outbound, network, cmd, target) } diff --git a/proxy/d/tcp.go b/proxy/d/tcp.go new file mode 100644 index 00000000..60e327bd --- /dev/null +++ b/proxy/d/tcp.go @@ -0,0 +1,96 @@ +package d + +import ( + "io" + "net" + "strconv" + + "github.com/eycorsican/go-tun2socks/common/log" + "github.com/eycorsican/go-tun2socks/common/lsof" + "github.com/eycorsican/go-tun2socks/core" +) + +// This handler allows you chain another proxy behind tun2socks locally, typically a rule-based proxy client, e.g. V2Ray. +// +// Rule-based proxy clients are very useful, they are able to dispatch requests to different servers based on powerful rule filters. +// By using this setup, you are able to make all your TCP/UDP traffic under control with your favorite rule-based proxy client. +// +// Here's an example setup on macOS: +// +// tun2socks -tunGw 10.255.0.1 -fakeDns -proxyType d -proxyServer 127.0.0.1:1086 -exceptionSendThrough 192.168.1.189:0 -exceptionApps "v2ray" +// +// route delete default +// route add default 10.255.0.1 +// route add default 192.168.1.1 -ifscope en0 +// +// Where 192.168.1.189 is the default interface address, in my case, it's the WiFi interface and it's en0. +// 192.168.1.1 is the default gateway. +// It's very important to have two default routes, and the default route to TUN should has the highest priority. +// +// Start v2ray (or any other chainable proxy clients) and has SOCKS inbound listen on 127.0.0.1:1086. +// +// Optinally with all outbounds have sendThrough set to 192.168.1.189, if applicable. +// https://v2ray.com/chapter_02/01_overview.html#outboundobject + +type tcpHandler struct { + proxyHandler core.TCPConnHandler + exceptionApps []string + sendThrough net.Addr +} + +func NewTCPHandler(proxyHandler core.TCPConnHandler, exceptionApps []string, sendThrough net.Addr) core.TCPConnHandler { + return &tcpHandler{ + proxyHandler, + exceptionApps, + sendThrough, + } +} + +func (h *tcpHandler) isExceptionApp(name string) bool { + for _, app := range h.exceptionApps { + if name == app { + return true + } + } + return false +} + +func (h *tcpHandler) relay(lhs, rhs net.Conn) { + cls := func() { + rhs.Close() + lhs.Close() + } + + go func() { + io.Copy(rhs, lhs) + cls() + }() + + io.Copy(lhs, rhs) + cls() +} + +func (h *tcpHandler) Handle(conn net.Conn, target net.Addr) error { + localHost, localPortStr, _ := net.SplitHostPort(conn.LocalAddr().String()) + localPortInt, _ := strconv.Atoi(localPortStr) + cmd, err := lsof.GetCommandNameBySocket("tcp", localHost, uint16(localPortInt)) + if err != nil { + cmd = "unknown process" + } + + if h.isExceptionApp(cmd) { + dialer := net.Dialer{LocalAddr: h.sendThrough} + rc, err := dialer.Dial("tcp", target.String()) + if err != nil { + return err + } + + go h.relay(conn, rc) + + log.Access("direct", target.Network(), conn.LocalAddr().String(), target.String()) + + return nil + } else { + return h.proxyHandler.Handle(conn, target) + } +} diff --git a/proxy/d/udp.go b/proxy/d/udp.go new file mode 100644 index 00000000..ab4cf5b1 --- /dev/null +++ b/proxy/d/udp.go @@ -0,0 +1,121 @@ +package d + +import ( + "net" + "strconv" + "sync" + "time" + + "github.com/eycorsican/go-tun2socks/common/log" + "github.com/eycorsican/go-tun2socks/common/lsof" + "github.com/eycorsican/go-tun2socks/core" +) + +type udpHandler struct { + sync.Mutex + + proxyHandler core.UDPConnHandler + exceptionApps []string + sendThrough net.Addr + exceptionConns map[core.UDPConn]*net.UDPConn + timeout time.Duration +} + +func (h *udpHandler) isExceptionApp(name string) bool { + for _, app := range h.exceptionApps { + if name == app { + return true + } + } + return false +} + +func NewUDPHandler(proxyHandler core.UDPConnHandler, exceptionApps []string, sendThrough net.Addr, timeout time.Duration) core.UDPConnHandler { + return &udpHandler{ + proxyHandler: proxyHandler, + exceptionApps: exceptionApps, + sendThrough: sendThrough, + exceptionConns: make(map[core.UDPConn]*net.UDPConn), + timeout: timeout, + } +} + +func (h *udpHandler) handleInput(conn core.UDPConn, pc net.PacketConn) { + buf := core.NewBytes(core.BufSize) + + defer func() { + h.Close(conn) + core.FreeBytes(buf) + }() + + for { + pc.SetDeadline(time.Now().Add(h.timeout)) + n, addr, err := pc.ReadFrom(buf) + if err != nil { + return + } + + _, err = conn.WriteFrom(buf[:n], addr) + if err != nil { + return + } + } +} + +func (h *udpHandler) Connect(conn core.UDPConn, target net.Addr) error { + localHost, localPortStr, _ := net.SplitHostPort(conn.LocalAddr().String()) + localPortInt, _ := strconv.Atoi(localPortStr) + cmd, err := lsof.GetCommandNameBySocket("udp", localHost, uint16(localPortInt)) + if err != nil { + cmd = "unknown process" + } + + if h.isExceptionApp(cmd) { + bindAddr, _ := net.ResolveUDPAddr( + "udp", + h.sendThrough.String(), + ) + pc, err := net.ListenUDP("udp", bindAddr) + if err != nil { + return err + } + h.Lock() + h.exceptionConns[conn] = pc + h.Unlock() + + go h.handleInput(conn, pc) + + log.Access("direct", target.Network(), conn.LocalAddr().String(), target.String()) + + return nil + } else { + return h.proxyHandler.Connect(conn, target) + } +} + +func (h *udpHandler) ReceiveTo(conn core.UDPConn, data []byte, addr net.Addr) error { + h.Lock() + defer h.Unlock() + + if pc, found := h.exceptionConns[conn]; found { + _, err := pc.WriteTo(data, addr) + if err != nil { + return err + } + return nil + } else { + return h.proxyHandler.ReceiveTo(conn, data, addr) + } +} + +func (h *udpHandler) Close(conn core.UDPConn) { + conn.Close() + + h.Lock() + defer h.Unlock() + + if pc, ok := h.exceptionConns[conn]; ok { + pc.Close() + delete(h.exceptionConns, conn) + } +} diff --git a/proxy/socks/tcp.go b/proxy/socks/tcp.go index 7a4842b5..63316364 100644 --- a/proxy/socks/tcp.go +++ b/proxy/socks/tcp.go @@ -74,7 +74,7 @@ func (h *tcpHandler) Handle(conn net.Conn, target net.Addr) error { go h.handleInput(conn, c) go h.handleOutput(conn, c) - log.Access(target.Network(), conn.LocalAddr().String(), dest) + log.Access("proxy", target.Network(), conn.LocalAddr().String(), dest) return nil } diff --git a/proxy/socks/udp.go b/proxy/socks/udp.go index c7bee911..d3032e19 100644 --- a/proxy/socks/udp.go +++ b/proxy/socks/udp.go @@ -186,7 +186,7 @@ func (h *udpHandler) connectInternal(conn core.UDPConn, dest string) error { h.Unlock() go h.fetchUDPInput(conn, pc) if len(dest) != 0 { - log.Access("udp", conn.LocalAddr().String(), dest) + log.Access("proxy", "udp", conn.LocalAddr().String(), dest) } return nil }