Skip to content
This repository has been archived by the owner on Apr 3, 2021. It is now read-only.

Commit

Permalink
chainable proxy handler
Browse files Browse the repository at this point in the history
  • Loading branch information
eycorsican committed Jun 1, 2019
1 parent 110d887 commit a77d387
Show file tree
Hide file tree
Showing 7 changed files with 294 additions and 28 deletions.
50 changes: 26 additions & 24 deletions cmd/tun2socks/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
47 changes: 47 additions & 0 deletions cmd/tun2socks/main_d.go
Original file line number Diff line number Diff line change
@@ -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)

This comment has been minimized.

Copy link
@fortuna

fortuna Jun 3, 2019

@eycorsican
Thank you so much for all the improvements you've been making!
While I don't have the bandwidth to contribute, I've worked with this things and my team is using your code for Outline and Intra.

The code for chaining proxies can be simplified and made more composable and testable if instead of working with Handlers you work with Dialers.

Take, for example, socks.TCPHandler:

dialer, err := proxy.SOCKS5("tcp", core.ParseTCPAddr(h.proxyHost, h.proxyPort).String(), nil, nil)

It could, instead, be a generic dialer handler that takes a dialer in the construction and all it does is to use the Dialer to dial to the target and plug it to the lwip connection.
Then if you want a SOCKS handler, all you need to do is create a SOCKS Dialer and pass that to the generic TCPHandler that takes a dialer.

You can them make the SOCKS Dialer itself take a dialer that is used to connect to the proxy, and that will give you chaining of proxies.

Another example of simplification: you can have a Shadowsocks Dialer, rather than a Shadowsocks handler.

Testing dialers are a lot easier than testing handlers. Conversely, testing a handler with a fake dialer is also easier.

By having developers only need to implement handlers, you will remove all the boilerplate in the handlers found in the proxy/ directory. They currently all need to reproduce the logic to plug the target connection to the lwip connection.

@alalamav @bemasc FYI

This comment has been minimized.

Copy link
@eycorsican

eycorsican via email Jun 3, 2019

Author Owner
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)
})
}
4 changes: 2 additions & 2 deletions common/log/log.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
96 changes: 96 additions & 0 deletions proxy/d/tcp.go
Original file line number Diff line number Diff line change
@@ -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)
}
}
121 changes: 121 additions & 0 deletions proxy/d/udp.go
Original file line number Diff line number Diff line change
@@ -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)
}
}
2 changes: 1 addition & 1 deletion proxy/socks/tcp.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
2 changes: 1 addition & 1 deletion proxy/socks/udp.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down

0 comments on commit a77d387

Please sign in to comment.