Release v0.3.0

Release v0.3.0
This commit is contained in:
fatedier 2016-04-06 12:19:31 +08:00
commit 2ba84d375a
20 changed files with 535 additions and 304 deletions

View File

@ -3,7 +3,7 @@ language: go
go: go:
- 1.4.2 - 1.4.2
- 1.5.1 - 1.5.3
install: install:
- make - make

View File

@ -2,7 +2,7 @@ FROM golang:1.5
MAINTAINER fatedier 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/ ADD ./ /usr/share/frp/
@ -11,4 +11,4 @@ RUN cd /usr/share/frp && make
EXPOSE 80 EXPOSE 80
EXPOSE 7000 EXPOSE 7000
CMD ["/usr/share/frp/bin/frps -c /usr/share/frps.ini"] CMD ["/usr/share/frp/bin/frps", "-c", "/usr/share/frps.ini"]

View File

@ -2,13 +2,17 @@
[![Build Status](https://travis-ci.org/fatedier/frp.svg)](https://travis-ci.org/fatedier/frp) [![Build Status](https://travis-ci.org/fatedier/frp.svg)](https://travis-ci.org/fatedier/frp)
[README](README.md) | [中文文档](README_zh.md)
## What is frp? ## 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 ## 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 ## 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! 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.
If you have some wanderful ideas, send email to fatedier@gmail.com. ## Contributors
* [fatedier](https://github.com/fatedier)
* [Hurricanezwf](https://github.com/Hurricanezwf)
* [vashstorm](https://github.com/vashstorm)

40
README_zh.md Normal file
View File

@ -0,0 +1,40 @@
# 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)

View File

@ -6,9 +6,12 @@ server_port = 7000
log_file = console log_file = console
# debug, info, warn, error # debug, info, warn, error
log_level = debug log_level = debug
# for authentication
auth_token = 123
# test1 is the proxy name same as server's configuration # test1 is the proxy name same as server's configuration
[test1] [test1]
passwd = 123
local_ip = 127.0.0.1 local_ip = 127.0.0.1
local_port = 22 local_port = 22
# true or false, if true, messages between frps and frpc will be encrypted, default is false
use_encryption = true

View File

@ -7,8 +7,8 @@ log_file = console
# debug, info, warn, error # debug, info, warn, error
log_level = debug 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] [test1]
passwd = 123 auth_token = 123
bind_addr = 0.0.0.0 bind_addr = 0.0.0.0
listen_port = 6000 listen_port = 6000

View File

@ -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 is the custom name of proxy and there can be many proxies with unique name in one configure file
[test] [test]
passwd = 123 auth_token = 123
bind_addr = 0.0.0.0 bind_addr = 0.0.0.0
# finally we connect to server A by this port # finally we connect to server A by this port
listen_port = 6000 listen_port = 6000
@ -59,10 +59,13 @@ server_addr = x.x.x.x
server_port = 7000 server_port = 7000
log_file = ./frpc.log log_file = ./frpc.log
log_level = info log_level = info
# for authentication
auth_token = 123
# test is proxy name same with configure in frps.ini # test is proxy name same with configure in frps.ini
[test] [test]
passwd = 123
# local port which need to be transferred # local port which need to be transferred
local_port = 22 local_port = 22
# if use_encryption equals true, messages between frpc and frps will be encrypted, default is false
use_encryption = true
``` ```

View File

@ -42,7 +42,7 @@ log_level = info
# test 为代理的自定义名称可以有多个不能重复和frpc中名称对应 # test 为代理的自定义名称可以有多个不能重复和frpc中名称对应
[test] [test]
passwd = 123 auth_token = 123
bind_addr = 0.0.0.0 bind_addr = 0.0.0.0
# 最后将通过此端口访问后端服务 # 最后将通过此端口访问后端服务
listen_port = 6000 listen_port = 6000
@ -57,10 +57,13 @@ server_addr = x.x.x.x
server_port = 7000 server_port = 7000
log_file = ./frpc.log log_file = ./frpc.log
log_level = info log_level = info
# 用于身份验证
auth_token = 123
# test需要和 frps.ini 中配置一致 # test需要和 frps.ini 中配置一致
[test] [test]
passwd = 123
# 需要转发的本地端口 # 需要转发的本地端口
local_port = 22 local_port = 22
# 启用加密frpc与frps之间通信加密默认为 false
use_encryption = true
``` ```

View File

@ -26,66 +26,101 @@ import (
"frp/models/msg" "frp/models/msg"
"frp/utils/conn" "frp/utils/conn"
"frp/utils/log" "frp/utils/log"
"frp/utils/pcrypto"
) )
var connection *conn.Conn = nil
var heartBeatTimer *time.Timer = nil
func ControlProcess(cli *client.ProxyClient, wait *sync.WaitGroup) { func ControlProcess(cli *client.ProxyClient, wait *sync.WaitGroup) {
defer wait.Done() defer wait.Done()
msgSendChan := make(chan interface{}, 1024)
c, err := loginToServer(cli) c, err := loginToServer(cli)
if err != nil { if err != nil {
log.Error("ProxyName [%s], connect to server failed!", cli.Name) log.Error("ProxyName [%s], connect to server failed!", cli.Name)
return return
} }
connection = c defer c.Close()
defer connection.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 { for {
// ignore response content now buf, err := c.ReadLine()
content, err := connection.ReadLine() if err == io.EOF || c == nil || c.IsClosed() {
if err == io.EOF || nil == connection || connection.IsClosed() { c.Close()
log.Debug("ProxyName [%s], server close this control conn", cli.Name) log.Warn("ProxyName [%s], frps close this control conn!", cli.Name)
var sleepTime time.Duration = 1 var delayTime time.Duration = 1
// loop until connect to server // loop until reconnect to frps
for { for {
log.Debug("ProxyName [%s], try to reconnect to server[%s:%d]...", cli.Name, client.ServerAddr, client.ServerPort) log.Info("ProxyName [%s], try to reconnect to frps [%s:%d]...", cli.Name, client.ServerAddr, client.ServerPort)
tmpConn, err := loginToServer(cli) c, err = loginToServer(cli)
if err == nil { if err == nil {
connection.Close() go heartbeatSender(c, msgSendChan)
connection = tmpConn
break break
} }
if sleepTime < 60 { if delayTime < 60 {
sleepTime = sleepTime * 2 delayTime = delayTime * 2
} }
time.Sleep(sleepTime * time.Second) time.Sleep(delayTime * time.Second)
} }
continue
} else if err != nil { } 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 continue
} }
clientCtlRes := &msg.ClientCtlRes{} ctlRes := &msg.ControlRes{}
if err := json.Unmarshal([]byte(content), clientCtlRes); err != nil { if err := json.Unmarshal([]byte(buf), &ctlRes); err != nil {
log.Warn("Parse err: %v : %s", err, content) 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 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) 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
}
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 return
} }
req := &msg.ClientCtlReq{ nowTime := time.Now().Unix()
Type: consts.CtlConn, authKey := pcrypto.GetAuthKey(cli.Name + cli.AuthToken + fmt.Sprintf("%d", nowTime))
req := &msg.ControlReq{
Type: consts.NewCtlConn,
ProxyName: cli.Name, ProxyName: cli.Name,
Passwd: cli.Passwd, AuthKey: authKey,
UseEncryption: cli.UseEncryption,
Timestamp: nowTime,
} }
buf, _ := json.Marshal(req) buf, _ := json.Marshal(req)
err = c.Write(string(buf) + "\n") 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) log.Debug("ProxyName [%s], read [%s]", cli.Name, res)
clientCtlRes := &msg.ClientCtlRes{} ctlRes := &msg.ControlRes{}
if err = json.Unmarshal([]byte(res), &clientCtlRes); err != nil { if err = json.Unmarshal([]byte(res), &ctlRes); err != nil {
log.Error("ProxyName [%s], format server response error, %v", cli.Name, err) log.Error("ProxyName [%s], format server response error, %v", cli.Name, err)
return return
} }
if clientCtlRes.Code != 0 { if ctlRes.Code != 0 {
log.Error("ProxyName [%s], start proxy error, %s", cli.Name, clientCtlRes.Msg) log.Error("ProxyName [%s], start proxy error, %s", cli.Name, ctlRes.Msg)
return c, fmt.Errorf("%s", clientCtlRes.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 return
} }
func startHeartBeat(c *conn.Conn) { func heartbeatSender(c *conn.Conn, msgSendChan chan interface{}) {
f := func() { heartbeatReq := &msg.ControlReq{
log.Error("HeartBeat timeout!") Type: consts.HeartbeatReq,
if c != nil {
c.Close()
} }
} log.Info("Start to send heartbeat to frps")
heartBeatTimer = time.AfterFunc(time.Duration(client.HeartBeatTimeout)*time.Second, f)
defer heartBeatTimer.Stop()
clientCtlReq := &msg.ClientCtlReq{
Type: consts.CSHeartBeatReq,
ProxyName: "",
Passwd: "",
}
request, err := json.Marshal(clientCtlReq)
if err != nil {
log.Warn("Serialize clientCtlReq err! Err: %v", err)
}
log.Debug("Start to send heartbeat")
for { for {
time.Sleep(time.Duration(client.HeartBeatInterval) * time.Second) time.Sleep(time.Duration(client.HeartBeatInterval) * time.Second)
if c != nil && !c.IsClosed() { if c != nil && !c.IsClosed() {
log.Debug("Send heartbeat to server") log.Debug("Send heartbeat to server")
err = c.Write(string(request) + "\n") msgSendChan <- heartbeatReq
if err != nil {
log.Error("Send hearbeat to server failed! Err:%v", err)
continue
}
} else { } else {
break break
} }

View File

@ -25,6 +25,7 @@ import (
"frp/models/server" "frp/models/server"
"frp/utils/conn" "frp/utils/conn"
"frp/utils/log" "frp/utils/log"
"frp/utils/pcrypto"
) )
func ProcessControlConn(l *conn.Listener) { func ProcessControlConn(l *conn.Listener) {
@ -33,87 +34,162 @@ func ProcessControlConn(l *conn.Listener) {
if err != nil { if err != nil {
return return
} }
log.Debug("Get one new conn, %v", c.GetRemoteAddr()) log.Debug("Get new connection, %v", c.GetRemoteAddr())
go controlWorker(c) go controlWorker(c)
} }
} }
// connection from every client and server // connection from every client and server
func controlWorker(c *conn.Conn) { func controlWorker(c *conn.Conn) {
// the first message is from client to server // if login message type is NewWorkConn, don't close this connection
// if error, close connection var closeFlag bool = true
res, err := c.ReadLine() 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 { if err != nil {
log.Warn("Read error, %v", err) log.Warn("Read error, %v", err)
return return
} }
log.Debug("get: %s", res) log.Debug("Get msg from frpc: %s", buf)
clientCtlReq := &msg.ClientCtlReq{} cliReq := &msg.ControlReq{}
clientCtlRes := &msg.ClientCtlRes{} if err := json.Unmarshal([]byte(buf), &cliReq); err != nil {
if err := json.Unmarshal([]byte(res), &clientCtlReq); err != nil { log.Warn("Parse msg from frpc error: %v : %s", err, buf)
log.Warn("Parse err: %v : %s", err, res)
return return
} }
// check // do login when type is NewCtlConn or NewWorkConn
succ, info, needRes := checkProxy(clientCtlReq, c) ret, info := doLogin(cliReq, c)
if !succ { s, ok := server.ProxyServers[cliReq.ProxyName]
clientCtlRes.Code = 1 if !ok {
clientCtlRes.Msg = info log.Warn("ProxyName [%s] is not exist", cliReq.ProxyName)
return
} }
// if login type is NewWorkConn, nothing will be send to frpc
if needRes { if cliReq.Type != consts.NewWorkConn {
defer c.Close() cliRes := &msg.ControlRes{
Type: consts.NewCtlConnRes,
buf, _ := json.Marshal(clientCtlRes) Code: ret,
err = c.Write(string(buf) + "\n") Msg: info,
}
byteBuf, _ := json.Marshal(cliRes)
err = c.Write(string(byteBuf) + "\n")
if err != nil { 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) time.Sleep(1 * time.Second)
return return
} }
} else { } else {
// work conn, just return closeFlag = false
return return
} }
// other messages is from server to client // create a channel for sending messages
s, ok := server.ProxyServers[clientCtlReq.ProxyName] msgSendChan := make(chan interface{}, 1024)
if !ok { go msgSender(s, c, msgSendChan)
log.Warn("ProxyName [%s] is not exist", clientCtlReq.ProxyName) go noticeUserConn(s, msgSendChan)
return
}
// read control msg from client // loop for reading control messages from frpc and deal with different types
go readControlMsgFromClient(s, c) msgReader(s, c, msgSendChan)
serverCtlReq := &msg.ClientCtlReq{}
serverCtlReq.Type = consts.WorkConn
for {
closeFlag := s.WaitUserConn()
if closeFlag {
log.Debug("ProxyName [%s], goroutine for dealing user conn is closed", s.Name)
break
}
buf, _ := json.Marshal(serverCtlReq)
err = c.Write(string(buf) + "\n")
if err != nil {
log.Warn("ProxyName [%s], write to client error, proxy exit", s.Name)
s.Close()
return
}
log.Debug("ProxyName [%s], write to client to add work conn success", s.Name)
}
close(msgSendChan)
log.Info("ProxyName [%s], I'm dead!", s.Name) log.Info("ProxyName [%s], I'm dead!", s.Name)
return return
} }
func checkProxy(req *msg.ClientCtlReq, c *conn.Conn) (succ bool, info string, needRes bool) { // when frps get one new user connection, send NoticeUserConn message to frpc and accept one new WorkConn later
succ = false func noticeUserConn(s *server.ProxyServer, msgSendChan chan interface{}) {
needRes = true for {
closeFlag := s.WaitUserConn()
if closeFlag {
log.Debug("ProxyName [%s], goroutine for noticing user conn is closed", s.Name)
break
}
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 {
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
}
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
}
// 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
}
}
}
// 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 // check if proxy name exist
s, ok := server.ProxyServers[req.ProxyName] s, ok := server.ProxyServers[req.ProxyName]
if !ok { if !ok {
@ -122,97 +198,53 @@ func checkProxy(req *msg.ClientCtlReq, c *conn.Conn) (succ bool, info string, ne
return return
} }
// check password // check authKey
if req.Passwd != s.Passwd { nowTime := time.Now().Unix()
info = fmt.Sprintf("ProxyName [%s], password is not correct", req.ProxyName) 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) log.Warn(info)
return return
} }
// control conn // control conn
if req.Type == consts.CtlConn { if req.Type == consts.NewCtlConn {
if s.Status != consts.Idle { if s.Status == consts.Working {
info = fmt.Sprintf("ProxyName [%s], already in use", req.ProxyName) info = fmt.Sprintf("ProxyName [%s], already in use", req.ProxyName)
log.Warn(info) log.Warn(info)
return 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() err := s.Start()
if err != nil { 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) log.Warn(info)
return return
} }
log.Info("ProxyName [%s], start proxy success", req.ProxyName) log.Info("ProxyName [%s], start proxy success", req.ProxyName)
} else if req.Type == consts.WorkConn { } else if req.Type == consts.NewWorkConn {
// work conn // work conn
needRes = false
if s.Status != consts.Working { 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 return
} }
// the connection will close after join over
s.GetNewCliConn(c) s.RecvNewWorkConn(c)
} else { } else {
info = fmt.Sprintf("ProxyName [%s], type [%d] unsupport", req.ProxyName, req.Type) info = fmt.Sprintf("Unsupport login message type [%d]", req.Type)
log.Warn(info) log.Warn("Unsupport login message type [%d]", req.Type)
return return
} }
succ = true ret = 0
return 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
}
}
}
}

View File

@ -16,18 +16,22 @@ package client
import ( import (
"encoding/json" "encoding/json"
"fmt"
"time"
"frp/models/consts" "frp/models/consts"
"frp/models/msg" "frp/models/msg"
"frp/utils/conn" "frp/utils/conn"
"frp/utils/log" "frp/utils/log"
"frp/utils/pcrypto"
) )
type ProxyClient struct { type ProxyClient struct {
Name string Name string
Passwd string AuthToken string
LocalIp string LocalIp string
LocalPort int64 LocalPort int64
UseEncryption bool
} }
func (p *ProxyClient) GetLocalConn() (c *conn.Conn, err error) { 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 return
} }
req := &msg.ClientCtlReq{ nowTime := time.Now().Unix()
Type: consts.WorkConn, authKey := pcrypto.GetAuthKey(p.Name + p.AuthToken + fmt.Sprintf("%d", nowTime))
req := &msg.ControlReq{
Type: consts.NewWorkConn,
ProxyName: p.Name, ProxyName: p.Name,
Passwd: p.Passwd, AuthKey: authKey,
Timestamp: nowTime,
} }
buf, _ := json.Marshal(req) buf, _ := json.Marshal(req)
@ -79,8 +86,13 @@ func (p *ProxyClient) StartTunnel(serverAddr string, serverPort int64) (err erro
} }
// l means local, r means remote // 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()) remoteConn.GetLocalAddr(), remoteConn.GetRemoteAddr())
if p.UseEncryption {
go conn.JoinMore(localConn, remoteConn, p.AuthToken)
} else {
go conn.Join(localConn, remoteConn) go conn.Join(localConn, remoteConn)
}
return nil return nil
} }

View File

@ -69,23 +69,32 @@ func LoadConf(confFile string) (err error) {
LogLevel = tmpStr 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 // proxies
for name, section := range conf { for name, section := range conf {
if name != "common" { if name != "common" {
proxyClient := &ProxyClient{} proxyClient := &ProxyClient{}
// name
proxyClient.Name = name proxyClient.Name = name
proxyClient.Passwd, ok = section["passwd"] // auth_token
if !ok { proxyClient.AuthToken = authToken
return fmt.Errorf("Parse ini file error: proxy [%s] no passwd found", proxyClient.Name)
}
// local_ip
proxyClient.LocalIp, ok = section["local_ip"] proxyClient.LocalIp, ok = section["local_ip"]
if !ok { if !ok {
// use 127.0.0.1 as default // use 127.0.0.1 as default
proxyClient.LocalIp = "127.0.0.1" proxyClient.LocalIp = "127.0.0.1"
} }
// local_port
portStr, ok := section["local_port"] portStr, ok := section["local_port"]
if ok { if ok {
proxyClient.LocalPort, err = strconv.ParseInt(portStr, 10, 64) 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) 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 ProxyClients[proxyClient.Name] = proxyClient
} }
} }

View File

@ -18,20 +18,15 @@ package consts
const ( const (
Idle = iota Idle = iota
Working Working
Closed
) )
// connection type // msg type
const ( const (
CtlConn = iota NewCtlConn = iota
WorkConn NewWorkConn
) NoticeUserConn
NewCtlConnRes
// msg from client to server HeartbeatReq
const ( HeartbeatRes
CSHeartBeatReq = 1
)
// msg from server to client
const (
SCHeartBeatRes = 100
) )

View File

@ -19,16 +19,17 @@ type GeneralRes struct {
Msg string `json:"msg"` Msg string `json:"msg"`
} }
type ClientCtlReq struct { // messages between control connection of frpc and frps
type ControlReq struct {
Type int64 `json:"type"` Type int64 `json:"type"`
ProxyName string `json:"proxy_name"` ProxyName string `json:"proxy_name,omitempty"`
Passwd string `json:"passwd"` AuthKey string `json:"auth_key, omitempty"`
UseEncryption bool `json:"use_encryption, omitempty"`
Timestamp int64 `json:"timestamp, omitempty"`
} }
type ClientCtlRes struct { type ControlRes struct {
GeneralRes
}
type ServerCtlReq struct {
Type int64 `json:"type"` Type int64 `json:"type"`
Code int64 `json:"code"`
Msg string `json:"msg"`
} }

View File

@ -75,9 +75,9 @@ func LoadConf(confFile string) (err error) {
proxyServer := &ProxyServer{} proxyServer := &ProxyServer{}
proxyServer.Name = name proxyServer.Name = name
proxyServer.Passwd, ok = section["passwd"] proxyServer.AuthToken, ok = section["auth_token"]
if !ok { 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"] proxyServer.BindAddr, ok = section["bind_addr"]

View File

@ -26,21 +26,22 @@ import (
type ProxyServer struct { type ProxyServer struct {
Name string Name string
Passwd string AuthToken string
UseEncryption bool
BindAddr string BindAddr string
ListenPort int64 ListenPort int64
Status int64 Status int64
listener *conn.Listener // accept new connection from remote users listener *conn.Listener // accept new connection from remote users
ctlMsgChan chan int64 // every time accept a new user conn, put "1" to the channel 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 userConnList *list.List // store user conns
mutex sync.Mutex mutex sync.Mutex
} }
func (p *ProxyServer) Init() { func (p *ProxyServer) Init() {
p.Status = consts.Idle p.Status = consts.Idle
p.cliConnChan = make(chan *conn.Conn) p.workConnChan = make(chan *conn.Conn)
p.ctlMsgChan = make(chan int64) p.ctlMsgChan = make(chan int64)
p.userConnList = list.New() 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 // start another goroutine for join two conns from client and user
go func() { go func() {
for { for {
cliConn, ok := <-p.cliConnChan workConn, ok := <-p.workConnChan
if !ok { if !ok {
return return
} }
@ -122,7 +123,7 @@ func (p *ProxyServer) Start() (err error) {
userConn = element.Value.(*conn.Conn) userConn = element.Value.(*conn.Conn)
p.userConnList.Remove(element) p.userConnList.Remove(element)
} else { } else {
cliConn.Close() workConn.Close()
p.Unlock() p.Unlock()
continue continue
} }
@ -130,9 +131,14 @@ func (p *ProxyServer) Start() (err error) {
// msg will transfer to another without modifying // msg will transfer to another without modifying
// l means local, r means remote // 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()) 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() { func (p *ProxyServer) Close() {
p.Lock() p.Lock()
p.Status = consts.Idle if p.Status != consts.Closed {
p.Status = consts.Closed
if p.listener != nil { if p.listener != nil {
p.listener.Close() p.listener.Close()
} }
close(p.ctlMsgChan) close(p.ctlMsgChan)
close(p.cliConnChan) close(p.workConnChan)
p.userConnList = list.New() p.userConnList = list.New()
}
p.Unlock() p.Unlock()
} }
@ -161,6 +169,6 @@ func (p *ProxyServer) WaitUserConn() (closeFlag bool) {
return return
} }
func (p *ProxyServer) GetNewCliConn(c *conn.Conn) { func (p *ProxyServer) RecvNewWorkConn(c *conn.Conn) {
p.cliConnChan <- c p.workConnChan <- c
} }

View File

@ -22,6 +22,7 @@ import (
"sync" "sync"
"frp/utils/log" "frp/utils/log"
"frp/utils/pcrypto"
) )
type Listener struct { type Listener struct {
@ -127,6 +128,7 @@ func (c *Conn) ReadLine() (buff string, err error) {
func (c *Conn) Write(content string) (err error) { func (c *Conn) Write(content string) (err error) {
_, err = c.TcpConn.Write([]byte(content)) _, err = c.TcpConn.Write([]byte(content))
return err return err
} }
func (c *Conn) Close() { func (c *Conn) Close() {
@ -151,7 +153,7 @@ func Join(c1 *Conn, c2 *Conn) {
var err error var err error
_, err = io.Copy(to.TcpConn, from.TcpConn) _, err = io.Copy(to.TcpConn, from.TcpConn)
if err != nil { 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() wait.Wait()
return 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
}

View File

@ -19,6 +19,7 @@ import (
"compress/gzip" "compress/gzip"
"crypto/aes" "crypto/aes"
"crypto/cipher" "crypto/cipher"
"crypto/md5"
"encoding/base64" "encoding/base64"
"encoding/hex" "encoding/hex"
"errors" "errors"
@ -33,43 +34,40 @@ type Pcrypto struct {
func (pc *Pcrypto) Init(key []byte) error { func (pc *Pcrypto) Init(key []byte) error {
var err error var err error
pc.pkey = PKCS7Padding(key, aes.BlockSize) pc.pkey = pKCS7Padding(key, aes.BlockSize)
pc.paes, err = aes.NewCipher(pc.pkey) pc.paes, err = aes.NewCipher(pc.pkey)
return err return err
} }
func (pc *Pcrypto) Encrypto(src []byte) ([]byte, error) { func (pc *Pcrypto) Encrypt(src []byte) ([]byte, error) {
// gzip
var zbuf bytes.Buffer
zwr, err := gzip.NewWriterLevel(&zbuf, -1)
if err != nil {
return nil, err
}
defer zwr.Close()
zwr.Write(src)
zwr.Flush()
// aes // aes
src = PKCS7Padding(src, aes.BlockSize) src = pKCS7Padding(zbuf.Bytes(), aes.BlockSize)
blockMode := cipher.NewCBCEncrypter(pc.paes, pc.pkey) blockMode := cipher.NewCBCEncrypter(pc.paes, pc.pkey)
crypted := make([]byte, len(src)) crypted := make([]byte, len(src))
blockMode.CryptBlocks(crypted, src) blockMode.CryptBlocks(crypted, src)
// gzip
var zbuf bytes.Buffer
zwr := gzip.NewWriter(&zbuf)
defer zwr.Close()
zwr.Write(crypted)
zwr.Flush()
// base64 // 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 // base64
data, err := base64.StdEncoding.DecodeString(string(str)) data, err := base64.StdEncoding.DecodeString(string(str))
if err != nil { if err != nil {
return nil, err return nil, err
} }
// gunzip
zbuf := bytes.NewBuffer(data)
zrd, _ := gzip.NewReader(zbuf)
defer zrd.Close()
data, _ = ioutil.ReadAll(zrd)
// aes // aes
decryptText, err := hex.DecodeString(fmt.Sprintf("%x", data)) decryptText, err := hex.DecodeString(fmt.Sprintf("%x", data))
if err != nil { if err != nil {
@ -83,19 +81,35 @@ func (pc *Pcrypto) Decrypto(str []byte) ([]byte, error) {
blockMode := cipher.NewCBCDecrypter(pc.paes, pc.pkey) blockMode := cipher.NewCBCDecrypter(pc.paes, pc.pkey)
blockMode.CryptBlocks(decryptText, decryptText) 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 padding := blockSize - len(ciphertext)%blockSize
padtext := bytes.Repeat([]byte{byte(padding)}, padding) padtext := bytes.Repeat([]byte{byte(padding)}, padding)
return append(ciphertext, padtext...) return append(ciphertext, padtext...)
} }
func PKCS7UnPadding(origData []byte) []byte { func pKCS7UnPadding(origData []byte) []byte {
length := len(origData) length := len(origData)
unpadding := int(origData[length-1]) unpadding := int(origData[length-1])
return origData[:(length - unpadding)] 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)
}

View File

@ -15,15 +15,14 @@
package pcrypto package pcrypto
import ( import (
"crypto/aes"
"fmt" "fmt"
"testing" "testing"
) )
func TestEncrypto(t *testing.T) { func TestEncrypt(t *testing.T) {
pp := new(Pcrypto) pp := new(Pcrypto)
pp.Init([]byte("Hana")) pp.Init([]byte("Hana"))
res, err := pp.Encrypto([]byte("Just One Test!")) res, err := pp.Encrypt([]byte("Just One Test!"))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -31,31 +30,18 @@ func TestEncrypto(t *testing.T) {
fmt.Printf("[%x]\n", res) fmt.Printf("[%x]\n", res)
} }
func TestDecrypto(t *testing.T) { func TestDecrypt(t *testing.T) {
pp := new(Pcrypto) pp := new(Pcrypto)
pp.Init([]byte("Hana")) pp.Init([]byte("Hana"))
res, err := pp.Encrypto([]byte("Just One Test!")) res, err := pp.Encrypt([]byte("Just One Test!"))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
res, err = pp.Decrypto(res) res, err = pp.Decrypt(res)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
fmt.Printf("[%s]\n", string(res)) 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)
}

View File

@ -19,7 +19,7 @@ import (
"strings" "strings"
) )
var version string = "0.2.0" var version string = "0.3.0"
func Full() string { func Full() string {
return version return version