Skip to content

Commit

Permalink
Support redirecting to HTTP proxy, close #7
Browse files Browse the repository at this point in the history
  • Loading branch information
hmgle committed Oct 27, 2018
1 parent 197edeb commit 35ee35c
Show file tree
Hide file tree
Showing 5 changed files with 139 additions and 34 deletions.
6 changes: 6 additions & 0 deletions graftcp-local/config.go
Expand Up @@ -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
}

Expand All @@ -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
Expand Down Expand Up @@ -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
}
Expand Down
10 changes: 10 additions & 0 deletions graftcp-local/example-graftcp-local.conf
Expand Up @@ -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
5 changes: 1 addition & 4 deletions graftcp-local/http_proxy.go
Expand Up @@ -3,8 +3,6 @@ package main
import (
"bufio"
"fmt"
"io"
"io/ioutil"
"net"
"net/http"
"net/url"
Expand Down Expand Up @@ -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])
Expand Down
136 changes: 111 additions & 25 deletions graftcp-local/local.go
Expand Up @@ -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
}
}

Expand Down Expand Up @@ -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
}

Expand All @@ -131,3 +213,7 @@ func (l *Local) UpdateProcessAddrInfo() {
StorePidAddr(s[2], s[0]+":"+s[1])
}
}

func init() {
rand.Seed(time.Now().UTC().UnixNano())
}
16 changes: 11 additions & 5 deletions graftcp-local/main.go
Expand Up @@ -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 {
Expand All @@ -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)
Expand Down Expand Up @@ -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))
Expand All @@ -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()
Expand Down

0 comments on commit 35ee35c

Please sign in to comment.