mirror of https://github.com/fatedier/frp
Hurricanezwf
9 years ago
24 changed files with 746 additions and 305 deletions
@ -0,0 +1,12 @@ |
|||||||
|
sudo: false |
||||||
|
language: go |
||||||
|
|
||||||
|
go: |
||||||
|
- 1.4.2 |
||||||
|
- 1.5.2 |
||||||
|
|
||||||
|
install: |
||||||
|
- make |
||||||
|
|
||||||
|
script: |
||||||
|
- make test |
@ -1,2 +1,5 @@ |
|||||||
# frp |
# frp |
||||||
|
|
||||||
|
[![Build Status](https://travis-ci.org/fatedier/frp.svg)](https://travis-ci.org/fatedier/frp) |
||||||
|
|
||||||
A fast reverse proxy. |
A fast reverse proxy. |
||||||
|
@ -0,0 +1,13 @@ |
|||||||
|
package consts |
||||||
|
|
||||||
|
// server status
|
||||||
|
const ( |
||||||
|
Idle = iota |
||||||
|
Working |
||||||
|
) |
||||||
|
|
||||||
|
// connection type
|
||||||
|
const ( |
||||||
|
CtlConn = iota |
||||||
|
WorkConn |
||||||
|
) |
@ -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,130 @@ |
|||||||
|
package server |
||||||
|
|
||||||
|
import ( |
||||||
|
"container/list" |
||||||
|
"sync" |
||||||
|
|
||||||
|
"github.com/fatedier/frp/models/consts" |
||||||
|
"github.com/fatedier/frp/utils/conn" |
||||||
|
"github.com/fatedier/frp/utils/log" |
||||||
|
) |
||||||
|
|
||||||
|
type ProxyServer struct { |
||||||
|
Name string |
||||||
|
Passwd string |
||||||
|
BindAddr string |
||||||
|
ListenPort int64 |
||||||
|
Status int64 |
||||||
|
CliConnChan chan *conn.Conn // get client conns from control goroutine
|
||||||
|
|
||||||
|
listener *conn.Listener // accept new connection from remote users
|
||||||
|
ctlMsgChan chan int64 // every time accept a new user conn, put "1" to the channel
|
||||||
|
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
|
||||||
|
go func() { |
||||||
|
for { |
||||||
|
// block
|
||||||
|
// if listener is closed, get nil
|
||||||
|
c := p.listener.GetConn() |
||||||
|
if c == 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 |
||||||
|
} |
||||||
|
}() |
||||||
|
|
||||||
|
// 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 |
||||||
|
} |
@ -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,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