Skip to content

Commit

Permalink
Merge branch 'release/1.0.0'
Browse files Browse the repository at this point in the history
  • Loading branch information
mingcheng committed Jul 7, 2022
2 parents efdd656 + b23f415 commit 7bdbf07
Show file tree
Hide file tree
Showing 15 changed files with 261 additions and 25 deletions.
1 change: 1 addition & 0 deletions .drone.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ steps:
dockerfile: Dockerfile
tags:
- latest
- 1.0.0

volumes:
- name: docker-sock
Expand Down
47 changes: 46 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,12 @@
- `20220706` 完成针对 Linux 的透明网关功能
- `20220620` 完成基本功能

![socks5lb](./asserts/socks5lb.png)

我们在科学使用网络的时候经常会碰到 Socks5 Proxy 无法联通的情况,这有可能是因为网络或者线路的调整和波动,这时候往往需要我们自己手工的切换节点,非常的麻烦而且会中断网络请求。

这个工具就是为了解决上述问题而编写的,它简单的说就是个针对 Socks5 Proxy 的前置负载均衡,能够提供经过检验的稳定可靠的 Socks Proxy 节点,如果是针对 Linux 系统下同时能够提供透明代理以及针对 Socks5 协议的转换,而且方便搭配 ipset 以及 iptables 使用。
这个工具就是为了解决上述问题而编写的,它简单的说就是个针对 Socks5 Proxy 的前置负载均衡,能够提供经过检验的稳定可靠的 Socks Proxy 节点,如果是针对 Linux 系统下同时能够提供透明代理以及针对 Socks5
协议的转换,而且方便搭配 ipset 以及 iptables 使用。

目前实现的部分特性有:

Expand All @@ -21,6 +24,8 @@

## 配置

首先是针对 socks5lb 的基本配置,例如以下的配置配置了三个 Socks5 Proxy 同时暴露到本地的 1080 端口,针对 Linux 的透明代理暴露在 8848 端口。

```yaml
socks5_listen: ":1080"
tproxy_listen: ":8848"
Expand All @@ -42,8 +47,48 @@ backends:
timeout: 3
```
#### 环境变量
- `SELECT_TIME_INTERVAL` 自动切换代理的时间,单位为秒(默认300秒,五分钟)
- `CHECK_TIME_INTERVAL` 健康检查的轮询时间,单位为秒(默认一分钟)
- `DEBUG` 是否打开 debug 模式

### 部署

首先,以下是 docker-compose 相关的配置,建议使用 `network_mode: 'host'` 方式,防止 DOCK 的 iptables 造成网络联通错误
```yaml
version: "3"
services:
socks5lb:
image: ghcr.io/mingcheng/socks5lb
restart: always
dns:
- 8.8.8.8
- 8.8.4.4
environment:
TZ: "Asia/Shanghai"
CHECK_TIME_INTERVAL: 3600
network_mode: "host"
privileged: true
volumes:
- ./socks5lb.yml:/etc/socks5lb.yml:ro
```
然后配置(供参考)iptable 参数,将所有的流量都通过 8848 代理端口转发(注意 redrock 链没有定义,请自行配置)。
```shell
iptables -t nat -I PREROUTING -p tcp -m set --match-set redrock dst -j REDIRECT --to-ports 8848
iptables -t nat -I OUTPUT -p tcp -m set --match-set redrock dst -j REDIRECT --to-ports 8848
```

## 常见问题

### 在其他非 Linux 系统下可以使用 tproxy_listen 这个配置吗?

不好意思,透明代理只针对 Linux 平台。

### 有没有类似功能的项目?

- https://github.com/ginuerzh/gost
- https://github.com/nadoo/glider
Binary file added asserts/socks5lb.graffle
Binary file not shown.
Binary file added asserts/socks5lb.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
10 changes: 10 additions & 0 deletions backend.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
/**
* File: backend.go
* Author: Ming Cheng<mingcheng@outlook.com>
*
* Created Date: Tuesday, June 21st 2022, 6:03:26 pm
* Last Modified: Thursday, July 7th 2022, 6:30:08 pm
*
* http://www.opensource.org/licenses/MIT
*/

package socks5lb

import (
Expand Down
37 changes: 29 additions & 8 deletions cmd/socks5lb/main.go
Original file line number Diff line number Diff line change
@@ -1,30 +1,50 @@
/**
* File: main.go
* Author: Ming Cheng<mingcheng@outlook.com>
*
* Created Date: Wednesday, June 22nd 2022, 12:39:47 pm
* Last Modified: Thursday, July 7th 2022, 6:29:42 pm
*
* http://www.opensource.org/licenses/MIT
*/

package main

import (
"flag"
"io/ioutil"
"syscall"

"github.com/judwhite/go-svc"
"github.com/mingcheng/socks5lb"
log "github.com/sirupsen/logrus"
"gopkg.in/yaml.v3"
"io/ioutil"
"syscall"

"os"
)

const AppName = "socks5lb"

var config *socks5lb.Configure
var err error
var configFilePath string
var (
config *socks5lb.Configure
err error
cfgPath string
)

func init() {
log.SetOutput(os.Stdout)
log.SetLevel(log.TraceLevel)

flag.StringVar(&configFilePath, "c", "/etc/"+AppName+".yml", "configure file path")
isDebug := socks5lb.GetEnv("DEBUG", "")
if isDebug != "" {
log.SetLevel(log.TraceLevel)
} else {
log.SetLevel(log.InfoLevel)
}

flag.StringVar(&cfgPath, "c", "/etc/"+AppName+".yml", "configure file cfgPath")
}

// NewConfig returns a new Config instance
func NewConfig(path string) (config *socks5lb.Configure, err error) {
var (
data []byte
Expand All @@ -44,7 +64,8 @@ func NewConfig(path string) (config *socks5lb.Configure, err error) {
func main() {
flag.Parse()

if config, err = NewConfig(configFilePath); err != nil {
// read the config if err != nil
if config, err = NewConfig(cfgPath); err != nil {
log.Fatal(err)
}

Expand Down
14 changes: 14 additions & 0 deletions cmd/socks5lb/program.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
/**
* File: program.go
* Author: Ming Cheng<mingcheng@outlook.com>
*
* Created Date: Wednesday, July 6th 2022, 2:14:35 pm
* Last Modified: Thursday, July 7th 2022, 6:29:55 pm
*
* http://www.opensource.org/licenses/MIT
*/

package main

import (
Expand All @@ -7,12 +17,14 @@ import (

import "github.com/judwhite/go-svc"

// program to run a specific version of the local package socks5lb
type program struct {
Config *socks5lb.Configure
pool *socks5lb.Pool
Server socks5lb.Server
}

// Init to initial the program
func (p *program) Init(env svc.Environment) (err error) {

log.Tracef("new initial backend pools")
Expand All @@ -31,6 +43,7 @@ func (p *program) Init(env svc.Environment) (err error) {
return
}

// Start when the program is start
func (p *program) Start() (err error) {
go func() {
err = p.Server.Start(p.Config.Socks5Listen, p.Config.TproxyListen)
Expand All @@ -39,6 +52,7 @@ func (p *program) Start() (err error) {
return
}

// Stop when the program is stop
func (p *program) Stop() (err error) {
return p.Server.Stop()
}
10 changes: 10 additions & 0 deletions config.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
/**
* File: config.go
* Author: Ming Cheng<mingcheng@outlook.com>
*
* Created Date: Tuesday, June 21st 2022, 6:03:38 pm
* Last Modified: Thursday, July 7th 2022, 6:30:15 pm
*
* http://www.opensource.org/licenses/MIT
*/

package socks5lb

type Configure struct {
Expand Down
10 changes: 10 additions & 0 deletions pool.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
/**
* File: pool.go
* Author: Ming Cheng<mingcheng@outlook.com>
*
* Created Date: Tuesday, June 21st 2022, 6:03:26 pm
* Last Modified: Thursday, July 7th 2022, 6:47:39 pm
*
* http://www.opensource.org/licenses/MIT
*/

package socks5lb

import (
Expand Down
10 changes: 10 additions & 0 deletions redirect.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
//go:build !linux

/**
* File: redirect.go
* Author: Ming Cheng<mingcheng@outlook.com>
*
* Created Date: Wednesday, July 6th 2022, 11:46:51 am
* Last Modified: Thursday, July 7th 2022, 6:31:04 pm
*
* http://www.opensource.org/licenses/MIT
*/

package socks5lb

import (
Expand Down
70 changes: 65 additions & 5 deletions redirect_linux.go
Original file line number Diff line number Diff line change
@@ -1,16 +1,30 @@
//go:build linux

/**
* File: redirect_linux.go
* Author: Ming Cheng<mingcheng@outlook.com>
*
* Created Date: Wednesday, July 6th 2022, 11:47:00 am
* Last Modified: Thursday, July 7th 2022, 6:32:42 pm
*
* http://www.opensource.org/licenses/MIT
*/

package socks5lb

import (
"errors"
"fmt"
"github.com/LiamHaworth/go-tproxy"
log "github.com/sirupsen/logrus"
"github.com/txthinking/socks5"
"net"
"sync"
"syscall"
"time"
)

// getOriginalDstAddr to get the original address from the socket
func getOriginalDstAddr(conn *net.TCPConn) (addr net.Addr, c *net.TCPConn, err error) {
defer conn.Close()

Expand Down Expand Up @@ -40,12 +54,42 @@ func getOriginalDstAddr(conn *net.TCPConn) (addr net.Addr, c *net.TCPConn, err e

c, ok := cc.(*net.TCPConn)
if !ok {
err = errors.New("not a TCP connection")
err = errors.New("sorry, this is not a TCP connection")
}

return
}

var (
socks5Clients *socks5.Client
clientLock sync.Mutex
)

func (s *Server) socks5Client() (client *socks5.Client, err error) {
clientLock.Lock()

defer func() {
clientLock.Unlock()
if err != nil || client == nil {
log.Error(err)
socks5Clients = nil
return
}

log.Infof("markup current proxy connection: %v", client.Server)
socks5Clients = client
}()

backend := s.Pool.Next()
if backend == nil {
log.Error("sorry, we don't have healthy backend, so close the connection")
socks5Clients = nil
return
}

return backend.socks5Client(0)
}

func (s *Server) ListenTProxy(addr string) (err error) {
tcpAddr, err := net.ResolveTCPAddr("tcp", addr)
if err != nil {
Expand All @@ -58,6 +102,22 @@ func (s *Server) ListenTProxy(addr string) (err error) {
log.Error(err)
return
}
defer s.tproxyListener.Close()

// connect to available socks5 proxy server
selectTimeInterval := SecFromEnv("SELECT_TIME_INTERVAL", 300)
log.Infof("auto select the socks5 proxy server every %v", selectTimeInterval)

timer := time.NewTicker(selectTimeInterval)
defer timer.Stop()
go func() {
for ; true; <-timer.C {
_, err := s.socks5Client()
if err != nil {
log.Error(err)
}
}
}()

for {
tproxyConn, err := s.tproxyListener.Accept()
Expand All @@ -69,11 +129,11 @@ func (s *Server) ListenTProxy(addr string) (err error) {
go func() {
defer tproxyConn.Close()

backend := s.Pool.Next()
if backend == nil {
log.Error("sorry, we don't have healthy backend, so close the connection")
if socks5Clients == nil {
log.Error("not found any suitable socks5 clients")
return
}
log.Tracef("using connected socks5 proxy client: %v", socks5Clients.Server)

connect, ok := tproxyConn.(*tproxy.Conn)
if !ok {
Expand All @@ -90,7 +150,7 @@ func (s *Server) ListenTProxy(addr string) (err error) {
defer orgDstConn.Close()

log.Tracef("[red-tcp] %s -> %s", srcAddr, dstAddr)
socks5Conn, err := backend.Socks5Conn("tcp", dstAddr.String(), 0)
socks5Conn, err := socks5Clients.Dial("tcp", dstAddr.String())
if err != nil {
log.Error(err)
}
Expand Down
Loading

0 comments on commit 7bdbf07

Please sign in to comment.