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 |
||||
|
||||
[![Build Status](https://travis-ci.org/fatedier/frp.svg)](https://travis-ci.org/fatedier/frp) |
||||
|
||||
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