mirror of https://github.com/fatedier/frp
fatedier
9 years ago
26 changed files with 985 additions and 447 deletions
@ -0,0 +1,12 @@
|
||||
sudo: false |
||||
language: go |
||||
|
||||
go: |
||||
- 1.4.2 |
||||
- 1.5.1 |
||||
|
||||
install: |
||||
- make |
||||
|
||||
script: |
||||
- make test |
@ -1,15 +1,22 @@
|
||||
export PATH := $(GOPATH)/bin:$(PATH)
|
||||
export NEW_GOPATH := $(shell pwd)
|
||||
|
||||
all: build |
||||
|
||||
build: godep frps frpc |
||||
build: godep fmt frps frpc |
||||
|
||||
godep: |
||||
@go get github.com/tools/godep
|
||||
godep restore
|
||||
|
||||
fmt: |
||||
@GOPATH=$(NEW_GOPATH) godep go fmt ./...
|
||||
|
||||
frps: |
||||
godep go build -o bin/frps ./cmd/frps
|
||||
GOPATH=$(NEW_GOPATH) godep go build -o bin/frps ./src/frp/cmd/frps
|
||||
|
||||
frpc: |
||||
godep go build -o bin/frpc ./cmd/frpc
|
||||
GOPATH=$(NEW_GOPATH) godep go build -o bin/frpc ./src/frp/cmd/frpc
|
||||
|
||||
test: |
||||
@GOPATH=$(NEW_GOPATH) godep go test ./...
|
||||
|
@ -1,2 +1,5 @@
|
||||
# frp |
||||
|
||||
[![Build Status](https://travis-ci.org/fatedier/frp.svg)](https://travis-ci.org/fatedier/frp) |
||||
|
||||
A fast reverse proxy. |
||||
|
@ -1,67 +0,0 @@
|
||||
package main |
||||
|
||||
import ( |
||||
"io" |
||||
"sync" |
||||
"encoding/json" |
||||
|
||||
"frp/pkg/models" |
||||
"frp/pkg/utils/conn" |
||||
"frp/pkg/utils/log" |
||||
) |
||||
|
||||
func ControlProcess(cli *models.ProxyClient, wait *sync.WaitGroup) { |
||||
defer wait.Done() |
||||
|
||||
c := &conn.Conn{} |
||||
err := c.ConnectServer(ServerAddr, ServerPort) |
||||
if err != nil { |
||||
log.Error("ProxyName [%s], connect to server [%s:%d] error, %v", cli.Name, ServerAddr, ServerPort, err) |
||||
return |
||||
} |
||||
defer c.Close() |
||||
|
||||
req := &models.ClientCtlReq{ |
||||
Type: models.ControlConn, |
||||
ProxyName: cli.Name, |
||||
Passwd: cli.Passwd, |
||||
} |
||||
buf, _ := json.Marshal(req) |
||||
err = c.Write(string(buf) + "\n") |
||||
if err != nil { |
||||
log.Error("ProxyName [%s], write to server error, %v", cli.Name, err) |
||||
return |
||||
} |
||||
|
||||
res, err := c.ReadLine() |
||||
if err != nil { |
||||
log.Error("ProxyName [%s], read from server error, %v", cli.Name, err) |
||||
return |
||||
} |
||||
log.Debug("ProxyName [%s], read [%s]", cli.Name, res) |
||||
|
||||
clientCtlRes := &models.ClientCtlRes{} |
||||
if err = json.Unmarshal([]byte(res), &clientCtlRes); 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 |
||||
} |
||||
|
||||
for { |
||||
// ignore response content now
|
||||
_, err := c.ReadLine() |
||||
if err == io.EOF { |
||||
log.Debug("ProxyName [%s], server close this control conn", cli.Name) |
||||
break |
||||
} else if err != nil { |
||||
log.Warn("ProxyName [%s], read from server error, %v", cli.Name, err) |
||||
continue |
||||
} |
||||
|
||||
cli.StartTunnel(ServerAddr, ServerPort) |
||||
} |
||||
} |
@ -1,134 +0,0 @@
|
||||
package main |
||||
|
||||
import ( |
||||
"fmt" |
||||
"encoding/json" |
||||
|
||||
"frp/pkg/utils/log" |
||||
"frp/pkg/utils/conn" |
||||
"frp/pkg/models" |
||||
) |
||||
|
||||
func ProcessControlConn(l *conn.Listener) { |
||||
for { |
||||
c := l.GetConn() |
||||
log.Debug("Get one new conn, %v", c.GetRemoteAddr()) |
||||
go controlWorker(c) |
||||
} |
||||
} |
||||
|
||||
// control 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 err != nil { |
||||
log.Warn("Read error, %v", err) |
||||
return |
||||
} |
||||
log.Debug("get: %s", res) |
||||
|
||||
clientCtlReq := &models.ClientCtlReq{} |
||||
clientCtlRes := &models.ClientCtlRes{} |
||||
if err := json.Unmarshal([]byte(res), &clientCtlReq); err != nil { |
||||
log.Warn("Parse err: %v : %s", err, res) |
||||
return |
||||
} |
||||
|
||||
// check
|
||||
succ, msg, needRes := checkProxy(clientCtlReq, c) |
||||
if !succ { |
||||
clientCtlRes.Code = 1 |
||||
clientCtlRes.Msg = msg |
||||
} |
||||
|
||||
if needRes { |
||||
buf, _ := json.Marshal(clientCtlRes) |
||||
err = c.Write(string(buf) + "\n") |
||||
if err != nil { |
||||
log.Warn("Write error, %v", err) |
||||
} |
||||
} else { |
||||
// work conn, just return
|
||||
return |
||||
} |
||||
|
||||
defer c.Close() |
||||
// others is from server to client
|
||||
server, ok := ProxyServers[clientCtlReq.ProxyName] |
||||
if !ok { |
||||
log.Warn("ProxyName [%s] is not exist", clientCtlReq.ProxyName) |
||||
return |
||||
} |
||||
|
||||
serverCtlReq := &models.ClientCtlReq{} |
||||
serverCtlReq.Type = models.WorkConn |
||||
for { |
||||
server.WaitUserConn() |
||||
buf, _ := json.Marshal(serverCtlReq) |
||||
err = c.Write(string(buf) + "\n") |
||||
if err != nil { |
||||
log.Warn("ProxyName [%s], write to client error, proxy exit", server.Name) |
||||
server.Close() |
||||
return |
||||
} |
||||
|
||||
log.Debug("ProxyName [%s], write to client to add work conn success", server.Name) |
||||
} |
||||
|
||||
return |
||||
} |
||||
|
||||
func checkProxy(req *models.ClientCtlReq, c *conn.Conn) (succ bool, msg string, needRes bool) { |
||||
succ = false |
||||
needRes = true |
||||
// check if proxy name exist
|
||||
server, ok := ProxyServers[req.ProxyName] |
||||
if !ok { |
||||
msg = fmt.Sprintf("ProxyName [%s] is not exist", req.ProxyName) |
||||
log.Warn(msg) |
||||
return |
||||
} |
||||
|
||||
// check password
|
||||
if req.Passwd != server.Passwd { |
||||
msg = fmt.Sprintf("ProxyName [%s], password is not correct", req.ProxyName) |
||||
log.Warn(msg) |
||||
return |
||||
} |
||||
|
||||
// control conn
|
||||
if req.Type == models.ControlConn { |
||||
if server.Status != models.Idle { |
||||
msg = fmt.Sprintf("ProxyName [%s], already in use", req.ProxyName) |
||||
log.Warn(msg) |
||||
return |
||||
} |
||||
|
||||
// start proxy and listen for user conn, no block
|
||||
err := server.Start() |
||||
if err != nil { |
||||
msg = fmt.Sprintf("ProxyName [%s], start proxy error: %v", req.ProxyName, err.Error()) |
||||
log.Warn(msg) |
||||
return |
||||
} |
||||
|
||||
log.Info("ProxyName [%s], start proxy success", req.ProxyName) |
||||
} else if req.Type == models.WorkConn { |
||||
// work conn
|
||||
needRes = false |
||||
if server.Status != models.Working { |
||||
log.Warn("ProxyName [%s], is not working when it gets one new work conn", req.ProxyName) |
||||
return |
||||
} |
||||
|
||||
server.CliConnChan <- c |
||||
} else { |
||||
msg = fmt.Sprintf("ProxyName [%s], type [%d] unsupport", req.ProxyName) |
||||
log.Warn(msg) |
||||
return |
||||
} |
||||
|
||||
succ = true |
||||
return |
||||
} |
@ -1,27 +0,0 @@
|
||||
package models |
||||
|
||||
type GeneralRes struct { |
||||
Code int64 `json:"code"` |
||||
Msg string `json:"msg"` |
||||
} |
||||
|
||||
// type
|
||||
const ( |
||||
ControlConn = iota |
||||
WorkConn |
||||
) |
||||
|
||||
type ClientCtlReq struct { |
||||
Type int64 `json:"type"` |
||||
ProxyName string `json:"proxy_name"` |
||||
Passwd string `json:"passwd"` |
||||
} |
||||
|
||||
type ClientCtlRes struct { |
||||
GeneralRes |
||||
} |
||||
|
||||
|
||||
type ServerCtlReq struct { |
||||
Type int64 `json:"type"` |
||||
} |
@ -1,116 +0,0 @@
|
||||
package models |
||||
|
||||
import ( |
||||
"sync" |
||||
"container/list" |
||||
|
||||
"frp/pkg/utils/conn" |
||||
"frp/pkg/utils/log" |
||||
) |
||||
|
||||
const ( |
||||
Idle = iota |
||||
Working |
||||
) |
||||
|
||||
type ProxyServer struct { |
||||
Name string |
||||
Passwd string |
||||
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
|
||||
UserConnList *list.List // store user conns
|
||||
Mutex sync.Mutex |
||||
} |
||||
|
||||
func (p *ProxyServer) Init() { |
||||
p.Status = Idle |
||||
p.CtlMsgChan = make(chan int64) |
||||
p.CliConnChan = make(chan *conn.Conn) |
||||
p.UserConnList = list.New() |
||||
} |
||||
|
||||
func (p *ProxyServer) Lock() { |
||||
p.Mutex.Lock() |
||||
} |
||||
|
||||
func (p *ProxyServer) Unlock() { |
||||
p.Mutex.Unlock() |
||||
} |
||||
|
||||
// start listening for user conns
|
||||
func (p *ProxyServer) Start() (err error) { |
||||
p.Listener, err = conn.Listen(p.BindAddr, p.ListenPort) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
p.Status = Working |
||||
|
||||
// start a goroutine for listener
|
||||
go func() { |
||||
for { |
||||
// block
|
||||
c := p.Listener.GetConn()
|
||||
log.Debug("ProxyName [%s], get one new user conn [%s]", p.Name, c.GetRemoteAddr()) |
||||
|
||||
// put to list
|
||||
p.Lock() |
||||
if p.Status != Working { |
||||
log.Debug("ProxyName [%s] is not working, new user conn close", p.Name) |
||||
c.Close() |
||||
p.Unlock() |
||||
return |
||||
} |
||||
p.UserConnList.PushBack(c) |
||||
p.Unlock() |
||||
|
||||
// put msg to control conn
|
||||
p.CtlMsgChan <- 1 |
||||
} |
||||
}() |
||||
|
||||
// start another goroutine for join two conns from client and user
|
||||
go func() { |
||||
for { |
||||
cliConn := <-p.CliConnChan |
||||
p.Lock() |
||||
element := p.UserConnList.Front() |
||||
|
||||
var userConn *conn.Conn |
||||
if element != nil { |
||||
userConn = element.Value.(*conn.Conn) |
||||
p.UserConnList.Remove(element) |
||||
} else { |
||||
cliConn.Close() |
||||
continue |
||||
} |
||||
p.Unlock() |
||||
|
||||
// msg will transfer to another without modifying
|
||||
log.Debug("Join two conns, (l[%s] r[%s]) (l[%s] r[%s])", cliConn.GetLocalAddr(), cliConn.GetRemoteAddr(), |
||||
userConn.GetLocalAddr(), userConn.GetRemoteAddr()) |
||||
go conn.Join(cliConn, userConn) |
||||
} |
||||
}() |
||||
|
||||
return nil |
||||
} |
||||
|
||||
func (p *ProxyServer) Close() { |
||||
p.Lock() |
||||
p.Status = Idle |
||||
p.CtlMsgChan = make(chan int64) |
||||
p.CliConnChan = make(chan *conn.Conn) |
||||
p.UserConnList = list.New() |
||||
p.Unlock() |
||||
} |
||||
|
||||
func (p *ProxyServer) WaitUserConn() (res int64) { |
||||
res = <-p.CtlMsgChan |
||||
return
|
||||
} |
@ -0,0 +1,155 @@
|
||||
package main |
||||
|
||||
import ( |
||||
"encoding/json" |
||||
"fmt" |
||||
"io" |
||||
"sync" |
||||
"time" |
||||
|
||||
"frp/models/client" |
||||
"frp/models/consts" |
||||
"frp/models/msg" |
||||
"frp/utils/conn" |
||||
"frp/utils/log" |
||||
) |
||||
|
||||
var connection *conn.Conn = nil |
||||
var heartBeatTimer *time.Timer = nil |
||||
|
||||
func ControlProcess(cli *client.ProxyClient, wait *sync.WaitGroup) { |
||||
defer wait.Done() |
||||
|
||||
c, err := loginToServer(cli) |
||||
if err != nil { |
||||
log.Error("ProxyName [%s], connect to server failed!", cli.Name) |
||||
return |
||||
} |
||||
connection = c |
||||
defer connection.Close() |
||||
|
||||
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 |
||||
|
||||
// loop until connect to server
|
||||
for { |
||||
log.Debug("ProxyName [%s], try to reconnect to server[%s:%d]...", cli.Name, client.ServerAddr, client.ServerPort) |
||||
tmpConn, err := loginToServer(cli) |
||||
if err == nil { |
||||
connection.Close() |
||||
connection = tmpConn |
||||
break |
||||
} |
||||
|
||||
if sleepTime < 60 { |
||||
sleepTime = sleepTime * 2 |
||||
} |
||||
time.Sleep(sleepTime * time.Second) |
||||
} |
||||
continue |
||||
} else if err != nil { |
||||
log.Warn("ProxyName [%s], read from server 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) |
||||
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 |
||||
} |
||||
|
||||
cli.StartTunnel(client.ServerAddr, client.ServerPort) |
||||
} |
||||
} |
||||
|
||||
func loginToServer(cli *client.ProxyClient) (c *conn.Conn, err error) { |
||||
c, err = conn.ConnectServer(client.ServerAddr, client.ServerPort) |
||||
if err != nil { |
||||
log.Error("ProxyName [%s], connect to server [%s:%d] error, %v", cli.Name, client.ServerAddr, client.ServerPort, err) |
||||
return |
||||
} |
||||
|
||||
req := &msg.ClientCtlReq{ |
||||
Type: consts.CtlConn, |
||||
ProxyName: cli.Name, |
||||
Passwd: cli.Passwd, |
||||
} |
||||
buf, _ := json.Marshal(req) |
||||
err = c.Write(string(buf) + "\n") |
||||
if err != nil { |
||||
log.Error("ProxyName [%s], write to server error, %v", cli.Name, err) |
||||
return |
||||
} |
||||
|
||||
res, err := c.ReadLine() |
||||
if err != nil { |
||||
log.Error("ProxyName [%s], read from server error, %v", cli.Name, err) |
||||
return |
||||
} |
||||
log.Debug("ProxyName [%s], read [%s]", cli.Name, res) |
||||
|
||||
clientCtlRes := &msg.ClientCtlRes{} |
||||
if err = json.Unmarshal([]byte(res), &clientCtlRes); 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) |
||||
} |
||||
|
||||
go startHeartBeat(c) |
||||
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: "", |
||||
} |
||||
request, err := json.Marshal(clientCtlReq) |
||||
if err != nil { |
||||
log.Warn("Serialize clientCtlReq err! Err: %v", err) |
||||
} |
||||
|
||||
log.Debug("Start to send heartbeat") |
||||
for { |
||||
time.Sleep(time.Duration(client.HeartBeatInterval) * time.Second) |
||||
if c != nil && !c.IsClosed() { |
||||
err = c.Write(string(request) + "\n") |
||||
if err != nil { |
||||
log.Error("Send hearbeat to server failed! Err:%v", err) |
||||
continue |
||||
} |
||||
} else { |
||||
break |
||||
} |
||||
} |
||||
log.Debug("Heartbeat exit") |
||||
} |
@ -0,0 +1,204 @@
|
||||
package main |
||||
|
||||
import ( |
||||
"encoding/json" |
||||
"fmt" |
||||
"io" |
||||
"time" |
||||
|
||||
"frp/models/consts" |
||||
"frp/models/msg" |
||||
"frp/models/server" |
||||
"frp/utils/conn" |
||||
"frp/utils/log" |
||||
) |
||||
|
||||
func ProcessControlConn(l *conn.Listener) { |
||||
for { |
||||
c, err := l.GetConn() |
||||
if err != nil { |
||||
return |
||||
} |
||||
log.Debug("Get one new conn, %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 err != nil { |
||||
log.Warn("Read error, %v", err) |
||||
return |
||||
} |
||||
log.Debug("get: %s", res) |
||||
|
||||
clientCtlReq := &msg.ClientCtlReq{} |
||||
clientCtlRes := &msg.ClientCtlRes{} |
||||
if err := json.Unmarshal([]byte(res), &clientCtlReq); err != nil { |
||||
log.Warn("Parse err: %v : %s", err, res) |
||||
return |
||||
} |
||||
|
||||
// check
|
||||
succ, info, needRes := checkProxy(clientCtlReq, c) |
||||
if !succ { |
||||
clientCtlRes.Code = 1 |
||||
clientCtlRes.Msg = info |
||||
} |
||||
|
||||
if needRes { |
||||
defer c.Close() |
||||
|
||||
buf, _ := json.Marshal(clientCtlRes) |
||||
err = c.Write(string(buf) + "\n") |
||||
if err != nil { |
||||
log.Warn("Write error, %v", err) |
||||
time.Sleep(1 * time.Second) |
||||
return |
||||
} |
||||
} else { |
||||
// work conn, just return
|
||||
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 |
||||
} |
||||
|
||||
// read control msg from client
|
||||
go readControlMsgFromClient(s, c) |
||||
|
||||
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) |
||||
} |
||||
|
||||
log.Info("ProxyName [%s], I'm dead!", s.Name) |
||||
return |
||||
} |
||||
|
||||
func checkProxy(req *msg.ClientCtlReq, c *conn.Conn) (succ bool, info string, needRes bool) { |
||||
succ = false |
||||
needRes = true |
||||
// check if proxy name exist
|
||||
s, ok := server.ProxyServers[req.ProxyName] |
||||
if !ok { |
||||
info = fmt.Sprintf("ProxyName [%s] is not exist", req.ProxyName) |
||||
log.Warn(info) |
||||
return |
||||
} |
||||
|
||||
// check password
|
||||
if req.Passwd != s.Passwd { |
||||
info = fmt.Sprintf("ProxyName [%s], password is not correct", req.ProxyName) |
||||
log.Warn(info) |
||||
return |
||||
} |
||||
|
||||
// control conn
|
||||
if req.Type == consts.CtlConn { |
||||
if s.Status != consts.Idle { |
||||
info = fmt.Sprintf("ProxyName [%s], already in use", req.ProxyName) |
||||
log.Warn(info) |
||||
return |
||||
} |
||||
|
||||
// start proxy and listen for user conn, no block
|
||||
err := s.Start() |
||||
if err != nil { |
||||
info = fmt.Sprintf("ProxyName [%s], start proxy error: %v", req.ProxyName, err.Error()) |
||||
log.Warn(info) |
||||
return |
||||
} |
||||
|
||||
log.Info("ProxyName [%s], start proxy success", req.ProxyName) |
||||
} else if req.Type == consts.WorkConn { |
||||
// 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) |
||||
return |
||||
} |
||||
|
||||
s.GetNewCliConn(c) |
||||
} else { |
||||
info = fmt.Sprintf("ProxyName [%s], type [%d] unsupport", req.ProxyName, req.Type) |
||||
log.Warn(info) |
||||
return |
||||
} |
||||
|
||||
succ = true |
||||
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 |
||||
} |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,23 @@
|
||||
package consts |
||||
|
||||
// server status
|
||||
const ( |
||||
Idle = iota |
||||
Working |
||||
) |
||||
|
||||
// connection type
|
||||
const ( |
||||
CtlConn = iota |
||||
WorkConn |
||||
) |
||||
|
||||
// msg from client to server
|
||||
const ( |
||||
CSHeartBeatReq = 1 |
||||
) |
||||
|
||||
// msg from server to client
|
||||
const ( |
||||
SCHeartBeatRes = 100 |
||||
) |
@ -0,0 +1,20 @@
|
||||
package msg |
||||
|
||||
type GeneralRes struct { |
||||
Code int64 `json:"code"` |
||||
Msg string `json:"msg"` |
||||
} |
||||
|
||||
type ClientCtlReq struct { |
||||
Type int64 `json:"type"` |
||||
ProxyName string `json:"proxy_name"` |
||||
Passwd string `json:"passwd"` |
||||
} |
||||
|
||||
type ClientCtlRes struct { |
||||
GeneralRes |
||||
} |
||||
|
||||
type ServerCtlReq struct { |
||||
Type int64 `json:"type"` |
||||
} |
@ -0,0 +1,150 @@
|
||||
package server |
||||
|
||||
import ( |
||||
"container/list" |
||||
"sync" |
||||
"time" |
||||
|
||||
"frp/models/consts" |
||||
"frp/utils/conn" |
||||
"frp/utils/log" |
||||
) |
||||
|
||||
type ProxyServer struct { |
||||
Name string |
||||
Passwd string |
||||
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
|
||||
userConnList *list.List // store user conns
|
||||
mutex sync.Mutex |
||||
} |
||||
|
||||
func (p *ProxyServer) Init() { |
||||
p.Status = consts.Idle |
||||
p.cliConnChan = make(chan *conn.Conn) |
||||
p.ctlMsgChan = make(chan int64) |
||||
p.userConnList = list.New() |
||||
} |
||||
|
||||
func (p *ProxyServer) Lock() { |
||||
p.mutex.Lock() |
||||
} |
||||
|
||||
func (p *ProxyServer) Unlock() { |
||||
p.mutex.Unlock() |
||||
} |
||||
|
||||
// start listening for user conns
|
||||
func (p *ProxyServer) Start() (err error) { |
||||
p.Init() |
||||
p.listener, err = conn.Listen(p.BindAddr, p.ListenPort) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
p.Status = consts.Working |
||||
|
||||
// start a goroutine for listener to accept user connection
|
||||
go func() { |
||||
for { |
||||
// block
|
||||
// if listener is closed, err returned
|
||||
c, err := p.listener.GetConn() |
||||
if err != nil { |
||||
log.Info("ProxyName [%s], listener is closed", p.Name) |
||||
return |
||||
} |
||||
log.Debug("ProxyName [%s], get one new user conn [%s]", p.Name, c.GetRemoteAddr()) |
||||
|
||||
// insert into list
|
||||
p.Lock() |
||||
if p.Status != consts.Working { |
||||
log.Debug("ProxyName [%s] is not working, new user conn close", p.Name) |
||||
c.Close() |
||||
p.Unlock() |
||||
return |
||||
} |
||||
p.userConnList.PushBack(c) |
||||
p.Unlock() |
||||
|
||||
// put msg to control conn
|
||||
p.ctlMsgChan <- 1 |
||||
|
||||
// set timeout
|
||||
time.AfterFunc(time.Duration(UserConnTimeout)*time.Second, func() { |
||||
p.Lock() |
||||
defer p.Unlock() |
||||
element := p.userConnList.Front() |
||||
if element == nil { |
||||
return |
||||
} |
||||
|
||||
userConn := element.Value.(*conn.Conn) |
||||
if userConn == c { |
||||
log.Warn("ProxyName [%s], user conn [%s] timeout", p.Name, c.GetRemoteAddr()) |
||||
} |
||||
}) |
||||
} |
||||
}() |
||||
|
||||
// start another goroutine for join two conns from client and user
|
||||
go func() { |
||||
for { |
||||
cliConn, ok := <-p.cliConnChan |
||||
if !ok { |
||||
return |
||||
} |
||||
|
||||
p.Lock() |
||||
element := p.userConnList.Front() |
||||
|
||||
var userConn *conn.Conn |
||||
if element != nil { |
||||
userConn = element.Value.(*conn.Conn) |
||||
p.userConnList.Remove(element) |
||||
} else { |
||||
cliConn.Close() |
||||
p.Unlock() |
||||
continue |
||||
} |
||||
p.Unlock() |
||||
|
||||
// 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(), |
||||
userConn.GetLocalAddr(), userConn.GetRemoteAddr()) |
||||
go conn.Join(cliConn, userConn) |
||||
} |
||||
}() |
||||
|
||||
return nil |
||||
} |
||||
|
||||
func (p *ProxyServer) Close() { |
||||
p.Lock() |
||||
p.Status = consts.Idle |
||||
p.listener.Close() |
||||
close(p.ctlMsgChan) |
||||
close(p.cliConnChan) |
||||
p.userConnList = list.New() |
||||
p.Unlock() |
||||
} |
||||
|
||||
func (p *ProxyServer) WaitUserConn() (closeFlag bool) { |
||||
closeFlag = false |
||||
|
||||
_, ok := <-p.ctlMsgChan |
||||
if !ok { |
||||
closeFlag = true |
||||
} |
||||
return |
||||
} |
||||
|
||||
func (p *ProxyServer) GetNewCliConn(c *conn.Conn) { |
||||
p.cliConnChan <- c |
||||
} |
@ -0,0 +1,73 @@
|
||||
package broadcast |
||||
|
||||
type Broadcast struct { |
||||
listeners []chan interface{} |
||||
reg chan (chan interface{}) |
||||
unreg chan (chan interface{}) |
||||
in chan interface{} |
||||
stop chan int64 |
||||
stopStatus bool |
||||
} |
||||
|
||||
func NewBroadcast() *Broadcast { |
||||
b := &Broadcast{ |
||||
listeners: make([]chan interface{}, 0), |
||||
reg: make(chan (chan interface{})), |
||||
unreg: make(chan (chan interface{})), |
||||
in: make(chan interface{}), |
||||
stop: make(chan int64), |
||||
stopStatus: false, |
||||
} |
||||
|
||||
go func() { |
||||
for { |
||||
select { |
||||
case l := <-b.unreg: |
||||
// remove L from b.listeners
|
||||
// this operation is slow: O(n) but not used frequently
|
||||
// unlike iterating over listeners
|
||||
oldListeners := b.listeners |
||||
b.listeners = make([]chan interface{}, 0, len(oldListeners)) |
||||
for _, oldL := range oldListeners { |
||||
if l != oldL { |
||||
b.listeners = append(b.listeners, oldL) |
||||
} |
||||
} |
||||
|
||||
case l := <-b.reg: |
||||
b.listeners = append(b.listeners, l) |
||||
|
||||
case item := <-b.in: |
||||
for _, l := range b.listeners { |
||||
l <- item |
||||
} |
||||
|
||||
case _ = <-b.stop: |
||||
b.stopStatus = true |
||||
break |
||||
} |
||||
} |
||||
}() |
||||
|
||||
return b |
||||
} |
||||
|
||||
func (b *Broadcast) In() chan interface{} { |
||||
return b.in |
||||
} |
||||
|
||||
func (b *Broadcast) Reg() chan interface{} { |
||||
listener := make(chan interface{}) |
||||
b.reg <- listener |
||||
return listener |
||||
} |
||||
|
||||
func (b *Broadcast) UnReg(listener chan interface{}) { |
||||
b.unreg <- listener |
||||
} |
||||
|
||||
func (b *Broadcast) Close() { |
||||
if b.stopStatus == false { |
||||
b.stop <- 1 |
||||
} |
||||
} |
@ -0,0 +1,63 @@
|
||||
package broadcast |
||||
|
||||
import ( |
||||
"sync" |
||||
"testing" |
||||
"time" |
||||
) |
||||
|
||||
var ( |
||||
totalNum int = 5 |
||||
succNum int = 0 |
||||
mutex sync.Mutex |
||||
) |
||||
|
||||
func TestBroadcast(t *testing.T) { |
||||
b := NewBroadcast() |
||||
if b == nil { |
||||
t.Errorf("New Broadcast error, nil return") |
||||
} |
||||
defer b.Close() |
||||
|
||||
var wait sync.WaitGroup |
||||
wait.Add(totalNum) |
||||
for i := 0; i < totalNum; i++ { |
||||
go worker(b, &wait) |
||||
} |
||||
|
||||
time.Sleep(1e6 * 20) |
||||
msg := "test" |
||||
b.In() <- msg |
||||
|
||||
wait.Wait() |
||||
if succNum != totalNum { |
||||
t.Errorf("TotalNum %d, FailNum(timeout) %d", totalNum, totalNum-succNum) |
||||
} |
||||
} |
||||
|
||||
func worker(b *Broadcast, wait *sync.WaitGroup) { |
||||
defer wait.Done() |
||||
msgChan := b.Reg() |
||||
|
||||
// exit if nothing got in 2 seconds
|
||||
timeout := make(chan bool, 1) |
||||
go func() { |
||||
time.Sleep(time.Duration(2) * time.Second) |
||||
timeout <- true |
||||
}() |
||||
|
||||
select { |
||||
case item := <-msgChan: |
||||
msg := item.(string) |
||||
if msg == "test" { |
||||
mutex.Lock() |
||||
succNum++ |
||||
mutex.Unlock() |
||||
} else { |
||||
break |
||||
} |
||||
|
||||
case <-timeout: |
||||
break |
||||
} |
||||
} |
@ -0,0 +1,87 @@
|
||||
package pcrypto |
||||
|
||||
import ( |
||||
"bytes" |
||||
"compress/gzip" |
||||
"crypto/aes" |
||||
"crypto/cipher" |
||||
"encoding/base64" |
||||
"encoding/hex" |
||||
"errors" |
||||
"fmt" |
||||
"io/ioutil" |
||||
) |
||||
|
||||
type Pcrypto struct { |
||||
pkey []byte |
||||
paes cipher.Block |
||||
} |
||||
|
||||
func (pc *Pcrypto) Init(key []byte) error { |
||||
var err error |
||||
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) |
||||
|
||||
// gzip
|
||||
var zbuf bytes.Buffer |
||||
zwr := gzip.NewWriter(&zbuf) |
||||
defer zwr.Close() |
||||
zwr.Write(crypted) |
||||
zwr.Flush() |
||||
|
||||
// base64
|
||||
return []byte(base64.StdEncoding.EncodeToString(zbuf.Bytes())), nil |
||||
} |
||||
|
||||
func (pc *Pcrypto) Decrypto(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 { |
||||
return nil, err |
||||
} |
||||
|
||||
if len(decryptText)%aes.BlockSize != 0 { |
||||
return nil, errors.New("crypto/cipher: ciphertext is not a multiple of the block size") |
||||
} |
||||
|
||||
blockMode := cipher.NewCBCDecrypter(pc.paes, pc.pkey) |
||||
|
||||
blockMode.CryptBlocks(decryptText, decryptText) |
||||
decryptText = PKCS7UnPadding(decryptText) |
||||
|
||||
return decryptText, nil |
||||
} |
||||
|
||||
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 { |
||||
length := len(origData) |
||||
unpadding := int(origData[length-1]) |
||||
return origData[:(length - unpadding)] |
||||
} |
@ -0,0 +1,47 @@
|
||||
package pcrypto |
||||
|
||||
import ( |
||||
"crypto/aes" |
||||
"fmt" |
||||
"testing" |
||||
) |
||||
|
||||
func TestEncrypto(t *testing.T) { |
||||
pp := new(Pcrypto) |
||||
pp.Init([]byte("Hana")) |
||||
res, err := pp.Encrypto([]byte("Just One Test!")) |
||||
if err != nil { |
||||
t.Error(err) |
||||
} |
||||
|
||||
fmt.Printf("[%x]\n", res) |
||||
} |
||||
|
||||
func TestDecrypto(t *testing.T) { |
||||
pp := new(Pcrypto) |
||||
pp.Init([]byte("Hana")) |
||||
res, err := pp.Encrypto([]byte("Just One Test!")) |
||||
if err != nil { |
||||
t.Error(err) |
||||
} |
||||
|
||||
res, err = pp.Decrypto(res) |
||||
if err != nil { |
||||
t.Error(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) |
||||
} |
Loading…
Reference in new issue