(转) golang 正向代理服务器(支持tcp)

声明:内容源自网络,版权归原作者所有。若有侵权请联系我们

所谓代理服务器就是位于发起请求的客户端与原始服务器端之间的一台跳板服务器,正向代理可以隐藏客户端,反向代理可以隐藏原始服务器。
向代理对用户则是不可知的,比如我们访问百度网站,百度的代理服务器对外的域名为 https://www.baidu.com 。具体内部的服务器节点我们不知道,现实中我们通过访问百度的代理服务器后,代理服务器给我们转发请求到他们N多的服务器节点中的一个给我们进行搜索后将结果返回。

package main

import (
	"fmt"
	"io"
	"net"
	"os"
	"strings"
	"sync"

	"github.com/urfave/cli/v2"
	"golang.org/x/sys/unix"
)

type tcpproxy struct {
	lock sync.Mutex
	dsts []string
	src  string
}

var TcpProxy = &cli.Command{
	Name:      "tcpproxy",
	Aliases:   []string{""},
	Usage:     "tcp port proxy",
	UsageText: "tcpproxy [--src=0.0.0.0:7777] [--dst=136.19.188.100:9999,136.19.188.110:9999,136.19.188.120:9999]",
	Flags: []cli.Flag{
		&cli.StringFlag{
			Name:   "src",
			Hidden: true,
		},
		&cli.StringFlag{
			Name:   "dst",
			Hidden: true,
		},
	},

	Action: func(cctx *cli.Context) error {
		src := cctx.String("src")
		dst := cctx.String("dst")
		if src == "" || dst == "" {
			fmt.Println(cctx.Command.UsageText)
			return nil
		}

		tp := &tcpproxy{
			src:  src,
			dsts: strings.Split(dst, ","),
		}
		tp.server()

		return nil
	},
}

func main() {
	local := []*cli.Command{
		TcpProxy,
	}

	app := &cli.App{
		Name:                 "proxy",
		Usage:                "proxy tcpproxy",
		Version:              "v0.0.1",
		EnableBashCompletion: true,
		Flags: []cli.Flag{
			&cli.StringFlag{
				Name:    "configfile",
				EnvVars: []string{""},
				Hidden:  true,
				Value:   "cfg.toml",
			},
		},
		Commands: local,
	}

	if err := app.Run(os.Args); err != nil {
		fmt.Fprintf(os.Stderr, "ERROR: %s\n\n", err) // nolint:errcheck
		os.Exit(1)
	}
}

func unixSetLimit(soft uint64, max uint64) error {
	rlimit := unix.Rlimit{
		Cur: soft,
		Max: max,
	}
	return unix.Setrlimit(unix.RLIMIT_NOFILE, &rlimit)
}

func (p *tcpproxy) server() {
	unixSetLimit(60000, 60000)
	listen, err := net.Listen("tcp", p.src)
	if err != nil {
		fmt.Println(err)
		return
	}
	defer listen.Close()
	fmt.Println("listen at:", p.src)
	for {
		conn, err := listen.Accept()
		if err != nil {
			fmt.Printf("接受客户端连接错误:%v\n", err)
			continue
		}
		fmt.Println("build new proxy connect. ", "client address =", conn.RemoteAddr(), " local server address=", conn.LocalAddr())
		go p.handle(conn)
	}
}

func (p *tcpproxy) handle(sconn net.Conn) {
	defer sconn.Close()
	dst, ok := p.select_dst()
	if !ok {
		return
	}
	dconn, err := net.Dial("tcp", dst)
	if err != nil {
		fmt.Printf("连接%v失败:%v\n", dst, err)
		return
	}
	defer dconn.Close()

	ExitChan := make(chan bool, 1)
	// 转发到目标服务器
	go func(sconn net.Conn, dconn net.Conn, Exit chan bool) {
		_, err := io.Copy(dconn, sconn)
		if err != nil {
			fmt.Printf("往%v发送数据失败:%v\n", dst, err)
			ExitChan <- true
		}
	}(sconn, dconn, ExitChan)

	// 从目标服务器返回数据到客户端
	go func(sconn net.Conn, dconn net.Conn, Exit chan bool) {
		_, err := io.Copy(sconn, dconn)
		if err != nil {
			fmt.Printf("从%v接收数据失败:%v\n", dst, err)
			ExitChan <- true
		}
	}(sconn, dconn, ExitChan)
	<-ExitChan
}

// ip 轮询
func (p *tcpproxy) select_dst() (string, bool) {
	p.lock.Lock()
	defer p.lock.Unlock()

	if len(p.dsts) < 1 {
		fmt.Println("failed select_dst()")
		return "", false
	}
	dst := p.dsts[0]
	p.dsts = append(p.dsts[1:], dst)
	return dst, true
}

相关文章