Browse Source

start refactoring

pull/316/head
fatedier 8 years ago
parent
commit
88083d21e8
  1. 6
      Dockerfile_alpine
  2. 22
      Godeps/Godeps.json
  3. 19
      Makefile
  4. 40
      Makefile.cross-compiles
  5. 2
      assets/assets.go
  6. 0
      assets/static/css/bootstrap.min.css
  7. 0
      assets/static/css/font-awesome.min.css
  8. 0
      assets/static/css/iconfont.css
  9. 0
      assets/static/favicon.ico
  10. 0
      assets/static/font/iconfont.eot
  11. 0
      assets/static/font/iconfont.svg
  12. 0
      assets/static/font/iconfont.ttf
  13. 0
      assets/static/font/iconfont.woff
  14. 0
      assets/static/index.html
  15. 0
      assets/static/js/angular.min.js
  16. 0
      assets/static/js/bootstrap.min.js
  17. 0
      assets/static/js/echarts.min.js
  18. 0
      assets/static/js/jquery.min.js
  19. 10
      assets/statik/statik.go
  20. 345
      client/control.go
  21. 0
      client/process_udp.go.bak
  22. 141
      client/proxy.go
  23. 43
      client/service.go
  24. 59
      cmd/frpc/main.go
  25. 117
      cmd/frps/main.go
  26. 48
      conf/frpc.ini
  27. 31
      conf/frps.ini
  28. 160
      models/config/client_common.go
  29. 446
      models/config/proxy.go
  30. 279
      models/config/server_common.go
  31. 31
      models/consts/consts.go
  32. 21
      models/errors/errors.go
  33. 10
      models/metric/server.go
  34. 122
      models/msg/msg.go
  35. 69
      models/msg/pack.go
  36. 86
      models/msg/pack_test.go
  37. 88
      models/msg/process.go
  38. 97
      models/msg/process_test.go
  39. 38
      models/proto/tcp/process.go
  40. 129
      models/proto/tcp/process_test.go
  41. 89
      models/proto/tcp/tcp.go
  42. 100
      models/proto/tcp/tcp_test.go
  43. 2
      models/proto/udp/udp.go
  44. 2
      models/proto/udp/udp_test.go
  45. 332
      server/control.go
  46. 11
      server/dashboard.go
  47. 2
      server/dashboard_api.go
  48. 2
      server/dashboard_view.go
  49. 75
      server/manager.go
  50. 219
      server/proxy.go
  51. 196
      server/service.go
  52. 10
      src/assets/statik/statik.go
  53. 231
      src/cmd/frpc/control.go
  54. 365
      src/cmd/frps/control.go
  55. 199
      src/cmd/frps/main.go
  56. 186
      src/models/client/client.go
  57. 302
      src/models/client/config.go
  58. 49
      src/models/msg/msg.go
  59. 257
      src/models/msg/process.go
  60. 456
      src/models/server/config.go
  61. 484
      src/models/server/server.go
  62. 87
      src/utils/broadcast/broadcast.go
  63. 77
      src/utils/broadcast/broadcast_test.go
  64. 314
      src/utils/conn/conn.go
  65. 139
      src/utils/pcrypto/pcrypto.go
  66. 77
      src/utils/pcrypto/pcrypto_test.go
  67. 12
      test/echo_server.go
  68. 15
      test/func_test.go
  69. 52
      utils/crypto/crypto_test.go
  70. 75
      utils/crypto/decode.go
  71. 93
      utils/crypto/encode.go
  72. 22
      utils/errors/errors.go
  73. 57
      utils/log/log.go
  74. 30
      utils/net/conn.go
  75. 162
      utils/net/tcp.go
  76. 243
      utils/net/udp.go
  77. 27
      utils/net/udp_test.go
  78. 0
      utils/pool/pool.go
  79. 51
      utils/pool/pool_test.go
  80. 62
      utils/shutdown/shutdown.go
  81. 21
      utils/shutdown/shutdown_test.go
  82. 47
      utils/util/util.go
  83. 22
      utils/util/util_test.go
  84. 42
      utils/version/version.go
  85. 39
      utils/version/version_test.go
  86. 19
      utils/vhost/http.go
  87. 11
      utils/vhost/https.go
  88. 0
      utils/vhost/router.go
  89. 31
      utils/vhost/vhost.go
  90. 0
      vendor/github.com/fatedier/beego/LICENSE
  91. 0
      vendor/github.com/fatedier/beego/logs/README.md
  92. 0
      vendor/github.com/fatedier/beego/logs/color.go
  93. 0
      vendor/github.com/fatedier/beego/logs/color_windows.go
  94. 0
      vendor/github.com/fatedier/beego/logs/conn.go
  95. 0
      vendor/github.com/fatedier/beego/logs/console.go
  96. 1
      vendor/github.com/fatedier/beego/logs/file.go
  97. 78
      vendor/github.com/fatedier/beego/logs/jianliao.go
  98. 48
      vendor/github.com/fatedier/beego/logs/log.go
  99. 0
      vendor/github.com/fatedier/beego/logs/logger.go
  100. 0
      vendor/github.com/fatedier/beego/logs/multifile.go
  101. Some files were not shown because too many files have changed in this diff Show More

6
Dockerfile_alpine

@ -1,7 +1,7 @@
FROM alpine:3.4
FROM alpine:3.5
COPY bin/frpc /frpc
COPY bin/frps /frps
COPY tmp/frpc /frpc
COPY tmp/frps /frps
COPY conf/frpc_min.ini /frpc.ini
COPY conf/frps_min.ini /frps.ini

22
Godeps/Godeps.json generated vendored

@ -1,16 +1,11 @@
{
"ImportPath": "github.com/fatedier/frp",
"GoVersion": "go1.7",
"GodepVersion": "v75",
"GoVersion": "go1.8",
"GodepVersion": "v79",
"Packages": [
"./..."
],
"Deps": [
{
"ImportPath": "github.com/astaxie/beego/logs",
"Comment": "v1.7.0-7-gefbde1e",
"Rev": "efbde1ee77517486eac03e814e01d724ddad18e6"
},
{
"ImportPath": "github.com/davecgh/go-spew/spew",
"Comment": "v1.1.0",
@ -21,6 +16,15 @@
"Comment": "0.6.2",
"Rev": "784ddc588536785e7299f7272f39101f7faccc3f"
},
{
"ImportPath": "github.com/fatedier/beego/logs",
"Comment": "v1.7.2-70-gcd7a213",
"Rev": "cd7a213b889b290cf450e1dd51398af12e5a10d4"
},
{
"ImportPath": "github.com/golang/snappy",
"Rev": "5979233c5d6225d4a8e438cdd0b411888449ddab"
},
{
"ImportPath": "github.com/pmezard/go-difflib/difflib",
"Comment": "v1.0.0",
@ -39,6 +43,10 @@
{
"ImportPath": "github.com/vaughan0/go-ini",
"Rev": "a98ad7ee00ec53921f08832bc06ecf7fd600e6a1"
},
{
"ImportPath": "golang.org/x/crypto/pbkdf2",
"Rev": "1f22c0103821b9390939b6776727195525381532"
}
]
}

19
Makefile

@ -11,21 +11,26 @@ build_test: echo_server http_server
assets:
go get -d github.com/rakyll/statik
@go install github.com/rakyll/statik
@rm -rf ./src/assets/statik
go generate ./src/...
@rm -rf ./assets/statik
go generate ./assets/...
fmt:
go fmt ./src/...
@go fmt ./assets/...
@go fmt ./cmd/...
@go fmt ./client/...
@go fmt ./server/...
@go fmt ./models/...
@go fmt ./utils/...
@go fmt ./test/echo_server.go
@go fmt ./test/http_server.go
@go fmt ./test/func_test.go
frps:
go build -o bin/frps ./src/cmd/frps
@cp -rf ./src/assets/static ./bin
go build -o bin/frps ./cmd/frps
@cp -rf ./assets/static ./bin
frpc:
go build -o bin/frpc ./src/cmd/frpc
go build -o bin/frpc ./cmd/frpc
echo_server:
go build -o test/bin/echo_server ./test/echo_server.go
@ -36,7 +41,7 @@ http_server:
test: gotest
gotest:
go test -v ./src/...
go test -v ./...
alltest:
cd ./test && ./run_test.sh && cd -

40
Makefile.cross-compiles

@ -6,23 +6,23 @@ all: build
build: app
app:
env CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -o ./frpc_darwin_amd64 ./src/cmd/frpc
env CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -o ./frps_darwin_amd64 ./src/cmd/frps
env CGO_ENABLED=0 GOOS=linux GOARCH=386 go build -o ./frpc_linux_386 ./src/cmd/frpc
env CGO_ENABLED=0 GOOS=linux GOARCH=386 go build -o ./frps_linux_386 ./src/cmd/frps
env CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o ./frpc_linux_amd64 ./src/cmd/frpc
env CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o ./frps_linux_amd64 ./src/cmd/frps
env CGO_ENABLED=0 GOOS=linux GOARCH=arm go build -o ./frpc_linux_arm ./src/cmd/frpc
env CGO_ENABLED=0 GOOS=linux GOARCH=arm go build -o ./frps_linux_arm ./src/cmd/frps
env CGO_ENABLED=0 GOOS=windows GOARCH=386 go build -o ./frpc_windows_386.exe ./src/cmd/frpc
env CGO_ENABLED=0 GOOS=windows GOARCH=386 go build -o ./frps_windows_386.exe ./src/cmd/frps
env CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -o ./frpc_windows_amd64.exe ./src/cmd/frpc
env CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -o ./frps_windows_amd64.exe ./src/cmd/frps
env CGO_ENABLED=0 GOOS=linux GOARCH=mips64 go build -o ./frpc_linux_mips64 ./src/cmd/frpc
env CGO_ENABLED=0 GOOS=linux GOARCH=mips64 go build -o ./frps_linux_mips64 ./src/cmd/frps
env CGO_ENABLED=0 GOOS=linux GOARCH=mips64le go build -o ./frpc_linux_mips64le ./src/cmd/frpc
env CGO_ENABLED=0 GOOS=linux GOARCH=mips64le go build -o ./frps_linux_mips64le ./src/cmd/frps
env CGO_ENABLED=0 GOOS=linux GOARCH=mips go build -o ./frpc_linux_mips ./src/cmd/frpc
env CGO_ENABLED=0 GOOS=linux GOARCH=mips go build -o ./frps_linux_mips ./src/cmd/frps
env CGO_ENABLED=0 GOOS=linux GOARCH=mipsle go build -o ./frpc_linux_mipsle ./src/cmd/frpc
env CGO_ENABLED=0 GOOS=linux GOARCH=mipsle go build -o ./frps_linux_mipsle ./src/cmd/frps
env CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -o ./frpc_darwin_amd64 ./cmd/frpc
env CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -o ./frps_darwin_amd64 ./cmd/frps
env CGO_ENABLED=0 GOOS=linux GOARCH=386 go build -o ./frpc_linux_386 ./cmd/frpc
env CGO_ENABLED=0 GOOS=linux GOARCH=386 go build -o ./frps_linux_386 ./cmd/frps
env CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o ./frpc_linux_amd64 ./cmd/frpc
env CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o ./frps_linux_amd64 ./cmd/frps
env CGO_ENABLED=0 GOOS=linux GOARCH=arm go build -o ./frpc_linux_arm ./cmd/frpc
env CGO_ENABLED=0 GOOS=linux GOARCH=arm go build -o ./frps_linux_arm ./cmd/frps
env CGO_ENABLED=0 GOOS=windows GOARCH=386 go build -o ./frpc_windows_386.exe ./cmd/frpc
env CGO_ENABLED=0 GOOS=windows GOARCH=386 go build -o ./frps_windows_386.exe ./cmd/frps
env CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -o ./frpc_windows_amd64.exe ./cmd/frpc
env CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -o ./frps_windows_amd64.exe ./cmd/frps
env CGO_ENABLED=0 GOOS=linux GOARCH=mips64 go build -o ./frpc_linux_mips64 ./cmd/frpc
env CGO_ENABLED=0 GOOS=linux GOARCH=mips64 go build -o ./frps_linux_mips64 ./cmd/frps
env CGO_ENABLED=0 GOOS=linux GOARCH=mips64le go build -o ./frpc_linux_mips64le ./cmd/frpc
env CGO_ENABLED=0 GOOS=linux GOARCH=mips64le go build -o ./frps_linux_mips64le ./cmd/frps
env CGO_ENABLED=0 GOOS=linux GOARCH=mips go build -o ./frpc_linux_mips ./cmd/frpc
env CGO_ENABLED=0 GOOS=linux GOARCH=mips go build -o ./frps_linux_mips ./cmd/frps
env CGO_ENABLED=0 GOOS=linux GOARCH=mipsle go build -o ./frpc_linux_mipsle ./cmd/frpc
env CGO_ENABLED=0 GOOS=linux GOARCH=mipsle go build -o ./frps_linux_mipsle ./cmd/frps

2
src/assets/assets.go → assets/assets.go

@ -25,7 +25,7 @@ import (
"github.com/rakyll/statik/fs"
_ "github.com/fatedier/frp/src/assets/statik"
_ "github.com/fatedier/frp/assets/statik"
)
var (

0
src/assets/static/css/bootstrap.min.css → assets/static/css/bootstrap.min.css vendored

0
src/assets/static/css/font-awesome.min.css → assets/static/css/font-awesome.min.css vendored

0
src/assets/static/css/iconfont.css → assets/static/css/iconfont.css

0
src/assets/static/favicon.ico → assets/static/favicon.ico

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 4.2 KiB

0
src/assets/static/font/iconfont.eot → assets/static/font/iconfont.eot

0
src/assets/static/font/iconfont.svg → assets/static/font/iconfont.svg

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

0
src/assets/static/font/iconfont.ttf → assets/static/font/iconfont.ttf

0
src/assets/static/font/iconfont.woff → assets/static/font/iconfont.woff

0
src/assets/static/index.html → assets/static/index.html

0
src/assets/static/js/angular.min.js → assets/static/js/angular.min.js vendored

0
src/assets/static/js/bootstrap.min.js → assets/static/js/bootstrap.min.js vendored

0
src/assets/static/js/echarts.min.js → assets/static/js/echarts.min.js vendored

0
src/assets/static/js/jquery.min.js → assets/static/js/jquery.min.js vendored

10
assets/statik/statik.go

File diff suppressed because one or more lines are too long

345
client/control.go

@ -0,0 +1,345 @@
// Copyright 2017 fatedier, fatedier@gmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package client
import (
"fmt"
"io"
"runtime"
"sync"
"time"
"github.com/fatedier/frp/models/config"
"github.com/fatedier/frp/models/msg"
"github.com/fatedier/frp/utils/log"
"github.com/fatedier/frp/utils/net"
"github.com/fatedier/frp/utils/util"
"github.com/fatedier/frp/utils/version"
)
type Control struct {
// frpc service
svr *Service
// login message to server
loginMsg *msg.Login
// proxy configures
pxyCfgs map[string]config.ProxyConf
// proxies
proxies map[string]Proxy
// control connection
conn net.Conn
// put a message in this channel to send it over control connection to server
sendCh chan (msg.Message)
// read from this channel to get the next message sent by server
readCh chan (msg.Message)
// run id got from server
runId string
// connection or other error happens , control will try to reconnect to server
closed int32
// goroutines can block by reading from this channel, it will be closed only in reader() when control connection is closed
closedCh chan int
// last time got the Pong message
lastPong time.Time
mu sync.RWMutex
log.Logger
}
func NewControl(svr *Service, pxyCfgs map[string]config.ProxyConf) *Control {
loginMsg := &msg.Login{
Arch: runtime.GOARCH,
Os: runtime.GOOS,
PoolCount: config.ClientCommonCfg.PoolCount,
User: config.ClientCommonCfg.User,
Version: version.Full(),
}
return &Control{
svr: svr,
loginMsg: loginMsg,
pxyCfgs: pxyCfgs,
proxies: make(map[string]Proxy),
sendCh: make(chan msg.Message, 10),
readCh: make(chan msg.Message, 10),
closedCh: make(chan int),
Logger: log.NewPrefixLogger(""),
}
}
// 1. login
// 2. start reader() writer() manager()
// 3. connection closed
// 4. In reader(): close closedCh and exit, controler() get it
// 5. In controler(): close readCh and sendCh, manager() and writer() will exit
// 6. In controler(): ini readCh, sendCh, closedCh
// 7. In controler(): start new reader(), writer(), manager()
// controler() will keep running
func (ctl *Control) Run() error {
err := ctl.login()
if err != nil {
return err
}
go ctl.controler()
go ctl.manager()
go ctl.writer()
go ctl.reader()
// send NewProxy message for all configured proxies
for _, cfg := range ctl.pxyCfgs {
var newProxyMsg msg.NewProxy
cfg.UnMarshalToMsg(&newProxyMsg)
ctl.sendCh <- &newProxyMsg
}
return nil
}
func (ctl *Control) NewWorkConn() {
workConn, err := net.ConnectTcpServerByHttpProxy(config.ClientCommonCfg.HttpProxy,
fmt.Sprintf("%s:%d", config.ClientCommonCfg.ServerAddr, config.ClientCommonCfg.ServerPort))
if err != nil {
ctl.Warn("start new work connection error: %v", err)
return
}
m := &msg.NewWorkConn{
RunId: ctl.runId,
}
if err = msg.WriteMsg(workConn, m); err != nil {
ctl.Warn("work connection write to server error: %v", err)
workConn.Close()
return
}
var startMsg msg.StartWorkConn
if err = msg.ReadMsgInto(workConn, &startMsg); err != nil {
ctl.Error("work connection closed and no response from server, %v", err)
workConn.Close()
return
}
workConn.AddLogPrefix(startMsg.ProxyName)
// dispatch this work connection to related proxy
if pxy, ok := ctl.proxies[startMsg.ProxyName]; ok {
go pxy.InWorkConn(workConn)
workConn.Info("start a new work connection")
}
}
func (ctl *Control) init() {
ctl.sendCh = make(chan msg.Message, 10)
ctl.readCh = make(chan msg.Message, 10)
ctl.closedCh = make(chan int)
}
// login send a login message to server and wait for a loginResp message.
func (ctl *Control) login() (err error) {
if ctl.conn != nil {
ctl.conn.Close()
}
conn, err := net.ConnectTcpServerByHttpProxy(config.ClientCommonCfg.HttpProxy,
fmt.Sprintf("%s:%d", config.ClientCommonCfg.ServerAddr, config.ClientCommonCfg.ServerPort))
if err != nil {
return err
}
now := time.Now().Unix()
ctl.loginMsg.PrivilegeKey = util.GetAuthKey(config.ClientCommonCfg.PrivilegeToken, now)
ctl.loginMsg.Timestamp = now
ctl.loginMsg.RunId = ctl.runId
if err = msg.WriteMsg(conn, ctl.loginMsg); err != nil {
return err
}
var loginRespMsg msg.LoginResp
if err = msg.ReadMsgInto(conn, &loginRespMsg); err != nil {
return err
}
if loginRespMsg.Error != "" {
err = fmt.Errorf("%s", loginRespMsg.Error)
ctl.Error("%s", loginRespMsg.Error)
return err
}
ctl.conn = conn
// update runId got from server
ctl.runId = loginRespMsg.RunId
ctl.ClearLogPrefix()
ctl.AddLogPrefix(loginRespMsg.RunId)
ctl.Info("login to server success, get run id [%s]", loginRespMsg.RunId)
// login success, so we let closedCh available again
ctl.closedCh = make(chan int)
ctl.lastPong = time.Now()
return nil
}
func (ctl *Control) reader() {
defer func() {
if err := recover(); err != nil {
ctl.Error("panic error: %v", err)
}
}()
for {
if m, err := msg.ReadMsg(ctl.conn); err != nil {
if err == io.EOF {
ctl.Debug("read from control connection EOF")
close(ctl.closedCh)
return
} else {
ctl.Warn("read error: %v", err)
continue
}
} else {
ctl.readCh <- m
}
}
}
func (ctl *Control) writer() {
for {
if m, ok := <-ctl.sendCh; !ok {
ctl.Info("control writer is closing")
return
} else {
if err := msg.WriteMsg(ctl.conn, m); err != nil {
ctl.Warn("write message to control connection error: %v", err)
return
}
}
}
}
func (ctl *Control) manager() {
defer func() {
if err := recover(); err != nil {
ctl.Error("panic error: %v", err)
}
}()
hbSend := time.NewTicker(time.Duration(config.ClientCommonCfg.HeartBeatInterval) * time.Second)
defer hbSend.Stop()
hbCheck := time.NewTicker(time.Second)
defer hbCheck.Stop()
for {
select {
case <-hbSend.C:
// send heartbeat to server
ctl.sendCh <- &msg.Ping{}
case <-hbCheck.C:
if time.Since(ctl.lastPong) > time.Duration(config.ClientCommonCfg.HeartBeatTimeout)*time.Second {
ctl.Warn("heartbeat timeout")
return
}
case rawMsg, ok := <-ctl.readCh:
if !ok {
return
}
switch m := rawMsg.(type) {
case *msg.ReqWorkConn:
go ctl.NewWorkConn()
case *msg.NewProxyResp:
// Server will return NewProxyResp message to each NewProxy message.
// Start a new proxy handler if no error got
if m.Error != "" {
ctl.Warn("[%s] start error: %s", m.ProxyName, m.Error)
continue
}
oldPxy, ok := ctl.proxies[m.ProxyName]
if ok {
oldPxy.Close()
}
cfg, ok := ctl.pxyCfgs[m.ProxyName]
if !ok {
// it will never go to this branch
ctl.Warn("[%s] no proxy conf found", m.ProxyName)
continue
}
pxy := NewProxy(ctl, cfg)
pxy.Run()
ctl.proxies[m.ProxyName] = pxy
ctl.Info("[%s] start proxy success", m.ProxyName)
case *msg.Pong:
ctl.lastPong = time.Now()
}
}
}
}
// control keep watching closedCh, start a new connection if previous control connection is closed
func (ctl *Control) controler() {
var err error
maxDelayTime := 30 * time.Second
delayTime := time.Second
for {
// we won't get any variable from this channel
_, ok := <-ctl.closedCh
if !ok {
// close related channels
close(ctl.readCh)
close(ctl.sendCh)
time.Sleep(time.Second)
// loop util reconnect to server success
for {
ctl.Info("try to reconnect to server...")
err = ctl.login()
if err != nil {
ctl.Warn("reconnect to server error: %v", err)
time.Sleep(delayTime)
delayTime = delayTime * 2
if delayTime > maxDelayTime {
delayTime = maxDelayTime
}
continue
}
// reconnect success, init the delayTime
delayTime = time.Second
break
}
// init related channels and variables
ctl.init()
// previous work goroutines should be closed and start them here
go ctl.manager()
go ctl.writer()
go ctl.reader()
// send NewProxy message for all configured proxies
for _, cfg := range ctl.pxyCfgs {
var newProxyMsg msg.NewProxy
cfg.UnMarshalToMsg(&newProxyMsg)
ctl.sendCh <- &newProxyMsg
}
}
}
}

0
src/models/client/process_udp.go → client/process_udp.go.bak

141
client/proxy.go

@ -0,0 +1,141 @@
// Copyright 2017 fatedier, fatedier@gmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package client
import (
"fmt"
"io"
"github.com/fatedier/frp/models/config"
"github.com/fatedier/frp/models/proto/tcp"
"github.com/fatedier/frp/utils/net"
)
type Proxy interface {
Run()
InWorkConn(conn net.Conn)
Close()
}
func NewProxy(ctl *Control, pxyConf config.ProxyConf) (pxy Proxy) {
switch cfg := pxyConf.(type) {
case *config.TcpProxyConf:
pxy = &TcpProxy{
cfg: cfg,
ctl: ctl,
}
case *config.UdpProxyConf:
pxy = &UdpProxy{
cfg: cfg,
ctl: ctl,
}
case *config.HttpProxyConf:
pxy = &HttpProxy{
cfg: cfg,
ctl: ctl,
}
case *config.HttpsProxyConf:
pxy = &HttpsProxy{
cfg: cfg,
ctl: ctl,
}
}
return
}
// TCP
type TcpProxy struct {
cfg *config.TcpProxyConf
ctl *Control
}
func (pxy *TcpProxy) Run() {
}
func (pxy *TcpProxy) Close() {
}
func (pxy *TcpProxy) InWorkConn(conn net.Conn) {
defer conn.Close()
localConn, err := net.ConnectTcpServer(fmt.Sprintf("%s:%d", pxy.cfg.LocalIp, pxy.cfg.LocalPort))
if err != nil {
conn.Error("connect to local service [%s:%d] error: %v", pxy.cfg.LocalIp, pxy.cfg.LocalPort, err)
return
}
var remote io.ReadWriteCloser
remote = conn
if pxy.cfg.UseEncryption {
remote, err = tcp.WithEncryption(remote, []byte(config.ClientCommonCfg.PrivilegeToken))
if err != nil {
conn.Error("create encryption stream error: %v", err)
return
}
}
if pxy.cfg.UseCompression {
remote = tcp.WithCompression(remote)
}
conn.Debug("join connections")
tcp.Join(localConn, remote)
conn.Debug("join connections closed")
}
// UDP
type UdpProxy struct {
cfg *config.UdpProxyConf
ctl *Control
}
func (pxy *UdpProxy) Run() {
}
func (pxy *UdpProxy) Close() {
}
func (pxy *UdpProxy) InWorkConn(conn net.Conn) {
defer conn.Close()
}
// HTTP
type HttpProxy struct {
cfg *config.HttpProxyConf
ctl *Control
}
func (pxy *HttpProxy) Run() {
}
func (pxy *HttpProxy) Close() {
}
func (pxy *HttpProxy) InWorkConn(conn net.Conn) {
defer conn.Close()
}
// HTTPS
type HttpsProxy struct {
cfg *config.HttpsProxyConf
ctl *Control
}
func (pxy *HttpsProxy) Run() {
}
func (pxy *HttpsProxy) Close() {
}
func (pxy *HttpsProxy) InWorkConn(conn net.Conn) {
defer conn.Close()
}

43
client/service.go

@ -0,0 +1,43 @@
// Copyright 2017 fatedier, fatedier@gmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package client
import "github.com/fatedier/frp/models/config"
type Service struct {
// manager control connection with server
ctl *Control
closedCh chan int
}
func NewService(pxyCfgs map[string]config.ProxyConf) (svr *Service) {
svr = &Service{
closedCh: make(chan int),
}
ctl := NewControl(svr, pxyCfgs)
svr.ctl = ctl
return
}
func (svr *Service) Run() error {
err := svr.ctl.Run()
if err != nil {
return err
}
<-svr.closedCh
return nil
}

59
src/cmd/frpc/main.go → cmd/frpc/main.go

@ -19,13 +19,14 @@ import (
"os"
"strconv"
"strings"
"sync"
docopt "github.com/docopt/docopt-go"
ini "github.com/vaughan0/go-ini"
"github.com/fatedier/frp/src/models/client"
"github.com/fatedier/frp/src/utils/log"
"github.com/fatedier/frp/src/utils/version"
"github.com/fatedier/frp/client"
"github.com/fatedier/frp/models/config"
"github.com/fatedier/frp/utils/log"
"github.com/fatedier/frp/utils/version"
)
var (
@ -49,29 +50,38 @@ Options:
`
func main() {
var err error
confFile := "./frpc.ini"
// the configures parsed from file will be replaced by those from command line if exist
args, err := docopt.Parse(usage, nil, true, version.Full(), false)
if args["-c"] != nil {
configFile = args["-c"].(string)
confFile = args["-c"].(string)
}
err = client.LoadConf(configFile)
conf, err := ini.LoadFile(confFile)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
config.ClientCommonCfg, err = config.LoadClientCommonConf(conf)
if err != nil {
fmt.Println(err)
os.Exit(-1)
os.Exit(1)
}
if args["-L"] != nil {
if args["-L"].(string) == "console" {
client.LogWay = "console"
config.ClientCommonCfg.LogWay = "console"
} else {
client.LogWay = "file"
client.LogFile = args["-L"].(string)
config.ClientCommonCfg.LogWay = "file"
config.ClientCommonCfg.LogFile = args["-L"].(string)
}
}
if args["--log-level"] != nil {
client.LogLevel = args["--log-level"].(string)
config.ClientCommonCfg.LogLevel = args["--log-level"].(string)
}
if args["--server-addr"] != nil {
@ -85,8 +95,8 @@ func main() {
fmt.Println("--server-addr format error, example 0.0.0.0:7000")
os.Exit(1)
}
client.ServerAddr = addr[0]
client.ServerPort = serverPort
config.ClientCommonCfg.ServerAddr = addr[0]
config.ClientCommonCfg.ServerPort = serverPort
}
if args["-v"] != nil {
@ -96,18 +106,19 @@ func main() {
}
}
log.InitLog(client.LogWay, client.LogFile, client.LogLevel, client.LogMaxDays)
// wait until all control goroutine exit
var wait sync.WaitGroup
wait.Add(len(client.ProxyClients))
for _, client := range client.ProxyClients {
go ControlProcess(client, &wait)
pxyCfgs, err := config.LoadProxyConfFromFile(conf)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
log.Info("Start frpc success")
log.InitLog(config.ClientCommonCfg.LogWay, config.ClientCommonCfg.LogFile,
config.ClientCommonCfg.LogLevel, config.ClientCommonCfg.LogMaxDays)
wait.Wait()
log.Warn("All proxy exit!")
svr := client.NewService(pxyCfgs)
err = svr.Run()
if err != nil {
fmt.Println(err)
os.Exit(1)
}
}

117
cmd/frps/main.go

@ -0,0 +1,117 @@
// Copyright 2016 fatedier, fatedier@gmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
"fmt"
"os"
"strconv"
"strings"
docopt "github.com/docopt/docopt-go"
ini "github.com/vaughan0/go-ini"
"github.com/fatedier/frp/models/config"
"github.com/fatedier/frp/server"
"github.com/fatedier/frp/utils/log"
"github.com/fatedier/frp/utils/version"
)
var usage string = `frps is the server of frp
Usage:
frps [-c config_file] [-L log_file] [--log-level=<log_level>] [--addr=<bind_addr>]
frps -h | --help
frps -v | --version
Options:
-c config_file set config file
-L log_file set output log file, including console
--log-level=<log_level> set log level: debug, info, warn, error
--addr=<bind_addr> listen addr for client, example: 0.0.0.0:7000
-h --help show this screen
-v --version show version
`
func main() {
var err error
confFile := "./frps.ini"
// the configures parsed from file will be replaced by those from command line if exist
args, err := docopt.Parse(usage, nil, true, version.Full(), false)
if args["-c"] != nil {
confFile = args["-c"].(string)
}
conf, err := ini.LoadFile(confFile)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
config.ServerCommonCfg, err = config.LoadServerCommonConf(conf)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
if args["-L"] != nil {
if args["-L"].(string) == "console" {
config.ServerCommonCfg.LogWay = "console"
} else {
config.ServerCommonCfg.LogWay = "file"
config.ServerCommonCfg.LogFile = args["-L"].(string)
}
}
if args["--log-level"] != nil {
config.ServerCommonCfg.LogLevel = args["--log-level"].(string)
}
if args["--addr"] != nil {
addr := strings.Split(args["--addr"].(string), ":")
if len(addr) != 2 {
fmt.Println("--addr format error: example 0.0.0.0:7000")
os.Exit(1)
}
bindPort, err := strconv.ParseInt(addr[1], 10, 64)
if err != nil {
fmt.Println("--addr format error, example 0.0.0.0:7000")
os.Exit(1)
}
config.ServerCommonCfg.BindAddr = addr[0]
config.ServerCommonCfg.BindPort = bindPort
}
if args["-v"] != nil {
if args["-v"].(bool) {
fmt.Println(version.Full())
os.Exit(0)
}
}
log.InitLog(config.ServerCommonCfg.LogWay, config.ServerCommonCfg.LogFile,
config.ServerCommonCfg.LogLevel, config.ServerCommonCfg.LogMaxDays)
svr, err := server.NewService()
if err != nil {
fmt.Println(err)
os.Exit(1)
}
log.Info("Start frps success")
if config.ServerCommonCfg.PrivilegeMode == true {
log.Info("PrivilegeMode is enabled, you should pay more attention to security issues")
}
svr.Run()
}

48
conf/frpc.ini

@ -17,70 +17,58 @@ log_level = info
log_max_days = 3
# for authentication
auth_token = 123
# for privilege mode
privilege_token = 12345678
# connections will be established in advance, default value is zero
pool_count = 5
# your proxy name will be changed to {user}.{proxy}
user = your_name
# heartbeat configure, it's not recommended to modify the default value
# the default value of heartbeat_interval is 10 and heartbeat_timeout is 30
# heartbeat_interval = 10
# heartbeat_timeout = 30
# ssh is the proxy name same as server's configuration
# if user in [common] section is not empty, it will be changed to {user}.{proxy} such as your_name.ssh
[ssh]
# tcp | http, default is tcp
# tcp | udp | http | https, default is tcp
type = tcp
local_ip = 127.0.0.1
local_port = 22
# true or false, if true, messages between frps and frpc will be encrypted, default is false
use_encryption = false
# default is false
use_gzip = false
# if true, message will be compressed
use_compression = false
# remote port listen by frps
remote_port = 6001
[dns]
type = udp
local_ip = 114.114.114.114
local_port = 53
# Resolve your domain names to [server_addr] so you can use http://web01.yourdomain.com to browse web01 and http://web02.yourdomain.com to browse web02, the domains are set in frps.ini
# Resolve your domain names to [server_addr] so you can use http://web01.yourdomain.com to browse web01 and http://web02.yourdomain.com to browse web02
[web01]
type = http
local_ip = 127.0.0.1
local_port = 80
use_gzip = true
# connections will be established in advance, default value is zero
pool_count = 20
use_compression = true
# http username and password are safety certification for http protocol
# if not set, you can access this custom_domains without certification
http_user = admin
http_pwd = admin
# if domain for frps is frps.com, then you can access [web01] proxy by URL http://test.frps.com
subdomain = test
subdomain = web01
[web02]
type = http
local_ip = 127.0.0.1
local_port = 8000
[privilege_ssh]
# if privilege_mode is enabled, this proxy will be created automatically
privilege_mode = true
type = tcp
local_ip = 127.0.0.1
local_port = 22
use_encryption = true
use_gzip = false
remote_port = 6001
[privilege_web]
privilege_mode = true
type = http
local_ip = 127.0.0.1
local_port = 80
use_gzip = true
custom_domains = web03.yourdomain.com
use_encryption = false
use_compression = false
custom_domains = web02.yourdomain.com
# locations is only useful for http type
locations = /,/pic
host_header_rewrite = example.com
subdomain = dev

31
conf/frps.ini

@ -26,8 +26,7 @@ log_level = info
log_max_days = 3
# if you enable privilege mode, frpc can create a proxy without pre-configure in frps when privilege_token is correct
privilege_mode = true
# privilege mode is the only supported mode since v0.10.0
privilege_token = 12345678
# heartbeat configure, it's not recommended to modify the default value
@ -38,7 +37,7 @@ privilege_token = 12345678
privilege_allow_ports = 2000-3000,3001,3003,4000-50000
# pool_count in each proxy will change to max_pool_count if they exceed the maximum value
max_pool_count = 100
max_pool_count = 10
# authentication_timeout means the timeout interval (seconds) when the frpc connects frps
# if authentication_timeout is zero, the time is not verified, default is 900s
@ -47,29 +46,3 @@ authentication_timeout = 900
# if subdomain_host is not empty, you can set subdomain when type is http or https in frpc's configure file
# when subdomain is test, the host used by routing is test.frps.com
subdomain_host = frps.com
# ssh is the proxy name, client will use this name and auth_token to connect to server
[ssh]
type = tcp
auth_token = 123
bind_addr = 0.0.0.0
listen_port = 6000
[dns]
type = udp
auth_token = 123
bind_addr = 0.0.0.0
listen_port = 5353
[web01]
# if type equals http, vhost_http_port must be set
type = http
auth_token = 123
# if proxy type equals http, custom_domains must be set separated by commas
custom_domains = web01.yourdomain.com,web01.yourdomain2.com
[web02]
# if type equals https, vhost_https_port must be set
type = https
auth_token = 123
custom_domains = web02.yourdomain.com

160
models/config/client_common.go

@ -0,0 +1,160 @@
// Copyright 2016 fatedier, fatedier@gmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package config
import (
"fmt"
"os"
"strconv"
ini "github.com/vaughan0/go-ini"
)
var ClientCommonCfg *ClientCommonConf
// client common config
type ClientCommonConf struct {
ConfigFile string
ServerAddr string
ServerPort int64
HttpProxy string
LogFile string
LogWay string
LogLevel string
LogMaxDays int64
PrivilegeToken string
PoolCount int
User string
HeartBeatInterval int64
HeartBeatTimeout int64
}
func GetDeaultClientCommonConf() *ClientCommonConf {
return &ClientCommonConf{
ConfigFile: "./frpc.ini",
ServerAddr: "0.0.0.0",
ServerPort: 7000,
HttpProxy: "",
LogFile: "console",
LogWay: "console",
LogLevel: "info",
LogMaxDays: 3,
PrivilegeToken: "",
PoolCount: 1,
User: "",
HeartBeatInterval: 10,
HeartBeatTimeout: 30,
}
}
func LoadClientCommonConf(conf ini.File) (cfg *ClientCommonConf, err error) {
var (
tmpStr string
ok bool
v int64
)
cfg = GetDeaultClientCommonConf()
tmpStr, ok = conf.Get("common", "server_addr")
if ok {
cfg.ServerAddr = tmpStr
}
tmpStr, ok = conf.Get("common", "server_port")
if ok {
cfg.ServerPort, _ = strconv.ParseInt(tmpStr, 10, 64)
}
tmpStr, ok = conf.Get("common", "http_proxy")
if ok {
cfg.HttpProxy = tmpStr
} else {
// get http_proxy from env
cfg.HttpProxy = os.Getenv("http_proxy")
}
tmpStr, ok = conf.Get("common", "log_file")
if ok {
cfg.LogFile = tmpStr
if cfg.LogFile == "console" {
cfg.LogWay = "console"
} else {
cfg.LogWay = "file"
}
}
tmpStr, ok = conf.Get("common", "log_level")
if ok {
cfg.LogLevel = tmpStr
}
tmpStr, ok = conf.Get("common", "log_max_days")
if ok {
cfg.LogMaxDays, _ = strconv.ParseInt(tmpStr, 10, 64)
}
tmpStr, ok = conf.Get("common", "privilege_token")
if ok {
cfg.PrivilegeToken = tmpStr
}
tmpStr, ok = conf.Get("common", "pool_count")
if ok {
v, err = strconv.ParseInt(tmpStr, 10, 64)
if err != nil {
cfg.PoolCount = 1
} else {
cfg.PoolCount = int(v)
}
}
tmpStr, ok = conf.Get("common", "user")
if ok {
cfg.User = tmpStr
}
tmpStr, ok = conf.Get("common", "heartbeat_timeout")
if ok {
v, err = strconv.ParseInt(tmpStr, 10, 64)
if err != nil {
err = fmt.Errorf("Parse conf error: heartbeat_timeout is incorrect")
return
} else {
cfg.HeartBeatTimeout = v
}
}
tmpStr, ok = conf.Get("common", "heartbeat_interval")
if ok {
v, err = strconv.ParseInt(tmpStr, 10, 64)
if err != nil {
err = fmt.Errorf("Parse conf error: heartbeat_interval is incorrect")
return
} else {
cfg.HeartBeatInterval = v
}
}
if cfg.HeartBeatInterval <= 0 {
err = fmt.Errorf("Parse conf error: heartbeat_interval is incorrect")
return
}
if cfg.HeartBeatTimeout < cfg.HeartBeatInterval {
err = fmt.Errorf("Parse conf error: heartbeat_timeout is incorrect, heartbeat_timeout is less than heartbeat_interval")
return
}
return
}

446
models/config/proxy.go

@ -0,0 +1,446 @@
// Copyright 2016 fatedier, fatedier@gmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package config
import (
"fmt"
"strconv"
"strings"
"github.com/fatedier/frp/models/consts"
"github.com/fatedier/frp/models/msg"
ini "github.com/vaughan0/go-ini"
)
type ProxyConf interface {
GetName() string
GetBaseInfo() *BaseProxyConf
LoadFromMsg(pMsg *msg.NewProxy)
LoadFromFile(name string, conf ini.Section) error
UnMarshalToMsg(pMsg *msg.NewProxy)
Check() error
}
func NewProxyConf(pMsg *msg.NewProxy) (cfg ProxyConf, err error) {
if pMsg.ProxyType == "" {
pMsg.ProxyType = consts.TcpProxy
}
switch pMsg.ProxyType {
case consts.TcpProxy:
cfg = &TcpProxyConf{}
case consts.UdpProxy:
cfg = &UdpProxyConf{}
case consts.HttpProxy:
cfg = &HttpProxyConf{}
case consts.HttpsProxy:
cfg = &HttpsProxyConf{}
default:
err = fmt.Errorf("proxy [%s] type [%s] error", pMsg.ProxyName, pMsg.ProxyType)
return
}
cfg.LoadFromMsg(pMsg)
err = cfg.Check()
return
}
func NewProxyConfFromFile(name string, section ini.Section) (cfg ProxyConf, err error) {
proxyType := section["type"]
if proxyType == "" {
proxyType = consts.TcpProxy
section["type"] = consts.TcpProxy
}
switch proxyType {
case consts.TcpProxy:
cfg = &TcpProxyConf{}
case consts.UdpProxy:
cfg = &UdpProxyConf{}
case consts.HttpProxy:
cfg = &HttpProxyConf{}
case consts.HttpsProxy:
cfg = &HttpsProxyConf{}
default:
err = fmt.Errorf("proxy [%s] type [%s] error", name, proxyType)
return
}
err = cfg.LoadFromFile(name, section)
return
}
// BaseProxy info
type BaseProxyConf struct {
ProxyName string
ProxyType string
UseEncryption bool
UseCompression bool
}
func (cfg *BaseProxyConf) GetName() string {
return cfg.ProxyName
}
func (cfg *BaseProxyConf) GetBaseInfo() *BaseProxyConf {
return cfg
}
func (cfg *BaseProxyConf) LoadFromMsg(pMsg *msg.NewProxy) {
cfg.ProxyName = pMsg.ProxyName
cfg.ProxyType = pMsg.ProxyType
cfg.UseEncryption = pMsg.UseEncryption
cfg.UseCompression = pMsg.UseCompression
}
func (cfg *BaseProxyConf) LoadFromFile(name string, section ini.Section) error {
var (
tmpStr string
ok bool
)
cfg.ProxyName = ClientCommonCfg.User + "." + name
cfg.ProxyType = section["type"]
tmpStr, ok = section["use_encryption"]
if ok && tmpStr == "true" {
cfg.UseEncryption = true
}
tmpStr, ok = section["use_compression"]
if ok && tmpStr == "true" {
cfg.UseCompression = true
}
return nil
}
func (cfg *BaseProxyConf) UnMarshalToMsg(pMsg *msg.NewProxy) {
pMsg.ProxyName = cfg.ProxyName
pMsg.ProxyType = cfg.ProxyType
pMsg.UseEncryption = cfg.UseEncryption
pMsg.UseCompression = cfg.UseCompression
}
// Bind info
type BindInfoConf struct {
BindAddr string
RemotePort int64
}
func (cfg *BindInfoConf) LoadFromMsg(pMsg *msg.NewProxy) {
cfg.BindAddr = ServerCommonCfg.BindAddr
cfg.RemotePort = pMsg.RemotePort
}
func (cfg *BindInfoConf) LoadFromFile(name string, section ini.Section) (err error) {
var (
tmpStr string
ok bool
)
if tmpStr, ok = section["remote_port"]; ok {
if cfg.RemotePort, err = strconv.ParseInt(tmpStr, 10, 64); err != nil {
return fmt.Errorf("Parse conf error: proxy [%s] remote_port error", name)
}
} else {
return fmt.Errorf("Parse conf error: proxy [%s] remote_port not found", name)
}
return nil
}
func (cfg *BindInfoConf) UnMarshalToMsg(pMsg *msg.NewProxy) {
pMsg.RemotePort = cfg.RemotePort
}
func (cfg *BindInfoConf) check() (err error) {
if len(ServerCommonCfg.PrivilegeAllowPorts) != 0 {
if _, ok := ServerCommonCfg.PrivilegeAllowPorts[cfg.RemotePort]; !ok {
return fmt.Errorf("remote port [%d] isn't allowed", cfg.RemotePort)
}
}
return nil
}
// Domain info
type DomainConf struct {
CustomDomains []string
SubDomain string
}
func (cfg *DomainConf) LoadFromMsg(pMsg *msg.NewProxy) {
cfg.CustomDomains = pMsg.CustomDomains
cfg.SubDomain = pMsg.SubDomain
}
func (cfg *DomainConf) LoadFromFile(name string, section ini.Section) (err error) {
var (
tmpStr string
ok bool
)
if tmpStr, ok = section["custom_domains"]; ok {
cfg.CustomDomains = strings.Split(tmpStr, ",")
for i, domain := range cfg.CustomDomains {
cfg.CustomDomains[i] = strings.ToLower(strings.TrimSpace(domain))
}
}
if tmpStr, ok = section["subdomain"]; ok {
cfg.SubDomain = tmpStr
}
if len(cfg.CustomDomains) == 0 && cfg.SubDomain == "" {
return fmt.Errorf("Parse conf error: proxy [%s] custom_domains and subdomain should set at least one of them", name)
}
return
}
func (cfg *DomainConf) UnMarshalToMsg(pMsg *msg.NewProxy) {
pMsg.CustomDomains = cfg.CustomDomains
pMsg.SubDomain = cfg.SubDomain
}
func (cfg *DomainConf) check() (err error) {
for _, domain := range cfg.CustomDomains {
if ServerCommonCfg.SubDomainHost != "" && len(strings.Split(ServerCommonCfg.SubDomainHost, ".")) < len(strings.Split(domain, ".")) {
if strings.Contains(domain, ServerCommonCfg.SubDomainHost) {
return fmt.Errorf("custom domain [%s] should not belong to subdomain_host [%s]", domain, ServerCommonCfg.SubDomainHost)
}
}
}
if cfg.SubDomain != "" {
if ServerCommonCfg.SubDomainHost == "" {
return fmt.Errorf("subdomain is not supported because this feature is not enabled by frps")
}
if strings.Contains(cfg.SubDomain, ".") || strings.Contains(cfg.SubDomain, "*") {
return fmt.Errorf("'.' and '*' is not supported in subdomain")
}
cfg.SubDomain += "." + ServerCommonCfg.SubDomainHost
}
return nil
}
type LocalSvrConf struct {
LocalIp string
LocalPort int
}
func (cfg *LocalSvrConf) LoadFromFile(name string, section ini.Section) (err error) {
if cfg.LocalIp = section["local_ip"]; cfg.LocalIp == "" {
cfg.LocalIp = "127.0.0.1"
}
if tmpStr, ok := section["local_port"]; ok {
if cfg.LocalPort, err = strconv.Atoi(tmpStr); err != nil {
return fmt.Errorf("Parse conf error: proxy [%s] local_port error", name)
}
} else {
return fmt.Errorf("Parse conf error: proxy [%s] local_port not found", name)
}
return nil
}
// TCP
type TcpProxyConf struct {
BaseProxyConf
BindInfoConf
LocalSvrConf
}
func (cfg *TcpProxyConf) LoadFromMsg(pMsg *msg.NewProxy) {
cfg.BaseProxyConf.LoadFromMsg(pMsg)
cfg.BindInfoConf.LoadFromMsg(pMsg)
}
func (cfg *TcpProxyConf) LoadFromFile(name string, section ini.Section) (err error) {
if err = cfg.BaseProxyConf.LoadFromFile(name, section); err != nil {
return
}
if err = cfg.BindInfoConf.LoadFromFile(name, section); err != nil {
return
}
if err = cfg.LocalSvrConf.LoadFromFile(name, section); err != nil {
return
}
return
}
func (cfg *TcpProxyConf) UnMarshalToMsg(pMsg *msg.NewProxy) {
cfg.BaseProxyConf.UnMarshalToMsg(pMsg)
cfg.BindInfoConf.UnMarshalToMsg(pMsg)
}
func (cfg *TcpProxyConf) Check() (err error) {
err = cfg.BindInfoConf.check()
return
}
// UDP
type UdpProxyConf struct {
BaseProxyConf
BindInfoConf
LocalSvrConf
}
func (cfg *UdpProxyConf) LoadFromMsg(pMsg *msg.NewProxy) {
cfg.BaseProxyConf.LoadFromMsg(pMsg)
cfg.BindInfoConf.LoadFromMsg(pMsg)
}
func (cfg *UdpProxyConf) LoadFromFile(name string, section ini.Section) (err error) {
if err = cfg.BaseProxyConf.LoadFromFile(name, section); err != nil {
return
}
if err = cfg.BindInfoConf.LoadFromFile(name, section); err != nil {
return
}
if err = cfg.LocalSvrConf.LoadFromFile(name, section); err != nil {
return
}
return
}
func (cfg *UdpProxyConf) UnMarshalToMsg(pMsg *msg.NewProxy) {
cfg.BaseProxyConf.UnMarshalToMsg(pMsg)
cfg.BindInfoConf.UnMarshalToMsg(pMsg)
}
func (cfg *UdpProxyConf) Check() (err error) {
err = cfg.BindInfoConf.check()
return
}
// HTTP
type HttpProxyConf struct {
BaseProxyConf
DomainConf
LocalSvrConf
Locations []string
HostHeaderRewrite string
HttpUser string
HttpPwd string
}
func (cfg *HttpProxyConf) LoadFromMsg(pMsg *msg.NewProxy) {
cfg.BaseProxyConf.LoadFromMsg(pMsg)
cfg.DomainConf.LoadFromMsg(pMsg)
cfg.Locations = pMsg.Locations
cfg.HostHeaderRewrite = pMsg.HostHeaderRewrite
cfg.HttpUser = pMsg.HttpUser
cfg.HttpPwd = pMsg.HttpPwd
}
func (cfg *HttpProxyConf) LoadFromFile(name string, section ini.Section) (err error) {
if err = cfg.BaseProxyConf.LoadFromFile(name, section); err != nil {
return
}
if err = cfg.DomainConf.LoadFromFile(name, section); err != nil {
return
}
if err = cfg.LocalSvrConf.LoadFromFile(name, section); err != nil {
return
}
var (
tmpStr string
ok bool
)
if tmpStr, ok = section["locations"]; ok {
cfg.Locations = strings.Split(tmpStr, ",")
} else {
cfg.Locations = []string{""}
}
cfg.HostHeaderRewrite = section["host_header_rewrite"]
cfg.HttpUser = section["http_user"]
cfg.HttpPwd = section["http_pwd"]
return
}
func (cfg *HttpProxyConf) UnMarshalToMsg(pMsg *msg.NewProxy) {
cfg.BaseProxyConf.UnMarshalToMsg(pMsg)
cfg.DomainConf.UnMarshalToMsg(pMsg)
pMsg.Locations = cfg.Locations
pMsg.HostHeaderRewrite = cfg.HostHeaderRewrite
pMsg.HttpUser = cfg.HttpUser
pMsg.HttpPwd = cfg.HttpPwd
}
func (cfg *HttpProxyConf) Check() (err error) {
if ServerCommonCfg.VhostHttpPort == 0 {
return fmt.Errorf("type [http] not support when vhost_http_port is not set")
}
err = cfg.DomainConf.check()
return
}
// HTTPS
type HttpsProxyConf struct {
BaseProxyConf
DomainConf
LocalSvrConf
}
func (cfg *HttpsProxyConf) LoadFromMsg(pMsg *msg.NewProxy) {
cfg.BaseProxyConf.LoadFromMsg(pMsg)
cfg.DomainConf.LoadFromMsg(pMsg)
}
func (cfg *HttpsProxyConf) LoadFromFile(name string, section ini.Section) (err error) {
if err = cfg.BaseProxyConf.LoadFromFile(name, section); err != nil {
return
}
if err = cfg.DomainConf.LoadFromFile(name, section); err != nil {
return
}
if err = cfg.LocalSvrConf.LoadFromFile(name, section); err != nil {
return
}
return
}
func (cfg *HttpsProxyConf) UnMarshalToMsg(pMsg *msg.NewProxy) {
cfg.BaseProxyConf.UnMarshalToMsg(pMsg)
cfg.DomainConf.UnMarshalToMsg(pMsg)
}
func (cfg *HttpsProxyConf) Check() (err error) {
if ServerCommonCfg.VhostHttpsPort == 0 {
return fmt.Errorf("type [https] not support when vhost_https_port is not set")
}
err = cfg.DomainConf.check()
return
}
func LoadProxyConfFromFile(conf ini.File) (proxyConfs map[string]ProxyConf, err error) {
var prefix string
if ClientCommonCfg.User != "" {
prefix = ClientCommonCfg.User + "."
}
proxyConfs = make(map[string]ProxyConf)
for name, section := range conf {
if name != "common" {
cfg, err := NewProxyConfFromFile(name, section)
if err != nil {
return proxyConfs, err
}
proxyConfs[prefix+name] = cfg
}
}
return
}

279
models/config/server_common.go

@ -0,0 +1,279 @@
// Copyright 2016 fatedier, fatedier@gmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package config
import (
"fmt"
"strconv"
"strings"
ini "github.com/vaughan0/go-ini"
)
var ServerCommonCfg *ServerCommonConf
// common config
type ServerCommonConf struct {
ConfigFile string
BindAddr string
BindPort int64
// If VhostHttpPort equals 0, don't listen a public port for http protocol.
VhostHttpPort int64
// if VhostHttpsPort equals 0, don't listen a public port for https protocol
VhostHttpsPort int64
// if DashboardPort equals 0, dashboard is not available
DashboardPort int64
DashboardUser string
DashboardPwd string
AssetsDir string
LogFile string
LogWay string // console or file
LogLevel string
LogMaxDays int64
PrivilegeMode bool
PrivilegeToken string
AuthTimeout int64
SubDomainHost string
// if PrivilegeAllowPorts is not nil, tcp proxies which remote port exist in this map can be connected
PrivilegeAllowPorts map[int64]struct{}
MaxPoolCount int64
HeartBeatTimeout int64
UserConnTimeout int64
}
func GetDefaultServerCommonConf() *ServerCommonConf {
return &ServerCommonConf{
ConfigFile: "./frps.ini",
BindAddr: "0.0.0.0",
BindPort: 7000,
VhostHttpPort: 0,
VhostHttpsPort: 0,
DashboardPort: 0,
DashboardUser: "admin",
DashboardPwd: "admin",
AssetsDir: "",
LogFile: "console",
LogWay: "console",
LogLevel: "info",
LogMaxDays: 3,
PrivilegeMode: true,
PrivilegeToken: "",
AuthTimeout: 900,
SubDomainHost: "",
MaxPoolCount: 10,
HeartBeatTimeout: 30,
UserConnTimeout: 10,
}
}
// Load server common configure.
func LoadServerCommonConf(conf ini.File) (cfg *ServerCommonConf, err error) {
var (
tmpStr string
ok bool
v int64
)
cfg = GetDefaultServerCommonConf()
tmpStr, ok = conf.Get("common", "bind_addr")
if ok {
cfg.BindAddr = tmpStr
}
tmpStr, ok = conf.Get("common", "bind_port")
if ok {
v, err = strconv.ParseInt(tmpStr, 10, 64)
if err == nil {
cfg.BindPort = v
}
}
tmpStr, ok = conf.Get("common", "vhost_http_port")
if ok {
cfg.VhostHttpPort, err = strconv.ParseInt(tmpStr, 10, 64)
if err != nil {
err = fmt.Errorf("Parse conf error: vhost_http_port is incorrect")
return
}
} else {
cfg.VhostHttpPort = 0
}
tmpStr, ok = conf.Get("common", "vhost_https_port")
if ok {
cfg.VhostHttpsPort, err = strconv.ParseInt(tmpStr, 10, 64)
if err != nil {
err = fmt.Errorf("Parse conf error: vhost_https_port is incorrect")
return
}
} else {
cfg.VhostHttpsPort = 0
}
tmpStr, ok = conf.Get("common", "dashboard_port")
if ok {
cfg.DashboardPort, err = strconv.ParseInt(tmpStr, 10, 64)
if err != nil {
err = fmt.Errorf("Parse conf error: dashboard_port is incorrect")
return
}
} else {
cfg.DashboardPort = 0
}
tmpStr, ok = conf.Get("common", "dashboard_user")
if ok {
cfg.DashboardUser = tmpStr
}
tmpStr, ok = conf.Get("common", "dashboard_pwd")
if ok {
cfg.DashboardPwd = tmpStr
}
tmpStr, ok = conf.Get("common", "assets_dir")
if ok {
cfg.AssetsDir = tmpStr
}
tmpStr, ok = conf.Get("common", "log_file")
if ok {
cfg.LogFile = tmpStr
if cfg.LogFile == "console" {
cfg.LogWay = "console"
} else {
cfg.LogWay = "file"
}
}
tmpStr, ok = conf.Get("common", "log_level")
if ok {
cfg.LogLevel = tmpStr
}
tmpStr, ok = conf.Get("common", "log_max_days")
if ok {
v, err = strconv.ParseInt(tmpStr, 10, 64)
if err == nil {
cfg.LogMaxDays = v
}
}
tmpStr, ok = conf.Get("common", "privilege_mode")
if ok {
if tmpStr == "true" {
cfg.PrivilegeMode = true
}
}
// PrivilegeMode configure
if cfg.PrivilegeMode == true {
tmpStr, ok = conf.Get("common", "privilege_token")
if ok {
if tmpStr == "" {
err = fmt.Errorf("Parse conf error: privilege_token can not be empty")
return
}
cfg.PrivilegeToken = tmpStr
} else {
err = fmt.Errorf("Parse conf error: privilege_token must be set if privilege_mode is enabled")
return
}
cfg.PrivilegeAllowPorts = make(map[int64]struct{})
tmpStr, ok = conf.Get("common", "privilege_allow_ports")
if ok {
// e.g. 1000-2000,2001,2002,3000-4000
portRanges := strings.Split(tmpStr, ",")
for _, portRangeStr := range portRanges {
// 1000-2000 or 2001
portArray := strings.Split(portRangeStr, "-")
// length: only 1 or 2 is correct
rangeType := len(portArray)
if rangeType == 1 {
// single port
singlePort, errRet := strconv.ParseInt(portArray[0], 10, 64)
if errRet != nil {
err = fmt.Errorf("Parse conf error: privilege_allow_ports is incorrect, %v", errRet)
return
}
ServerCommonCfg.PrivilegeAllowPorts[singlePort] = struct{}{}
} else if rangeType == 2 {
// range ports
min, errRet := strconv.ParseInt(portArray[0], 10, 64)
if errRet != nil {
err = fmt.Errorf("Parse conf error: privilege_allow_ports is incorrect, %v", errRet)
return
}
max, errRet := strconv.ParseInt(portArray[1], 10, 64)
if errRet != nil {
err = fmt.Errorf("Parse conf error: privilege_allow_ports is incorrect, %v", errRet)
return
}
if max < min {
err = fmt.Errorf("Parse conf error: privilege_allow_ports range incorrect")
return
}
for i := min; i <= max; i++ {
cfg.PrivilegeAllowPorts[i] = struct{}{}
}
} else {
err = fmt.Errorf("Parse conf error: privilege_allow_ports is incorrect")
return
}
}
}
}
tmpStr, ok = conf.Get("common", "max_pool_count")
if ok {
v, err = strconv.ParseInt(tmpStr, 10, 64)
if err == nil && v >= 0 {
cfg.MaxPoolCount = v
}
}
tmpStr, ok = conf.Get("common", "authentication_timeout")
if ok {
v, errRet := strconv.ParseInt(tmpStr, 10, 64)
if errRet != nil {
err = fmt.Errorf("Parse conf error: authentication_timeout is incorrect")
return
} else {
cfg.AuthTimeout = v
}
}
tmpStr, ok = conf.Get("common", "subdomain_host")
if ok {
cfg.SubDomainHost = strings.ToLower(strings.TrimSpace(tmpStr))
}
tmpStr, ok = conf.Get("common", "heartbeat_timeout")
if ok {
v, errRet := strconv.ParseInt(tmpStr, 10, 64)
if errRet != nil {
err = fmt.Errorf("Parse conf error: heartbeat_timeout is incorrect")
return
} else {
cfg.HeartBeatTimeout = v
}
}
return
}

31
src/models/consts/consts.go → models/consts/consts.go

@ -14,28 +14,15 @@
package consts
// server status
const (
Idle = iota
Working
Closed
)
var (
StatusStr = []string{
"idle",
"working",
"closed",
}
)
// server status
Idle string = "idle"
Working string = "working"
Closed string = "closed"
// msg type
const (
NewCtlConn = iota
NewWorkConn
NoticeUserConn
NewCtlConnRes
HeartbeatReq
HeartbeatRes
NewWorkConnUdp
// proxy type
TcpProxy string = "tcp"
UdpProxy string = "udp"
HttpProxy string = "http"
HttpsProxy string = "https"
)

21
models/errors/errors.go

@ -0,0 +1,21 @@
// Copyright 2016 fatedier, fatedier@gmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package errors
import "errors"
var (
ErrMsgType = errors.New("message type error")
)

10
src/models/metric/server.go → models/metric/server.go

@ -14,14 +14,7 @@
package metric
import (
"sort"
"sync"
"time"
"github.com/fatedier/frp/src/models/consts"
)
/*
var (
DailyDataKeepDays int = 7
ServerMetricInfoMap map[string]*ServerMetric
@ -225,3 +218,4 @@ func AddFlowOut(proxyName string, value int64) {
metric.mutex.Unlock()
}
}
*/

122
models/msg/msg.go

@ -0,0 +1,122 @@
// Copyright 2016 fatedier, fatedier@gmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package msg
import "reflect"
const (
TypeLogin = 'o'
TypeLoginResp = '1'
TypeNewProxy = 'p'
TypeNewProxyResp = '2'
TypeNewWorkConn = 'w'
TypeReqWorkConn = 'r'
TypeStartWorkConn = 's'
TypePing = 'h'
TypePong = '4'
)
var (
TypeMap map[byte]reflect.Type
TypeStringMap map[reflect.Type]byte
)
func init() {
TypeMap = make(map[byte]reflect.Type)
TypeStringMap = make(map[reflect.Type]byte)
TypeMap[TypeLogin] = getTypeFn((*Login)(nil))
TypeMap[TypeLoginResp] = getTypeFn((*LoginResp)(nil))
TypeMap[TypeNewProxy] = getTypeFn((*NewProxy)(nil))
TypeMap[TypeNewProxyResp] = getTypeFn((*NewProxyResp)(nil))
TypeMap[TypeNewWorkConn] = getTypeFn((*NewWorkConn)(nil))
TypeMap[TypeReqWorkConn] = getTypeFn((*ReqWorkConn)(nil))
TypeMap[TypeStartWorkConn] = getTypeFn((*StartWorkConn)(nil))
TypeMap[TypePing] = getTypeFn((*Ping)(nil))
TypeMap[TypePong] = getTypeFn((*Pong)(nil))
for k, v := range TypeMap {
TypeStringMap[v] = k
}
}
func getTypeFn(obj interface{}) reflect.Type {
return reflect.TypeOf(obj).Elem()
}
// Message wraps socket packages for communicating between frpc and frps.
type Message interface{}
// When frpc start, client send this message to login to server.
type Login struct {
Version string `json:"version"`
Hostname string `json:"hostname"`
Os string `json:"os"`
Arch string `json:"arch"`
User string `json:"user"`
PrivilegeKey string `json:"privilege_key"`
Timestamp int64 `json:"timestamp"`
RunId string `json:"run_id"`
// Some global configures.
PoolCount int `json:"pool_count"`
}
type LoginResp struct {
Version string `json:"version"`
RunId string `json:"run_id"`
Error string `json:"error"`
}
// When frpc login success, send this message to frps for running a new proxy.
type NewProxy struct {
ProxyName string `json:"proxy_name"`
ProxyType string `json:"proxy_type"`
UseEncryption bool `json:"use_encryption"`
UseCompression bool `json:"use_compression"`
// tcp and udp only
RemotePort int64 `json:"remote_port"`
// http and https only
CustomDomains []string `json:"custom_domains"`
SubDomain string `json:"subdomain"`
Locations []string `json:"locations"`
HostHeaderRewrite string `json:"host_header_rewrite"`
HttpUser string `json:"http_user"`
HttpPwd string `json:"http_pwd"`
}
type NewProxyResp struct {
ProxyName string `json:"proxy_name"`
Error string `json:"error"`
}
type NewWorkConn struct {
RunId string `json:"run_id"`
}
type ReqWorkConn struct {
}
type StartWorkConn struct {
ProxyName string `json:"proxy_name"`
}
type Ping struct {
}
type Pong struct {
}

69
models/msg/pack.go

@ -0,0 +1,69 @@
// Copyright 2016 fatedier, fatedier@gmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package msg
import (
"bytes"
"encoding/binary"
"encoding/json"
"fmt"
"reflect"
"github.com/fatedier/frp/utils/errors"
)
func unpack(typeByte byte, buffer []byte, msgIn Message) (msg Message, err error) {
if msgIn == nil {
t, ok := TypeMap[typeByte]
if !ok {
err = fmt.Errorf("Unsupported message type %b", typeByte)
return
}
msg = reflect.New(t).Interface().(Message)
} else {
msg = msgIn
}
err = json.Unmarshal(buffer, &msg)
return
}
func UnPackInto(buffer []byte, msg Message) (err error) {
_, err = unpack(' ', buffer, msg)
return
}
func UnPack(typeByte byte, buffer []byte) (msg Message, err error) {
return unpack(typeByte, buffer, nil)
}
func Pack(msg Message) ([]byte, error) {
typeByte, ok := TypeStringMap[reflect.TypeOf(msg).Elem()]
if !ok {
return nil, errors.ErrMsgType
}
content, err := json.Marshal(msg)
if err != nil {
return nil, err
}
buffer := bytes.NewBuffer(nil)
buffer.WriteByte(typeByte)
binary.Write(buffer, binary.BigEndian, int64(len(content)))
buffer.Write(content)
return buffer.Bytes(), nil
}

86
models/msg/pack_test.go

@ -0,0 +1,86 @@
// Copyright 2016 fatedier, fatedier@gmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package msg
import (
"bytes"
"encoding/binary"
"testing"
"github.com/stretchr/testify/assert"
"github.com/fatedier/frp/utils/errors"
)
type TestStruct struct{}
func TestPack(t *testing.T) {
assert := assert.New(t)
var (
msg Message
buffer []byte
err error
)
// error type
msg = &TestStruct{}
buffer, err = Pack(msg)
assert.Error(err, errors.ErrMsgType.Error())
// correct
msg = &Ping{}
buffer, err = Pack(msg)
assert.NoError(err)
b := bytes.NewBuffer(nil)
b.WriteByte(TypePing)
binary.Write(b, binary.BigEndian, int64(2))
b.WriteString("{}")
assert.True(bytes.Equal(b.Bytes(), buffer))
}
func TestUnPack(t *testing.T) {
assert := assert.New(t)
var (
msg Message
err error
)
// error message type
msg, err = UnPack('-', []byte("{}"))
assert.Error(err)
// correct
msg, err = UnPack(TypePong, []byte("{}"))
assert.NoError(err)
assert.Equal(getTypeFn(msg), getTypeFn((*Pong)(nil)))
}
func TestUnPackInto(t *testing.T) {
assert := assert.New(t)
var err error
// correct type
pongMsg := &Pong{}
err = UnPackInto([]byte("{}"), pongMsg)
assert.NoError(err)
// wrong type
loginMsg := &Login{}
err = UnPackInto([]byte(`{"version": 123}`), loginMsg)
assert.Error(err)
}

88
models/msg/process.go

@ -0,0 +1,88 @@
// Copyright 2016 fatedier, fatedier@gmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package msg
import (
"encoding/binary"
"fmt"
"io"
)
var (
MaxMsgLength int64 = 10240
)
func readMsg(c io.Reader) (typeByte byte, buffer []byte, err error) {
buffer = make([]byte, 1)
_, err = c.Read(buffer)
if err != nil {
return
}
typeByte = buffer[0]
if _, ok := TypeMap[typeByte]; !ok {
err = fmt.Errorf("Message type error")
return
}
var length int64
err = binary.Read(c, binary.BigEndian, &length)
if err != nil {
return
}
if length > MaxMsgLength {
err = fmt.Errorf("Message length exceed the limit")
return
}
buffer = make([]byte, length)
n, err := io.ReadFull(c, buffer)
if err != nil {
return
}
if int64(n) != length {
err = fmt.Errorf("Message format error")
}
return
}
func ReadMsg(c io.Reader) (msg Message, err error) {
typeByte, buffer, err := readMsg(c)
if err != nil {
return
}
return UnPack(typeByte, buffer)
}
func ReadMsgInto(c io.Reader, msg Message) (err error) {
_, buffer, err := readMsg(c)
if err != nil {
return
}
return UnPackInto(buffer, msg)
}
func WriteMsg(c io.Writer, msg interface{}) (err error) {
buffer, err := Pack(msg)
if err != nil {
return
}
if _, err = c.Write(buffer); err != nil {
return
}
return nil
}

97
models/msg/process_test.go

@ -0,0 +1,97 @@
// Copyright 2016 fatedier, fatedier@gmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package msg
import (
"bytes"
"encoding/binary"
"reflect"
"testing"
"github.com/stretchr/testify/assert"
)
func TestProcess(t *testing.T) {
assert := assert.New(t)
var (
msg Message
resMsg Message
err error
)
// empty struct
msg = &Ping{}
buffer := bytes.NewBuffer(nil)
err = WriteMsg(buffer, msg)
assert.NoError(err)
resMsg, err = ReadMsg(buffer)
assert.NoError(err)
assert.Equal(reflect.TypeOf(resMsg).Elem(), TypeMap[TypePing])
// normal message
msg = &StartWorkConn{
ProxyName: "test",
}
buffer = bytes.NewBuffer(nil)
err = WriteMsg(buffer, msg)
assert.NoError(err)
resMsg, err = ReadMsg(buffer)
assert.NoError(err)
assert.Equal(reflect.TypeOf(resMsg).Elem(), TypeMap[TypeStartWorkConn])
startWorkConnMsg, ok := resMsg.(*StartWorkConn)
assert.True(ok)
assert.Equal("test", startWorkConnMsg.ProxyName)
// ReadMsgInto correct
msg = &Pong{}
buffer = bytes.NewBuffer(nil)
err = WriteMsg(buffer, msg)
assert.NoError(err)
err = ReadMsgInto(buffer, msg)
assert.NoError(err)
// ReadMsgInto error type
content := []byte(`{"run_id": 123}`)
buffer = bytes.NewBuffer(nil)
buffer.WriteByte(TypeNewWorkConn)
binary.Write(buffer, binary.BigEndian, int64(len(content)))
buffer.Write(content)
resMsg = &NewWorkConn{}
err = ReadMsgInto(buffer, resMsg)
assert.Error(err)
// message format error
buffer = bytes.NewBuffer([]byte("1234"))
resMsg = &NewProxyResp{}
err = ReadMsgInto(buffer, resMsg)
assert.Error(err)
// MaxLength, real message length is 2
MaxMsgLength = 1
msg = &Ping{}
buffer = bytes.NewBuffer(nil)
err = WriteMsg(buffer, msg)
assert.NoError(err)
_, err = ReadMsg(buffer)
assert.Error(err)
return
}

38
models/proto/tcp/process.go

@ -0,0 +1,38 @@
// Copyright 2016 fatedier, fatedier@gmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package tcp
import (
"io"
"sync"
)
// Join two io.ReadWriteCloser and do some operations.
func Join(c1 io.ReadWriteCloser, c2 io.ReadWriteCloser) (inCount int64, outCount int64) {
var wait sync.WaitGroup
pipe := func(to io.ReadWriteCloser, from io.ReadWriteCloser, count *int64) {
defer to.Close()
defer from.Close()
defer wait.Done()
*count, _ = io.Copy(to, from)
}
wait.Add(2)
go pipe(c1, c2, &inCount)
go pipe(c2, c1, &outCount)
wait.Wait()
return
}

129
models/proto/tcp/process_test.go

@ -0,0 +1,129 @@
// Copyright 2017 fatedier, fatedier@gmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package tcp
import (
"io"
"testing"
"github.com/stretchr/testify/assert"
"github.com/fatedier/frp/utils/crypto"
)
func TestJoin(t *testing.T) {
assert := assert.New(t)
var (
n int
err error
)
text1 := "A document that gives tips for writing clear, idiomatic Go code. A must read for any new Go programmer. It augments the tour and the language specification, both of which should be read first."
text2 := "A document that specifies the conditions under which reads of a variable in one goroutine can be guaranteed to observe values produced by writes to the same variable in a different goroutine."
// Forward bytes directly.
pr, pw := io.Pipe()
pr2, pw2 := io.Pipe()
pr3, pw3 := io.Pipe()
pr4, pw4 := io.Pipe()
conn1 := WrapReadWriteCloser(pr, pw2)
conn2 := WrapReadWriteCloser(pr2, pw)
conn3 := WrapReadWriteCloser(pr3, pw4)
conn4 := WrapReadWriteCloser(pr4, pw3)
go func() {
Join(conn2, conn3)
}()
buf1 := make([]byte, 1024)
buf2 := make([]byte, 1024)
conn1.Write([]byte(text1))
conn4.Write([]byte(text2))
n, err = conn4.Read(buf1)
assert.NoError(err)
assert.Equal(text1, string(buf1[:n]))
n, err = conn1.Read(buf2)
assert.NoError(err)
assert.Equal(text2, string(buf2[:n]))
conn1.Close()
conn2.Close()
conn3.Close()
conn4.Close()
}
func TestJoinEncrypt(t *testing.T) {
assert := assert.New(t)
var (
n int
err error
)
text1 := "1234567890"
text2 := "abcdefghij"
key := "authkey"
// Forward enrypted bytes.
pr, pw := io.Pipe()
pr2, pw2 := io.Pipe()
pr3, pw3 := io.Pipe()
pr4, pw4 := io.Pipe()
pr5, pw5 := io.Pipe()
pr6, pw6 := io.Pipe()
conn1 := WrapReadWriteCloser(pr, pw2)
conn2 := WrapReadWriteCloser(pr2, pw)
conn3 := WrapReadWriteCloser(pr3, pw4)
conn4 := WrapReadWriteCloser(pr4, pw3)
conn5 := WrapReadWriteCloser(pr5, pw6)
conn6 := WrapReadWriteCloser(pr6, pw5)
r1, err := crypto.NewReader(conn3, []byte(key))
assert.NoError(err)
w1, err := crypto.NewWriter(conn3, []byte(key))
assert.NoError(err)
r2, err := crypto.NewReader(conn4, []byte(key))
assert.NoError(err)
w2, err := crypto.NewWriter(conn4, []byte(key))
assert.NoError(err)
go Join(conn2, WrapReadWriteCloser(r1, w1))
go Join(WrapReadWriteCloser(r2, w2), conn5)
buf := make([]byte, 128)
conn1.Write([]byte(text1))
conn6.Write([]byte(text2))
n, err = conn6.Read(buf)
assert.NoError(err)
assert.Equal(text1, string(buf[:n]))
n, err = conn1.Read(buf)
assert.NoError(err)
assert.Equal(text2, string(buf[:n]))
conn1.Close()
conn2.Close()
conn3.Close()
conn4.Close()
conn5.Close()
conn6.Close()
}

89
models/proto/tcp/tcp.go

@ -0,0 +1,89 @@
// Copyright 2017 fatedier, fatedier@gmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package tcp
import (
"io"
"github.com/golang/snappy"
"github.com/fatedier/frp/utils/crypto"
)
func WithEncryption(rwc io.ReadWriteCloser, key []byte) (res io.ReadWriteCloser, err error) {
var (
r io.Reader
w io.Writer
)
r, err = crypto.NewReader(rwc, key)
if err != nil {
return
}
w, err = crypto.NewWriter(rwc, key)
if err != nil {
return
}
res = WrapReadWriteCloser(r, w)
return
}
func WithCompression(rwc io.ReadWriteCloser) (res io.ReadWriteCloser) {
var (
r io.Reader
w io.Writer
)
r = snappy.NewReader(rwc)
w = snappy.NewWriter(rwc)
res = WrapReadWriteCloser(r, w)
return
}
func WrapReadWriteCloser(r io.Reader, w io.Writer) io.ReadWriteCloser {
return &ReadWriteCloser{
r: r,
w: w,
}
}
type ReadWriteCloser struct {
r io.Reader
w io.Writer
}
func (rwc *ReadWriteCloser) Read(p []byte) (n int, err error) {
return rwc.r.Read(p)
}
func (rwc *ReadWriteCloser) Write(p []byte) (n int, err error) {
return rwc.w.Write(p)
}
func (rwc *ReadWriteCloser) Close() (errRet error) {
var err error
if rc, ok := rwc.r.(io.Closer); ok {
err = rc.Close()
if err != nil {
errRet = err
}
}
if wc, ok := rwc.w.(io.Closer); ok {
err = wc.Close()
if err != nil {
errRet = err
}
}
return
}

100
models/proto/tcp/tcp_test.go

@ -0,0 +1,100 @@
// Copyright 2017 fatedier, fatedier@gmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package tcp
import (
"io"
"testing"
"github.com/stretchr/testify/assert"
)
func TestWithCompression(t *testing.T) {
assert := assert.New(t)
// Forward compression bytes.
pr, pw := io.Pipe()
pr2, pw2 := io.Pipe()
conn1 := WrapReadWriteCloser(pr, pw2)
conn2 := WrapReadWriteCloser(pr2, pw)
compressionStream1 := WithCompression(conn1)
compressionStream2 := WithCompression(conn2)
var (
n int
err error
)
text := "1234567812345678"
buf := make([]byte, 256)
go compressionStream1.Write([]byte(text))
n, err = compressionStream2.Read(buf)
assert.NoError(err)
assert.Equal(text, string(buf[:n]))
go compressionStream2.Write([]byte(text))
n, err = compressionStream1.Read(buf)
assert.NoError(err)
assert.Equal(text, string(buf[:n]))
}
func TestWithEncryption(t *testing.T) {
assert := assert.New(t)
var (
n int
err error
)
text1 := "Go is expressive, concise, clean, and efficient. Its concurrency mechanisms make it easy to write programs that get the most out of multicore and networked machines, while its novel type system enables flexible and modular program construction. Go compiles quickly to machine code yet has the convenience of garbage collection and the power of run-time reflection. It's a fast, statically typed, compiled language that feels like a dynamically typed, interpreted language."
text2 := "An interactive introduction to Go in three sections. The first section covers basic syntax and data structures; the second discusses methods and interfaces; and the third introduces Go's concurrency primitives. Each section concludes with a few exercises so you can practice what you've learned. You can take the tour online or install it locally with"
key := "authkey"
// Forward enrypted bytes.
pr, pw := io.Pipe()
pr2, pw2 := io.Pipe()
pr3, pw3 := io.Pipe()
pr4, pw4 := io.Pipe()
pr5, pw5 := io.Pipe()
pr6, pw6 := io.Pipe()
conn1 := WrapReadWriteCloser(pr, pw2)
conn2 := WrapReadWriteCloser(pr2, pw)
conn3 := WrapReadWriteCloser(pr3, pw4)
conn4 := WrapReadWriteCloser(pr4, pw3)
conn5 := WrapReadWriteCloser(pr5, pw6)
conn6 := WrapReadWriteCloser(pr6, pw5)
encryptStream1, err := WithEncryption(conn3, []byte(key))
assert.NoError(err)
encryptStream2, err := WithEncryption(conn4, []byte(key))
assert.NoError(err)
go Join(conn2, encryptStream1)
go Join(encryptStream2, conn5)
buf := make([]byte, 1024)
conn1.Write([]byte(text1))
conn6.Write([]byte(text2))
n, err = conn6.Read(buf)
assert.NoError(err)
assert.Equal(text1, string(buf[:n]))
n, err = conn1.Read(buf)
assert.NoError(err)
}

2
src/models/msg/udp.go → models/proto/udp/udp.go

@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package msg
package udp
import (
"encoding/base64"

2
src/models/msg/udp_test.go → models/proto/udp/udp_test.go

@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package msg
package udp
import (
"net"

332
server/control.go

@ -0,0 +1,332 @@
package server
import (
"fmt"
"io"
"sync"
"time"
"github.com/fatedier/frp/models/config"
"github.com/fatedier/frp/models/consts"
"github.com/fatedier/frp/models/msg"
"github.com/fatedier/frp/utils/errors"
"github.com/fatedier/frp/utils/net"
"github.com/fatedier/frp/utils/shutdown"
"github.com/fatedier/frp/utils/version"
)
type Control struct {
// frps service
svr *Service
// login message
loginMsg *msg.Login
// control connection
conn net.Conn
// put a message in this channel to send it over control connection to client
sendCh chan (msg.Message)
// read from this channel to get the next message sent by client
readCh chan (msg.Message)
// work connections
workConnCh chan net.Conn
// proxies in one client
proxies []Proxy
// pool count
poolCount int
// last time got the Ping message
lastPing time.Time
// A new run id will be generated when a new client login.
// If run id got from login message has same run id, it means it's the same client, so we can
// replace old controller instantly.
runId string
// control status
status string
readerShutdown *shutdown.Shutdown
writerShutdown *shutdown.Shutdown
managerShutdown *shutdown.Shutdown
allShutdown *shutdown.Shutdown
mu sync.RWMutex
}
func NewControl(svr *Service, ctlConn net.Conn, loginMsg *msg.Login) *Control {
return &Control{
svr: svr,
conn: ctlConn,
loginMsg: loginMsg,
sendCh: make(chan msg.Message, 10),
readCh: make(chan msg.Message, 10),
workConnCh: make(chan net.Conn, loginMsg.PoolCount+10),
proxies: make([]Proxy, 0),
poolCount: loginMsg.PoolCount,
lastPing: time.Now(),
runId: loginMsg.RunId,
status: consts.Working,
readerShutdown: shutdown.New(),
writerShutdown: shutdown.New(),
managerShutdown: shutdown.New(),
allShutdown: shutdown.New(),
}
}
// Start send a login success message to client and start working.
func (ctl *Control) Start() {
go ctl.writer()
ctl.sendCh <- &msg.LoginResp{
Version: version.Full(),
RunId: ctl.runId,
Error: "",
}
for i := 0; i < ctl.poolCount; i++ {
ctl.sendCh <- &msg.ReqWorkConn{}
}
go ctl.manager()
go ctl.reader()
go ctl.stoper()
}
func (ctl *Control) RegisterWorkConn(conn net.Conn) {
defer func() {
if err := recover(); err != nil {
ctl.conn.Error("panic error: %v", err)
}
}()
select {
case ctl.workConnCh <- conn:
ctl.conn.Debug("new work connection registered.")
default:
ctl.conn.Debug("work connection pool is full, discarding.")
conn.Close()
}
}
// When frps get one user connection, we get one work connection from the pool and return it.
// If no workConn available in the pool, send message to frpc to get one or more
// and wait until it is available.
// return an error if wait timeout
func (ctl *Control) GetWorkConn() (workConn net.Conn, err error) {
defer func() {
if err := recover(); err != nil {
ctl.conn.Error("panic error: %v", err)
}
}()
var ok bool
// get a work connection from the pool
select {
case workConn, ok = <-ctl.workConnCh:
if !ok {
err = fmt.Errorf("no work connections available, control is closing")
return
}
ctl.conn.Debug("get work connection from pool")
default:
// no work connections available in the poll, send message to frpc to get more
err = errors.PanicToError(func() {
ctl.sendCh <- &msg.ReqWorkConn{}
})
if err != nil {
ctl.conn.Error("%v", err)
return
}
select {
case workConn, ok = <-ctl.workConnCh:
if !ok {
err = fmt.Errorf("no work connections available, control is closing")
ctl.conn.Warn("%v", err)
return
}
case <-time.After(time.Duration(config.ServerCommonCfg.UserConnTimeout) * time.Second):
err = fmt.Errorf("timeout trying to get work connection")
ctl.conn.Warn("%v", err)
return
}
}
// When we get a work connection from pool, replace it with a new one.
errors.PanicToError(func() {
ctl.sendCh <- &msg.ReqWorkConn{}
})
return
}
func (ctl *Control) Replaced(newCtl *Control) {
ctl.conn.Info("Replaced by client [%s]", newCtl.runId)
ctl.runId = ""
ctl.allShutdown.Start()
}
func (ctl *Control) writer() {
defer func() {
if err := recover(); err != nil {
ctl.conn.Error("panic error: %v", err)
}
}()
defer ctl.allShutdown.Start()
defer ctl.writerShutdown.Done()
for {
if m, ok := <-ctl.sendCh; !ok {
ctl.conn.Info("control writer is closing")
return
} else {
if err := msg.WriteMsg(ctl.conn, m); err != nil {
ctl.conn.Warn("write message to control connection error: %v", err)
return
}
}
}
}
func (ctl *Control) reader() {
defer func() {
if err := recover(); err != nil {
ctl.conn.Error("panic error: %v", err)
}
}()
defer ctl.allShutdown.Start()
defer ctl.readerShutdown.Done()
for {
if m, err := msg.ReadMsg(ctl.conn); err != nil {
if err == io.EOF {
ctl.conn.Debug("control connection closed")
return
} else {
ctl.conn.Warn("read error: %v", err)
return
}
} else {
ctl.readCh <- m
}
}
}
func (ctl *Control) stoper() {
defer func() {
if err := recover(); err != nil {
ctl.conn.Error("panic error: %v", err)
}
}()
ctl.allShutdown.WaitStart()
close(ctl.readCh)
ctl.managerShutdown.WaitDown()
close(ctl.sendCh)
ctl.writerShutdown.WaitDown()
ctl.conn.Close()
close(ctl.workConnCh)
for workConn := range ctl.workConnCh {
workConn.Close()
}
for _, pxy := range ctl.proxies {
ctl.svr.DelProxy(pxy.GetName())
pxy.Close()
}
ctl.allShutdown.Done()
ctl.conn.Info("all shutdown success")
}
func (ctl *Control) manager() {
defer func() {
if err := recover(); err != nil {
ctl.conn.Error("panic error: %v", err)
}
}()
defer ctl.allShutdown.Start()
defer ctl.managerShutdown.Done()
heartbeat := time.NewTicker(time.Second)
defer heartbeat.Stop()
for {
select {
case <-heartbeat.C:
if time.Since(ctl.lastPing) > time.Duration(config.ServerCommonCfg.HeartBeatTimeout)*time.Second {
ctl.conn.Warn("heartbeat timeout")
ctl.allShutdown.Start()
}
case rawMsg, ok := <-ctl.readCh:
if !ok {
return
}
switch m := rawMsg.(type) {
case *msg.NewProxy:
// register proxy in this control
err := ctl.RegisterProxy(m)
resp := &msg.NewProxyResp{
ProxyName: m.ProxyName,
}
if err != nil {
resp.Error = err.Error()
ctl.conn.Warn("new proxy [%s] error: %v", m.ProxyName, err)
} else {
ctl.conn.Info("new proxy [%s] success", m.ProxyName)
}
ctl.sendCh <- resp
case *msg.Ping:
ctl.lastPing = time.Now()
ctl.sendCh <- &msg.Pong{}
}
}
}
}
func (ctl *Control) RegisterProxy(pxyMsg *msg.NewProxy) (err error) {
var pxyConf config.ProxyConf
// Load configures from NewProxy message and check.
pxyConf, err = config.NewProxyConf(pxyMsg)
if err != nil {
return err
}
// NewProxy will return a interface Proxy.
// In fact it create different proxies by different proxy type, we just call run() here.
pxy, err := NewProxy(ctl, pxyConf)
if err != nil {
return err
}
err = pxy.Run()
if err != nil {
return err
}
defer func() {
if err != nil {
pxy.Close()
}
}()
err = ctl.svr.RegisterProxy(pxyMsg.ProxyName, pxy)
if err != nil {
return err
}
ctl.proxies = append(ctl.proxies, pxy)
return nil
}

11
src/models/server/dashboard.go → server/dashboard.go

@ -22,7 +22,8 @@ import (
"strings"
"time"
"github.com/fatedier/frp/src/assets"
"github.com/fatedier/frp/assets"
"github.com/fatedier/frp/models/config"
)
var (
@ -34,13 +35,13 @@ func RunDashboardServer(addr string, port int64) (err error) {
// url router
mux := http.NewServeMux()
// api, see dashboard_api.go
mux.HandleFunc("/api/reload", use(apiReload, basicAuth))
mux.HandleFunc("/api/proxies", apiProxies)
//mux.HandleFunc("/api/reload", use(apiReload, basicAuth))
//mux.HandleFunc("/api/proxies", apiProxies)
// view, see dashboard_view.go
mux.Handle("/favicon.ico", http.FileServer(assets.FileSystem))
mux.Handle("/static/", http.StripPrefix("/static/", http.FileServer(assets.FileSystem)))
mux.HandleFunc("/", use(viewDashboard, basicAuth))
//mux.HandleFunc("/", use(viewDashboard, basicAuth))
address := fmt.Sprintf("%s:%d", addr, port)
server := &http.Server{
@ -92,7 +93,7 @@ func basicAuth(h http.HandlerFunc) http.HandlerFunc {
return
}
if pair[0] != DashboardUsername || pair[1] != DashboardPassword {
if pair[0] != config.ServerCommonCfg.DashboardUser || pair[1] != config.ServerCommonCfg.DashboardPwd {
http.Error(w, "Not authorized", 401)
return
}

2
src/models/server/dashboard_api.go → server/dashboard_api.go

@ -14,6 +14,7 @@
package server
/*
import (
"encoding/json"
"fmt"
@ -65,3 +66,4 @@ func apiProxies(w http.ResponseWriter, r *http.Request) {
buf, _ = json.Marshal(res)
w.Write(buf)
}
*/

2
src/models/server/dashboard_view.go → server/dashboard_view.go

@ -14,6 +14,7 @@
package server
/*
import (
"html/template"
"net/http"
@ -38,3 +39,4 @@ func viewDashboard(w http.ResponseWriter, r *http.Request) {
http.Error(w, "parse template file error", http.StatusInternalServerError)
}
}
*/

75
server/manager.go

@ -0,0 +1,75 @@
package server
import (
"fmt"
"sync"
)
type ControlManager struct {
// controls indexed by run id
ctlsByRunId map[string]*Control
mu sync.RWMutex
}
func NewControlManager() *ControlManager {
return &ControlManager{
ctlsByRunId: make(map[string]*Control),
}
}
func (cm *ControlManager) Add(runId string, ctl *Control) (oldCtl *Control) {
cm.mu.Lock()
defer cm.mu.Unlock()
oldCtl, ok := cm.ctlsByRunId[runId]
if ok {
oldCtl.Replaced(ctl)
}
cm.ctlsByRunId[runId] = ctl
return
}
func (cm *ControlManager) GetById(runId string) (ctl *Control, ok bool) {
cm.mu.RLock()
defer cm.mu.RUnlock()
ctl, ok = cm.ctlsByRunId[runId]
return
}
type ProxyManager struct {
// proxies indexed by proxy name
pxys map[string]Proxy
mu sync.RWMutex
}
func NewProxyManager() *ProxyManager {
return &ProxyManager{
pxys: make(map[string]Proxy),
}
}
func (pm *ProxyManager) Add(name string, pxy Proxy) error {
pm.mu.Lock()
defer pm.mu.Unlock()
if _, ok := pm.pxys[name]; ok {
return fmt.Errorf("proxy name [%s] is already in use", name)
}
pm.pxys[name] = pxy
return nil
}
func (pm *ProxyManager) Del(name string) {
pm.mu.Lock()
defer pm.mu.Unlock()
delete(pm.pxys, name)
}
func (pm *ProxyManager) GetByName(name string) (pxy Proxy, ok bool) {
pm.mu.RLock()
defer pm.mu.RUnlock()
pxy, ok = pm.pxys[name]
return
}

219
server/proxy.go

@ -0,0 +1,219 @@
package server
import (
"fmt"
"io"
"github.com/fatedier/frp/models/config"
"github.com/fatedier/frp/models/msg"
"github.com/fatedier/frp/models/proto/tcp"
"github.com/fatedier/frp/utils/log"
"github.com/fatedier/frp/utils/net"
)
type Proxy interface {
Run() error
GetControl() *Control
GetName() string
GetConf() config.ProxyConf
Close()
log.Logger
}
type BaseProxy struct {
name string
ctl *Control
listeners []net.Listener
log.Logger
}
func (pxy *BaseProxy) GetName() string {
return pxy.name
}
func (pxy *BaseProxy) GetControl() *Control {
return pxy.ctl
}
func (pxy *BaseProxy) Close() {
pxy.Info("proxy closing")
for _, l := range pxy.listeners {
l.Close()
}
}
func NewProxy(ctl *Control, pxyConf config.ProxyConf) (pxy Proxy, err error) {
basePxy := BaseProxy{
name: pxyConf.GetName(),
ctl: ctl,
listeners: make([]net.Listener, 0),
Logger: log.NewPrefixLogger(ctl.runId),
}
switch cfg := pxyConf.(type) {
case *config.TcpProxyConf:
pxy = &TcpProxy{
BaseProxy: basePxy,
cfg: cfg,
}
case *config.HttpProxyConf:
pxy = &HttpProxy{
BaseProxy: basePxy,
cfg: cfg,
}
case *config.HttpsProxyConf:
pxy = &HttpsProxy{
BaseProxy: basePxy,
cfg: cfg,
}
case *config.UdpProxyConf:
pxy = &UdpProxy{
BaseProxy: basePxy,
cfg: cfg,
}
default:
return pxy, fmt.Errorf("proxy type not support")
}
pxy.AddLogPrefix(pxy.GetName())
return
}
type TcpProxy struct {
BaseProxy
cfg *config.TcpProxyConf
}
func (pxy *TcpProxy) Run() error {
listener, err := net.ListenTcp(config.ServerCommonCfg.BindAddr, int64(pxy.cfg.RemotePort))
if err != nil {
return err
}
pxy.listeners = append(pxy.listeners, listener)
go func(l net.Listener) {
for {
// block
// if listener is closed, err returned
c, err := l.Accept()
if err != nil {
pxy.Info("listener is closed")
return
}
pxy.Debug("got one user connection [%s]", c.RemoteAddr().String())
go HandleUserTcpConnection(pxy, c)
}
}(listener)
return nil
}
func (pxy *TcpProxy) GetConf() config.ProxyConf {
return pxy.cfg
}
func (pxy *TcpProxy) Close() {
pxy.BaseProxy.Close()
}
type HttpProxy struct {
BaseProxy
cfg *config.HttpProxyConf
}
func (pxy *HttpProxy) Run() (err error) {
return
}
func (pxy *HttpProxy) GetConf() config.ProxyConf {
return pxy.cfg
}
func (pxy *HttpProxy) Close() {
pxy.BaseProxy.Close()
}
type HttpsProxy struct {
BaseProxy
cfg *config.HttpsProxyConf
}
func (pxy *HttpsProxy) Run() (err error) {
return
}
func (pxy *HttpsProxy) GetConf() config.ProxyConf {
return pxy.cfg
}
func (pxy *HttpsProxy) Close() {
pxy.BaseProxy.Close()
}
type UdpProxy struct {
BaseProxy
cfg *config.UdpProxyConf
}
func (pxy *UdpProxy) Run() (err error) {
return
}
func (pxy *UdpProxy) GetConf() config.ProxyConf {
return pxy.cfg
}
func (pxy *UdpProxy) Close() {
pxy.BaseProxy.Close()
}
// HandleUserTcpConnection is used for incoming tcp user connections.
func HandleUserTcpConnection(pxy Proxy, userConn net.Conn) {
defer userConn.Close()
ctl := pxy.GetControl()
var (
workConn net.Conn
err error
)
// try all connections from the pool
for i := 0; i < ctl.poolCount+1; i++ {
if workConn, err = ctl.GetWorkConn(); err != nil {
pxy.Warn("failed to get work connection: %v", err)
return
}
defer workConn.Close()
pxy.Info("get one new work connection: %s", workConn.RemoteAddr().String())
workConn.AddLogPrefix(pxy.GetName())
err := msg.WriteMsg(workConn, &msg.StartWorkConn{
ProxyName: pxy.GetName(),
})
if err != nil {
workConn.Warn("failed to send message to work connection from pool: %v, times: %d", err, i)
workConn.Close()
} else {
break
}
}
if err != nil {
pxy.Error("try to get work connection failed in the end")
return
}
var (
local io.ReadWriteCloser
remote io.ReadWriteCloser
)
local = workConn
remote = userConn
cfg := pxy.GetConf().GetBaseInfo()
if cfg.UseEncryption {
local, err = tcp.WithEncryption(local, []byte(config.ServerCommonCfg.PrivilegeToken))
if err != nil {
pxy.Error("create encryption stream error: %v", err)
return
}
}
if cfg.UseCompression {
local = tcp.WithCompression(local)
}
tcp.Join(local, remote)
}

196
server/service.go

@ -0,0 +1,196 @@
package server
import (
"fmt"
"time"
"github.com/fatedier/frp/assets"
"github.com/fatedier/frp/models/config"
"github.com/fatedier/frp/models/msg"
"github.com/fatedier/frp/utils/log"
"github.com/fatedier/frp/utils/net"
"github.com/fatedier/frp/utils/util"
"github.com/fatedier/frp/utils/version"
"github.com/fatedier/frp/utils/vhost"
)
// Server service.
type Service struct {
// Accept connections from client.
listener net.Listener
// For http proxies, route requests to different clients by hostname and other infomation.
VhostHttpMuxer *vhost.HttpMuxer
// For https proxies, route requests to different clients by hostname and other infomation.
VhostHttpsMuxer *vhost.HttpsMuxer
// Manage all controllers.
ctlManager *ControlManager
// Manage all proxies.
pxyManager *ProxyManager
}
func NewService() (svr *Service, err error) {
svr = &Service{
ctlManager: NewControlManager(),
pxyManager: NewProxyManager(),
}
// Init assets.
err = assets.Load(config.ServerCommonCfg.AssetsDir)
if err != nil {
err = fmt.Errorf("Load assets error: %v", err)
return
}
// Listen for accepting connections from client.
svr.listener, err = net.ListenTcp(config.ServerCommonCfg.BindAddr, config.ServerCommonCfg.BindPort)
if err != nil {
err = fmt.Errorf("Create server listener error, %v", err)
return
}
// Create http vhost muxer.
if config.ServerCommonCfg.VhostHttpPort != 0 {
var l net.Listener
l, err = net.ListenTcp(config.ServerCommonCfg.BindAddr, config.ServerCommonCfg.VhostHttpPort)
if err != nil {
err = fmt.Errorf("Create vhost http listener error, %v", err)
return
}
svr.VhostHttpMuxer, err = vhost.NewHttpMuxer(l, 30*time.Second)
if err != nil {
err = fmt.Errorf("Create vhost httpMuxer error, %v", err)
return
}
}
// Create https vhost muxer.
if config.ServerCommonCfg.VhostHttpsPort != 0 {
var l net.Listener
l, err = net.ListenTcp(config.ServerCommonCfg.BindAddr, config.ServerCommonCfg.VhostHttpsPort)
if err != nil {
err = fmt.Errorf("Create vhost https listener error, %v", err)
return
}
svr.VhostHttpsMuxer, err = vhost.NewHttpsMuxer(l, 30*time.Second)
if err != nil {
err = fmt.Errorf("Create vhost httpsMuxer error, %v", err)
return
}
}
// Create dashboard web server.
if config.ServerCommonCfg.DashboardPort != 0 {
err = RunDashboardServer(config.ServerCommonCfg.BindAddr, config.ServerCommonCfg.DashboardPort)
if err != nil {
err = fmt.Errorf("Create dashboard web server error, %v", err)
return
}
}
return
}
func (svr *Service) Run() {
// Listen for incoming connections from client.
for {
c, err := svr.listener.Accept()
if err != nil {
log.Warn("Listener for incoming connections from client closed")
return
}
// Start a new goroutine for dealing connections.
go func(frpConn net.Conn) {
var rawMsg msg.Message
if rawMsg, err = msg.ReadMsg(frpConn); err != nil {
log.Warn("Failed to read message: %v", err)
frpConn.Close()
return
}
switch m := rawMsg.(type) {
case *msg.Login:
err = svr.RegisterControl(frpConn, m)
// If login failed, send error message there.
// Otherwise send success message in control's work goroutine.
if err != nil {
frpConn.Warn("%v", err)
msg.WriteMsg(frpConn, &msg.LoginResp{
Version: version.Full(),
Error: err.Error(),
})
frpConn.Close()
}
case *msg.NewWorkConn:
svr.RegisterWorkConn(frpConn, m)
default:
log.Warn("Error message type for the new connection [%s]", frpConn.RemoteAddr().String())
frpConn.Close()
}
}(c)
}
}
func (svr *Service) RegisterControl(ctlConn net.Conn, loginMsg *msg.Login) (err error) {
ctlConn.Info("client login info: ip [%s] version [%s] hostname [%s] os [%s] arch [%s]",
ctlConn.RemoteAddr().String(), loginMsg.Version, loginMsg.Hostname, loginMsg.Os, loginMsg.Arch)
// Check client version.
if ok, msg := version.Compat(loginMsg.Version); !ok {
err = fmt.Errorf("%s", msg)
return
}
// Check auth.
nowTime := time.Now().Unix()
if config.ServerCommonCfg.AuthTimeout != 0 && nowTime-loginMsg.Timestamp > config.ServerCommonCfg.AuthTimeout {
err = fmt.Errorf("authorization timeout")
return
}
if util.GetAuthKey(config.ServerCommonCfg.PrivilegeToken, loginMsg.Timestamp) != loginMsg.PrivilegeKey {
err = fmt.Errorf("authorization failed")
return
}
// If client's RunId is empty, it's a new client, we just create a new controller.
// Otherwise, we check if there is one controller has the same run id. If so, we release previous controller and start new one.
if loginMsg.RunId == "" {
loginMsg.RunId, err = util.RandId()
if err != nil {
return
}
}
ctl := NewControl(svr, ctlConn, loginMsg)
if oldCtl := svr.ctlManager.Add(loginMsg.RunId, ctl); oldCtl != nil {
oldCtl.allShutdown.WaitDown()
}
ctlConn.AddLogPrefix(loginMsg.RunId)
ctl.Start()
return
}
// RegisterWorkConn register a new work connection to control and proxies need it.
func (svr *Service) RegisterWorkConn(workConn net.Conn, newMsg *msg.NewWorkConn) {
ctl, exist := svr.ctlManager.GetById(newMsg.RunId)
if !exist {
workConn.Warn("No client control found for run id [%s]", newMsg.RunId)
return
}
ctl.RegisterWorkConn(workConn)
return
}
func (svr *Service) RegisterProxy(name string, pxy Proxy) error {
err := svr.pxyManager.Add(name, pxy)
return err
}
func (svr *Service) DelProxy(name string) {
svr.pxyManager.Del(name)
}

10
src/assets/statik/statik.go

File diff suppressed because one or more lines are too long

231
src/cmd/frpc/control.go

@ -1,231 +0,0 @@
// Copyright 2016 fatedier, fatedier@gmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
"encoding/json"
"fmt"
"io"
"sync"
"time"
"github.com/fatedier/frp/src/models/client"
"github.com/fatedier/frp/src/models/consts"
"github.com/fatedier/frp/src/models/msg"
"github.com/fatedier/frp/src/utils/conn"
"github.com/fatedier/frp/src/utils/log"
"github.com/fatedier/frp/src/utils/pcrypto"
)
func ControlProcess(cli *client.ProxyClient, wait *sync.WaitGroup) {
defer wait.Done()
msgSendChan := make(chan interface{}, 1024)
c, err := loginToServer(cli)
if err != nil {
log.Error("ProxyName [%s], connect to server failed!", cli.Name)
return
}
defer c.Close()
go heartbeatSender(c, msgSendChan)
go msgSender(cli, c, msgSendChan)
msgReader(cli, c, msgSendChan)
close(msgSendChan)
}
// loop for reading messages from frpc after control connection is established
func msgReader(cli *client.ProxyClient, c *conn.Conn, msgSendChan chan interface{}) error {
// for heartbeat
var heartbeatTimeout bool = false
timer := time.AfterFunc(time.Duration(client.HeartBeatTimeout)*time.Second, func() {
heartbeatTimeout = true
if c != nil {
c.Close()
}
if cli != nil {
// if it's not udp type, nothing will happen
cli.CloseUdpTunnel()
cli.SetCloseFlag(true)
}
log.Error("ProxyName [%s], heartbeatRes from frps timeout", cli.Name)
})
defer timer.Stop()
for {
buf, err := c.ReadLine()
if err == io.EOF || c.IsClosed() {
timer.Stop()
c.Close()
cli.SetCloseFlag(true)
log.Warn("ProxyName [%s], frps close this control conn!", cli.Name)
var delayTime time.Duration = 1
// loop until reconnect to frps
for {
log.Info("ProxyName [%s], try to reconnect to frps [%s:%d]...", cli.Name, client.ServerAddr, client.ServerPort)
c, err = loginToServer(cli)
if err == nil {
close(msgSendChan)
msgSendChan = make(chan interface{}, 1024)
go heartbeatSender(c, msgSendChan)
go msgSender(cli, c, msgSendChan)
cli.SetCloseFlag(false)
break
}
if delayTime < 30 {
delayTime = delayTime * 2
} else {
delayTime = 30
}
time.Sleep(delayTime * time.Second)
}
continue
} else if err != nil {
log.Warn("ProxyName [%s], read from frps error: %v", cli.Name, err)
continue
}
ctlRes := &msg.ControlRes{}
if err := json.Unmarshal([]byte(buf), &ctlRes); err != nil {
log.Warn("ProxyName [%s], parse msg from frps error: %v : %s", cli.Name, err, buf)
continue
}
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)
// join local and remote connections, async
go 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.WriteString(string(buf) + "\n")
if err != nil {
log.Warn("ProxyName [%s], write to server error, proxy exit", cli.Name)
c.Close()
break
}
}
}
func loginToServer(cli *client.ProxyClient) (c *conn.Conn, err error) {
if client.HttpProxy == "" {
c, err = conn.ConnectServer(fmt.Sprintf("%s:%d", client.ServerAddr, client.ServerPort))
} else {
c, err = conn.ConnectServerByHttpProxy(client.HttpProxy, fmt.Sprintf("%s:%d", 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
}
nowTime := time.Now().Unix()
req := &msg.ControlReq{
Type: consts.NewCtlConn,
ProxyName: cli.Name,
UseEncryption: cli.UseEncryption,
UseGzip: cli.UseGzip,
PrivilegeMode: cli.PrivilegeMode,
ProxyType: cli.Type,
PoolCount: cli.PoolCount,
HostHeaderRewrite: cli.HostHeaderRewrite,
HttpUserName: cli.HttpUserName,
HttpPassWord: cli.HttpPassWord,
SubDomain: cli.SubDomain,
Timestamp: nowTime,
}
if cli.PrivilegeMode {
privilegeKey := pcrypto.GetAuthKey(cli.Name + client.PrivilegeToken + fmt.Sprintf("%d", nowTime))
req.RemotePort = cli.RemotePort
req.CustomDomains = cli.CustomDomains
req.Locations = cli.Locations
req.PrivilegeKey = privilegeKey
} else {
authKey := pcrypto.GetAuthKey(cli.Name + cli.AuthToken + fmt.Sprintf("%d", nowTime))
req.AuthKey = authKey
}
buf, _ := json.Marshal(req)
err = c.WriteString(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)
ctlRes := &msg.ControlRes{}
if err = json.Unmarshal([]byte(res), &ctlRes); err != nil {
log.Error("ProxyName [%s], format server response error, %v", cli.Name, err)
return
}
if ctlRes.Code != 0 {
log.Error("ProxyName [%s], start proxy error, %s", cli.Name, ctlRes.Msg)
return c, fmt.Errorf("%s", ctlRes.Msg)
}
log.Info("ProxyName [%s], connect to server [%s:%d] success!", cli.Name, client.ServerAddr, client.ServerPort)
if cli.Type == "udp" {
// we only need one udp work connection
// all udp messages will be forwarded throngh this connection
go cli.StartUdpTunnelOnce(client.ServerAddr, client.ServerPort)
}
return
}
func heartbeatSender(c *conn.Conn, msgSendChan chan interface{}) {
heartbeatReq := &msg.ControlReq{
Type: consts.HeartbeatReq,
}
log.Info("Start to send heartbeat to frps")
for {
time.Sleep(time.Duration(client.HeartBeatInterval) * time.Second)
if c != nil && !c.IsClosed() {
log.Debug("Send heartbeat to server")
msgSendChan <- heartbeatReq
} else {
break
}
}
log.Info("Heartbeat goroutine exit")
}

365
src/cmd/frps/control.go

@ -1,365 +0,0 @@
// Copyright 2016 fatedier, fatedier@gmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
"encoding/json"
"fmt"
"io"
"strings"
"time"
"github.com/fatedier/frp/src/models/consts"
"github.com/fatedier/frp/src/models/metric"
"github.com/fatedier/frp/src/models/msg"
"github.com/fatedier/frp/src/models/server"
"github.com/fatedier/frp/src/utils/conn"
"github.com/fatedier/frp/src/utils/log"
"github.com/fatedier/frp/src/utils/pcrypto"
)
func ProcessControlConn(l *conn.Listener) {
for {
c, err := l.Accept()
if err != nil {
return
}
log.Debug("Get new connection, %v", c.GetRemoteAddr())
go controlWorker(c)
}
}
// connection from every client and server
func controlWorker(c *conn.Conn) {
// if login message type is NewWorkConn, don't close this connection
var closeFlag bool = true
var s *server.ProxyServer
defer func() {
if closeFlag {
c.Close()
if s != nil {
s.Close()
}
}
}()
// get login message
buf, err := c.ReadLine()
if err != nil {
log.Warn("Read error, %v", err)
return
}
log.Debug("Get msg from frpc: %s", buf)
cliReq := &msg.ControlReq{}
if err := json.Unmarshal([]byte(buf), &cliReq); err != nil {
log.Warn("Parse msg from frpc error: %v : %s", err, buf)
return
}
// login when type is NewCtlConn or NewWorkConn
ret, info, s := doLogin(cliReq, c)
// if login type is NewWorkConn, nothing will be send to frpc
if cliReq.Type == consts.NewCtlConn {
cliRes := &msg.ControlRes{
Type: consts.NewCtlConnRes,
Code: ret,
Msg: info,
}
byteBuf, _ := json.Marshal(cliRes)
err = c.WriteString(string(byteBuf) + "\n")
if err != nil {
log.Warn("ProxyName [%s], write to client error, proxy exit", cliReq.ProxyName)
return
}
} else {
if ret == 0 {
closeFlag = false
}
return
}
// if login failed, just return
if ret > 0 {
return
}
// create a channel for sending messages
msgSendChan := make(chan interface{}, 1024)
go msgSender(s, c, msgSendChan)
go noticeUserConn(s, msgSendChan)
// loop for reading control messages from frpc and deal with different types
msgReader(s, c, msgSendChan)
close(msgSendChan)
log.Info("ProxyName [%s], I'm dead!", s.Name)
return
}
// when frps get one new user connection, send NoticeUserConn message to frpc and accept one new WorkConn later
func noticeUserConn(s *server.ProxyServer, msgSendChan chan interface{}) {
for {
closeFlag := s.WaitUserConn()
if closeFlag {
log.Debug("ProxyName [%s], goroutine for 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()
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)
s.Close()
return err
} else if c == nil || c.IsClosed() {
log.Warn("ProxyName [%s], client connection is closed", s.Name)
s.Close()
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.WriteString(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
// NewCtlConn
// NewWorkConn
// NewWorkConnUdp
func doLogin(req *msg.ControlReq, c *conn.Conn) (ret int64, info string, s *server.ProxyServer) {
ret = 1
// check if PrivilegeMode is enabled
if req.PrivilegeMode && !server.PrivilegeMode {
info = fmt.Sprintf("ProxyName [%s], PrivilegeMode is disabled in frps", req.ProxyName)
log.Warn("info")
return
}
var ok bool
s, ok = server.GetProxyServer(req.ProxyName)
if req.PrivilegeMode && req.Type == consts.NewCtlConn {
log.Debug("ProxyName [%s], doLogin and privilege mode is enabled", req.ProxyName)
} else {
if !ok {
info = fmt.Sprintf("ProxyName [%s] is not exist", req.ProxyName)
log.Warn(info)
return
}
}
// check authKey or privilegeKey
nowTime := time.Now().Unix()
if req.PrivilegeMode {
privilegeKey := pcrypto.GetAuthKey(req.ProxyName + server.PrivilegeToken + fmt.Sprintf("%d", req.Timestamp))
// privilegeKey unavaiable after server.AuthTimeout minutes
if server.AuthTimeout != 0 && nowTime-req.Timestamp > server.AuthTimeout {
info = fmt.Sprintf("ProxyName [%s], privilege mode authorization timeout", req.ProxyName)
log.Warn(info)
return
} else if req.PrivilegeKey != privilegeKey {
info = fmt.Sprintf("ProxyName [%s], privilege mode authorization failed", req.ProxyName)
log.Warn(info)
log.Debug("PrivilegeKey [%s] and get [%s]", privilegeKey, req.PrivilegeKey)
return
}
} else {
authKey := pcrypto.GetAuthKey(req.ProxyName + s.AuthToken + fmt.Sprintf("%d", req.Timestamp))
if server.AuthTimeout != 0 && nowTime-req.Timestamp > server.AuthTimeout {
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.Debug("AuthKey [%s] and get [%s]", authKey, req.AuthKey)
return
}
}
// control conn
if req.Type == consts.NewCtlConn {
if req.PrivilegeMode {
s = server.NewProxyServerFromCtlMsg(req)
// we check listen_port if privilege_allow_ports are set
// and PrivilegeMode is enabled
if s.Type == "tcp" {
if len(server.PrivilegeAllowPorts) != 0 {
_, ok := server.PrivilegeAllowPorts[s.ListenPort]
if !ok {
info = fmt.Sprintf("ProxyName [%s], remote_port [%d] isn't allowed", req.ProxyName, s.ListenPort)
log.Warn(info)
return
}
}
} else if s.Type == "http" || s.Type == "https" {
for _, domain := range s.CustomDomains {
if server.SubDomainHost != "" && strings.Contains(domain, server.SubDomainHost) {
info = fmt.Sprintf("ProxyName [%s], custom domain [%s] should not belong to subdomain_host [%s]", req.ProxyName, domain, server.SubDomainHost)
log.Warn(info)
return
}
}
if s.SubDomain != "" {
if strings.Contains(s.SubDomain, ".") || strings.Contains(s.SubDomain, "*") {
info = fmt.Sprintf("ProxyName [%s], '.' and '*' is not supported in subdomain", req.ProxyName)
log.Warn(info)
return
}
if server.SubDomainHost == "" {
info = fmt.Sprintf("ProxyName [%s], subdomain is not supported because this feature is not enabled by remote server", req.ProxyName)
log.Warn(info)
return
}
s.SubDomain = s.SubDomain + "." + server.SubDomainHost
}
}
err := server.CreateProxy(s)
if err != nil {
info = fmt.Sprintf("ProxyName [%s], %v", req.ProxyName, err)
log.Warn(info)
return
}
}
// check if vhost_port is set
if s.Type == "http" && server.VhostHttpMuxer == nil {
info = fmt.Sprintf("ProxyName [%s], type [http] not support when vhost_http_port is not set", req.ProxyName)
log.Warn(info)
return
}
if s.Type == "https" && server.VhostHttpsMuxer == nil {
info = fmt.Sprintf("ProxyName [%s], type [https] not support when vhost_https_port is not set", req.ProxyName)
log.Warn(info)
return
}
// set infomations from frpc
s.BindAddr = server.BindAddr
s.UseEncryption = req.UseEncryption
s.UseGzip = req.UseGzip
s.HostHeaderRewrite = req.HostHeaderRewrite
s.HttpUserName = req.HttpUserName
s.HttpPassWord = req.HttpPassWord
if req.PoolCount > server.MaxPoolCount {
s.PoolCount = server.MaxPoolCount
} else if req.PoolCount < 0 {
s.PoolCount = 0
} else {
s.PoolCount = req.PoolCount
}
if s.Status == consts.Working {
info = fmt.Sprintf("ProxyName [%s], already in use", req.ProxyName)
log.Warn(info)
return
}
// update metric's proxy status
metric.SetProxyInfo(s.Name, s.Type, s.BindAddr, s.UseEncryption, s.UseGzip, s.PrivilegeMode, s.CustomDomains, s.Locations, s.ListenPort)
// start proxy and listen for user connections, no block
err := s.Start(c)
if err != nil {
info = fmt.Sprintf("ProxyName [%s], start proxy error: %v", req.ProxyName, err)
log.Warn(info)
return
}
log.Info("ProxyName [%s], start proxy success", req.ProxyName)
if req.PrivilegeMode {
log.Info("ProxyName [%s], created by PrivilegeMode", req.ProxyName)
}
} else if req.Type == consts.NewWorkConn {
// work conn
if s.Status != consts.Working {
log.Warn("ProxyName [%s], is not working when it gets one new work connnection", req.ProxyName)
return
}
// the connection will close after join over
s.RegisterNewWorkConn(c)
} else if req.Type == consts.NewWorkConnUdp {
// work conn for udp
if s.Status != consts.Working {
log.Warn("ProxyName [%s], is not working when it gets one new work connnection for udp", req.ProxyName)
return
}
s.RegisterNewWorkConnUdp(c)
} else {
info = fmt.Sprintf("Unsupport login message type [%d]", req.Type)
log.Warn("Unsupport login message type [%d]", req.Type)
return
}
ret = 0
return
}

199
src/cmd/frps/main.go

@ -1,199 +0,0 @@
// Copyright 2016 fatedier, fatedier@gmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
"encoding/base64"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"os"
"strconv"
"strings"
"time"
docopt "github.com/docopt/docopt-go"
"github.com/fatedier/frp/src/assets"
"github.com/fatedier/frp/src/models/server"
"github.com/fatedier/frp/src/utils/conn"
"github.com/fatedier/frp/src/utils/log"
"github.com/fatedier/frp/src/utils/version"
"github.com/fatedier/frp/src/utils/vhost"
)
var usage string = `frps is the server of frp
Usage:
frps [-c config_file] [-L log_file] [--log-level=<log_level>] [--addr=<bind_addr>]
frps [-c config_file] --reload
frps -h | --help
frps -v | --version
Options:
-c config_file set config file
-L log_file set output log file, including console
--log-level=<log_level> set log level: debug, info, warn, error
--addr=<bind_addr> listen addr for client, example: 0.0.0.0:7000
--reload reload ini file and configures in common section won't be changed
-h --help show this screen
-v --version show version
`
func main() {
// the configures parsed from file will be replaced by those from command line if exist
args, err := docopt.Parse(usage, nil, true, version.Full(), false)
if args["-c"] != nil {
server.ConfigFile = args["-c"].(string)
}
err = server.LoadConf(server.ConfigFile)
if err != nil {
fmt.Println(err)
os.Exit(-1)
}
// reload check
if args["--reload"] != nil {
if args["--reload"].(bool) {
req, err := http.NewRequest("GET", "http://"+server.BindAddr+":"+fmt.Sprintf("%d", server.DashboardPort)+"/api/reload", nil)
if err != nil {
fmt.Printf("frps reload error: %v\n", err)
os.Exit(1)
}
authStr := "Basic " + base64.StdEncoding.EncodeToString([]byte(server.DashboardUsername+":"+server.DashboardPassword))
req.Header.Add("Authorization", authStr)
defaultClient := &http.Client{}
resp, err := defaultClient.Do(req)
if err != nil {
fmt.Printf("frps reload error: %v\n", err)
os.Exit(1)
} else {
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
fmt.Printf("frps reload error: %v\n", err)
os.Exit(1)
}
res := &server.GeneralResponse{}
err = json.Unmarshal(body, &res)
if err != nil {
fmt.Printf("http response error: %s\n", strings.TrimSpace(string(body)))
os.Exit(1)
} else if res.Code != 0 {
fmt.Printf("reload error: %s\n", res.Msg)
os.Exit(1)
}
fmt.Printf("reload success\n")
os.Exit(0)
}
}
}
if args["-L"] != nil {
if args["-L"].(string) == "console" {
server.LogWay = "console"
} else {
server.LogWay = "file"
server.LogFile = args["-L"].(string)
}
}
if args["--log-level"] != nil {
server.LogLevel = args["--log-level"].(string)
}
if args["--addr"] != nil {
addr := strings.Split(args["--addr"].(string), ":")
if len(addr) != 2 {
fmt.Println("--addr format error: example 0.0.0.0:7000")
os.Exit(1)
}
bindPort, err := strconv.ParseInt(addr[1], 10, 64)
if err != nil {
fmt.Println("--addr format error, example 0.0.0.0:7000")
os.Exit(1)
}
server.BindAddr = addr[0]
server.BindPort = bindPort
}
if args["-v"] != nil {
if args["-v"].(bool) {
fmt.Println(version.Full())
os.Exit(0)
}
}
log.InitLog(server.LogWay, server.LogFile, server.LogLevel, server.LogMaxDays)
// init assets
err = assets.Load(server.AssetsDir)
if err != nil {
log.Error("Load assets error: %v", err)
os.Exit(1)
}
l, err := conn.Listen(server.BindAddr, server.BindPort)
if err != nil {
log.Error("Create server listener error, %v", err)
os.Exit(1)
}
// create vhost if VhostHttpPort != 0
if server.VhostHttpPort != 0 {
vhostListener, err := conn.Listen(server.BindAddr, server.VhostHttpPort)
if err != nil {
log.Error("Create vhost http listener error, %v", err)
os.Exit(1)
}
server.VhostHttpMuxer, err = vhost.NewHttpMuxer(vhostListener, 30*time.Second)
if err != nil {
log.Error("Create vhost httpMuxer error, %v", err)
}
}
// create vhost if VhostHttpPort != 0
if server.VhostHttpsPort != 0 {
vhostListener, err := conn.Listen(server.BindAddr, server.VhostHttpsPort)
if err != nil {
log.Error("Create vhost https listener error, %v", err)
os.Exit(1)
}
server.VhostHttpsMuxer, err = vhost.NewHttpsMuxer(vhostListener, 30*time.Second)
if err != nil {
log.Error("Create vhost httpsMuxer error, %v", err)
}
}
// create dashboard web server if DashboardPort is set, so it won't be 0
if server.DashboardPort != 0 {
err := server.RunDashboardServer(server.BindAddr, server.DashboardPort)
if err != nil {
log.Error("Create dashboard web server error, %v", err)
os.Exit(1)
}
}
log.Info("Start frps success")
if server.PrivilegeMode == true {
log.Info("PrivilegeMode is enabled, you should pay more attention to security issues")
}
ProcessControlConn(l)
}

186
src/models/client/client.go

@ -1,186 +0,0 @@
// Copyright 2016 fatedier, fatedier@gmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package client
import (
"encoding/json"
"fmt"
"sync"
"time"
"github.com/fatedier/frp/src/models/config"
"github.com/fatedier/frp/src/models/consts"
"github.com/fatedier/frp/src/models/msg"
"github.com/fatedier/frp/src/utils/conn"
"github.com/fatedier/frp/src/utils/log"
"github.com/fatedier/frp/src/utils/pcrypto"
)
type ProxyClient struct {
config.BaseConf
LocalIp string
LocalPort int64
RemotePort int64
CustomDomains []string
Locations []string
udpTunnel *conn.Conn
once sync.Once
closeFlag bool
mutex sync.RWMutex
}
// if proxy type is udp, keep a tcp connection for transferring udp packages
func (pc *ProxyClient) StartUdpTunnelOnce(addr string, port int64) {
pc.once.Do(func() {
var err error
var c *conn.Conn
udpProcessor := NewUdpProcesser(nil, pc.LocalIp, pc.LocalPort)
for {
if !pc.IsClosed() && (pc.udpTunnel == nil || pc.udpTunnel.IsClosed()) {
if HttpProxy == "" {
c, err = conn.ConnectServer(fmt.Sprintf("%s:%d", addr, port))
} else {
c, err = conn.ConnectServerByHttpProxy(HttpProxy, fmt.Sprintf("%s:%d", addr, port))
}
if err != nil {
log.Error("ProxyName [%s], udp tunnel connect to server [%s:%d] error, %v", pc.Name, addr, port, err)
time.Sleep(10 * time.Second)
continue
}
log.Info("ProxyName [%s], udp tunnel connect to server [%s:%d] success", pc.Name, addr, port)
nowTime := time.Now().Unix()
req := &msg.ControlReq{
Type: consts.NewWorkConnUdp,
ProxyName: pc.Name,
PrivilegeMode: pc.PrivilegeMode,
Timestamp: nowTime,
}
if pc.PrivilegeMode == true {
req.PrivilegeKey = pcrypto.GetAuthKey(pc.Name + PrivilegeToken + fmt.Sprintf("%d", nowTime))
} else {
req.AuthKey = pcrypto.GetAuthKey(pc.Name + pc.AuthToken + fmt.Sprintf("%d", nowTime))
}
buf, _ := json.Marshal(req)
err = c.WriteString(string(buf) + "\n")
if err != nil {
log.Error("ProxyName [%s], udp tunnel write to server error, %v", pc.Name, err)
c.Close()
time.Sleep(1 * time.Second)
continue
}
pc.mutex.Lock()
pc.udpTunnel = c
udpProcessor.UpdateTcpConn(pc.udpTunnel)
pc.mutex.Unlock()
udpProcessor.Run()
}
time.Sleep(1 * time.Second)
}
})
}
func (pc *ProxyClient) CloseUdpTunnel() {
pc.mutex.RLock()
defer pc.mutex.RUnlock()
if pc.udpTunnel != nil {
pc.udpTunnel.Close()
}
}
func (pc *ProxyClient) GetLocalConn() (c *conn.Conn, err error) {
c, err = conn.ConnectServer(fmt.Sprintf("%s:%d", pc.LocalIp, pc.LocalPort))
if err != nil {
log.Error("ProxyName [%s], connect to local port error, %v", pc.Name, err)
}
return
}
func (pc *ProxyClient) GetRemoteConn(addr string, port int64) (c *conn.Conn, err error) {
defer func() {
if err != nil && c != nil {
c.Close()
}
}()
if HttpProxy == "" {
c, err = conn.ConnectServer(fmt.Sprintf("%s:%d", addr, port))
} else {
c, err = conn.ConnectServerByHttpProxy(HttpProxy, fmt.Sprintf("%s:%d", addr, port))
}
if err != nil {
log.Error("ProxyName [%s], connect to server [%s:%d] error, %v", pc.Name, addr, port, err)
return
}
nowTime := time.Now().Unix()
req := &msg.ControlReq{
Type: consts.NewWorkConn,
ProxyName: pc.Name,
PrivilegeMode: pc.PrivilegeMode,
Timestamp: nowTime,
}
if pc.PrivilegeMode == true {
req.PrivilegeKey = pcrypto.GetAuthKey(pc.Name + PrivilegeToken + fmt.Sprintf("%d", nowTime))
} else {
req.AuthKey = pcrypto.GetAuthKey(pc.Name + pc.AuthToken + fmt.Sprintf("%d", nowTime))
}
buf, _ := json.Marshal(req)
err = c.WriteString(string(buf) + "\n")
if err != nil {
log.Error("ProxyName [%s], write to server error, %v", pc.Name, err)
return
}
err = nil
return
}
func (pc *ProxyClient) StartTunnel(serverAddr string, serverPort int64) (err error) {
localConn, err := pc.GetLocalConn()
if err != nil {
return
}
remoteConn, err := pc.GetRemoteConn(serverAddr, serverPort)
if err != nil {
return
}
// l means local, r means remote
log.Debug("Join two connections, (l[%s] r[%s]) (l[%s] r[%s])", localConn.GetLocalAddr(), localConn.GetRemoteAddr(),
remoteConn.GetLocalAddr(), remoteConn.GetRemoteAddr())
needRecord := false
go msg.JoinMore(localConn, remoteConn, pc.BaseConf, needRecord)
return nil
}
func (pc *ProxyClient) SetCloseFlag(closeFlag bool) {
pc.mutex.Lock()
defer pc.mutex.Unlock()
pc.closeFlag = closeFlag
}
func (pc *ProxyClient) IsClosed() bool {
pc.mutex.RLock()
defer pc.mutex.RUnlock()
return pc.closeFlag
}

302
src/models/client/config.go

@ -1,302 +0,0 @@
// Copyright 2016 fatedier, fatedier@gmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package client
import (
"fmt"
"os"
"strconv"
"strings"
ini "github.com/vaughan0/go-ini"
)
// common config
var (
ServerAddr string = "0.0.0.0"
ServerPort int64 = 7000
HttpProxy string = ""
LogFile string = "console"
LogWay string = "console"
LogLevel string = "info"
LogMaxDays int64 = 3
PrivilegeToken string = ""
HeartBeatInterval int64 = 10
HeartBeatTimeout int64 = 30
)
var ProxyClients map[string]*ProxyClient = make(map[string]*ProxyClient)
func LoadConf(confFile string) (err error) {
var tmpStr string
var ok bool
conf, err := ini.LoadFile(confFile)
if err != nil {
return err
}
// common
tmpStr, ok = conf.Get("common", "server_addr")
if ok {
ServerAddr = tmpStr
}
tmpStr, ok = conf.Get("common", "server_port")
if ok {
ServerPort, _ = strconv.ParseInt(tmpStr, 10, 64)
}
tmpStr, ok = conf.Get("common", "http_proxy")
if ok {
HttpProxy = tmpStr
} else {
// get http_proxy from env
HttpProxy = os.Getenv("http_proxy")
}
tmpStr, ok = conf.Get("common", "log_file")
if ok {
LogFile = tmpStr
if LogFile == "console" {
LogWay = "console"
} else {
LogWay = "file"
}
}
tmpStr, ok = conf.Get("common", "log_level")
if ok {
LogLevel = tmpStr
}
tmpStr, ok = conf.Get("common", "log_max_days")
if ok {
LogMaxDays, _ = strconv.ParseInt(tmpStr, 10, 64)
}
tmpStr, ok = conf.Get("common", "privilege_token")
if ok {
PrivilegeToken = tmpStr
}
var authToken string
tmpStr, ok = conf.Get("common", "auth_token")
if ok {
authToken = tmpStr
}
tmpStr, ok = conf.Get("common", "heartbeat_timeout")
if ok {
v, err := strconv.ParseInt(tmpStr, 10, 64)
if err != nil {
return fmt.Errorf("Parse conf error: heartbeat_timeout is incorrect")
} else {
HeartBeatTimeout = v
}
}
tmpStr, ok = conf.Get("common", "heartbeat_interval")
if ok {
v, err := strconv.ParseInt(tmpStr, 10, 64)
if err != nil {
return fmt.Errorf("Parse conf error: heartbeat_interval is incorrect")
} else {
HeartBeatInterval = v
}
}
if HeartBeatInterval <= 0 {
return fmt.Errorf("Parse conf error: heartbeat_interval is incorrect")
}
if HeartBeatTimeout < HeartBeatInterval {
return fmt.Errorf("Parse conf error: heartbeat_timeout is incorrect, heartbeat_timeout is less than heartbeat_interval")
}
// proxies
for name, section := range conf {
if name != "common" {
proxyClient := &ProxyClient{}
// name
proxyClient.Name = name
// auth_token
proxyClient.AuthToken = authToken
// local_ip
proxyClient.LocalIp, ok = section["local_ip"]
if !ok {
// use 127.0.0.1 as default
proxyClient.LocalIp = "127.0.0.1"
}
// local_port
tmpStr, ok = section["local_port"]
if ok {
proxyClient.LocalPort, err = strconv.ParseInt(tmpStr, 10, 64)
if err != nil {
return fmt.Errorf("Parse conf error: proxy [%s] local_port error", proxyClient.Name)
}
} else {
return fmt.Errorf("Parse conf error: proxy [%s] local_port not found", proxyClient.Name)
}
// type
proxyClient.Type = "tcp"
tmpStr, ok = section["type"]
if ok {
if tmpStr != "tcp" && tmpStr != "http" && tmpStr != "https" && tmpStr != "udp" {
return fmt.Errorf("Parse conf error: proxy [%s] type error", proxyClient.Name)
}
proxyClient.Type = tmpStr
}
// use_encryption
proxyClient.UseEncryption = false
tmpStr, ok = section["use_encryption"]
if ok && tmpStr == "true" {
proxyClient.UseEncryption = true
}
// use_gzip
proxyClient.UseGzip = false
tmpStr, ok = section["use_gzip"]
if ok && tmpStr == "true" {
proxyClient.UseGzip = true
}
if proxyClient.Type == "http" {
// host_header_rewrite
tmpStr, ok = section["host_header_rewrite"]
if ok {
proxyClient.HostHeaderRewrite = tmpStr
}
// http_user
tmpStr, ok = section["http_user"]
if ok {
proxyClient.HttpUserName = tmpStr
}
// http_pwd
tmpStr, ok = section["http_pwd"]
if ok {
proxyClient.HttpPassWord = tmpStr
}
}
if proxyClient.Type == "http" || proxyClient.Type == "https" {
// subdomain
tmpStr, ok = section["subdomain"]
if ok {
proxyClient.SubDomain = tmpStr
}
}
// privilege_mode
proxyClient.PrivilegeMode = false
tmpStr, ok = section["privilege_mode"]
if ok && tmpStr == "true" {
proxyClient.PrivilegeMode = true
}
// pool_count
proxyClient.PoolCount = 0
tmpStr, ok = section["pool_count"]
if ok {
tmpInt, err := strconv.ParseInt(tmpStr, 10, 64)
if err != nil || tmpInt < 0 {
return fmt.Errorf("Parse conf error: proxy [%s] pool_count error", proxyClient.Name)
}
proxyClient.PoolCount = tmpInt
}
// configures used in privilege mode
if proxyClient.PrivilegeMode == true {
if PrivilegeToken == "" {
return fmt.Errorf("Parse conf error: proxy [%s] privilege_token must be set when privilege_mode = true", proxyClient.Name)
} else {
proxyClient.PrivilegeToken = PrivilegeToken
}
if proxyClient.Type == "tcp" || proxyClient.Type == "udp" {
// remote_port
tmpStr, ok = section["remote_port"]
if ok {
proxyClient.RemotePort, err = strconv.ParseInt(tmpStr, 10, 64)
if err != nil {
return fmt.Errorf("Parse conf error: proxy [%s] remote_port error", proxyClient.Name)
}
} else {
return fmt.Errorf("Parse conf error: proxy [%s] remote_port not found", proxyClient.Name)
}
} else if proxyClient.Type == "http" {
// custom_domains
tmpStr, ok = section["custom_domains"]
if ok {
proxyClient.CustomDomains = strings.Split(tmpStr, ",")
for i, domain := range proxyClient.CustomDomains {
proxyClient.CustomDomains[i] = strings.ToLower(strings.TrimSpace(domain))
}
}
// subdomain
tmpStr, ok = section["subdomain"]
if ok {
proxyClient.SubDomain = tmpStr
}
if len(proxyClient.CustomDomains) == 0 && proxyClient.SubDomain == "" {
return fmt.Errorf("Parse conf error: proxy [%s] custom_domains and subdomain should set at least one of them when type is http", proxyClient.Name)
}
// locations
tmpStr, ok = section["locations"]
if ok {
proxyClient.Locations = strings.Split(tmpStr, ",")
} else {
proxyClient.Locations = []string{""}
}
} else if proxyClient.Type == "https" {
// custom_domains
tmpStr, ok = section["custom_domains"]
if ok {
proxyClient.CustomDomains = strings.Split(tmpStr, ",")
for i, domain := range proxyClient.CustomDomains {
proxyClient.CustomDomains[i] = strings.ToLower(strings.TrimSpace(domain))
}
}
// subdomain
tmpStr, ok = section["subdomain"]
if ok {
proxyClient.SubDomain = tmpStr
}
if len(proxyClient.CustomDomains) == 0 && proxyClient.SubDomain == "" {
return fmt.Errorf("Parse conf error: proxy [%s] custom_domains and subdomain should set at least one of them when type is https", proxyClient.Name)
}
}
}
ProxyClients[proxyClient.Name] = proxyClient
}
}
if len(ProxyClients) == 0 {
return fmt.Errorf("Parse conf error: no proxy config found")
}
return nil
}

49
src/models/msg/msg.go

@ -1,49 +0,0 @@
// Copyright 2016 fatedier, fatedier@gmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package msg
type GeneralRes struct {
Code int64 `json:"code"`
Msg string `json:"msg"`
}
// messages between control connections of frpc and frps
type ControlReq struct {
Type int64 `json:"type"`
ProxyName string `json:"proxy_name"`
AuthKey string `json:"auth_key"`
UseEncryption bool `json:"use_encryption"`
UseGzip bool `json:"use_gzip"`
PoolCount int64 `json:"pool_count"`
// configures used if privilege_mode is enabled
PrivilegeMode bool `json:"privilege_mode"`
PrivilegeKey string `json:"privilege_key"`
ProxyType string `json:"proxy_type"`
RemotePort int64 `json:"remote_port"`
CustomDomains []string `json:"custom_domains, omitempty"`
Locations []string `json:"locations"`
HostHeaderRewrite string `json:"host_header_rewrite"`
HttpUserName string `json:"http_username"`
HttpPassWord string `json:"http_password"`
SubDomain string `json:"subdomain"`
Timestamp int64 `json:"timestamp"`
}
type ControlRes struct {
Type int64 `json:"type"`
Code int64 `json:"code"`
Msg string `json:"msg"`
}

257
src/models/msg/process.go

@ -1,257 +0,0 @@
// Copyright 2016 fatedier, fatedier@gmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package msg
import (
"bytes"
"encoding/binary"
"fmt"
"io"
"sync"
"github.com/fatedier/frp/src/models/config"
"github.com/fatedier/frp/src/models/metric"
"github.com/fatedier/frp/src/utils/conn"
"github.com/fatedier/frp/src/utils/log"
"github.com/fatedier/frp/src/utils/pcrypto"
"github.com/fatedier/frp/src/utils/pool"
)
// deprecated
// will block until connection close
func Join(c1 *conn.Conn, c2 *conn.Conn) {
var wait sync.WaitGroup
pipe := func(to *conn.Conn, from *conn.Conn) {
defer to.Close()
defer from.Close()
defer wait.Done()
var err error
_, err = io.Copy(to.TcpConn, from.TcpConn)
if err != nil {
log.Warn("join connections error, %v", err)
}
}
wait.Add(2)
go pipe(c1, c2)
go pipe(c2, c1)
wait.Wait()
return
}
// join two connections and do some operations
func JoinMore(c1 io.ReadWriteCloser, c2 io.ReadWriteCloser, conf config.BaseConf, needRecord bool) {
var wait sync.WaitGroup
encryptPipe := func(from io.ReadCloser, to io.WriteCloser) {
defer from.Close()
defer to.Close()
defer wait.Done()
// we don't care about errors here
pipeEncrypt(from, to, conf, needRecord)
}
decryptPipe := func(to io.ReadCloser, from io.WriteCloser) {
defer from.Close()
defer to.Close()
defer wait.Done()
// we don't care about errors here
pipeDecrypt(to, from, conf, needRecord)
}
if needRecord {
metric.OpenConnection(conf.Name)
}
wait.Add(2)
go encryptPipe(c1, c2)
go decryptPipe(c2, c1)
wait.Wait()
if needRecord {
metric.CloseConnection(conf.Name)
}
log.Debug("ProxyName [%s], One tunnel stopped", conf.Name)
return
}
func pkgMsg(data []byte) []byte {
llen := uint32(len(data))
buf := new(bytes.Buffer)
binary.Write(buf, binary.BigEndian, llen)
buf.Write(data)
return buf.Bytes()
}
func unpkgMsg(data []byte) (int, []byte, []byte) {
if len(data) < 4 {
return -1, nil, data
}
llen := int(binary.BigEndian.Uint32(data[0:4]))
// no complete
if len(data) < llen+4 {
return -1, nil, data
}
return 0, data[4 : llen+4], data[llen+4:]
}
// decrypt msg from reader, then write into writer
func pipeDecrypt(r io.Reader, w io.Writer, conf config.BaseConf, needRecord bool) (err error) {
laes := new(pcrypto.Pcrypto)
key := conf.AuthToken
if conf.PrivilegeMode {
key = conf.PrivilegeToken
}
if err := laes.Init([]byte(key)); err != nil {
log.Warn("ProxyName [%s], Pcrypto Init error: %v", conf.Name, err)
return fmt.Errorf("Pcrypto Init error: %v", err)
}
// get []byte from buffer pool
buf := pool.GetBuf(5*1024 + 4)
defer pool.PutBuf(buf)
var left, res []byte
var cnt int = -1
// record
var flowBytes int64 = 0
if needRecord {
defer func() {
metric.AddFlowOut(conf.Name, flowBytes)
}()
}
for {
// there may be more than 1 package in variable
// and we read more bytes if unpkgMsg returns an error
var newBuf []byte
if cnt < 0 {
n, err := r.Read(buf)
if err != nil {
return err
}
newBuf = append(left, buf[0:n]...)
} else {
newBuf = left
}
cnt, res, left = unpkgMsg(newBuf)
if cnt < 0 {
// limit one package length, maximum is 1MB
if len(res) > 1024*1024 {
log.Warn("ProxyName [%s], package length exceeds the limit")
return fmt.Errorf("package length error")
}
continue
}
// aes
if conf.UseEncryption {
res, err = laes.Decrypt(res)
if err != nil {
log.Warn("ProxyName [%s], decrypt error, %v", conf.Name, err)
return fmt.Errorf("Decrypt error: %v", err)
}
}
// gzip
if conf.UseGzip {
res, err = laes.Decompression(res)
if err != nil {
log.Warn("ProxyName [%s], decompression error, %v", conf.Name, err)
return fmt.Errorf("Decompression error: %v", err)
}
}
_, err = w.Write(res)
if err != nil {
return err
}
if needRecord {
flowBytes += int64(len(res))
if flowBytes >= 1024*1024 {
metric.AddFlowOut(conf.Name, flowBytes)
flowBytes = 0
}
}
}
return nil
}
// recvive msg from reader, then encrypt msg into writer
func pipeEncrypt(r io.Reader, w io.Writer, conf config.BaseConf, needRecord bool) (err error) {
laes := new(pcrypto.Pcrypto)
key := conf.AuthToken
if conf.PrivilegeMode {
key = conf.PrivilegeToken
}
if err := laes.Init([]byte(key)); err != nil {
log.Warn("ProxyName [%s], Pcrypto Init error: %v", conf.Name, err)
return fmt.Errorf("Pcrypto Init error: %v", err)
}
// record
var flowBytes int64 = 0
if needRecord {
defer func() {
metric.AddFlowIn(conf.Name, flowBytes)
}()
}
// get []byte from buffer pool
buf := pool.GetBuf(5*1024 + 4)
defer pool.PutBuf(buf)
for {
n, err := r.Read(buf)
if err != nil {
return err
}
if needRecord {
flowBytes += int64(n)
if flowBytes >= 1024*1024 {
metric.AddFlowIn(conf.Name, flowBytes)
flowBytes = 0
}
}
res := buf[0:n]
// gzip
if conf.UseGzip {
res, err = laes.Compression(res)
if err != nil {
log.Warn("ProxyName [%s], compression error: %v", conf.Name, err)
return fmt.Errorf("Compression error: %v", err)
}
}
// aes
if conf.UseEncryption {
res, err = laes.Encrypt(res)
if err != nil {
log.Warn("ProxyName [%s], encrypt error: %v", conf.Name, err)
return fmt.Errorf("Encrypt error: %v", err)
}
}
res = pkgMsg(res)
_, err = w.Write(res)
if err != nil {
return err
}
}
return nil
}

456
src/models/server/config.go

@ -1,456 +0,0 @@
// Copyright 2016 fatedier, fatedier@gmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package server
import (
"fmt"
"strconv"
"strings"
"sync"
ini "github.com/vaughan0/go-ini"
"github.com/fatedier/frp/src/models/consts"
"github.com/fatedier/frp/src/models/metric"
"github.com/fatedier/frp/src/utils/log"
"github.com/fatedier/frp/src/utils/vhost"
)
// common config
var (
ConfigFile string = "./frps.ini"
BindAddr string = "0.0.0.0"
BindPort int64 = 7000
VhostHttpPort int64 = 0 // if VhostHttpPort equals 0, don't listen a public port for http protocol
VhostHttpsPort int64 = 0 // if VhostHttpsPort equals 0, don't listen a public port for https protocol
DashboardPort int64 = 0 // if DashboardPort equals 0, dashboard is not available
DashboardUsername string = "admin"
DashboardPassword string = "admin"
AssetsDir string = ""
LogFile string = "console"
LogWay string = "console" // console or file
LogLevel string = "info"
LogMaxDays int64 = 3
PrivilegeMode bool = false
PrivilegeToken string = ""
AuthTimeout int64 = 900
SubDomainHost string = ""
// if PrivilegeAllowPorts is not nil, tcp proxies which remote port exist in this map can be connected
PrivilegeAllowPorts map[int64]struct{}
MaxPoolCount int64 = 100
HeartBeatTimeout int64 = 30
UserConnTimeout int64 = 10
VhostHttpMuxer *vhost.HttpMuxer
VhostHttpsMuxer *vhost.HttpsMuxer
ProxyServers map[string]*ProxyServer = make(map[string]*ProxyServer) // all proxy servers info and resources
ProxyServersMutex sync.RWMutex
)
func LoadConf(confFile string) (err error) {
err = loadCommonConf(confFile)
if err != nil {
return err
}
// load all proxy server's configure and initialize
// and set ProxyServers map
newProxyServers, err := loadProxyConf(confFile)
if err != nil {
return err
}
for _, proxyServer := range newProxyServers {
proxyServer.Init()
}
ProxyServersMutex.Lock()
ProxyServers = newProxyServers
ProxyServersMutex.Unlock()
return nil
}
func loadCommonConf(confFile string) error {
var tmpStr string
var ok bool
conf, err := ini.LoadFile(confFile)
if err != nil {
return err
}
// common
tmpStr, ok = conf.Get("common", "bind_addr")
if ok {
BindAddr = tmpStr
}
tmpStr, ok = conf.Get("common", "bind_port")
if ok {
v, err := strconv.ParseInt(tmpStr, 10, 64)
if err == nil {
BindPort = v
}
}
tmpStr, ok = conf.Get("common", "vhost_http_port")
if ok {
VhostHttpPort, _ = strconv.ParseInt(tmpStr, 10, 64)
} else {
VhostHttpPort = 0
}
tmpStr, ok = conf.Get("common", "vhost_https_port")
if ok {
VhostHttpsPort, _ = strconv.ParseInt(tmpStr, 10, 64)
} else {
VhostHttpsPort = 0
}
tmpStr, ok = conf.Get("common", "dashboard_port")
if ok {
DashboardPort, _ = strconv.ParseInt(tmpStr, 10, 64)
} else {
DashboardPort = 0
}
tmpStr, ok = conf.Get("common", "dashboard_user")
if ok {
DashboardUsername = tmpStr
}
tmpStr, ok = conf.Get("common", "dashboard_pwd")
if ok {
DashboardPassword = tmpStr
}
tmpStr, ok = conf.Get("common", "assets_dir")
if ok {
AssetsDir = tmpStr
}
tmpStr, ok = conf.Get("common", "log_file")
if ok {
LogFile = tmpStr
if LogFile == "console" {
LogWay = "console"
} else {
LogWay = "file"
}
}
tmpStr, ok = conf.Get("common", "log_level")
if ok {
LogLevel = tmpStr
}
tmpStr, ok = conf.Get("common", "log_max_days")
if ok {
v, err := strconv.ParseInt(tmpStr, 10, 64)
if err == nil {
LogMaxDays = v
}
}
tmpStr, ok = conf.Get("common", "privilege_mode")
if ok {
if tmpStr == "true" {
PrivilegeMode = true
}
}
if PrivilegeMode == true {
tmpStr, ok = conf.Get("common", "privilege_token")
if ok {
if tmpStr == "" {
return fmt.Errorf("Parse conf error: privilege_token can not be null")
}
PrivilegeToken = tmpStr
} else {
return fmt.Errorf("Parse conf error: privilege_token must be set if privilege_mode is enabled")
}
PrivilegeAllowPorts = make(map[int64]struct{})
tmpStr, ok = conf.Get("common", "privilege_allow_ports")
if ok {
// for example: 1000-2000,2001,2002,3000-4000
portRanges := strings.Split(tmpStr, ",")
for _, portRangeStr := range portRanges {
// 1000-2000 or 2001
portArray := strings.Split(portRangeStr, "-")
// lenght: only 1 or 2 is correct
rangeType := len(portArray)
if rangeType == 1 {
singlePort, err := strconv.ParseInt(portArray[0], 10, 64)
if err != nil {
return fmt.Errorf("Parse conf error: privilege_allow_ports is incorrect, %v", err)
}
PrivilegeAllowPorts[singlePort] = struct{}{}
} else if rangeType == 2 {
min, err := strconv.ParseInt(portArray[0], 10, 64)
if err != nil {
return fmt.Errorf("Parse conf error: privilege_allow_ports is incorrect, %v", err)
}
max, err := strconv.ParseInt(portArray[1], 10, 64)
if err != nil {
return fmt.Errorf("Parse conf error: privilege_allow_ports is incorrect, %v", err)
}
if max < min {
return fmt.Errorf("Parse conf error: privilege_allow_ports range incorrect")
}
for i := min; i <= max; i++ {
PrivilegeAllowPorts[i] = struct{}{}
}
} else {
return fmt.Errorf("Parse conf error: privilege_allow_ports is incorrect")
}
}
}
}
tmpStr, ok = conf.Get("common", "max_pool_count")
if ok {
v, err := strconv.ParseInt(tmpStr, 10, 64)
if err == nil && v >= 0 {
MaxPoolCount = v
}
}
tmpStr, ok = conf.Get("common", "authentication_timeout")
if ok {
v, err := strconv.ParseInt(tmpStr, 10, 64)
if err != nil {
return fmt.Errorf("Parse conf error: authentication_timeout is incorrect")
} else {
AuthTimeout = v
}
}
SubDomainHost, ok = conf.Get("common", "subdomain_host")
if ok {
SubDomainHost = strings.ToLower(strings.TrimSpace(SubDomainHost))
}
tmpStr, ok = conf.Get("common", "heartbeat_timeout")
if ok {
v, err := strconv.ParseInt(tmpStr, 10, 64)
if err != nil {
return fmt.Errorf("Parse conf error: heartbeat_timeout is incorrect")
} else {
HeartBeatTimeout = v
}
}
return nil
}
func loadProxyConf(confFile string) (proxyServers map[string]*ProxyServer, err error) {
var ok bool
proxyServers = make(map[string]*ProxyServer)
conf, err := ini.LoadFile(confFile)
if err != nil {
return proxyServers, err
}
// servers
for name, section := range conf {
if name != "common" {
proxyServer := NewProxyServer()
proxyServer.Name = name
proxyServer.Type, ok = section["type"]
if ok {
if proxyServer.Type != "tcp" && proxyServer.Type != "http" && proxyServer.Type != "https" && proxyServer.Type != "udp" {
return proxyServers, fmt.Errorf("Parse conf error: proxy [%s] type error", proxyServer.Name)
}
} else {
proxyServer.Type = "tcp"
}
proxyServer.AuthToken, ok = section["auth_token"]
if !ok {
return proxyServers, fmt.Errorf("Parse conf error: proxy [%s] no auth_token found", proxyServer.Name)
}
// for tcp and udp
if proxyServer.Type == "tcp" || proxyServer.Type == "udp" {
proxyServer.BindAddr, ok = section["bind_addr"]
if !ok {
proxyServer.BindAddr = "0.0.0.0"
}
portStr, ok := section["listen_port"]
if ok {
proxyServer.ListenPort, err = strconv.ParseInt(portStr, 10, 64)
if err != nil {
return proxyServers, fmt.Errorf("Parse conf error: proxy [%s] listen_port error", proxyServer.Name)
}
} else {
return proxyServers, fmt.Errorf("Parse conf error: proxy [%s] listen_port not found", proxyServer.Name)
}
} else if proxyServer.Type == "http" {
// for http
proxyServer.ListenPort = VhostHttpPort
domainStr, ok := section["custom_domains"]
if ok {
proxyServer.CustomDomains = strings.Split(domainStr, ",")
for i, domain := range proxyServer.CustomDomains {
domain = strings.ToLower(strings.TrimSpace(domain))
// custom domain should not belong to subdomain_host
if SubDomainHost != "" && strings.Contains(domain, SubDomainHost) {
return proxyServers, fmt.Errorf("Parse conf error: proxy [%s] custom domain should not belong to subdomain_host", proxyServer.Name)
}
proxyServer.CustomDomains[i] = domain
}
}
// subdomain
subdomainStr, ok := section["subdomain"]
if ok {
if strings.Contains(subdomainStr, ".") || strings.Contains(subdomainStr, "*") {
return proxyServers, fmt.Errorf("Parse conf error: proxy [%s] '.' and '*' is not supported in subdomain", proxyServer.Name)
}
if SubDomainHost == "" {
return proxyServers, fmt.Errorf("Parse conf error: proxy [%s] subdomain is not supported because subdomain_host is empty", proxyServer.Name)
}
proxyServer.SubDomain = subdomainStr + "." + SubDomainHost
}
if len(proxyServer.CustomDomains) == 0 && proxyServer.SubDomain == "" {
return proxyServers, fmt.Errorf("Parse conf error: proxy [%s] custom_domains and subdomain should set at least one of them when type is http", proxyServer.Name)
}
// locations
locations, ok := section["locations"]
if ok {
proxyServer.Locations = strings.Split(locations, ",")
} else {
proxyServer.Locations = []string{""}
}
} else if proxyServer.Type == "https" {
// for https
proxyServer.ListenPort = VhostHttpsPort
domainStr, ok := section["custom_domains"]
if ok {
proxyServer.CustomDomains = strings.Split(domainStr, ",")
for i, domain := range proxyServer.CustomDomains {
domain = strings.ToLower(strings.TrimSpace(domain))
if SubDomainHost != "" && strings.Contains(domain, SubDomainHost) {
return proxyServers, fmt.Errorf("Parse conf error: proxy [%s] custom domain should not belong to subdomain_host", proxyServer.Name)
}
proxyServer.CustomDomains[i] = domain
}
}
// subdomain
subdomainStr, ok := section["subdomain"]
if ok {
if strings.Contains(subdomainStr, ".") || strings.Contains(subdomainStr, "*") {
return proxyServers, fmt.Errorf("Parse conf error: proxy [%s] '.' and '*' is not supported in subdomain", proxyServer.Name)
}
if SubDomainHost == "" {
return proxyServers, fmt.Errorf("Parse conf error: proxy [%s] subdomain is not supported because subdomain_host is empty", proxyServer.Name)
}
proxyServer.SubDomain = subdomainStr + "." + SubDomainHost
}
if len(proxyServer.CustomDomains) == 0 && proxyServer.SubDomain == "" {
return proxyServers, fmt.Errorf("Parse conf error: proxy [%s] custom_domains and subdomain should set at least one of them when type is https", proxyServer.Name)
}
}
proxyServers[proxyServer.Name] = proxyServer
}
}
// set metric statistics of all proxies
for name, p := range proxyServers {
metric.SetProxyInfo(name, p.Type, p.BindAddr, p.UseEncryption, p.UseGzip,
p.PrivilegeMode, p.CustomDomains, p.Locations, p.ListenPort)
}
return proxyServers, nil
}
// the function can only reload proxy configures
// common section won't be changed
func ReloadConf(confFile string) (err error) {
loadProxyServers, err := loadProxyConf(confFile)
if err != nil {
return err
}
ProxyServersMutex.Lock()
for name, proxyServer := range loadProxyServers {
oldProxyServer, ok := ProxyServers[name]
if ok {
if !oldProxyServer.Compare(proxyServer) {
oldProxyServer.Close()
proxyServer.Init()
ProxyServers[name] = proxyServer
log.Info("ProxyName [%s] configure change, restart", name)
}
} else {
proxyServer.Init()
ProxyServers[name] = proxyServer
log.Info("ProxyName [%s] is new, init it", name)
}
}
// proxies created by PrivilegeMode won't be deleted
for name, oldProxyServer := range ProxyServers {
_, ok := loadProxyServers[name]
if !ok {
if !oldProxyServer.PrivilegeMode {
oldProxyServer.Close()
delete(ProxyServers, name)
log.Info("ProxyName [%s] deleted, close it", name)
} else {
log.Info("ProxyName [%s] created by PrivilegeMode, won't be closed", name)
}
}
}
ProxyServersMutex.Unlock()
return nil
}
func CreateProxy(s *ProxyServer) error {
ProxyServersMutex.Lock()
defer ProxyServersMutex.Unlock()
oldServer, ok := ProxyServers[s.Name]
if ok {
if oldServer.Status == consts.Working {
return fmt.Errorf("this proxy is already working now")
}
oldServer.Lock()
oldServer.Release()
oldServer.Unlock()
if oldServer.PrivilegeMode {
delete(ProxyServers, s.Name)
}
}
ProxyServers[s.Name] = s
metric.SetProxyInfo(s.Name, s.Type, s.BindAddr, s.UseEncryption, s.UseGzip,
s.PrivilegeMode, s.CustomDomains, s.Locations, s.ListenPort)
return nil
}
func DeleteProxy(proxyName string) {
ProxyServersMutex.Lock()
defer ProxyServersMutex.Unlock()
delete(ProxyServers, proxyName)
}
func GetProxyServer(proxyName string) (p *ProxyServer, ok bool) {
ProxyServersMutex.RLock()
defer ProxyServersMutex.RUnlock()
p, ok = ProxyServers[proxyName]
return
}

484
src/models/server/server.go

@ -1,484 +0,0 @@
// Copyright 2016 fatedier, fatedier@gmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package server
import (
"fmt"
"net"
"sync"
"time"
"github.com/fatedier/frp/src/models/config"
"github.com/fatedier/frp/src/models/consts"
"github.com/fatedier/frp/src/models/metric"
"github.com/fatedier/frp/src/models/msg"
"github.com/fatedier/frp/src/utils/conn"
"github.com/fatedier/frp/src/utils/log"
"github.com/fatedier/frp/src/utils/pool"
)
type Listener interface {
Accept() (*conn.Conn, error)
Close() error
}
type ProxyServer struct {
config.BaseConf
BindAddr string
ListenPort int64
CustomDomains []string
Locations []string
Status int64
CtlConn *conn.Conn // control connection with frpc
WorkConnUdp *conn.Conn // work connection for udp
udpConn *net.UDPConn
listeners []Listener // accept new connection from remote users
ctlMsgChan chan int64 // every time accept a new user conn, put "1" to the channel
workConnChan chan *conn.Conn // get new work conns from control goroutine
udpSenderChan chan *msg.UdpPacket
mutex sync.RWMutex
closeChan chan struct{} // close this channel for notifying other goroutines that the proxy is closed
}
func NewProxyServer() (p *ProxyServer) {
p = &ProxyServer{
CustomDomains: make([]string, 0),
Locations: make([]string, 0),
}
return p
}
func NewProxyServerFromCtlMsg(req *msg.ControlReq) (p *ProxyServer) {
p = &ProxyServer{}
p.Name = req.ProxyName
p.Type = req.ProxyType
p.UseEncryption = req.UseEncryption
p.UseGzip = req.UseGzip
p.PrivilegeMode = req.PrivilegeMode
p.PrivilegeToken = PrivilegeToken
p.BindAddr = BindAddr
if p.Type == "tcp" || p.Type == "udp" {
p.ListenPort = req.RemotePort
} else if p.Type == "http" {
p.ListenPort = VhostHttpPort
} else if p.Type == "https" {
p.ListenPort = VhostHttpsPort
}
p.CustomDomains = req.CustomDomains
p.SubDomain = req.SubDomain
p.Locations = req.Locations
p.HostHeaderRewrite = req.HostHeaderRewrite
p.HttpUserName = req.HttpUserName
p.HttpPassWord = req.HttpPassWord
p.Init()
return
}
func (p *ProxyServer) Init() {
p.Lock()
p.Status = consts.Idle
metric.SetStatus(p.Name, p.Status)
p.workConnChan = make(chan *conn.Conn, p.PoolCount+10)
p.ctlMsgChan = make(chan int64, p.PoolCount+10)
p.udpSenderChan = make(chan *msg.UdpPacket, 1024)
p.listeners = make([]Listener, 0)
p.closeChan = make(chan struct{})
p.Unlock()
}
func (p *ProxyServer) Compare(p2 *ProxyServer) bool {
if p.Name != p2.Name || p.AuthToken != p2.AuthToken || p.Type != p2.Type ||
p.BindAddr != p2.BindAddr || p.ListenPort != p2.ListenPort || p.HostHeaderRewrite != p2.HostHeaderRewrite {
return false
}
if len(p.CustomDomains) != len(p2.CustomDomains) {
return false
}
for i, _ := range p.CustomDomains {
if p.CustomDomains[i] != p2.CustomDomains[i] {
return false
}
}
if len(p.Locations) != len(p2.Locations) {
return false
}
for i, _ := range p.Locations {
if p.Locations[i] != p2.Locations[i] {
return false
}
}
return true
}
func (p *ProxyServer) Lock() {
p.mutex.Lock()
}
func (p *ProxyServer) Unlock() {
p.mutex.Unlock()
}
// start listening for user conns
func (p *ProxyServer) Start(c *conn.Conn) (err error) {
p.CtlConn = c
p.Init()
if p.Type == "tcp" {
l, err := conn.Listen(p.BindAddr, p.ListenPort)
if err != nil {
return err
}
p.listeners = append(p.listeners, l)
} else if p.Type == "http" {
for _, domain := range p.CustomDomains {
if len(p.Locations) == 0 {
l, err := VhostHttpMuxer.Listen(domain, "", p.HostHeaderRewrite, p.HttpUserName, p.HttpPassWord)
if err != nil {
return err
}
log.Info("ProxyName [%s], type http listen for host [%s] location [%s]", p.Name, domain, "")
p.listeners = append(p.listeners, l)
} else {
for _, location := range p.Locations {
l, err := VhostHttpMuxer.Listen(domain, location, p.HostHeaderRewrite, p.HttpUserName, p.HttpPassWord)
if err != nil {
return err
}
log.Info("ProxyName [%s], type http listen for host [%s] location [%s]", p.Name, domain, location)
p.listeners = append(p.listeners, l)
}
}
}
if p.SubDomain != "" {
if len(p.Locations) == 0 {
l, err := VhostHttpMuxer.Listen(p.SubDomain, "", p.HostHeaderRewrite, p.HttpUserName, p.HttpPassWord)
if err != nil {
return err
}
log.Info("ProxyName [%s], type http listen for host [%s] location [%s]", p.Name, p.SubDomain, "")
p.listeners = append(p.listeners, l)
} else {
for _, location := range p.Locations {
l, err := VhostHttpMuxer.Listen(p.SubDomain, location, p.HostHeaderRewrite, p.HttpUserName, p.HttpPassWord)
if err != nil {
return err
}
log.Info("ProxyName [%s], type http listen for host [%s] location [%s]", p.Name, p.SubDomain, location)
p.listeners = append(p.listeners, l)
}
}
}
} else if p.Type == "https" {
for _, domain := range p.CustomDomains {
l, err := VhostHttpsMuxer.Listen(domain, "", p.HostHeaderRewrite, p.HttpUserName, p.HttpPassWord)
if err != nil {
return err
}
log.Info("ProxyName [%s], type https listen for host [%s]", p.Name, domain)
p.listeners = append(p.listeners, l)
}
if p.SubDomain != "" {
l, err := VhostHttpsMuxer.Listen(p.SubDomain, "", p.HostHeaderRewrite, p.HttpUserName, p.HttpPassWord)
if err != nil {
return err
}
log.Info("ProxyName [%s], type https listen for host [%s]", p.Name, p.SubDomain)
p.listeners = append(p.listeners, l)
}
}
p.Lock()
p.Status = consts.Working
p.Unlock()
metric.SetStatus(p.Name, p.Status)
if p.Type == "udp" {
// udp is special
p.udpConn, err = conn.ListenUDP(p.BindAddr, p.ListenPort)
if err != nil {
log.Warn("ProxyName [%s], listen udp port error: %v", p.Name, err)
return err
}
go func() {
for {
buf := pool.GetBuf(2048)
n, remoteAddr, err := p.udpConn.ReadFromUDP(buf)
if err != nil {
log.Info("ProxyName [%s], udp listener is closed", p.Name)
return
}
localAddr, _ := net.ResolveUDPAddr("udp", p.udpConn.LocalAddr().String())
udpPacket := msg.NewUdpPacket(buf[0:n], remoteAddr, localAddr)
select {
case p.udpSenderChan <- udpPacket:
default:
log.Warn("ProxyName [%s], udp sender channel is full", p.Name)
}
pool.PutBuf(buf)
}
}()
} else {
// create connection pool if needed
if p.PoolCount > 0 {
go p.connectionPoolManager(p.closeChan)
}
// start a goroutine for every listener to accept user connection
for _, listener := range p.listeners {
go func(l Listener) {
for {
// block
// if listener is closed, err returned
c, err := l.Accept()
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())
if p.Status != consts.Working {
log.Debug("ProxyName [%s] is not working, new user conn close", p.Name)
c.Close()
return
}
go func(userConn *conn.Conn) {
workConn, err := p.getWorkConn()
if err != nil {
return
}
// message will be transferred to another without modifying
// l means local, r means remote
log.Debug("Join two connections, (l[%s] r[%s]) (l[%s] r[%s])", workConn.GetLocalAddr(), workConn.GetRemoteAddr(),
userConn.GetLocalAddr(), userConn.GetRemoteAddr())
needRecord := true
go msg.JoinMore(userConn, workConn, p.BaseConf, needRecord)
}(c)
}
}(listener)
}
}
return nil
}
func (p *ProxyServer) Close() {
p.Lock()
defer p.Unlock()
oldStatus := p.Status
p.Release()
// if the proxy created by PrivilegeMode, delete it when closed
if p.PrivilegeMode && oldStatus == consts.Working {
// NOTE: this will take the global ProxyServerMap's lock
// if we only want to release resources, use Release() instead
DeleteProxy(p.Name)
}
}
func (p *ProxyServer) Release() {
if p.Status != consts.Closed {
p.Status = consts.Closed
for _, l := range p.listeners {
if l != nil {
l.Close()
}
}
if p.ctlMsgChan != nil {
close(p.ctlMsgChan)
p.ctlMsgChan = nil
}
if p.workConnChan != nil {
close(p.workConnChan)
p.workConnChan = nil
}
if p.udpSenderChan != nil {
close(p.udpSenderChan)
p.udpSenderChan = nil
}
if p.closeChan != nil {
close(p.closeChan)
p.closeChan = nil
}
if p.CtlConn != nil {
p.CtlConn.Close()
}
if p.WorkConnUdp != nil {
p.WorkConnUdp.Close()
}
if p.udpConn != nil {
p.udpConn.Close()
p.udpConn = nil
}
}
metric.SetStatus(p.Name, p.Status)
}
func (p *ProxyServer) WaitUserConn() (closeFlag bool) {
closeFlag = false
_, ok := <-p.ctlMsgChan
if !ok {
closeFlag = true
}
return
}
func (p *ProxyServer) RegisterNewWorkConn(c *conn.Conn) {
select {
case p.workConnChan <- c:
default:
log.Debug("ProxyName [%s], workConnChan is full, so close this work connection", p.Name)
c.Close()
}
}
// create a tcp connection for forwarding udp packages
func (p *ProxyServer) RegisterNewWorkConnUdp(c *conn.Conn) {
if p.WorkConnUdp != nil && !p.WorkConnUdp.IsClosed() {
p.WorkConnUdp.Close()
}
p.WorkConnUdp = c
// read
go func() {
var (
buf string
err error
)
for {
buf, err = c.ReadLine()
if err != nil {
log.Warn("ProxyName [%s], work connection for udp closed", p.Name)
return
}
udpPacket := &msg.UdpPacket{}
err = udpPacket.UnPack([]byte(buf))
if err != nil {
log.Warn("ProxyName [%s], unpack udp packet error: %v", p.Name, err)
continue
}
// send to user
_, err = p.udpConn.WriteToUDP(udpPacket.Content, udpPacket.Dst)
if err != nil {
continue
}
}
}()
// write
go func() {
for {
udpPacket, ok := <-p.udpSenderChan
if !ok {
return
}
err := c.WriteString(string(udpPacket.Pack()) + "\n")
if err != nil {
log.Debug("ProxyName [%s], write to work connection for udp error: %v", p.Name, err)
return
}
}
}()
}
// When frps get one user connection, we get one work connection from the pool and return it.
// If no workConn available in the pool, send message to frpc to get one or more
// and wait until it is available.
// return an error if wait timeout
func (p *ProxyServer) getWorkConn() (workConn *conn.Conn, err error) {
var ok bool
// get a work connection from the pool
for {
select {
case workConn, ok = <-p.workConnChan:
if !ok {
err = fmt.Errorf("ProxyName [%s], no work connections available, control is closing", p.Name)
return
}
log.Debug("ProxyName [%s], get work connection from pool", p.Name)
default:
// no work connections available in the poll, send message to frpc to get more
p.ctlMsgChan <- 1
select {
case workConn, ok = <-p.workConnChan:
if !ok {
err = fmt.Errorf("ProxyName [%s], no work connections available, control is closing", p.Name)
return
}
case <-time.After(time.Duration(UserConnTimeout) * time.Second):
log.Warn("ProxyName [%s], timeout trying to get work connection", p.Name)
err = fmt.Errorf("ProxyName [%s], timeout trying to get work connection", p.Name)
return
}
}
// if connection pool is not used, we don't check the status
// function CheckClosed will consume at least 1 millisecond if the connection isn't closed
if p.PoolCount == 0 || !workConn.CheckClosed() {
break
} else {
log.Warn("ProxyName [%s], connection got from pool, but it's already closed", p.Name)
}
}
return
}
func (p *ProxyServer) connectionPoolManager(closeCh <-chan struct{}) {
defer func() {
if r := recover(); r != nil {
log.Warn("ProxyName [%s], connectionPoolManager panic %v", p.Name, r)
}
}()
for {
// check if we need more work connections and send messages to frpc to get more
time.Sleep(time.Duration(2) * time.Second)
select {
// if the channel closed, it means the proxy is closed, so just return
case <-closeCh:
log.Info("ProxyName [%s], connectionPoolManager exit", p.Name)
return
default:
curWorkConnNum := int64(len(p.workConnChan))
diff := p.PoolCount - curWorkConnNum
if diff > 0 {
if diff < p.PoolCount/5 {
diff = p.PoolCount*4/5 + 1
} else if diff < p.PoolCount/2 {
diff = p.PoolCount/4 + 1
} else if diff < p.PoolCount*4/5 {
diff = p.PoolCount/5 + 1
} else {
diff = p.PoolCount/10 + 1
}
if diff+curWorkConnNum > p.PoolCount {
diff = p.PoolCount - curWorkConnNum
}
for i := 0; i < int(diff); i++ {
p.ctlMsgChan <- 1
}
}
}
}
}

87
src/utils/broadcast/broadcast.go

@ -1,87 +0,0 @@
// Copyright 2016 fatedier, fatedier@gmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
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
}
}

77
src/utils/broadcast/broadcast_test.go

@ -1,77 +0,0 @@
// Copyright 2016 fatedier, fatedier@gmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
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.Fatalf("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.Fatalf("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
}
}

314
src/utils/conn/conn.go

@ -1,314 +0,0 @@
// Copyright 2016 fatedier, fatedier@gmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package conn
import (
"bufio"
"bytes"
"encoding/base64"
"fmt"
"io"
"net"
"net/http"
"net/url"
"strings"
"sync"
"time"
"github.com/fatedier/frp/src/utils/pool"
)
type Listener struct {
addr net.Addr
l *net.TCPListener
accept chan *Conn
closeFlag bool
}
func Listen(bindAddr string, bindPort int64) (l *Listener, err error) {
tcpAddr, err := net.ResolveTCPAddr("tcp", fmt.Sprintf("%s:%d", bindAddr, bindPort))
if err != nil {
return l, err
}
listener, err := net.ListenTCP("tcp", tcpAddr)
if err != nil {
return l, err
}
l = &Listener{
addr: listener.Addr(),
l: listener,
accept: make(chan *Conn),
closeFlag: false,
}
go func() {
for {
conn, err := l.l.AcceptTCP()
if err != nil {
if l.closeFlag {
return
}
continue
}
c := NewConn(conn)
l.accept <- c
}
}()
return l, err
}
// wait util get one new connection or listener is closed
// if listener is closed, err returned
func (l *Listener) Accept() (*Conn, error) {
conn, ok := <-l.accept
if !ok {
return conn, fmt.Errorf("channel close")
}
return conn, nil
}
func (l *Listener) Close() error {
if l.l != nil && l.closeFlag == false {
l.closeFlag = true
l.l.Close()
close(l.accept)
}
return nil
}
// wrap for TCPConn
type Conn struct {
TcpConn net.Conn
Reader *bufio.Reader
buffer *bytes.Buffer
closeFlag bool
mutex sync.RWMutex
}
func NewConn(conn net.Conn) (c *Conn) {
c = &Conn{
TcpConn: conn,
buffer: nil,
closeFlag: false,
}
c.Reader = bufio.NewReader(c.TcpConn)
return
}
func ConnectServer(addr string) (c *Conn, err error) {
servertAddr, err := net.ResolveTCPAddr("tcp", addr)
if err != nil {
return
}
conn, err := net.DialTCP("tcp", nil, servertAddr)
if err != nil {
return
}
c = NewConn(conn)
return c, nil
}
func ConnectServerByHttpProxy(httpProxy string, serverAddr string) (c *Conn, err error) {
var proxyUrl *url.URL
if proxyUrl, err = url.Parse(httpProxy); err != nil {
return
}
var proxyAuth string
if proxyUrl.User != nil {
proxyAuth = "Basic " + base64.StdEncoding.EncodeToString([]byte(proxyUrl.User.String()))
}
if proxyUrl.Scheme != "http" {
err = fmt.Errorf("Proxy URL scheme must be http, not [%s]", proxyUrl.Scheme)
return
}
if c, err = ConnectServer(proxyUrl.Host); err != nil {
return
}
req, err := http.NewRequest("CONNECT", "http://"+serverAddr, nil)
if err != nil {
return
}
if proxyAuth != "" {
req.Header.Set("Proxy-Authorization", proxyAuth)
}
req.Header.Set("User-Agent", "Mozilla/5.0")
req.Write(c.TcpConn)
resp, err := http.ReadResponse(bufio.NewReader(c), req)
if err != nil {
return
}
resp.Body.Close()
if resp.StatusCode != 200 {
err = fmt.Errorf("ConnectServer using proxy error, StatusCode [%d]", resp.StatusCode)
return
}
return
}
// if the tcpConn is different with c.TcpConn
// you should call c.Close() first
func (c *Conn) SetTcpConn(tcpConn net.Conn) {
c.mutex.Lock()
defer c.mutex.Unlock()
c.TcpConn = tcpConn
c.closeFlag = false
c.Reader = bufio.NewReader(c.TcpConn)
}
func (c *Conn) GetRemoteAddr() (addr string) {
return c.TcpConn.RemoteAddr().String()
}
func (c *Conn) GetLocalAddr() (addr string) {
return c.TcpConn.LocalAddr().String()
}
func (c *Conn) Read(p []byte) (n int, err error) {
c.mutex.RLock()
if c.buffer == nil {
c.mutex.RUnlock()
return c.Reader.Read(p)
}
c.mutex.RUnlock()
n, err = c.buffer.Read(p)
if err == io.EOF {
c.mutex.Lock()
c.buffer = nil
c.mutex.Unlock()
var n2 int
n2, err = c.Reader.Read(p[n:])
n += n2
}
return
}
func (c *Conn) ReadLine() (buff string, err error) {
buff, err = c.Reader.ReadString('\n')
if err != nil {
// wsarecv error in windows means connection closed?
if err == io.EOF || strings.Contains(err.Error(), "wsarecv") {
c.mutex.Lock()
c.closeFlag = true
c.mutex.Unlock()
}
}
return buff, err
}
func (c *Conn) Write(content []byte) (n int, err error) {
n, err = c.TcpConn.Write(content)
return
}
func (c *Conn) WriteString(content string) (err error) {
_, err = c.TcpConn.Write([]byte(content))
return err
}
func (c *Conn) AppendReaderBuffer(content []byte) {
c.mutex.Lock()
defer c.mutex.Unlock()
if c.buffer == nil {
c.buffer = bytes.NewBuffer(make([]byte, 0, 2048))
}
c.buffer.Write(content)
}
func (c *Conn) SetDeadline(t time.Time) error {
return c.TcpConn.SetDeadline(t)
}
func (c *Conn) SetReadDeadline(t time.Time) error {
return c.TcpConn.SetReadDeadline(t)
}
func (c *Conn) Close() error {
c.mutex.Lock()
defer c.mutex.Unlock()
if c.TcpConn != nil && c.closeFlag == false {
c.closeFlag = true
c.TcpConn.Close()
}
return nil
}
func (c *Conn) IsClosed() (closeFlag bool) {
c.mutex.RLock()
defer c.mutex.RUnlock()
closeFlag = c.closeFlag
return
}
// when you call this function, you should make sure that
// no bytes were read before
func (c *Conn) CheckClosed() bool {
c.mutex.RLock()
if c.closeFlag {
c.mutex.RUnlock()
return true
}
c.mutex.RUnlock()
tmp := pool.GetBuf(2048)
defer pool.PutBuf(tmp)
err := c.TcpConn.SetReadDeadline(time.Now().Add(time.Millisecond))
if err != nil {
c.Close()
return true
}
n, err := c.TcpConn.Read(tmp)
if err == io.EOF {
return true
}
var tmp2 []byte = make([]byte, 1)
err = c.TcpConn.SetReadDeadline(time.Now().Add(time.Millisecond))
if err != nil {
c.Close()
return true
}
n2, err := c.TcpConn.Read(tmp2)
if err == io.EOF {
return true
}
err = c.TcpConn.SetReadDeadline(time.Time{})
if err != nil {
c.Close()
return true
}
if n > 0 {
c.AppendReaderBuffer(tmp[:n])
}
if n2 > 0 {
c.AppendReaderBuffer(tmp2[:n2])
}
return false
}

139
src/utils/pcrypto/pcrypto.go

@ -1,139 +0,0 @@
// Copyright 2016 fatedier, fatedier@gmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package pcrypto
import (
"bytes"
"compress/gzip"
"crypto/aes"
"crypto/cipher"
"crypto/md5"
"crypto/rand"
"encoding/hex"
"fmt"
"io"
"io/ioutil"
)
type Pcrypto struct {
pkey []byte
paes cipher.Block
}
func (pc *Pcrypto) Init(key []byte) error {
var err error
pc.pkey = pkKeyPadding(key)
pc.paes, err = aes.NewCipher(pc.pkey)
return err
}
func (pc *Pcrypto) Encrypt(src []byte) ([]byte, error) {
// aes
src = pKCS5Padding(src, aes.BlockSize)
ciphertext := make([]byte, aes.BlockSize+len(src))
// The IV needs to be unique, but not secure. Therefore it's common to
// include it at the beginning of the ciphertext.
iv := ciphertext[:aes.BlockSize]
if _, err := io.ReadFull(rand.Reader, iv); err != nil {
return nil, err
}
blockMode := cipher.NewCBCEncrypter(pc.paes, iv)
blockMode.CryptBlocks(ciphertext[aes.BlockSize:], src)
return ciphertext, nil
}
func (pc *Pcrypto) Decrypt(str []byte) ([]byte, error) {
// aes
ciphertext, err := hex.DecodeString(fmt.Sprintf("%x", str))
if err != nil {
return nil, err
}
if len(ciphertext) < aes.BlockSize {
return nil, fmt.Errorf("ciphertext too short")
}
iv := ciphertext[:aes.BlockSize]
ciphertext = ciphertext[aes.BlockSize:]
if len(ciphertext)%aes.BlockSize != 0 {
return nil, fmt.Errorf("crypto/cipher: ciphertext is not a multiple of the block size")
}
blockMode := cipher.NewCBCDecrypter(pc.paes, iv)
blockMode.CryptBlocks(ciphertext, ciphertext)
return pKCS5UnPadding(ciphertext), nil
}
func (pc *Pcrypto) Compression(src []byte) ([]byte, error) {
var zbuf bytes.Buffer
zwr, err := gzip.NewWriterLevel(&zbuf, gzip.DefaultCompression)
if err != nil {
return nil, err
}
defer zwr.Close()
zwr.Write(src)
zwr.Flush()
return zbuf.Bytes(), nil
}
func (pc *Pcrypto) Decompression(src []byte) ([]byte, error) {
zbuf := bytes.NewBuffer(src)
zrd, err := gzip.NewReader(zbuf)
if err != nil {
return nil, err
}
defer zrd.Close()
str, _ := ioutil.ReadAll(zrd)
return str, nil
}
func pkKeyPadding(key []byte) []byte {
l := len(key)
if l == 16 || l == 24 || l == 32 {
return key
}
if l < 16 {
return append(key, bytes.Repeat([]byte{byte(0)}, 16-l)...)
} else if l < 24 {
return append(key, bytes.Repeat([]byte{byte(0)}, 24-l)...)
} else if l < 32 {
return append(key, bytes.Repeat([]byte{byte(0)}, 32-l)...)
} else {
md5Ctx := md5.New()
md5Ctx.Write(key)
md5Str := md5Ctx.Sum(nil)
return []byte(hex.EncodeToString(md5Str))
}
}
func pKCS5Padding(ciphertext []byte, blockSize int) []byte {
padding := blockSize - len(ciphertext)%blockSize
padtext := bytes.Repeat([]byte{byte(padding)}, padding)
return append(ciphertext, padtext...)
}
func pKCS5UnPadding(origData []byte) []byte {
length := len(origData)
unpadding := int(origData[length-1])
return origData[:(length - unpadding)]
}
func GetAuthKey(str string) (authKey string) {
md5Ctx := md5.New()
md5Ctx.Write([]byte(str))
md5Str := md5Ctx.Sum(nil)
return hex.EncodeToString(md5Str)
}

77
src/utils/pcrypto/pcrypto_test.go

@ -1,77 +0,0 @@
// Copyright 2016 fatedier, fatedier@gmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package pcrypto
import (
"testing"
)
var (
pp *Pcrypto
)
func init() {
pp = &Pcrypto{}
pp.Init([]byte("12234567890123451223456789012345321:wq"))
}
func TestEncrypt(t *testing.T) {
testStr := "Test Encrypt!"
res, err := pp.Encrypt([]byte(testStr))
if err != nil {
t.Fatalf("encrypt error: %v", err)
}
res, err = pp.Decrypt([]byte(res))
if err != nil {
t.Fatalf("decrypt error: %v", err)
}
if string(res) != testStr {
t.Fatalf("test encrypt error, from [%s] to [%s]", testStr, string(res))
}
}
func TestCompression(t *testing.T) {
testStr := "Test Compression!"
res, err := pp.Compression([]byte(testStr))
if err != nil {
t.Fatalf("compression error: %v", err)
}
res, err = pp.Decompression(res)
if err != nil {
t.Fatalf("decompression error: %v", err)
}
if string(res) != testStr {
t.Fatalf("test compression error, from [%s] to [%s]", testStr, string(res))
}
}
func BenchmarkEncrypt(b *testing.B) {
testStr := "Test Encrypt!"
for i := 0; i < b.N; i++ {
pp.Encrypt([]byte(testStr))
}
}
func BenchmarkDecrypt(b *testing.B) {
testStr := "Test Encrypt!"
res, _ := pp.Encrypt([]byte(testStr))
for i := 0; i < b.N; i++ {
pp.Decrypt([]byte(res))
}
}

12
test/echo_server.go

@ -1,10 +1,11 @@
package main
import (
"bufio"
"fmt"
"io"
"github.com/fatedier/frp/src/utils/conn"
"github.com/fatedier/frp/utils/net"
)
var (
@ -12,7 +13,7 @@ var (
)
func main() {
l, err := conn.Listen("0.0.0.0", PORT)
l, err := net.ListenTcp("127.0.0.1", PORT)
if err != nil {
fmt.Printf("echo server listen error: %v\n", err)
return
@ -29,9 +30,10 @@ func main() {
}
}
func echoWorker(c *conn.Conn) {
func echoWorker(c net.Conn) {
br := bufio.NewReader(c)
for {
buff, err := c.ReadLine()
buf, err := br.ReadString('\n')
if err == io.EOF {
break
}
@ -40,6 +42,6 @@ func echoWorker(c *conn.Conn) {
return
}
c.WriteString(buff)
c.Write([]byte(buf + "\n"))
}
}

15
test/func_test.go

@ -1,6 +1,7 @@
package test
import (
"bufio"
"fmt"
"io/ioutil"
"net/http"
@ -8,7 +9,7 @@ import (
"testing"
"time"
"github.com/fatedier/frp/src/utils/conn"
"github.com/fatedier/frp/utils/net"
)
var (
@ -19,22 +20,24 @@ var (
)
func TestEchoServer(t *testing.T) {
c, err := conn.ConnectServer(fmt.Sprintf("0.0.0.0:%d", ECHO_PORT))
c, err := net.ConnectTcpServer(fmt.Sprintf("127.0.0.1:%d", ECHO_PORT))
if err != nil {
t.Fatalf("connect to echo server error: %v", err)
}
timer := time.Now().Add(time.Duration(5) * time.Second)
c.SetDeadline(timer)
c.WriteString(ECHO_TEST_STR)
c.Write([]byte(ECHO_TEST_STR))
c.Write('\n')
buff, err := c.ReadLine()
br := bufio.NewReader(c)
buf, err := br.ReadString('\n')
if err != nil {
t.Fatalf("read from echo server error: %v", err)
}
if ECHO_TEST_STR != buff {
t.Fatalf("content error, send [%s], get [%s]", strings.Trim(ECHO_TEST_STR, "\n"), strings.Trim(buff, "\n"))
if ECHO_TEST_STR != buf {
t.Fatalf("content error, send [%s], get [%s]", strings.Trim(ECHO_TEST_STR, "\n"), strings.Trim(buf, "\n"))
}
}

52
utils/crypto/crypto_test.go

@ -0,0 +1,52 @@
// Copyright 2017 fatedier, fatedier@gmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package crypto
import (
"bytes"
"io"
"testing"
"github.com/stretchr/testify/assert"
)
func TestWriter(t *testing.T) {
// Empty key.
assert := assert.New(t)
key := ""
buffer := bytes.NewBuffer(nil)
_, err := NewWriter(buffer, []byte(key))
assert.NoError(err)
}
func TestCrypto(t *testing.T) {
assert := assert.New(t)
text := "1234567890abcdefghigklmnopqrstuvwxyzeeeeeeeeeeeeeeeeeeeeeewwwwwwwwwwwwwwwwwwwwwwwwwwzzzzzzzzzzzzzzzzzzzzzzzzdddddddddddddddddddddddddddddddddddddrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrllllllllllllllllllllllllllllllllllqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeewwwwwwwwwwwwwwwwwwwwww"
key := "123456"
buffer := bytes.NewBuffer(nil)
encWriter, err := NewWriter(buffer, []byte(key))
assert.NoError(err)
encWriter.Write([]byte(text))
decReader, err := NewReader(buffer, []byte(key))
assert.NoError(err)
c := bytes.NewBuffer(nil)
io.Copy(c, decReader)
assert.Equal(text, string(c.Bytes()))
}

75
utils/crypto/decode.go

@ -0,0 +1,75 @@
// Copyright 2017 fatedier, fatedier@gmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package crypto
import (
"crypto/aes"
"crypto/cipher"
"crypto/sha1"
"io"
"golang.org/x/crypto/pbkdf2"
)
// NewReader returns a new Reader that decrypts bytes from r
func NewReader(r io.Reader, key []byte) (*Reader, error) {
key = pbkdf2.Key(key, []byte(salt), 64, aes.BlockSize, sha1.New)
return &Reader{
r: r,
key: key,
}, nil
}
// Reader is an io.Reader that can read encrypted bytes.
// Now it only supports aes-128-cfb.
type Reader struct {
r io.Reader
dec *cipher.StreamReader
key []byte
iv []byte
err error
}
// Read satisfies the io.Reader interface.
func (r *Reader) Read(p []byte) (nRet int, errRet error) {
if r.err != nil {
return 0, r.err
}
if r.dec == nil {
iv := make([]byte, aes.BlockSize)
if _, errRet = io.ReadFull(r.r, iv); errRet != nil {
return
}
r.iv = iv
block, err := aes.NewCipher(r.key)
if err != nil {
errRet = err
return
}
r.dec = &cipher.StreamReader{
S: cipher.NewCFBDecrypter(block, iv),
R: r.r,
}
}
nRet, errRet = r.dec.Read(p)
if errRet != nil {
r.err = errRet
}
return
}

93
utils/crypto/encode.go

@ -0,0 +1,93 @@
// Copyright 2017 fatedier, fatedier@gmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package crypto
import (
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"crypto/sha1"
"io"
"golang.org/x/crypto/pbkdf2"
)
const (
salt = "frp"
)
// NewWriter returns a new Writer that encrypts bytes to w.
func NewWriter(w io.Writer, key []byte) (*Writer, error) {
key = pbkdf2.Key(key, []byte(salt), 64, aes.BlockSize, sha1.New)
// random iv
iv := make([]byte, aes.BlockSize)
if _, err := io.ReadFull(rand.Reader, iv); err != nil {
return nil, err
}
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
return &Writer{
w: w,
enc: &cipher.StreamWriter{
S: cipher.NewCFBEncrypter(block, iv),
W: w,
},
key: key,
iv: iv,
}, nil
}
// Writer is an io.Writer that can write encrypted bytes.
// Now it only support aes-128-cfb.
type Writer struct {
w io.Writer
enc *cipher.StreamWriter
key []byte
iv []byte
ivSend bool
err error
}
// Write satisfies the io.Writer interface.
func (w *Writer) Write(p []byte) (nRet int, errRet error) {
return w.write(p)
}
func (w *Writer) write(p []byte) (nRet int, errRet error) {
if w.err != nil {
return 0, w.err
}
// When write is first called, iv will be written to w.w
if !w.ivSend {
w.ivSend = true
_, errRet = w.w.Write(w.iv)
if errRet != nil {
w.err = errRet
return
}
}
nRet, errRet = w.enc.Write(p)
if errRet != nil {
w.err = errRet
}
return
}

22
src/utils/conn/udp_conn.go → utils/errors/errors.go

@ -12,18 +12,24 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package conn
package errors
import (
"errors"
"fmt"
"net"
)
func ListenUDP(bindAddr string, bindPort int64) (conn *net.UDPConn, err error) {
udpAddr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", bindAddr, bindPort))
if err != nil {
return conn, err
}
conn, err = net.ListenUDP("udp", udpAddr)
var (
ErrMsgType = errors.New("message type error")
)
func PanicToError(fn func()) (err error) {
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("Panic error: %v", r)
}
}()
fn()
return
}

57
src/utils/log/log.go → utils/log/log.go

@ -16,13 +16,14 @@ package log
import (
"fmt"
"github.com/astaxie/beego/logs"
"github.com/fatedier/beego/logs"
)
var Log *logs.BeeLogger
func init() {
Log = logs.NewLogger(1000)
Log = logs.NewLogger(100)
Log.EnableFuncCallDepth(true)
Log.SetLogFuncCallDepth(Log.GetLogFuncCallDepth() + 1)
}
@ -61,6 +62,7 @@ func SetLogLevel(logLevel string) {
}
// wrap log
func Error(format string, v ...interface{}) {
Log.Error(format, v...)
}
@ -76,3 +78,54 @@ func Info(format string, v ...interface{}) {
func Debug(format string, v ...interface{}) {
Log.Debug(format, v...)
}
// Logger
type Logger interface {
AddLogPrefix(string)
ClearLogPrefix()
Error(string, ...interface{})
Warn(string, ...interface{})
Info(string, ...interface{})
Debug(string, ...interface{})
}
type PrefixLogger struct {
prefix string
}
func NewPrefixLogger(prefix string) *PrefixLogger {
logger := &PrefixLogger{}
logger.AddLogPrefix(prefix)
return logger
}
func (pl *PrefixLogger) AddLogPrefix(prefix string) {
if len(prefix) == 0 {
return
}
if len(pl.prefix) > 0 {
pl.prefix += " "
}
pl.prefix += "[" + prefix + "] "
}
func (pl *PrefixLogger) ClearLogPrefix() {
pl.prefix = ""
}
func (pl *PrefixLogger) Error(format string, v ...interface{}) {
Log.Error(pl.prefix+format, v...)
}
func (pl *PrefixLogger) Warn(format string, v ...interface{}) {
Log.Warn(pl.prefix+format, v...)
}
func (pl *PrefixLogger) Info(format string, v ...interface{}) {
Log.Info(pl.prefix+format, v...)
}
func (pl *PrefixLogger) Debug(format string, v ...interface{}) {
Log.Debug(pl.prefix+format, v...)
}

30
src/models/config/config.go → utils/net/conn.go

@ -12,19 +12,21 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package config
package net
type BaseConf struct {
Name string
AuthToken string
Type string
UseEncryption bool
UseGzip bool
PrivilegeMode bool
PrivilegeToken string
PoolCount int64
HostHeaderRewrite string
HttpUserName string
HttpPassWord string
SubDomain string
import (
"net"
"github.com/fatedier/frp/utils/log"
)
// Conn is the interface of connections used in frp.
type Conn interface {
net.Conn
log.Logger
}
type Listener interface {
Accept() (Conn, error)
Close() error
}

162
utils/net/tcp.go

@ -0,0 +1,162 @@
// Copyright 2016 fatedier, fatedier@gmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package net
import (
"bufio"
"encoding/base64"
"fmt"
"net"
"net/http"
"net/url"
"github.com/fatedier/frp/utils/log"
)
type TcpListener struct {
net.Addr
listener net.Listener
accept chan Conn
closeFlag bool
}
func ListenTcp(bindAddr string, bindPort int64) (l *TcpListener, err error) {
tcpAddr, err := net.ResolveTCPAddr("tcp", fmt.Sprintf("%s:%d", bindAddr, bindPort))
if err != nil {
return l, err
}
listener, err := net.ListenTCP("tcp", tcpAddr)
if err != nil {
return l, err
}
l = &TcpListener{
Addr: listener.Addr(),
listener: listener,
accept: make(chan Conn),
closeFlag: false,
}
go func() {
for {
conn, err := listener.AcceptTCP()
if err != nil {
if l.closeFlag {
close(l.accept)
return
}
continue
}
c := NewTcpConn(conn)
l.accept <- c
}
}()
return l, err
}
// Wait util get one new connection or listener is closed
// if listener is closed, err returned.
func (l *TcpListener) Accept() (Conn, error) {
conn, ok := <-l.accept
if !ok {
return conn, fmt.Errorf("channel for tcp listener closed")
}
return conn, nil
}
func (l *TcpListener) Close() error {
if !l.closeFlag {
l.closeFlag = true
l.listener.Close()
}
return nil
}
// Wrap for TCPConn.
type TcpConn struct {
net.Conn
log.Logger
}
func NewTcpConn(conn *net.TCPConn) (c *TcpConn) {
c = &TcpConn{
Conn: conn,
Logger: log.NewPrefixLogger(""),
}
return
}
func ConnectTcpServer(addr string) (c Conn, err error) {
servertAddr, err := net.ResolveTCPAddr("tcp", addr)
if err != nil {
return
}
conn, err := net.DialTCP("tcp", nil, servertAddr)
if err != nil {
return
}
c = NewTcpConn(conn)
return
}
// ConnectTcpServerByHttpProxy try to connect remote server by http proxy.
// If httpProxy is empty, it will connect server directly.
func ConnectTcpServerByHttpProxy(httpProxy string, serverAddr string) (c Conn, err error) {
if httpProxy == "" {
return ConnectTcpServer(serverAddr)
}
var proxyUrl *url.URL
if proxyUrl, err = url.Parse(httpProxy); err != nil {
return
}
var proxyAuth string
if proxyUrl.User != nil {
proxyAuth = "Basic " + base64.StdEncoding.EncodeToString([]byte(proxyUrl.User.String()))
}
if proxyUrl.Scheme != "http" {
err = fmt.Errorf("Proxy URL scheme must be http, not [%s]", proxyUrl.Scheme)
return
}
if c, err = ConnectTcpServer(proxyUrl.Host); err != nil {
return
}
req, err := http.NewRequest("CONNECT", "http://"+serverAddr, nil)
if err != nil {
return
}
if proxyAuth != "" {
req.Header.Set("Proxy-Authorization", proxyAuth)
}
req.Header.Set("User-Agent", "Mozilla/5.0")
req.Write(c)
resp, err := http.ReadResponse(bufio.NewReader(c), req)
if err != nil {
return
}
resp.Body.Close()
if resp.StatusCode != 200 {
err = fmt.Errorf("ConnectTcpServer using proxy error, StatusCode [%d]", resp.StatusCode)
return
}
return
}

243
utils/net/udp.go

@ -0,0 +1,243 @@
// Copyright 2017 fatedier, fatedier@gmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package net
import (
"fmt"
"io"
"net"
"sync"
"time"
flog "github.com/fatedier/frp/utils/log"
"github.com/fatedier/frp/utils/pool"
)
type UdpPacket struct {
Buf []byte
LocalAddr net.Addr
RemoteAddr net.Addr
}
type FakeUdpConn struct {
flog.Logger
l *UdpListener
localAddr net.Addr
remoteAddr net.Addr
packets chan []byte
closeFlag bool
lastActive time.Time
mu sync.RWMutex
}
func NewFakeUdpConn(l *UdpListener, laddr, raddr net.Addr) *FakeUdpConn {
fc := &FakeUdpConn{
Logger: flog.NewPrefixLogger(""),
l: l,
localAddr: laddr,
remoteAddr: raddr,
packets: make(chan []byte, 20),
}
go func() {
for {
time.Sleep(5 * time.Second)
fc.mu.RLock()
if time.Now().Sub(fc.lastActive) > 10*time.Second {
fc.mu.RUnlock()
fc.Close()
break
}
fc.mu.RUnlock()
}
}()
return fc
}
func (c *FakeUdpConn) putPacket(content []byte) {
defer func() {
if err := recover(); err != nil {
}
}()
select {
case c.packets <- content:
default:
}
}
func (c *FakeUdpConn) Read(b []byte) (n int, err error) {
content, ok := <-c.packets
if !ok {
return 0, io.EOF
}
c.mu.Lock()
c.lastActive = time.Now()
c.mu.Unlock()
if len(b) < len(content) {
n = len(b)
} else {
n = len(content)
}
copy(b, content)
return n, nil
}
func (c *FakeUdpConn) Write(b []byte) (n int, err error) {
c.mu.RLock()
if c.closeFlag {
c.mu.RUnlock()
return 0, io.ErrClosedPipe
}
c.mu.RUnlock()
packet := &UdpPacket{
Buf: b,
LocalAddr: c.localAddr,
RemoteAddr: c.remoteAddr,
}
c.l.writeUdpPacket(packet)
c.mu.Lock()
c.lastActive = time.Now()
c.mu.Unlock()
return len(b), nil
}
func (c *FakeUdpConn) Close() error {
c.mu.Lock()
defer c.mu.Unlock()
if !c.closeFlag {
c.closeFlag = true
close(c.packets)
}
return nil
}
func (c *FakeUdpConn) IsClosed() bool {
c.mu.RLock()
defer c.mu.RUnlock()
return c.closeFlag
}
func (c *FakeUdpConn) LocalAddr() net.Addr {
return c.localAddr
}
func (c *FakeUdpConn) RemoteAddr() net.Addr {
return c.remoteAddr
}
func (c *FakeUdpConn) SetDeadline(t time.Time) error {
return nil
}
func (c *FakeUdpConn) SetReadDeadline(t time.Time) error {
return nil
}
func (c *FakeUdpConn) SetWriteDeadline(t time.Time) error {
return nil
}
type UdpListener struct {
net.Addr
accept chan Conn
writeCh chan *UdpPacket
readConn net.Conn
closeFlag bool
fakeConns map[string]*FakeUdpConn
}
func ListenUDP(bindAddr string, bindPort int64) (l *UdpListener, err error) {
udpAddr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", bindAddr, bindPort))
if err != nil {
return l, err
}
readConn, err := net.ListenUDP("udp", udpAddr)
l = &UdpListener{
Addr: udpAddr,
accept: make(chan Conn),
writeCh: make(chan *UdpPacket, 1000),
fakeConns: make(map[string]*FakeUdpConn),
}
// for reading
go func() {
for {
buf := pool.GetBuf(1450)
n, remoteAddr, err := readConn.ReadFromUDP(buf)
if err != nil {
close(l.accept)
close(l.writeCh)
return
}
fakeConn, exist := l.fakeConns[remoteAddr.String()]
if !exist || fakeConn.IsClosed() {
fakeConn = NewFakeUdpConn(l, l.Addr, remoteAddr)
l.fakeConns[remoteAddr.String()] = fakeConn
}
fakeConn.putPacket(buf[:n])
l.accept <- fakeConn
}
}()
// for writing
go func() {
for {
packet, ok := <-l.writeCh
if !ok {
return
}
if addr, ok := packet.RemoteAddr.(*net.UDPAddr); ok {
readConn.WriteToUDP(packet.Buf, addr)
}
}
}()
return
}
func (l *UdpListener) writeUdpPacket(packet *UdpPacket) {
defer func() {
if err := recover(); err != nil {
}
}()
l.writeCh <- packet
}
func (l *UdpListener) Accept() (Conn, error) {
conn, ok := <-l.accept
if !ok {
return conn, fmt.Errorf("channel for udp listener closed")
}
return conn, nil
}
func (l *UdpListener) Close() error {
if !l.closeFlag {
l.closeFlag = true
l.readConn.Close()
}
return nil
}

27
utils/net/udp_test.go

@ -0,0 +1,27 @@
package net
import (
"fmt"
"testing"
)
func TestA(t *testing.T) {
l, err := ListenUDP("0.0.0.0", 9000)
if err != nil {
fmt.Println(err)
}
for {
c, _ := l.Accept()
go func() {
for {
buf := make([]byte, 1450)
n, err := c.Read(buf)
if err != nil {
fmt.Println(buf[:n])
}
c.Write(buf[:n])
}
}()
}
}

0
src/utils/pool/pool.go → utils/pool/pool.go

51
utils/pool/pool_test.go

@ -0,0 +1,51 @@
// Copyright 2016 fatedier, fatedier@gmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package pool
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestPutBuf(t *testing.T) {
buf := make([]byte, 512)
PutBuf(buf)
buf = make([]byte, 1025)
PutBuf(buf)
buf = make([]byte, 2*1025)
PutBuf(buf)
buf = make([]byte, 5*1025)
PutBuf(buf)
}
func TestGetBuf(t *testing.T) {
assert := assert.New(t)
buf := GetBuf(200)
assert.Len(buf, 200)
buf = GetBuf(1025)
assert.Len(buf, 1025)
buf = GetBuf(2 * 1024)
assert.Len(buf, 2*1024)
buf = GetBuf(5 * 2000)
assert.Len(buf, 5*2000)
}

62
utils/shutdown/shutdown.go

@ -0,0 +1,62 @@
// Copyright 2017 fatedier, fatedier@gmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package shutdown
import (
"sync"
)
type Shutdown struct {
doing bool
ending bool
start chan struct{}
down chan struct{}
mu sync.Mutex
}
func New() *Shutdown {
return &Shutdown{
doing: false,
ending: false,
start: make(chan struct{}),
down: make(chan struct{}),
}
}
func (s *Shutdown) Start() {
s.mu.Lock()
defer s.mu.Unlock()
if !s.doing {
s.doing = true
close(s.start)
}
}
func (s *Shutdown) WaitStart() {
<-s.start
}
func (s *Shutdown) Done() {
s.mu.Lock()
defer s.mu.Unlock()
if !s.ending {
s.ending = true
close(s.down)
}
}
func (s *Shutdown) WaitDown() {
<-s.down
}

21
utils/shutdown/shutdown_test.go

@ -0,0 +1,21 @@
package shutdown
import (
"testing"
"time"
)
func TestShutdown(t *testing.T) {
s := New()
go func() {
time.Sleep(time.Millisecond)
s.Start()
}()
s.WaitStart()
go func() {
time.Sleep(time.Millisecond)
s.Done()
}()
s.WaitDown()
}

47
utils/util/util.go

@ -0,0 +1,47 @@
// Copyright 2017 fatedier, fatedier@gmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package util
import (
"crypto/md5"
"crypto/rand"
"encoding/hex"
"fmt"
)
// RandId return a rand string used in frp.
func RandId() (id string, err error) {
return RandIdWithLen(8)
}
// RandIdWithLen return a rand string with idLen length.
func RandIdWithLen(idLen int) (id string, err error) {
b := make([]byte, idLen)
_, err = rand.Read(b)
if err != nil {
return
}
id = fmt.Sprintf("%x", b)
return
}
func GetAuthKey(token string, timestamp int64) (key string) {
token = token + fmt.Sprintf("%d", timestamp)
md5Ctx := md5.New()
md5Ctx.Write([]byte(token))
data := md5Ctx.Sum(nil)
return hex.EncodeToString(data)
}

22
utils/util/util_test.go

@ -0,0 +1,22 @@
package util
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestRandId(t *testing.T) {
assert := assert.New(t)
id, err := RandId()
assert.NoError(err)
t.Log(id)
assert.Equal(16, len(id))
}
func TestGetAuthKey(t *testing.T) {
assert := assert.New(t)
key := GetAuthKey("1234", 1488720000)
t.Log(key)
assert.Equal("6df41a43725f0c770fd56379e12acf8c", key)
}

42
src/utils/version/version.go → utils/version/version.go

@ -19,7 +19,7 @@ import (
"strings"
)
var version string = "0.9.3"
var version string = "0.10.0"
func Full() string {
return version
@ -27,7 +27,7 @@ func Full() string {
func Proto(v string) int64 {
arr := strings.Split(v, ".")
if len(arr) < 2 {
if len(arr) < 3 {
return 0
}
res, _ := strconv.ParseInt(arr[0], 10, 64)
@ -36,7 +36,7 @@ func Proto(v string) int64 {
func Major(v string) int64 {
arr := strings.Split(v, ".")
if len(arr) < 2 {
if len(arr) < 3 {
return 0
}
res, _ := strconv.ParseInt(arr[1], 10, 64)
@ -45,7 +45,7 @@ func Major(v string) int64 {
func Minor(v string) int64 {
arr := strings.Split(v, ".")
if len(arr) < 2 {
if len(arr) < 3 {
return 0
}
res, _ := strconv.ParseInt(arr[2], 10, 64)
@ -53,6 +53,36 @@ func Minor(v string) int64 {
}
// add every case there if server will not accept client's protocol and return false
func Compat(client string, server string) bool {
return true
func Compat(client string) (ok bool, msg string) {
if LessThan(client, version) {
return false, "Please upgrade your frpc version to 0.10.0"
}
return true, ""
}
func LessThan(client string, server string) bool {
vc := Proto(client)
vs := Proto(server)
if vc > vs {
return false
} else if vc < vs {
return true
}
vc = Major(client)
vs = Major(server)
if vc > vs {
return false
} else if vc < vs {
return true
}
vc = Minor(client)
vs = Minor(server)
if vc > vs {
return false
} else if vc < vs {
return true
}
return false
}

39
src/utils/version/version_test.go → utils/version/version_test.go

@ -19,38 +19,47 @@ import (
"strconv"
"strings"
"testing"
"github.com/stretchr/testify/assert"
)
func TestFull(t *testing.T) {
assert := assert.New(t)
version := Full()
arr := strings.Split(version, ".")
if len(arr) != 3 {
t.Fatalf("Version string error: %s", version)
}
assert.Equal(3, len(arr))
proto, err := strconv.ParseInt(arr[0], 10, 64)
if err != nil || proto < 0 {
t.Fatalf("Version proto error")
}
assert.NoError(err)
assert.True(proto >= 0)
major, err := strconv.ParseInt(arr[1], 10, 64)
if err != nil || major < 0 {
t.Fatalf("Version major error")
}
assert.NoError(err)
assert.True(major >= 0)
minor, err := strconv.ParseInt(arr[2], 10, 64)
if err != nil || minor < 0 {
t.Fatalf("Version minor error")
}
assert.NoError(err)
assert.True(minor >= 0)
}
func TestVersion(t *testing.T) {
assert := assert.New(t)
proto := Proto(Full())
major := Major(Full())
minor := Minor(Full())
parseVerion := fmt.Sprintf("%d.%d.%d", proto, major, minor)
version := Full()
if parseVerion != version {
t.Fatalf("Get version incorrect, version [%s], parseVerion [%s]", version, parseVerion)
}
assert.Equal(parseVerion, version)
}
func TestCompact(t *testing.T) {
assert := assert.New(t)
ok, _ := Compat("0.9.0")
assert.False(ok)
ok, _ = Compat("10.0.0")
assert.True(ok)
ok, _ = Compat("0.10.0")
assert.False(ok)
}

19
src/utils/vhost/http.go → utils/vhost/http.go

@ -20,23 +20,22 @@ import (
"encoding/base64"
"fmt"
"io"
"net"
"net/http"
"net/url"
"strings"
"time"
"github.com/fatedier/frp/src/utils/conn"
"github.com/fatedier/frp/src/utils/pool"
frpNet "github.com/fatedier/frp/utils/net"
"github.com/fatedier/frp/utils/pool"
)
type HttpMuxer struct {
*VhostMuxer
}
func GetHttpRequestInfo(c *conn.Conn) (_ net.Conn, _ map[string]string, err error) {
func GetHttpRequestInfo(c frpNet.Conn) (_ frpNet.Conn, _ map[string]string, err error) {
reqInfoMap := make(map[string]string, 0)
sc, rd := newShareConn(c.TcpConn)
sc, rd := newShareConn(c)
request, err := http.ReadRequest(bufio.NewReader(rd))
if err != nil {
@ -57,13 +56,13 @@ func GetHttpRequestInfo(c *conn.Conn) (_ net.Conn, _ map[string]string, err erro
return sc, reqInfoMap, nil
}
func NewHttpMuxer(listener *conn.Listener, timeout time.Duration) (*HttpMuxer, error) {
func NewHttpMuxer(listener frpNet.Listener, timeout time.Duration) (*HttpMuxer, error) {
mux, err := NewVhostMuxer(listener, GetHttpRequestInfo, HttpAuthFunc, HttpHostNameRewrite, timeout)
return &HttpMuxer{mux}, err
}
func HttpHostNameRewrite(c *conn.Conn, rewriteHost string) (_ net.Conn, err error) {
sc, rd := newShareConn(c.TcpConn)
func HttpHostNameRewrite(c frpNet.Conn, rewriteHost string) (_ frpNet.Conn, err error) {
sc, rd := newShareConn(c)
var buff []byte
if buff, err = hostNameRewrite(rd, rewriteHost); err != nil {
return sc, err
@ -181,11 +180,11 @@ func changeHostName(buff *bytes.Buffer, rewriteHost string) (_ []byte, err error
return retBuf.Bytes(), err
}
func HttpAuthFunc(c *conn.Conn, userName, passWord, authorization string) (bAccess bool, err error) {
func HttpAuthFunc(c frpNet.Conn, userName, passWord, authorization string) (bAccess bool, err error) {
s := strings.SplitN(authorization, " ", 2)
if len(s) != 2 {
res := noAuthResponse()
res.Write(c.TcpConn)
res.Write(c)
return
}
b, err := base64.StdEncoding.DecodeString(s[1])

11
src/utils/vhost/https.go → utils/vhost/https.go

@ -17,12 +17,11 @@ package vhost
import (
"fmt"
"io"
"net"
"strings"
"time"
"github.com/fatedier/frp/src/utils/conn"
"github.com/fatedier/frp/src/utils/pool"
frpNet "github.com/fatedier/frp/utils/net"
"github.com/fatedier/frp/utils/pool"
)
const (
@ -47,7 +46,7 @@ type HttpsMuxer struct {
*VhostMuxer
}
func NewHttpsMuxer(listener *conn.Listener, timeout time.Duration) (*HttpsMuxer, error) {
func NewHttpsMuxer(listener frpNet.Listener, timeout time.Duration) (*HttpsMuxer, error) {
mux, err := NewVhostMuxer(listener, GetHttpsHostname, nil, nil, timeout)
return &HttpsMuxer{mux}, err
}
@ -178,9 +177,9 @@ func readHandshake(rd io.Reader) (host string, err error) {
return
}
func GetHttpsHostname(c *conn.Conn) (sc net.Conn, _ map[string]string, err error) {
func GetHttpsHostname(c frpNet.Conn) (sc frpNet.Conn, _ map[string]string, err error) {
reqInfoMap := make(map[string]string, 0)
sc, rd := newShareConn(c.TcpConn)
sc, rd := newShareConn(c)
host, err := readHandshake(rd)
if err != nil {
return sc, reqInfoMap, err

0
src/utils/vhost/router.go → utils/vhost/router.go

31
src/utils/vhost/vhost.go → utils/vhost/vhost.go

@ -16,20 +16,19 @@ import (
"bytes"
"fmt"
"io"
"net"
"strings"
"sync"
"time"
"github.com/fatedier/frp/src/utils/conn"
frpNet "github.com/fatedier/frp/utils/net"
)
type muxFunc func(*conn.Conn) (net.Conn, map[string]string, error)
type httpAuthFunc func(*conn.Conn, string, string, string) (bool, error)
type hostRewriteFunc func(*conn.Conn, string) (net.Conn, error)
type muxFunc func(frpNet.Conn) (frpNet.Conn, map[string]string, error)
type httpAuthFunc func(frpNet.Conn, string, string, string) (bool, error)
type hostRewriteFunc func(frpNet.Conn, string) (frpNet.Conn, error)
type VhostMuxer struct {
listener *conn.Listener
listener frpNet.Listener
timeout time.Duration
vhostFunc muxFunc
authFunc httpAuthFunc
@ -38,7 +37,7 @@ type VhostMuxer struct {
mutex sync.RWMutex
}
func NewVhostMuxer(listener *conn.Listener, vhostFunc muxFunc, authFunc httpAuthFunc, rewriteFunc hostRewriteFunc, timeout time.Duration) (mux *VhostMuxer, err error) {
func NewVhostMuxer(listener frpNet.Listener, vhostFunc muxFunc, authFunc httpAuthFunc, rewriteFunc hostRewriteFunc, timeout time.Duration) (mux *VhostMuxer, err error) {
mux = &VhostMuxer{
listener: listener,
timeout: timeout,
@ -68,7 +67,7 @@ func (v *VhostMuxer) Listen(name, location, rewriteHost, userName, passWord stri
userName: userName,
passWord: passWord,
mux: v,
accept: make(chan *conn.Conn),
accept: make(chan frpNet.Conn),
}
v.registryRouter.Add(name, location, l)
return l, nil
@ -110,7 +109,7 @@ func (v *VhostMuxer) run() {
}
}
func (v *VhostMuxer) handle(c *conn.Conn) {
func (v *VhostMuxer) handle(c frpNet.Conn) {
if err := c.SetDeadline(time.Now().Add(v.timeout)); err != nil {
c.Close()
return
@ -137,7 +136,7 @@ func (v *VhostMuxer) handle(c *conn.Conn) {
bAccess, err := l.mux.authFunc(c, l.userName, l.passWord, reqInfoMap["Authorization"])
if bAccess == false || err != nil {
res := noAuthResponse()
res.Write(c.TcpConn)
res.Write(c)
c.Close()
return
}
@ -147,7 +146,7 @@ func (v *VhostMuxer) handle(c *conn.Conn) {
c.Close()
return
}
c.SetTcpConn(sConn)
c = sConn
l.accept <- c
}
@ -159,10 +158,10 @@ type Listener struct {
userName string
passWord string
mux *VhostMuxer // for closing VhostMuxer
accept chan *conn.Conn
accept chan frpNet.Conn
}
func (l *Listener) Accept() (*conn.Conn, error) {
func (l *Listener) Accept() (frpNet.Conn, error) {
conn, ok := <-l.accept
if !ok {
return nil, fmt.Errorf("Listener closed")
@ -175,7 +174,7 @@ func (l *Listener) Accept() (*conn.Conn, error) {
if err != nil {
return nil, fmt.Errorf("http host header rewrite failed")
}
conn.SetTcpConn(sConn)
conn = sConn
}
return conn, nil
@ -192,13 +191,13 @@ func (l *Listener) Name() string {
}
type sharedConn struct {
net.Conn
frpNet.Conn
sync.Mutex
buff *bytes.Buffer
}
// the bytes you read in io.Reader, will be reserved in sharedConn
func newShareConn(conn net.Conn) (*sharedConn, io.Reader) {
func newShareConn(conn frpNet.Conn) (*sharedConn, io.Reader) {
sc := &sharedConn{
Conn: conn,
buff: bytes.NewBuffer(make([]byte, 0, 1024)),

0
vendor/github.com/astaxie/beego/LICENSE → vendor/github.com/fatedier/beego/LICENSE generated vendored

0
vendor/github.com/astaxie/beego/logs/README.md → vendor/github.com/fatedier/beego/logs/README.md generated vendored

0
vendor/github.com/astaxie/beego/logs/color.go → vendor/github.com/fatedier/beego/logs/color.go generated vendored

0
vendor/github.com/astaxie/beego/logs/color_windows.go → vendor/github.com/fatedier/beego/logs/color_windows.go generated vendored

0
vendor/github.com/astaxie/beego/logs/conn.go → vendor/github.com/fatedier/beego/logs/conn.go generated vendored

0
vendor/github.com/astaxie/beego/logs/console.go → vendor/github.com/fatedier/beego/logs/console.go generated vendored

1
vendor/github.com/astaxie/beego/logs/file.go → vendor/github.com/fatedier/beego/logs/file.go generated vendored

@ -270,6 +270,7 @@ func (w *fileLogWriter) doRotate(logTime time.Time) error {
// Rename the file to its new found name
// even if occurs error,we MUST guarantee to restart new logger
err = os.Rename(w.Filename, fName)
err = os.Chmod(fName, os.FileMode(440))
// re-start logger
RESTART_LOGGER:

78
vendor/github.com/fatedier/beego/logs/jianliao.go generated vendored

@ -0,0 +1,78 @@
package logs
import (
"encoding/json"
"fmt"
"net/http"
"net/url"
"time"
)
// JLWriter implements beego LoggerInterface and is used to send jiaoliao webhook
type JLWriter struct {
AuthorName string `json:"authorname"`
Title string `json:"title"`
WebhookURL string `json:"webhookurl"`
RedirectURL string `json:"redirecturl,omitempty"`
ImageURL string `json:"imageurl,omitempty"`
Level int `json:"level"`
}
// newJLWriter create jiaoliao writer.
func newJLWriter() Logger {
return &JLWriter{Level: LevelTrace}
}
// Init JLWriter with json config string
func (s *JLWriter) Init(jsonconfig string) error {
err := json.Unmarshal([]byte(jsonconfig), s)
if err != nil {
return err
}
return nil
}
// WriteMsg write message in smtp writer.
// it will send an email with subject and only this message.
func (s *JLWriter) WriteMsg(when time.Time, msg string, level int) error {
if level > s.Level {
return nil
}
text := fmt.Sprintf("%s %s", when.Format("2006-01-02 15:04:05"), msg)
form := url.Values{}
form.Add("authorName", s.AuthorName)
form.Add("title", s.Title)
form.Add("text", text)
if s.RedirectURL != "" {
form.Add("redirectUrl", s.RedirectURL)
}
if s.ImageURL != "" {
form.Add("imageUrl", s.ImageURL)
}
resp, err := http.PostForm(s.WebhookURL, form)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("Post webhook failed %s %d", resp.Status, resp.StatusCode)
}
return nil
}
// Flush implementing method. empty.
func (s *JLWriter) Flush() {
return
}
// Destroy implementing method. empty.
func (s *JLWriter) Destroy() {
return
}
func init() {
Register(AdapterJianLiao, newJLWriter)
}

48
vendor/github.com/astaxie/beego/logs/log.go → vendor/github.com/fatedier/beego/logs/log.go generated vendored

@ -66,9 +66,12 @@ const (
AdapterConsole = "console"
AdapterFile = "file"
AdapterMultiFile = "multifile"
AdapterMail = "stmp"
AdapterMail = "smtp"
AdapterConn = "conn"
AdapterEs = "es"
AdapterJianLiao = "jianliao"
AdapterSlack = "slack"
AdapterAliLS = "alils"
)
// Legacy log level constants to ensure backwards compatibility.
@ -260,12 +263,7 @@ func (bl *BeeLogger) writeMsg(logLevel int, msg string, v ...interface{}) error
bl.setLogger(AdapterConsole)
bl.lock.Unlock()
}
if logLevel == levelLoggerImpl {
// set to emergency to ensure all log will be print out correctly
logLevel = LevelEmergency
} else {
msg = levelPrefix[logLevel] + msg
}
if len(v) > 0 {
msg = fmt.Sprintf(msg, v...)
}
@ -275,10 +273,27 @@ func (bl *BeeLogger) writeMsg(logLevel int, msg string, v ...interface{}) error
if !ok {
file = "???"
line = 0
} else {
if strings.Contains(file, "<autogenerated>") {
_, file, line, ok = runtime.Caller(bl.loggerFuncCallDepth + 1)
if !ok {
file = "???"
line = 0
}
}
}
_, filename := path.Split(file)
msg = "[" + filename + ":" + strconv.FormatInt(int64(line), 10) + "] " + msg
}
//set level info in front of filename info
if logLevel == levelLoggerImpl {
// set to emergency to ensure all log will be print out correctly
logLevel = LevelEmergency
} else {
msg = levelPrefix[logLevel] + msg
}
if bl.asynchronous {
lm := logMsgPool.Get().(*logMsg)
lm.level = logLevel
@ -374,7 +389,10 @@ func (bl *BeeLogger) Error(format string, v ...interface{}) {
// Warning Log WARNING level message.
func (bl *BeeLogger) Warning(format string, v ...interface{}) {
bl.Warn(format, v...)
if LevelWarn > bl.level {
return
}
bl.writeMsg(LevelWarn, format, v...)
}
// Notice Log NOTICE level message.
@ -387,7 +405,10 @@ func (bl *BeeLogger) Notice(format string, v ...interface{}) {
// Informational Log INFORMATIONAL level message.
func (bl *BeeLogger) Informational(format string, v ...interface{}) {
bl.Info(format, v...)
if LevelInfo > bl.level {
return
}
bl.writeMsg(LevelInfo, format, v...)
}
// Debug Log DEBUG level message.
@ -419,7 +440,10 @@ func (bl *BeeLogger) Info(format string, v ...interface{}) {
// Trace Log TRACE level message.
// compatibility alias for Debug()
func (bl *BeeLogger) Trace(format string, v ...interface{}) {
bl.Debug(format, v...)
if LevelDebug > bl.level {
return
}
bl.writeMsg(LevelDebug, format, v...)
}
// Flush flush all chan data.
@ -532,10 +556,10 @@ func EnableFuncCallDepth(b bool) {
beeLogger.enableFuncCallDepth = b
}
// SetLogFuncCall set the CallDepth, default is 3
// SetLogFuncCall set the CallDepth, default is 4
func SetLogFuncCall(b bool) {
beeLogger.EnableFuncCallDepth(b)
beeLogger.SetLogFuncCallDepth(3)
beeLogger.SetLogFuncCallDepth(4)
}
// SetLogFuncCallDepth set log funcCallDepth

0
vendor/github.com/astaxie/beego/logs/logger.go → vendor/github.com/fatedier/beego/logs/logger.go generated vendored

0
vendor/github.com/astaxie/beego/logs/multifile.go → vendor/github.com/fatedier/beego/logs/multifile.go generated vendored

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save