Skip to content

Commit

Permalink
Merge pull request #2 from keminar/todo
Browse files Browse the repository at this point in the history
增加指定代理功能
  • Loading branch information
keminar authored Mar 26, 2021
2 parents 68392c0 + d2e8058 commit 266ea16
Show file tree
Hide file tree
Showing 8 changed files with 124 additions and 30 deletions.
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,9 +116,10 @@ docker run -p 3000:3000 anyproxy:latest -p '127.0.0.1:3001'
sudo useradd -M -s /sbin/nologin anyproxy
# uid为anyproxy的tcp请求不转发,并用anyproxy用户启动anyproxy程序
sudo iptables -t nat -A OUTPUT -p tcp -m owner --uid-owner anyproxy -j RETURN
# 单独指定root账号走代理
sudo iptables -t nat -A OUTPUT -p tcp -j REDIRECT -m owner --uid-owner 0 --to-port 3000
# 其它用户的tcp请求转发到本地3000端口
sudo iptables -t nat -A OUTPUT -p tcp -j REDIRECT --to-port 3000
```

> 如果删除全局代理
Expand Down Expand Up @@ -154,7 +155,7 @@ sudo iptables -t nat -D OUTPUT 2
* ~~日志信息完善~~
* ~~DNS解析增加cache~~
* ~~自动路由模式下可设置检测时间和cache~~
* 可以支持多个server,如果一个不可用用下一个
* ~~可以自定义代理server,如果不可用则用全局的~~
* ~~server多级转发~~
* ~~加域名黑名单功能,不给请求~~
* 请求Body内容体记录, 涉及安全,可能不会实现
Expand All @@ -166,6 +167,7 @@ sudo iptables -t nat -D OUTPUT 2
* ~~支持HTTP/1.1 keep-alive 一外链接多次请求不同域名~~
* HTTP/1.1 keep-alive后端也能复用tcp
* ~~修复iptables转发后百度贴吧无法访问的问题~~
* 转发的mysql的连接请求会一直卡住

# 感谢

Expand Down
3 changes: 3 additions & 0 deletions conf/router.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,12 @@ hosts:
# contain 包含,equal 完全相等, preg 正则
match: contain
# 参考全局target
# 如果有用proxy自定义代理可用,target强制当remote使用,proxy代理不可用,target按原逻辑处理
target: remote
# 参考全局localDns
dns: local
# 支持 http:// , tunnel:// , socks5:// 三种协议,默认 tunnel://
proxy: http://127.0.0.1:8888
- name: golang.org
match: contain
target: auto
Expand Down
4 changes: 2 additions & 2 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ import (
"github.com/keminar/anyproxy/utils/tools"
)

// ProxyScheme 协议
var ProxyScheme string = "http"
// ProxyScheme 协议,目前支持 tunnel为自定义协议,socks5,http为标准协议
var ProxyScheme string = "tunnel"

// ProxyServer 代理服务器
var ProxyServer string
Expand Down
1 change: 1 addition & 0 deletions proto/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,7 @@ func (that *httpStream) readRequest(from string) (canProxy bool, err error) {
}
}
}
//todo Vue的 /sockjs-node/ 请求,走了代理首行GET后面会有域名,服务会响应错的内容
//that.Header.Set("Connection", "Close")
that.readBody()
that.getNameIPPort()
Expand Down
101 changes: 79 additions & 22 deletions proto/tunnel.go
Original file line number Diff line number Diff line change
Expand Up @@ -262,11 +262,27 @@ func (s *tunnel) handshake(proto string, dstName, dstIP string, dstPort uint16)
err = fmt.Errorf("deny visit %s (%s)", dstName, dstIP)
return
}

if config.ProxyServer != "" && config.ProxyPort > 0 && confTarget != "local" {
proxyScheme := config.ProxyScheme
proxyServer := config.ProxyServer
proxyPort := config.ProxyPort
if host.Proxy != "" { //如果有自定义代理,则走自定义
proxyScheme2, proxyServer2, proxyPort2, err := getProxyServer(host.Proxy)
if err != nil {
// 如果自定义代理不可用,confTarget走原来逻辑
log.Println(trace.ID(s.req.ID), "host.proxy err", host.Proxy, err)
} else {
proxyScheme = proxyScheme2
proxyServer = proxyServer2
proxyPort = proxyPort2
if confTarget != "remote" { //如果有定制代理,就不能用local 和 auto
confTarget = "remote"
}
}
}
if proxyServer != "" && proxyPort > 0 && confTarget != "local" {
if confTarget == "auto" {
if state != cache.StateFail {
//local dial成功则返回
//local dial成功则返回,走本地网络
//auto 只能优化ip ping 不通的情况,能dail通访问不了的需要手动remote
err = s.dail(dstIP, dstPort)
if err == nil {
Expand Down Expand Up @@ -294,16 +310,18 @@ func (s *tunnel) handshake(proto string, dstName, dstIP string, dstPort uint16)
err = errors.New("target host is empty")
return
}
log.Println(trace.ID(s.req.ID), fmt.Sprintf("PROXY %s:%d for %s", config.ProxyServer, config.ProxyPort, target))
log.Println(trace.ID(s.req.ID), fmt.Sprintf("PROXY %s:%d for %s", proxyServer, proxyPort, target))

switch config.ProxyScheme {
switch proxyScheme {
case "socks5":
err = s.socks5(target)
err = s.socks5(target, proxyServer, proxyPort)
case "tunnel":
err = s.httpConnect(target, proxyServer, proxyPort, true)
case "http":
err = s.httpConnect(target)
err = s.httpConnect(target, proxyServer, proxyPort, false)
default:
log.Println(trace.ID(s.req.ID), "proxy scheme", config.ProxyScheme, "is error")
err = fmt.Errorf("%s is error", config.ProxyScheme)
log.Println(trace.ID(s.req.ID), "proxy scheme", proxyScheme, "is error")
err = fmt.Errorf("%s is error", proxyScheme)
return
}
} else {
Expand All @@ -320,9 +338,44 @@ func (s *tunnel) handshake(proto string, dstName, dstIP string, dstPort uint16)
return
}

// getProxyServer 解析代理服务器
func getProxyServer(proxySpec string) (string, string, uint16, error) {
if proxySpec == "" {
return "", "", 0, errors.New("proxy 长度为空")
}
proxyScheme := "tunnel"
var proxyServer string
var proxyPort uint16
// 先检查协议
tmp := strings.Split(proxySpec, "://")
if len(tmp) == 2 {
proxyScheme = tmp[0]
proxySpec = tmp[1]
}
// 检查端口,和上面的顺序不能反
tmp = strings.Split(proxySpec, ":")
if len(tmp) == 2 {
portInt, err := strconv.Atoi(tmp[1])
if err == nil {
proxyServer = tmp[0]
proxyPort = uint16(portInt)
// 检查是否可连通
connTimeout := time.Duration(1) * time.Second
conn, err := net.DialTimeout("tcp", fmt.Sprintf("%s:%d", proxyServer, proxyPort), connTimeout)
if err != nil {
return "", "", 0, err
}
conn.Close()
return proxyScheme, proxyServer, proxyPort, nil
}
return "", "", 0, err
}
return "", "", 0, errors.New("proxy 格式不对")
}

// socket5代理
func (s *tunnel) socks5(target string) (err error) {
address := fmt.Sprintf("%s:%d", config.ProxyServer, config.ProxyPort)
func (s *tunnel) socks5(target string, proxyServer string, proxyPort uint16) (err error) {
address := fmt.Sprintf("%s:%d", proxyServer, proxyPort)
var dialProxy proxy.Dialer
dialProxy, err = proxy.SOCKS5("tcp", address, nil, proxy.Direct)
if err != nil {
Expand All @@ -341,22 +394,26 @@ func (s *tunnel) socks5(target string) (err error) {
}

// http代理
func (s *tunnel) httpConnect(target string) (err error) {
err = s.dail(config.ProxyServer, config.ProxyPort)
func (s *tunnel) httpConnect(target string, proxyServer string, proxyPort uint16, encrypt bool) (err error) {
err = s.dail(proxyServer, proxyPort)
if err != nil {
log.Println(trace.ID(s.req.ID), "dail err", err.Error())
return
}
key := []byte(getToken())
var x1 []byte
x1, err = crypto.EncryptAES([]byte(target), key)
if err != nil {
log.Println(trace.ID(s.req.ID), "encrypt err", err.Error())
return
var connectString string
if encrypt {
key := []byte(getToken())
var x1 []byte
x1, err = crypto.EncryptAES([]byte(target), key)
if err != nil {
log.Println(trace.ID(s.req.ID), "encrypt err", err.Error())
return
}
// CONNECT实现的加密
connectString = fmt.Sprintf("CONNECT %s HTTP/1.1\r\n\r\n", base64.StdEncoding.EncodeToString(x1))
} else {
connectString = fmt.Sprintf("CONNECT %s HTTP/1.1\r\n\r\n", target)
}

// CONNECT实现的加密
connectString := fmt.Sprintf("CONNECT %s HTTP/1.1\r\n\r\n", base64.StdEncoding.EncodeToString(x1))
fmt.Fprintf(s.conn, connectString)
var status string
status, err = bufio.NewReader(s.conn).ReadString('\n')
Expand Down
7 changes: 6 additions & 1 deletion scripts/build.sh
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
#!/bin/bash
SCRIPT=$(readlink -f $0)
ROOT_DIR=$(dirname $SCRIPT)/../
cd $ROOT_DIR

# for alpine
go build -tags netgo -o anyproxy.netgo anyproxy.go

# common
go build -o anyproxy anyproxy.go
go build -o tunnel/tunneld tunnel/tunneld.go
go build -o tunnel/tunneld tunnel/tunneld.go
31 changes: 28 additions & 3 deletions scripts/setup.sh
Original file line number Diff line number Diff line change
@@ -1,3 +1,28 @@
sudo useradd -M -s /sbin/nologin anyproxy
sudo iptables -t nat -A OUTPUT -p tcp -m owner --uid-owner anyproxy -j RETURN
sudo iptables -t nat -A OUTPUT -p tcp -j REDIRECT --to-port 3000
#!/bin/bash
SCRIPT=$(readlink -f $0)
ROOT_DIR=$(dirname $SCRIPT)/../
cd $ROOT_DIR
ulimit -n 65536

if ! (sudo cat /etc/passwd|grep ^anyproxy: > /dev/null); then
echo "添加账号"
sudo useradd -M -s /sbin/nologin anyproxy
fi

# 要启动的端口
port=3000
if ! (sudo iptables -t nat -L|grep "redir ports $port" > /dev/null); then
echo "添加iptables"
# anyproxy 账号不走代理直接请求
sudo iptables -t nat -A OUTPUT -p tcp -m owner --uid-owner anyproxy -j RETURN

# 注:如果有虚拟机,虚拟机的启动账号不要走代理端口,会导致虚拟机里的浏览器设置的代理无效果
# 如果有本地账号要连mysql等服务,也不要走代理端口,会一直卡住(目前发现mysql协议不兼容)

#指定root账号走代理
sudo iptables -t nat -A OUTPUT -p tcp -j REDIRECT -m owner --uid-owner 0 --to-port $port
#其它账号可以走代理
#sudo iptables -t nat -A OUTPUT -p tcp -j REDIRECT --to-port $port
fi
echo "启动anyproxy"
sudo -u anyproxy ./anyproxy -daemon -l $port
1 change: 1 addition & 0 deletions utils/conf/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ type Host struct {
Target string `yaml:"target"` //local 当前环境, remote 远程, deny 禁止, auto根据dial选择
DNS string `yaml:"dns"` //local 当前环境, remote 远程, 仅当target使用remote有效
IP string `yaml:"ip"` //本地解析ip
Proxy string `yaml:"proxy"` //指定代理服务器
}

// Log 日志
Expand Down

0 comments on commit 266ea16

Please sign in to comment.