提交 2ba84d37 编写于 作者: F fatedier

Release v0.3.0

Release v0.3.0
......@@ -3,7 +3,7 @@ language: go
go:
- 1.4.2
- 1.5.1
- 1.5.3
install:
- make
......
......@@ -2,7 +2,7 @@ FROM golang:1.5
MAINTAINER fatedier
RUN echo "[common]\nbind_addr = 0.0.0.0\nbind_port = 7000\n[wiki]\npasswd = 123\nbind_addr = 0.0.0.0\nlisten_port = 80" > /usr/share/frps.ini
RUN echo "[common]\nbind_addr = 0.0.0.0\nbind_port = 7000\n[test]\npasswd = 123\nbind_addr = 0.0.0.0\nlisten_port = 80" > /usr/share/frps.ini
ADD ./ /usr/share/frp/
......@@ -11,4 +11,4 @@ RUN cd /usr/share/frp && make
EXPOSE 80
EXPOSE 7000
CMD ["/usr/share/frp/bin/frps -c /usr/share/frps.ini"]
CMD ["/usr/share/frp/bin/frps", "-c", "/usr/share/frps.ini"]
......@@ -2,13 +2,17 @@
[![Build Status](https://travis-ci.org/fatedier/frp.svg)](https://travis-ci.org/fatedier/frp)
[README](README.md) | [中文文档](README_zh.md)
## What is frp?
frp is a fast reverse proxy which can help you expose a local server behind a NAT or firewall to the internet.
frp is a fast reverse proxy to help you expose a local server behind a NAT or firewall to the internet.
## Status
frp is under development and you can try it with available version 0.2.0.
frp is under development and you can try it with latest release version.Master branch for releasing stable version when dev branch for developing.
**We may change any protocol and can't promise backward compatible before version 1.x.**
## Quick Start
......@@ -28,6 +32,11 @@ Read the [QuickStart](doc/quick_start_en.md) | [使用文档](doc/quick_start_zh
Interested in getting involved? We would love to help you!
For simple bug fixes, just submit a PR with the fix and we can discuss the fix directly in the PR. If the fix is more complex, start with an issue.
* Take a look at our [issues list](https://github.com/fatedier/frp/issues) and consider submitting a patch
* If you have some wanderful ideas, send email to fatedier@gmail.com.
## Contributors
If you have some wanderful ideas, send email to fatedier@gmail.com.
* [fatedier](https://github.com/fatedier)
* [Hurricanezwf](https://github.com/Hurricanezwf)
* [vashstorm](https://github.com/vashstorm)
# frp
[![Build Status](https://travis-ci.org/fatedier/frp.svg)](https://travis-ci.org/fatedier/frp)
[README](README.md) | [中文文档](README_zh.md)
>frp 是一个高性能的反向代理应用,可以帮助你轻松的进行内网穿透,对外网提供服务。
## 开发状态
frp 目前正在前期开发阶段,master分支用于发布稳定版本,dev分支用于开发,您可以尝试下载最新的 release 版本进行测试。
**在 1.x 版本以前,交互协议都可能会被改变,不能保证向后兼容。**
## 快速开始
[QuickStart](doc/quick_start_en.md) | [使用文档](doc/quick_start_zh.md)
## 架构
![architecture](doc/pic/architecture.png)
## frp 的作用?
* 利用处于内网或防火墙后的机器,对外网环境提供http服务。(针对http的优化正在开发中)
* 利用处于内网或防火墙后的机器,对外网环境提供tcp服务。
* 可查看通过代理的所有http请求和响应信息。(待开发)
## 贡献代码
如果您对这个项目感兴趣,并且想要参与其中,我们非常欢迎!
* 如果您需要提交问题,可以通过 [issues](https://github.com/fatedier/frp/issues) 来完成。
* 如果您有新的功能需求,可以反馈至 fatedier@gmail.com 共同讨论。
## 贡献者
* [fatedier](https://github.com/fatedier)
* [Hurricanezwf](https://github.com/Hurricanezwf)
* [vashstorm](https://github.com/vashstorm)
......@@ -6,9 +6,12 @@ server_port = 7000
log_file = console
# debug, info, warn, error
log_level = debug
# for authentication
auth_token = 123
# test1 is the proxy name same as server's configuration
[test1]
passwd = 123
local_ip = 127.0.0.1
local_port = 22
# true or false, if true, messages between frps and frpc will be encrypted, default is false
use_encryption = true
......@@ -7,8 +7,8 @@ log_file = console
# debug, info, warn, error
log_level = debug
# test1 is the proxy name, client will use this name and passwd to connect to server
# test1 is the proxy name, client will use this name and auth_token to connect to server
[test1]
passwd = 123
auth_token = 123
bind_addr = 0.0.0.0
listen_port = 6000
......@@ -44,7 +44,7 @@ log_level = info
# test is the custom name of proxy and there can be many proxies with unique name in one configure file
[test]
passwd = 123
auth_token = 123
bind_addr = 0.0.0.0
# finally we connect to server A by this port
listen_port = 6000
......@@ -59,10 +59,13 @@ server_addr = x.x.x.x
server_port = 7000
log_file = ./frpc.log
log_level = info
# for authentication
auth_token = 123
# test is proxy name same with configure in frps.ini
[test]
passwd = 123
# local port which need to be transferred
local_port = 22
# if use_encryption equals true, messages between frpc and frps will be encrypted, default is false
use_encryption = true
```
......@@ -42,7 +42,7 @@ log_level = info
# test 为代理的自定义名称,可以有多个,不能重复,和frpc中名称对应
[test]
passwd = 123
auth_token = 123
bind_addr = 0.0.0.0
# 最后将通过此端口访问后端服务
listen_port = 6000
......@@ -57,10 +57,13 @@ server_addr = x.x.x.x
server_port = 7000
log_file = ./frpc.log
log_level = info
# 用于身份验证
auth_token = 123
# test需要和 frps.ini 中配置一致
[test]
passwd = 123
# 需要转发的本地端口
local_port = 22
# 启用加密,frpc与frps之间通信加密,默认为 false
use_encryption = true
```
......@@ -26,66 +26,101 @@ import (
"frp/models/msg"
"frp/utils/conn"
"frp/utils/log"
"frp/utils/pcrypto"
)
var connection *conn.Conn = nil
var heartBeatTimer *time.Timer = nil
func ControlProcess(cli *client.ProxyClient, wait *sync.WaitGroup) {
defer wait.Done()
msgSendChan := make(chan interface{}, 1024)
c, err := loginToServer(cli)
if err != nil {
log.Error("ProxyName [%s], connect to server failed!", cli.Name)
return
}
connection = c
defer connection.Close()
defer c.Close()
go heartbeatSender(c, msgSendChan)
go msgSender(cli, c, msgSendChan)
msgReader(cli, c, msgSendChan)
close(msgSendChan)
}
// loop for reading messages from frpc after control connection is established
func msgReader(cli *client.ProxyClient, c *conn.Conn, msgSendChan chan interface{}) error {
// for heartbeat
var heartbeatTimeout bool = false
timer := time.AfterFunc(time.Duration(client.HeartBeatTimeout)*time.Second, func() {
heartbeatTimeout = true
c.Close()
log.Error("ProxyName [%s], heartbeatRes from frps timeout", cli.Name)
})
defer timer.Stop()
for {
// ignore response content now
content, err := connection.ReadLine()
if err == io.EOF || nil == connection || connection.IsClosed() {
log.Debug("ProxyName [%s], server close this control conn", cli.Name)
var sleepTime time.Duration = 1
buf, err := c.ReadLine()
if err == io.EOF || c == nil || c.IsClosed() {
c.Close()
log.Warn("ProxyName [%s], frps close this control conn!", cli.Name)
var delayTime time.Duration = 1
// loop until connect to server
// loop until reconnect to frps
for {
log.Debug("ProxyName [%s], try to reconnect to server[%s:%d]...", cli.Name, client.ServerAddr, client.ServerPort)
tmpConn, err := loginToServer(cli)
log.Info("ProxyName [%s], try to reconnect to frps [%s:%d]...", cli.Name, client.ServerAddr, client.ServerPort)
c, err = loginToServer(cli)
if err == nil {
connection.Close()
connection = tmpConn
go heartbeatSender(c, msgSendChan)
break
}
if sleepTime < 60 {
sleepTime = sleepTime * 2
if delayTime < 60 {
delayTime = delayTime * 2
}
time.Sleep(sleepTime * time.Second)
time.Sleep(delayTime * time.Second)
}
continue
} else if err != nil {
log.Warn("ProxyName [%s], read from server error, %v", cli.Name, err)
log.Warn("ProxyName [%s], read from frps error: %v", cli.Name, err)
continue
}
clientCtlRes := &msg.ClientCtlRes{}
if err := json.Unmarshal([]byte(content), clientCtlRes); err != nil {
log.Warn("Parse err: %v : %s", err, content)
ctlRes := &msg.ControlRes{}
if err := json.Unmarshal([]byte(buf), &ctlRes); err != nil {
log.Warn("ProxyName [%s], parse msg from frps error: %v : %s", cli.Name, err, buf)
continue
}
if consts.SCHeartBeatRes == clientCtlRes.GeneralRes.Code {
if heartBeatTimer != nil {
log.Debug("Client rcv heartbeat response")
heartBeatTimer.Reset(time.Duration(client.HeartBeatTimeout) * time.Second)
} else {
log.Error("heartBeatTimer is nil")
}
continue
switch ctlRes.Type {
case consts.HeartbeatRes:
log.Debug("ProxyName [%s], receive heartbeat response", cli.Name)
timer.Reset(time.Duration(client.HeartBeatTimeout) * time.Second)
case consts.NoticeUserConn:
log.Debug("ProxyName [%s], new user connection", cli.Name)
cli.StartTunnel(client.ServerAddr, client.ServerPort)
default:
log.Warn("ProxyName [%s}, unsupport msgType [%d]", cli.Name, ctlRes.Type)
}
}
return nil
}
// loop for sending messages from channel to frps
func msgSender(cli *client.ProxyClient, c *conn.Conn, msgSendChan chan interface{}) {
for {
msg, ok := <-msgSendChan
if !ok {
break
}
cli.StartTunnel(client.ServerAddr, client.ServerPort)
buf, _ := json.Marshal(msg)
err := c.Write(string(buf) + "\n")
if err != nil {
log.Warn("ProxyName [%s], write to client error, proxy exit", cli.Name)
c.Close()
break
}
}
}
......@@ -96,10 +131,14 @@ func loginToServer(cli *client.ProxyClient) (c *conn.Conn, err error) {
return
}
req := &msg.ClientCtlReq{
Type: consts.CtlConn,
ProxyName: cli.Name,
Passwd: cli.Passwd,
nowTime := time.Now().Unix()
authKey := pcrypto.GetAuthKey(cli.Name + cli.AuthToken + fmt.Sprintf("%d", nowTime))
req := &msg.ControlReq{
Type: consts.NewCtlConn,
ProxyName: cli.Name,
AuthKey: authKey,
UseEncryption: cli.UseEncryption,
Timestamp: nowTime,
}
buf, _ := json.Marshal(req)
err = c.Write(string(buf) + "\n")
......@@ -115,53 +154,31 @@ func loginToServer(cli *client.ProxyClient) (c *conn.Conn, err error) {
}
log.Debug("ProxyName [%s], read [%s]", cli.Name, res)
clientCtlRes := &msg.ClientCtlRes{}
if err = json.Unmarshal([]byte(res), &clientCtlRes); err != nil {
ctlRes := &msg.ControlRes{}
if err = json.Unmarshal([]byte(res), &ctlRes); err != nil {
log.Error("ProxyName [%s], format server response error, %v", cli.Name, err)
return
}
if clientCtlRes.Code != 0 {
log.Error("ProxyName [%s], start proxy error, %s", cli.Name, clientCtlRes.Msg)
return c, fmt.Errorf("%s", clientCtlRes.Msg)
if ctlRes.Code != 0 {
log.Error("ProxyName [%s], start proxy error, %s", cli.Name, ctlRes.Msg)
return c, fmt.Errorf("%s", ctlRes.Msg)
}
go startHeartBeat(c)
log.Debug("ProxyName [%s], connect to server[%s:%d] success!", cli.Name, client.ServerAddr, client.ServerPort)
log.Debug("ProxyName [%s], connect to server [%s:%d] success!", cli.Name, client.ServerAddr, client.ServerPort)
return
}
func startHeartBeat(c *conn.Conn) {
f := func() {
log.Error("HeartBeat timeout!")
if c != nil {
c.Close()
}
}
heartBeatTimer = time.AfterFunc(time.Duration(client.HeartBeatTimeout)*time.Second, f)
defer heartBeatTimer.Stop()
clientCtlReq := &msg.ClientCtlReq{
Type: consts.CSHeartBeatReq,
ProxyName: "",
Passwd: "",
func heartbeatSender(c *conn.Conn, msgSendChan chan interface{}) {
heartbeatReq := &msg.ControlReq{
Type: consts.HeartbeatReq,
}
request, err := json.Marshal(clientCtlReq)
if err != nil {
log.Warn("Serialize clientCtlReq err! Err: %v", err)
}
log.Debug("Start to send heartbeat")
log.Info("Start to send heartbeat to frps")
for {
time.Sleep(time.Duration(client.HeartBeatInterval) * time.Second)
if c != nil && !c.IsClosed() {
log.Debug("Send heartbeat to server")
err = c.Write(string(request) + "\n")
if err != nil {
log.Error("Send hearbeat to server failed! Err:%v", err)
continue
}
msgSendChan <- heartbeatReq
} else {
break
}
......
......@@ -25,6 +25,7 @@ import (
"frp/models/server"
"frp/utils/conn"
"frp/utils/log"
"frp/utils/pcrypto"
)
func ProcessControlConn(l *conn.Listener) {
......@@ -33,87 +34,162 @@ func ProcessControlConn(l *conn.Listener) {
if err != nil {
return
}
log.Debug("Get one new conn, %v", c.GetRemoteAddr())
log.Debug("Get new connection, %v", c.GetRemoteAddr())
go controlWorker(c)
}
}
// connection from every client and server
func controlWorker(c *conn.Conn) {
// the first message is from client to server
// if error, close connection
res, err := c.ReadLine()
// if login message type is NewWorkConn, don't close this connection
var closeFlag bool = true
var s *server.ProxyServer
defer func() {
if closeFlag {
c.Close()
if s != nil {
s.Close()
}
}
}()
// get login message
buf, err := c.ReadLine()
if err != nil {
log.Warn("Read error, %v", err)
return
}
log.Debug("get: %s", res)
log.Debug("Get msg from frpc: %s", buf)
clientCtlReq := &msg.ClientCtlReq{}
clientCtlRes := &msg.ClientCtlRes{}
if err := json.Unmarshal([]byte(res), &clientCtlReq); err != nil {
log.Warn("Parse err: %v : %s", err, res)
cliReq := &msg.ControlReq{}
if err := json.Unmarshal([]byte(buf), &cliReq); err != nil {
log.Warn("Parse msg from frpc error: %v : %s", err, buf)
return
}
// check
succ, info, needRes := checkProxy(clientCtlReq, c)
if !succ {
clientCtlRes.Code = 1
clientCtlRes.Msg = info
// do login when type is NewCtlConn or NewWorkConn
ret, info := doLogin(cliReq, c)
s, ok := server.ProxyServers[cliReq.ProxyName]
if !ok {
log.Warn("ProxyName [%s] is not exist", cliReq.ProxyName)
return
}
if needRes {
defer c.Close()
buf, _ := json.Marshal(clientCtlRes)
err = c.Write(string(buf) + "\n")
// if login type is NewWorkConn, nothing will be send to frpc
if cliReq.Type != consts.NewWorkConn {
cliRes := &msg.ControlRes{
Type: consts.NewCtlConnRes,
Code: ret,
Msg: info,
}
byteBuf, _ := json.Marshal(cliRes)
err = c.Write(string(byteBuf) + "\n")
if err != nil {
log.Warn("Write error, %v", err)
log.Warn("ProxyName [%s], write to client error, proxy exit", s.Name)
time.Sleep(1 * time.Second)
return
}
} else {
// work conn, just return
closeFlag = false
return
}
// other messages is from server to client
s, ok := server.ProxyServers[clientCtlReq.ProxyName]
if !ok {
log.Warn("ProxyName [%s] is not exist", clientCtlReq.ProxyName)
return
}
// create a channel for sending messages
msgSendChan := make(chan interface{}, 1024)
go msgSender(s, c, msgSendChan)
go noticeUserConn(s, msgSendChan)
// read control msg from client
go readControlMsgFromClient(s, c)
// loop for reading control messages from frpc and deal with different types
msgReader(s, c, msgSendChan)
serverCtlReq := &msg.ClientCtlReq{}
serverCtlReq.Type = consts.WorkConn
close(msgSendChan)
log.Info("ProxyName [%s], I'm dead!", s.Name)
return
}
// when frps get one new user connection, send NoticeUserConn message to frpc and accept one new WorkConn later
func noticeUserConn(s *server.ProxyServer, msgSendChan chan interface{}) {
for {
closeFlag := s.WaitUserConn()
if closeFlag {
log.Debug("ProxyName [%s], goroutine for dealing user conn is closed", s.Name)
log.Debug("ProxyName [%s], goroutine for noticing user conn is closed", s.Name)
break
}
buf, _ := json.Marshal(serverCtlReq)
err = c.Write(string(buf) + "\n")
notice := &msg.ControlRes{
Type: consts.NoticeUserConn,
}
msgSendChan <- notice
log.Debug("ProxyName [%s], notice client to add work conn", s.Name)
}
}
// loop for reading messages from frpc after control connection is established
func msgReader(s *server.ProxyServer, c *conn.Conn, msgSendChan chan interface{}) error {
// for heartbeat
var heartbeatTimeout bool = false
timer := time.AfterFunc(time.Duration(server.HeartBeatTimeout)*time.Second, func() {
heartbeatTimeout = true
s.Close()
c.Close()
log.Error("ProxyName [%s], client heartbeat timeout", s.Name)
})
defer timer.Stop()
for {
buf, err := c.ReadLine()
if err != nil {
log.Warn("ProxyName [%s], write to client error, proxy exit", s.Name)
s.Close()
return
if err == io.EOF {
log.Warn("ProxyName [%s], client is dead!", s.Name)
return err
} else if c == nil || c.IsClosed() {
log.Warn("ProxyName [%s], client connection is closed", s.Name)
return err
}
log.Warn("ProxyName [%s], read error: %v", s.Name, err)
continue
}
cliReq := &msg.ControlReq{}
if err := json.Unmarshal([]byte(buf), &cliReq); err != nil {
log.Warn("ProxyName [%s], parse msg from frpc error: %v : %s", s.Name, err, buf)
continue
}
log.Debug("ProxyName [%s], write to client to add work conn success", s.Name)
switch cliReq.Type {
case consts.HeartbeatReq:
log.Debug("ProxyName [%s], get heartbeat", s.Name)
timer.Reset(time.Duration(server.HeartBeatTimeout) * time.Second)
heartbeatRes := msg.ControlRes{
Type: consts.HeartbeatRes,
}
msgSendChan <- heartbeatRes
default:
log.Warn("ProxyName [%s}, unsupport msgType [%d]", s.Name, cliReq.Type)
}
}
return nil
}
log.Info("ProxyName [%s], I'm dead!", s.Name)
return
// loop for sending messages from channel to frpc
func msgSender(s *server.ProxyServer, c *conn.Conn, msgSendChan chan interface{}) {
for {
msg, ok := <-msgSendChan
if !ok {
break
}
buf, _ := json.Marshal(msg)
err := c.Write(string(buf) + "\n")
if err != nil {
log.Warn("ProxyName [%s], write to client error, proxy exit", s.Name)
s.Close()
break
}
}
}
func checkProxy(req *msg.ClientCtlReq, c *conn.Conn) (succ bool, info string, needRes bool) {
succ = false
needRes = true
// if success, ret equals 0, otherwise greater than 0
func doLogin(req *msg.ControlReq, c *conn.Conn) (ret int64, info string) {
ret = 1
// check if proxy name exist
s, ok := server.ProxyServers[req.ProxyName]
if !ok {
......@@ -122,97 +198,53 @@ func checkProxy(req *msg.ClientCtlReq, c *conn.Conn) (succ bool, info string, ne
return
}
// check password
if req.Passwd != s.Passwd {
info = fmt.Sprintf("ProxyName [%s], password is not correct", req.ProxyName)
// check authKey
nowTime := time.Now().Unix()
authKey := pcrypto.GetAuthKey(req.ProxyName + s.AuthToken + fmt.Sprintf("%d", req.Timestamp))
// authKey avaiable in 15 minutes
if nowTime-req.Timestamp > 15*60 {
info = fmt.Sprintf("ProxyName [%s], authorization timeout", req.ProxyName)
log.Warn(info)
return
} else if req.AuthKey != authKey {
info = fmt.Sprintf("ProxyName [%s], authorization failed", req.ProxyName)
log.Warn(info)
return
}
// control conn
if req.Type == consts.CtlConn {
if s.Status != consts.Idle {
if req.Type == consts.NewCtlConn {
if s.Status == consts.Working {
info = fmt.Sprintf("ProxyName [%s], already in use", req.ProxyName)
log.Warn(info)
return
}
// start proxy and listen for user conn, no block
// set infomations from frpc
s.UseEncryption = req.UseEncryption
// start proxy and listen for user connections, no block
err := s.Start()
if err != nil {
info = fmt.Sprintf("ProxyName [%s], start proxy error: %v", req.ProxyName, err.Error())
info = fmt.Sprintf("ProxyName [%s], start proxy error: %v", req.ProxyName, err)
log.Warn(info)
return
}
log.Info("ProxyName [%s], start proxy success", req.ProxyName)
} else if req.Type == consts.WorkConn {
} else if req.Type == consts.NewWorkConn {
// work conn
needRes = false
if s.Status != consts.Working {
log.Warn("ProxyName [%s], is not working when it gets one new work conn", req.ProxyName)
log.Warn("ProxyName [%s], is not working when it gets one new work connnection", req.ProxyName)
return
}
s.GetNewCliConn(c)
// the connection will close after join over
s.RecvNewWorkConn(c)
} else {
info = fmt.Sprintf("ProxyName [%s], type [%d] unsupport", req.ProxyName, req.Type)
log.Warn(info)
info = fmt.Sprintf("Unsupport login message type [%d]", req.Type)
log.Warn("Unsupport login message type [%d]", req.Type)
return
}
succ = true
ret = 0
return
}
func readControlMsgFromClient(s *server.ProxyServer, c *conn.Conn) {
isContinueRead := true
f := func() {
isContinueRead = false
s.Close()
log.Error("ProxyName [%s], client heartbeat timeout", s.Name)
}
timer := time.AfterFunc(time.Duration(server.HeartBeatTimeout)*time.Second, f)
defer timer.Stop()
for isContinueRead {
content, err := c.ReadLine()
if err != nil {
if err == io.EOF {
log.Warn("ProxyName [%s], client is dead!", s.Name)
s.Close()
break
} else if nil == c || c.IsClosed() {
log.Warn("ProxyName [%s], client connection is closed", s.Name)
break
}
log.Error("ProxyName [%s], read error: %v", s.Name, err)
continue
}
clientCtlReq := &msg.ClientCtlReq{}
if err := json.Unmarshal([]byte(content), clientCtlReq); err != nil {
log.Warn("Parse err: %v : %s", err, content)
continue
}
if consts.CSHeartBeatReq == clientCtlReq.Type {
log.Debug("ProxyName [%s], get heartbeat", s.Name)
timer.Reset(time.Duration(server.HeartBeatTimeout) * time.Second)
clientCtlRes := &msg.ClientCtlRes{}
clientCtlRes.GeneralRes.Code = consts.SCHeartBeatRes
response, err := json.Marshal(clientCtlRes)
if err != nil {
log.Warn("Serialize ClientCtlRes err! err: %v", err)
continue
}
err = c.Write(string(response) + "\n")
if err != nil {
log.Error("Send heartbeat response to client failed! Err:%v", err)
continue
}
}
}
}
......@@ -16,18 +16,22 @@ package client
import (
"encoding/json"
"fmt"
"time"
"frp/models/consts"
"frp/models/msg"
"frp/utils/conn"
"frp/utils/log"
"frp/utils/pcrypto"
)
type ProxyClient struct {
Name string
Passwd string
LocalIp string
LocalPort int64
Name string
AuthToken string
LocalIp string
LocalPort int64
UseEncryption bool
}
func (p *ProxyClient) GetLocalConn() (c *conn.Conn, err error) {
......@@ -51,10 +55,13 @@ func (p *ProxyClient) GetRemoteConn(addr string, port int64) (c *conn.Conn, err
return
}
req := &msg.ClientCtlReq{
Type: consts.WorkConn,
nowTime := time.Now().Unix()
authKey := pcrypto.GetAuthKey(p.Name + p.AuthToken + fmt.Sprintf("%d", nowTime))
req := &msg.ControlReq{
Type: consts.NewWorkConn,
ProxyName: p.Name,
Passwd: p.Passwd,
AuthKey: authKey,
Timestamp: nowTime,
}
buf, _ := json.Marshal(req)
......@@ -79,8 +86,13 @@ func (p *ProxyClient) StartTunnel(serverAddr string, serverPort int64) (err erro
}
// l means local, r means remote
log.Debug("Join two conns, (l[%s] r[%s]) (l[%s] r[%s])", localConn.GetLocalAddr(), localConn.GetRemoteAddr(),
log.Debug("Join two connections, (l[%s] r[%s]) (l[%s] r[%s])", localConn.GetLocalAddr(), localConn.GetRemoteAddr(),
remoteConn.GetLocalAddr(), remoteConn.GetRemoteAddr())
go conn.Join(localConn, remoteConn)
if p.UseEncryption {
go conn.JoinMore(localConn, remoteConn, p.AuthToken)
} else {
go conn.Join(localConn, remoteConn)
}
return nil
}
......@@ -69,23 +69,32 @@ func LoadConf(confFile string) (err error) {
LogLevel = tmpStr
}
var authToken string
tmpStr, ok = conf.Get("common", "auth_token")
if ok {
authToken = tmpStr
} else {
return fmt.Errorf("auth_token not found")
}
// proxies
for name, section := range conf {
if name != "common" {
proxyClient := &ProxyClient{}
// name
proxyClient.Name = name
proxyClient.Passwd, ok = section["passwd"]
if !ok {
return fmt.Errorf("Parse ini file error: proxy [%s] no passwd found", proxyClient.Name)
}
// auth_token
proxyClient.AuthToken = authToken
// local_ip
proxyClient.LocalIp, ok = section["local_ip"]
if !ok {
// use 127.0.0.1 as default
proxyClient.LocalIp = "127.0.0.1"
}
// local_port
portStr, ok := section["local_port"]
if ok {
proxyClient.LocalPort, err = strconv.ParseInt(portStr, 10, 64)
......@@ -96,6 +105,13 @@ func LoadConf(confFile string) (err error) {
return fmt.Errorf("Parse ini file error: proxy [%s] local_port not found", proxyClient.Name)
}
// use_encryption
proxyClient.UseEncryption = false
useEncryptionStr, ok := section["use_encryption"]
if ok && useEncryptionStr == "true" {
proxyClient.UseEncryption = true
}
ProxyClients[proxyClient.Name] = proxyClient
}
}
......
......@@ -18,20 +18,15 @@ package consts
const (
Idle = iota
Working
Closed
)
// connection type
// msg type
const (
CtlConn = iota
WorkConn
)
// msg from client to server
const (
CSHeartBeatReq = 1
)
// msg from server to client
const (
SCHeartBeatRes = 100
NewCtlConn = iota
NewWorkConn
NoticeUserConn
NewCtlConnRes
HeartbeatReq
HeartbeatRes
)
......@@ -19,16 +19,17 @@ type GeneralRes struct {
Msg string `json:"msg"`
}
type ClientCtlReq struct {
Type int64 `json:"type"`
ProxyName string `json:"proxy_name"`
Passwd string `json:"passwd"`
// messages between control connection of frpc and frps
type ControlReq struct {
Type int64 `json:"type"`
ProxyName string `json:"proxy_name,omitempty"`
AuthKey string `json:"auth_key, omitempty"`
UseEncryption bool `json:"use_encryption, omitempty"`
Timestamp int64 `json:"timestamp, omitempty"`
}
type ClientCtlRes struct {
GeneralRes
}
type ServerCtlReq struct {
Type int64 `json:"type"`
type ControlRes struct {
Type int64 `json:"type"`
Code int64 `json:"code"`
Msg string `json:"msg"`
}
......@@ -75,9 +75,9 @@ func LoadConf(confFile string) (err error) {
proxyServer := &ProxyServer{}
proxyServer.Name = name
proxyServer.Passwd, ok = section["passwd"]
proxyServer.AuthToken, ok = section["auth_token"]
if !ok {
return fmt.Errorf("Parse ini file error: proxy [%s] no passwd found", proxyServer.Name)
return fmt.Errorf("Parse ini file error: proxy [%s] no auth_token found", proxyServer.Name)
}
proxyServer.BindAddr, ok = section["bind_addr"]
......
......@@ -25,22 +25,23 @@ import (
)
type ProxyServer struct {
Name string
Passwd string
BindAddr string
ListenPort int64
Status int64
Name string
AuthToken string
UseEncryption bool
BindAddr string
ListenPort int64
Status int64
listener *conn.Listener // accept new connection from remote users
ctlMsgChan chan int64 // every time accept a new user conn, put "1" to the channel
cliConnChan chan *conn.Conn // get client conns from control goroutine
workConnChan chan *conn.Conn // get new work conns from control goroutine
userConnList *list.List // store user conns
mutex sync.Mutex
}
func (p *ProxyServer) Init() {
p.Status = consts.Idle
p.cliConnChan = make(chan *conn.Conn)
p.workConnChan = make(chan *conn.Conn)
p.ctlMsgChan = make(chan int64)
p.userConnList = list.New()
}
......@@ -109,7 +110,7 @@ func (p *ProxyServer) Start() (err error) {
// start another goroutine for join two conns from client and user
go func() {
for {
cliConn, ok := <-p.cliConnChan
workConn, ok := <-p.workConnChan
if !ok {
return
}
......@@ -122,7 +123,7 @@ func (p *ProxyServer) Start() (err error) {
userConn = element.Value.(*conn.Conn)
p.userConnList.Remove(element)
} else {
cliConn.Close()
workConn.Close()
p.Unlock()
continue
}
......@@ -130,9 +131,14 @@ func (p *ProxyServer) Start() (err error) {
// msg will transfer to another without modifying
// l means local, r means remote
log.Debug("Join two conns, (l[%s] r[%s]) (l[%s] r[%s])", cliConn.GetLocalAddr(), cliConn.GetRemoteAddr(),
log.Debug("Join two connections, (l[%s] r[%s]) (l[%s] r[%s])", workConn.GetLocalAddr(), workConn.GetRemoteAddr(),
userConn.GetLocalAddr(), userConn.GetRemoteAddr())
go conn.Join(cliConn, userConn)
if p.UseEncryption {
go conn.JoinMore(userConn, workConn, p.AuthToken)
} else {
go conn.Join(userConn, workConn)
}
}
}()
......@@ -141,13 +147,15 @@ func (p *ProxyServer) Start() (err error) {
func (p *ProxyServer) Close() {
p.Lock()
p.Status = consts.Idle
if p.listener != nil {
p.listener.Close()
if p.Status != consts.Closed {
p.Status = consts.Closed
if p.listener != nil {
p.listener.Close()
}
close(p.ctlMsgChan)
close(p.workConnChan)
p.userConnList = list.New()
}
close(p.ctlMsgChan)
close(p.cliConnChan)
p.userConnList = list.New()
p.Unlock()
}
......@@ -161,6 +169,6 @@ func (p *ProxyServer) WaitUserConn() (closeFlag bool) {
return
}
func (p *ProxyServer) GetNewCliConn(c *conn.Conn) {
p.cliConnChan <- c
func (p *ProxyServer) RecvNewWorkConn(c *conn.Conn) {
p.workConnChan <- c
}
......@@ -22,6 +22,7 @@ import (
"sync"
"frp/utils/log"
"frp/utils/pcrypto"
)
type Listener struct {
......@@ -127,6 +128,7 @@ func (c *Conn) ReadLine() (buff string, err error) {
func (c *Conn) Write(content string) (err error) {
_, err = c.TcpConn.Write([]byte(content))
return err
}
func (c *Conn) Close() {
......@@ -151,7 +153,7 @@ func Join(c1 *Conn, c2 *Conn) {
var err error
_, err = io.Copy(to.TcpConn, from.TcpConn)
if err != nil {
log.Warn("join conns error, %v", err)
log.Warn("join connections error, %v", err)
}
}
......@@ -161,3 +163,93 @@ func Join(c1 *Conn, c2 *Conn) {
wait.Wait()
return
}
// messages from c1 to c2 will be encrypted
// and from c2 to c1 will be decrypted
func JoinMore(c1 *Conn, c2 *Conn, cryptKey string) {
var wait sync.WaitGroup
encryptPipe := func(from *Conn, to *Conn, key string) {
defer from.Close()
defer to.Close()
defer wait.Done()
// we don't care about errors here
PipeEncrypt(from.TcpConn, to.TcpConn, key)
}
decryptPipe := func(to *Conn, from *Conn, key string) {
defer from.Close()
defer to.Close()
defer wait.Done()
// we don't care about errors here
PipeDecrypt(to.TcpConn, from.TcpConn, key)
}
wait.Add(2)
go encryptPipe(c1, c2, cryptKey)
go decryptPipe(c2, c1, cryptKey)
wait.Wait()
log.Debug("One tunnel stopped")
return
}
// decrypt msg from reader, then write into writer
func PipeDecrypt(r net.Conn, w net.Conn, key string) error {
laes := new(pcrypto.Pcrypto)
if err := laes.Init([]byte(key)); err != nil {
log.Error("Pcrypto Init error: %v", err)
return fmt.Errorf("Pcrypto Init error: %v", err)
}
nreader := bufio.NewReader(r)
for {
buf, err := nreader.ReadBytes('\n')
if err != nil {
return err
}
res, err := laes.Decrypt(buf)
if err != nil {
log.Error("Decrypt [%s] error, %v", string(buf), err)
return fmt.Errorf("Decrypt [%s] error: %v", string(buf), err)
}
_, err = w.Write(res)
if err != nil {
return err
}
}
return nil
}
// recvive msg from reader, then encrypt msg into write
func PipeEncrypt(r net.Conn, w net.Conn, key string) error {
laes := new(pcrypto.Pcrypto)
if err := laes.Init([]byte(key)); err != nil {
log.Error("Pcrypto Init error: %v", err)
return fmt.Errorf("Pcrypto Init error: %v", err)
}
nreader := bufio.NewReader(r)
buf := make([]byte, 10*1024)
for {
n, err := nreader.Read(buf)
if err != nil {
return err
}
res, err := laes.Encrypt(buf[:n])
if err != nil {
log.Error("Encrypt error: %v", err)
return fmt.Errorf("Encrypt error: %v", err)
}
res = append(res, '\n')
_, err = w.Write(res)
if err != nil {
return err
}
}
return nil
}
......@@ -19,6 +19,7 @@ import (
"compress/gzip"
"crypto/aes"
"crypto/cipher"
"crypto/md5"
"encoding/base64"
"encoding/hex"
"errors"
......@@ -33,43 +34,40 @@ type Pcrypto struct {
func (pc *Pcrypto) Init(key []byte) error {
var err error
pc.pkey = PKCS7Padding(key, aes.BlockSize)
pc.pkey = pKCS7Padding(key, aes.BlockSize)
pc.paes, err = aes.NewCipher(pc.pkey)
return err
}
func (pc *Pcrypto) Encrypto(src []byte) ([]byte, error) {
// aes
src = PKCS7Padding(src, aes.BlockSize)
blockMode := cipher.NewCBCEncrypter(pc.paes, pc.pkey)
crypted := make([]byte, len(src))
blockMode.CryptBlocks(crypted, src)
func (pc *Pcrypto) Encrypt(src []byte) ([]byte, error) {
// gzip
var zbuf bytes.Buffer
zwr := gzip.NewWriter(&zbuf)
zwr, err := gzip.NewWriterLevel(&zbuf, -1)
if err != nil {
return nil, err
}
defer zwr.Close()
zwr.Write(crypted)
zwr.Write(src)
zwr.Flush()
// aes
src = pKCS7Padding(zbuf.Bytes(), aes.BlockSize)
blockMode := cipher.NewCBCEncrypter(pc.paes, pc.pkey)
crypted := make([]byte, len(src))
blockMode.CryptBlocks(crypted, src)
// base64
return []byte(base64.StdEncoding.EncodeToString(zbuf.Bytes())), nil
return []byte(base64.StdEncoding.EncodeToString(crypted)), nil
}
func (pc *Pcrypto) Decrypto(str []byte) ([]byte, error) {
func (pc *Pcrypto) Decrypt(str []byte) ([]byte, error) {
// base64
data, err := base64.StdEncoding.DecodeString(string(str))
if err != nil {
return nil, err
}
// gunzip
zbuf := bytes.NewBuffer(data)
zrd, _ := gzip.NewReader(zbuf)
defer zrd.Close()
data, _ = ioutil.ReadAll(zrd)
// aes
decryptText, err := hex.DecodeString(fmt.Sprintf("%x", data))
if err != nil {
......@@ -83,19 +81,35 @@ func (pc *Pcrypto) Decrypto(str []byte) ([]byte, error) {
blockMode := cipher.NewCBCDecrypter(pc.paes, pc.pkey)
blockMode.CryptBlocks(decryptText, decryptText)
decryptText = PKCS7UnPadding(decryptText)
decryptText = pKCS7UnPadding(decryptText)
return decryptText, nil
// gunzip
zbuf := bytes.NewBuffer(decryptText)
zrd, err := gzip.NewReader(zbuf)
if err != nil {
return nil, err
}
defer zrd.Close()
data, _ = ioutil.ReadAll(zrd)
return data, nil
}
func PKCS7Padding(ciphertext []byte, blockSize int) []byte {
func pKCS7Padding(ciphertext []byte, blockSize int) []byte {
padding := blockSize - len(ciphertext)%blockSize
padtext := bytes.Repeat([]byte{byte(padding)}, padding)
return append(ciphertext, padtext...)
}
func PKCS7UnPadding(origData []byte) []byte {
func pKCS7UnPadding(origData []byte) []byte {
length := len(origData)
unpadding := int(origData[length-1])
return origData[:(length - unpadding)]
}
func GetAuthKey(str string) (authKey string) {
md5Ctx := md5.New()
md5Ctx.Write([]byte(str))
md5Str := md5Ctx.Sum(nil)
return hex.EncodeToString(md5Str)
}
......@@ -15,15 +15,14 @@
package pcrypto
import (
"crypto/aes"
"fmt"
"testing"
)
func TestEncrypto(t *testing.T) {
func TestEncrypt(t *testing.T) {
pp := new(Pcrypto)
pp.Init([]byte("Hana"))
res, err := pp.Encrypto([]byte("Just One Test!"))
res, err := pp.Encrypt([]byte("Just One Test!"))
if err != nil {
t.Fatal(err)
}
......@@ -31,31 +30,18 @@ func TestEncrypto(t *testing.T) {
fmt.Printf("[%x]\n", res)
}
func TestDecrypto(t *testing.T) {
func TestDecrypt(t *testing.T) {
pp := new(Pcrypto)
pp.Init([]byte("Hana"))
res, err := pp.Encrypto([]byte("Just One Test!"))
res, err := pp.Encrypt([]byte("Just One Test!"))
if err != nil {
t.Fatal(err)
}
res, err = pp.Decrypto(res)
res, err = pp.Decrypt(res)
if err != nil {
t.Fatal(err)
}
fmt.Printf("[%s]\n", string(res))
}
func TestPKCS7Padding(t *testing.T) {
ltt := []byte("Test_PKCS7Padding")
ltt = PKCS7Padding(ltt, aes.BlockSize)
// fmt.Printf("[%x]\n", (ltt))
}
func TestPKCS7UnPadding(t *testing.T) {
ltt := []byte("Test_PKCS7Padding")
ltt = PKCS7Padding(ltt, aes.BlockSize)
ltt = PKCS7UnPadding(ltt)
// fmt.Printf("[%x]\n", ltt)
}
......@@ -19,7 +19,7 @@ import (
"strings"
)
var version string = "0.2.0"
var version string = "0.3.0"
func Full() string {
return version
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册