diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..a7b080b --- /dev/null +++ b/Makefile @@ -0,0 +1,11 @@ +.PHONY: build +build: + go build +release: + # clean + go clean + CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -o gproxy-windows-amd64-${VERSION}.exe + CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o gproxy-linux-amd64-${VERSION} + CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -o gproxy-darwin-amd64-${VERSION} +help: + echo "run \"make release VERSION=$VERSION\" to build all binary" \ No newline at end of file diff --git a/README.md b/README.md index 2e7096b..7f73010 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,45 @@ -# Usage +# gproxy -代理访问单个域名的全部请求 Proxy all requests to a website +| English | [简体中文](./README.zh-CN.md) | -``gproxy http://example.com/ -p 8080`` +## Usage -这样就可以启动一个代理进程,在本地的8080端口代理example.com的内容。 then, a simple http server will run on local 8080 port to serve the content of example.com +### Arguments + +gproxy [-m mode] [-p port] targetURL + +- ``mode``: http or tcp +- ``port``: local port, example: 8080 + +### Proxy a http website: + +``gproxy -m http -p 8080 http://github.com/`` + +then, a simple http server will run on local 8080 port to serve the content of github.com + +### Proxy a tcp service: + +``gproxy -m tcp -p 2333 github.com:443`` + +then, all tcp request to localhost:2333 will be sent to github.com:443 and response from github.com:443 will be sent +back to client + +## Build + +### With makefile + +``make release``: will generate all linux/windows/osx binary + +### Run by command + +#### Target linux + +``CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o gproxy-linux-amd64`` + +#### Target windows + +``CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -o gproxy-windows-amd64.exe`` + +#### Target mac + +``CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -o gproxy-darwin-amd64`` diff --git a/README.zh-CN.md b/README.zh-CN.md new file mode 100644 index 0000000..6c6376e --- /dev/null +++ b/README.zh-CN.md @@ -0,0 +1,22 @@ +# gproxy + +| [English](./README.md) | 简体中文 | + +## 使用 + +### 参数 +gproxy [-m 模式] [-p 端口] 目标地址 + +- ``mode``: http 或 tcp +- ``port``: 本地端口, 比如: 8080 + + +### 代理一个http网站: +``gproxy -m http -p 8080 http://github.com/`` + +本地8080端口即可以直接访问github + +### 代理一个tcp服务: +``gproxy -m tcp -p 2333 github.com:443`` + +本地2333端口的所有tcp流量都会代理到github.com:443对应的tcp服务 \ No newline at end of file diff --git a/proxy.go b/http.go similarity index 88% rename from proxy.go rename to http.go index b2ed425..2b66a7f 100644 --- a/proxy.go +++ b/http.go @@ -5,17 +5,18 @@ import ( "github.com/gin-gonic/gin" "log" "net/http" + "strconv" "strings" ) -// NewProxy 启动代理服务 -func NewProxy(url string, localPort string) { +// NewHttpProxy 启动代理服务 +func NewHttpProxy(url string, localPort int) { r := gin.Default() - r.Any("/*path", proxy(url)) - r.Run("0.0.0.0:" + localPort) + r.Any("/*path", httpProxy(url)) + r.Run("0.0.0.0:" + strconv.Itoa(localPort)) } -func proxy(url string) func(c *gin.Context) { +func httpProxy(url string) func(c *gin.Context) { return func(c *gin.Context) { // 读取请求头 headers := make(map[string]string) diff --git a/main.go b/main.go index 58eabe3..efc808d 100644 --- a/main.go +++ b/main.go @@ -1,18 +1,42 @@ package main -import "os" +import ( + "log" + "os" + "strconv" +) func main() { - // 解析命令行参数(proxy domain [-p port]) + // 解析命令行参数(httpProxy domain [-p port]) args := os.Args[1:] if len(args) < 1 { panic("invalid args") } domain := args[0] - port := "80" - if len(args) > 1 && args[1] == "-p" { - port = args[2] + var err error + port := 80 + mode := "http" + for i, arg := range args { + if arg == "-p" && len(args) > i+1 { + port, err = strconv.Atoi(args[i+1]) + if err != nil { + log.Fatalf("error occured: %v", err) + } + i++ + continue + } + if arg == "-m" && len(args) > i+1 { + mode = args[i+1] + i++ + continue + } + domain = arg + } + switch mode { + case "http": + // 启动HTTP代理服务 + NewHttpProxy(domain, port) + case "tcp": + NewTcpProxy(domain, port) } - // 启动代理服务 - NewProxy(domain, port) } diff --git a/tcp.go b/tcp.go new file mode 100644 index 0000000..b75d245 --- /dev/null +++ b/tcp.go @@ -0,0 +1,117 @@ +package main + +import ( + "io" + "log" + "net" + "strconv" + "time" +) + +func NewTcpProxy(url string, localPort int) { + listener, err := net.Listen("tcp", ":"+strconv.Itoa(localPort)) + defer listener.Close() + if err != nil { + log.Fatal(err) + } + + for { + conn, err := listener.Accept() + if err != nil { + log.Fatal(err) + } + go tcpProxy(url, conn) + } +} + +func tcpProxy(url string, client net.Conn) error { + cs := make(chan bool) + ss := make(chan bool) + + // 连接远程服务器 + server, err := net.Dial("tcp", url) + if err != nil { + log.Printf("dial error %v\n", err) + return err + } + + // 启动上行goroutine + go func() { + defer client.Close() + if err := client.SetDeadline(time.Now().Add(10 * time.Second)); err != nil { + log.Println("Failed to set deadline:", err) + return + } + + buf := make([]byte, 1024*256) + for { + select { + case <-cs: + break + default: + n, err := client.Read(buf) + if err != nil { + if err != io.EOF { + log.Printf("Failed to read from client: %v, close connection\n", err) + cs <- true + ss <- true + return + } + } + if n > 0 { + log.Printf("received from client bytes %d\n", n) + log.Printf("Received data from client %s: %s\n", client.RemoteAddr().String(), string(buf[:n])) + + // write to server + if n, err := server.Write(buf); err != nil { + log.Println("Failed to write to server:\n", err) + } else { + log.Printf("write to server %d\n", n) + } + // 清空缓冲区 + buf = make([]byte, 1024*256) + } + } + } + }() + + // 启动下行goroutine + go func() { + defer server.Close() + if err := server.SetDeadline(time.Now().Add(10 * time.Second)); err != nil { + log.Println("Failed to set deadline:\n", err) + return + } + buf := make([]byte, 1024*256) + for { + select { + case <-ss: + break + default: + n, err := server.Read(buf) + if err != nil { + if err != io.EOF { + log.Printf("Failed to read from server: %v, close connection\n", err) + ss <- true + cs <- true + return + } + } + if n > 0 { + log.Printf("received from server bytes %d\n", n) + log.Printf("Received data from server %s: %s\n", server.RemoteAddr().String(), string(buf[:n])) + + // write to client + if n, err := client.Write(buf); err != nil { + log.Println("Failed to write to client:", err) + } else { + log.Printf("write to client %d", n) + } + // 清空缓冲区 + buf = make([]byte, 1024*256) + } + } + } + }() + return nil +}