From 35ee35ca3945a64f2ffe18df66391b35de3810f2 Mon Sep 17 00:00:00 2001 From: "mingang.he" Date: Sat, 27 Oct 2018 14:35:26 +0800 Subject: [PATCH] Support redirecting to HTTP proxy, close #7 --- graftcp-local/config.go | 6 + graftcp-local/example-graftcp-local.conf | 10 ++ graftcp-local/http_proxy.go | 5 +- graftcp-local/local.go | 136 ++++++++++++++++++----- graftcp-local/main.go | 16 ++- 5 files changed, 139 insertions(+), 34 deletions(-) diff --git a/graftcp-local/config.go b/graftcp-local/config.go index 5b2ef81..384a981 100644 --- a/graftcp-local/config.go +++ b/graftcp-local/config.go @@ -18,6 +18,7 @@ type Config struct { Loglevel int // Log level (0-6) PipePath string // Pipe path for graftcp to send address info Socks5 string // SOCKS5 address + HttpProxy string // HTTP proxy address UseSyslog bool // Use the system logger } @@ -38,6 +39,8 @@ func setCfg(key, val string) { Cfg.PipePath = val case "socks5": Cfg.Socks5 = val + case "http_proxy": + Cfg.HttpProxy = val case "usesyslog": if strings.ToLower(val) == "true" { Cfg.UseSyslog = true @@ -101,6 +104,9 @@ func overrideConfig(app *App) { if !flagset["socks5"] && Cfg.Socks5 != "" { app.Socks5Addr = Cfg.Socks5 } + if !flagset["http_proxy"] && Cfg.HttpProxy != "" { + app.HttpProxyAddr = Cfg.HttpProxy + } if !flagset["pipepath"] && Cfg.PipePath != "" { app.PipePath = Cfg.PipePath } diff --git a/graftcp-local/example-graftcp-local.conf b/graftcp-local/example-graftcp-local.conf index e2f539e..3df2ae0 100644 --- a/graftcp-local/example-graftcp-local.conf +++ b/graftcp-local/example-graftcp-local.conf @@ -16,5 +16,15 @@ loglevel = 1 ## SOCKS5 address (default "127.0.0.1:1080") # socks5 = 127.0.0.1:1080 +## HTTP proxy address (default "") +# http_proxy = 127.0.0.1:8080 + +## Set the mode for select a proxy (default "auto") +## "auto": select socks5 if socks5 is reachable, else HTTP proxy. +## "random": select the reachable proxy randomly. +## "only_http_proxy": only use http proxy. +## "only_socks5": only use socks5 proxy. +# select_proxy_mode = random + ## Use the system logger (syslog on Unix, Event Log on Windows) # use_syslog = true diff --git a/graftcp-local/http_proxy.go b/graftcp-local/http_proxy.go index 9baffd5..4b05c07 100644 --- a/graftcp-local/http_proxy.go +++ b/graftcp-local/http_proxy.go @@ -3,8 +3,6 @@ package main import ( "bufio" "fmt" - "io" - "io/ioutil" "net" "net/http" "net/url" @@ -52,8 +50,7 @@ func (h *httpDialer) Dial(network, addr string) (net.Conn, error) { conn.Close() return nil, err } - defer resp.Body.Close() - io.Copy(ioutil.Discard, resp.Body) + resp.Body.Close() if resp.StatusCode != 200 { conn.Close() return nil, fmt.Errorf("connect proxy error: %v", strings.SplitN(resp.Status, " ", 2)[1]) diff --git a/graftcp-local/local.go b/graftcp-local/local.go index 6a1e011..c407096 100644 --- a/graftcp-local/local.go +++ b/graftcp-local/local.go @@ -4,38 +4,120 @@ import ( "bufio" "fmt" "io" + "math/rand" "net" + "net/url" "os" "strings" + "time" "github.com/jedisct1/dlog" "golang.org/x/net/proxy" ) +type modeT int + +const ( + // AutoSelectMode: select socks5 if socks5 is reachable, else HTTP proxy + AutoSelectMode modeT = iota + // RandomSelectMode: select the reachable proxy randomly + RandomSelectMode + // OnlySocks5Mode: force use socks5 + OnlySocks5Mode + // OnlyHttpProxyMode: force use HTTP proxy + OnlyHttpProxyMode +) + type Local struct { faddr *net.TCPAddr // Frontend address: graftcp-local address - baddr *net.TCPAddr // Backend address: socks5 address faddrString string - baddrString string + + socks5Dialer proxy.Dialer + httpProxyDialer proxy.Dialer FifoFd *os.File + + selectMode modeT } -func NewLocal(listenAddr, socks5Addr string) *Local { - a1, err := net.ResolveTCPAddr("tcp", listenAddr) +func NewLocal(listenAddr, socks5Addr, httpProxyAddr string) *Local { + listenTCPAddr, err := net.ResolveTCPAddr("tcp", listenAddr) if err != nil { dlog.Fatalf("resolve frontend(%s) error: %s", listenAddr, err.Error()) } - a2, err := net.ResolveTCPAddr("tcp", socks5Addr) - if err != nil { - dlog.Fatalf("resolve backend(%s) error: %s", socks5Addr, err.Error()) - } - return &Local{ - faddr: a1, - baddr: a2, + local := &Local{ + faddr: listenTCPAddr, faddrString: listenAddr, - baddrString: socks5Addr, + } + + socks5TCPAddr, err1 := net.ResolveTCPAddr("tcp", socks5Addr) + httpProxyTCPAddr, err2 := net.ResolveTCPAddr("tcp", httpProxyAddr) + if err1 != nil && err2 != nil { + dlog.Fatalf( + "neither %s nor %s can be resolved, resolve(%s): %v, resolve(%s): %v, please check the config for proxy", + socks5Addr, httpProxyAddr, socks5Addr, err1, httpProxyAddr, err2) + } + if err1 == nil { + dialerSocks5, err := proxy.SOCKS5("tcp", socks5TCPAddr.String(), nil, proxy.Direct) + if err != nil { + dlog.Errorf("proxy.SOCKS5(%s) fail: %s", socks5TCPAddr.String(), err.Error()) + } else { + local.socks5Dialer = dialerSocks5 + } + } + if err2 == nil { + httpProxyURI, _ := url.Parse("http://" + httpProxyTCPAddr.String()) + dialerHttpProxy, err := proxy.FromURL(httpProxyURI, proxy.Direct) + if err != nil { + dlog.Errorf("proxy.FromURL(%v) err: %s", httpProxyURI, err.Error()) + } else { + local.httpProxyDialer = dialerHttpProxy + } + } + return local +} + +// SetSelectMode set the select mode for l. +func (l *Local) SetSelectMode(mode string) { + switch mode { + case "auto": + l.selectMode = AutoSelectMode + case "random": + l.selectMode = RandomSelectMode + case "only_http_proxy": + l.selectMode = OnlyHttpProxyMode + case "only_socks5": + l.selectMode = OnlySocks5Mode + } +} + +func (l *Local) proxySelector() proxy.Dialer { + if l == nil { + return nil + } + switch l.selectMode { + case AutoSelectMode: + if l.socks5Dialer != nil { + return l.socks5Dialer + } + return l.httpProxyDialer + case RandomSelectMode: + if l.socks5Dialer != nil && l.httpProxyDialer != nil { + if rand.Intn(2) == 0 { + return l.socks5Dialer + } + return l.httpProxyDialer + } else if l.socks5Dialer != nil { + return l.socks5Dialer + } + return l.httpProxyDialer + case OnlySocks5Mode: + return l.socks5Dialer + case OnlyHttpProxyMode: + return l.httpProxyDialer + default: + return l.socks5Dialer } } @@ -79,33 +161,33 @@ func (l *Local) getPidByAddr(localAddr string) (pid string, destAddr string) { func (l *Local) HandleConn(conn net.Conn) error { raddr := conn.RemoteAddr() - pid, addr := l.getPidByAddr(raddr.String()) - if pid == "" || addr == "" { + pid, destAddr := l.getPidByAddr(raddr.String()) + if pid == "" || destAddr == "" { dlog.Errorf("getPidByAddr(%s) failed", raddr.String()) conn.Close() - return fmt.Errorf("can't find the pid and addr for %s", raddr.String()) + return fmt.Errorf("can't find the pid and destAddr for %s", raddr.String()) } - dlog.Infof("Request PID: %s, Source Addr: %s, Dest Addr: %s", pid, raddr.String(), addr) + dlog.Infof("Request PID: %s, Source Addr: %s, Dest Addr: %s", pid, raddr.String(), destAddr) - dialer, err := proxy.SOCKS5("tcp", l.baddr.String(), nil, proxy.Direct) - if err != nil { - dlog.Errorf("proxy.SOCKS5(\"tcp\", %s,...) err: %s", l.baddr, err.Error()) + dialer := l.proxySelector() + if dialer == nil { + dlog.Errorf("bad dialer, please check the config for proxy") conn.Close() - return err + return fmt.Errorf("bad dialer") } - socks5Conn, err := dialer.Dial("tcp", addr) + destConn, err := dialer.Dial("tcp", destAddr) if err != nil { - dlog.Errorf("dialer.Dial(%s) err: %s", addr, err.Error()) + dlog.Errorf("dialer.Dial(%s) err: %s", destAddr, err.Error()) conn.Close() return err } readChan, writeChan := make(chan int64), make(chan int64) - go pipe(conn, socks5Conn, writeChan) - go pipe(socks5Conn, conn, readChan) + go pipe(conn, destConn, writeChan) + go pipe(destConn, conn, readChan) <-writeChan <-readChan conn.Close() - socks5Conn.Close() + destConn.Close() return nil } @@ -131,3 +213,7 @@ func (l *Local) UpdateProcessAddrInfo() { StorePidAddr(s[2], s[0]+":"+s[1]) } } + +func init() { + rand.Seed(time.Now().UTC().UnixNano()) +} diff --git a/graftcp-local/main.go b/graftcp-local/main.go index 97bf3ec..c17e4b6 100644 --- a/graftcp-local/main.go +++ b/graftcp-local/main.go @@ -10,10 +10,13 @@ import ( "github.com/kardianos/service" ) +var selectProxyMode string + type App struct { - ListenAddr string - Socks5Addr string - PipePath string + ListenAddr string + Socks5Addr string + HttpProxyAddr string + PipePath string } func (app *App) Start(s service.Service) error { @@ -30,7 +33,8 @@ func (app *App) Start(s service.Service) error { func (app *App) run() { var err error - l := NewLocal(app.ListenAddr, app.Socks5Addr) + l := NewLocal(app.ListenAddr, app.Socks5Addr, app.HttpProxyAddr) + l.SetSelectMode(selectProxyMode) syscall.Mkfifo(app.PipePath, uint32(os.ModePerm)) os.Chmod(app.PipePath, 0666) @@ -59,7 +63,7 @@ func main() { svcConfig := &service.Config{ Name: "graftcp-local", DisplayName: "graftcp local service", - Description: "Translate graftcp TCP to SOCKS5", + Description: "Translate graftcp TCP to SOCKS5 or HTTP proxy", WorkingDirectory: pwd, } svcFlag := flag.String("service", "", fmt.Sprintf("Control the system service: %q", service.ControlAction)) @@ -72,6 +76,8 @@ func main() { flag.StringVar(&app.ListenAddr, "listen", ":2233", "Listen address") flag.StringVar(&app.Socks5Addr, "socks5", "127.0.0.1:1080", "SOCKS5 address") + flag.StringVar(&app.HttpProxyAddr, "http_proxy", "", "http proxy address, e.g.: 127.0.0.1:8080") + flag.StringVar(&selectProxyMode, "select_proxy_mode", "auto", "Set the mode for select a proxy [auto | random | only_http_proxy | only_socks5]") flag.StringVar(&configFile, "config", "", "Path to the configuration file") flag.StringVar(&app.PipePath, "pipepath", "/tmp/graftcplocal.fifo", "Pipe path for graftcp to send address info") flag.Parse()