mirror of
https://github.com/fatedier/frp.git
synced 2024-12-15 19:21:01 +01:00
commit
fd268b5082
21
.github/ISSUE_TEMPLATE
vendored
Normal file
21
.github/ISSUE_TEMPLATE
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
Issue is only used for submiting bug report and documents typo. If there are same issues or answers can be found in documents, we will close it directly.
|
||||
|
||||
Please answer these questions before submitting your issue. Thanks!
|
||||
|
||||
1. What did you do?
|
||||
If possible, provide a recipe for reproducing the error.
|
||||
|
||||
|
||||
2. What did you expect to see?
|
||||
|
||||
|
||||
|
||||
3. What did you see instead?
|
||||
|
||||
|
||||
|
||||
4. What version of frp are you using (./frpc -v or ./frps -v)?
|
||||
|
||||
|
||||
|
||||
5. Can you point out what caused this issue (optional)?
|
@ -2,9 +2,8 @@ sudo: false
|
||||
language: go
|
||||
|
||||
go:
|
||||
- 1.5.4
|
||||
- 1.6.4
|
||||
- 1.7.4
|
||||
- 1.7.5
|
||||
- 1.8
|
||||
|
||||
install:
|
||||
- make
|
||||
|
@ -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
|
||||
|
||||
|
27
Godeps/Godeps.json
generated
27
Godeps/Godeps.json
generated
@ -1,16 +1,11 @@
|
||||
{
|
||||
"ImportPath": "github.com/fatedier/frp",
|
||||
"GoVersion": "go1.7",
|
||||
"GodepVersion": "v75",
|
||||
"GoVersion": "go1.6",
|
||||
"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,20 @@
|
||||
"Comment": "0.6.2",
|
||||
"Rev": "784ddc588536785e7299f7272f39101f7faccc3f"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/fatedier/beego/logs",
|
||||
"Comment": "v1.7.2-72-gf73c369",
|
||||
"Rev": "f73c3692bbd70a83728cb59b2c0423ff95e4ecea"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/golang/snappy",
|
||||
"Rev": "5979233c5d6225d4a8e438cdd0b411888449ddab"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/julienschmidt/httprouter",
|
||||
"Comment": "v1.1-41-g8a45e95",
|
||||
"Rev": "8a45e95fc75cb77048068a62daed98cc22fdac7c"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/pmezard/go-difflib/difflib",
|
||||
"Comment": "v1.0.0",
|
||||
@ -39,6 +48,10 @@
|
||||
{
|
||||
"ImportPath": "github.com/vaughan0/go-ini",
|
||||
"Rev": "a98ad7ee00ec53921f08832bc06ecf7fd600e6a1"
|
||||
},
|
||||
{
|
||||
"ImportPath": "golang.org/x/crypto/pbkdf2",
|
||||
"Rev": "1f22c0103821b9390939b6776727195525381532"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
49
Makefile
49
Makefile
@ -3,53 +3,44 @@ export GO15VENDOREXPERIMENT := 1
|
||||
|
||||
all: fmt build
|
||||
|
||||
build: frps frpc build_test
|
||||
|
||||
build_test: echo_server http_server
|
||||
build: frps frpc
|
||||
|
||||
# compile assets into binary file
|
||||
assets:
|
||||
file:
|
||||
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 ./test/echo_server.go
|
||||
@go fmt ./test/http_server.go
|
||||
@go fmt ./test/func_test.go
|
||||
|
||||
go fmt ./...
|
||||
|
||||
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
|
||||
|
||||
echo_server:
|
||||
go build -o test/bin/echo_server ./test/echo_server.go
|
||||
|
||||
http_server:
|
||||
go build -o test/bin/http_server ./test/http_server.go
|
||||
go build -o bin/frpc ./cmd/frpc
|
||||
|
||||
test: gotest
|
||||
|
||||
gotest:
|
||||
go test -v ./src/...
|
||||
go test -v ./assets/...
|
||||
go test -v ./client/...
|
||||
go test -v ./cmd/...
|
||||
go test -v ./models/...
|
||||
go test -v ./server/...
|
||||
go test -v ./utils/...
|
||||
|
||||
alltest:
|
||||
cd ./test && ./run_test.sh && cd -
|
||||
go test -v ./src/...
|
||||
go test -v ./test/func_test.go
|
||||
cd ./test && ./clean_test.sh && cd -
|
||||
alltest: gotest
|
||||
cd ./tests && ./run_test.sh && cd -
|
||||
go test -v ./tests/...
|
||||
cd ./tests && ./clean_test.sh && cd -
|
||||
|
||||
clean:
|
||||
rm -f ./bin/frpc
|
||||
rm -f ./bin/frps
|
||||
rm -f ./test/bin/echo_server
|
||||
rm -f ./test/bin/http_server
|
||||
cd ./test && ./clean_test.sh && cd -
|
||||
|
||||
save:
|
||||
godep save ./src/...
|
||||
godep save ./...
|
||||
|
@ -1,28 +1,32 @@
|
||||
export PATH := $(GOPATH)/bin:$(PATH)
|
||||
export GO15VENDOREXPERIMENT := 1
|
||||
LDFLAGS := -s -w
|
||||
|
||||
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 -ldflags "$(LDFLAGS)" -o ./frpc_darwin_amd64 ./cmd/frpc
|
||||
env CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -ldflags "$(LDFLAGS)" -o ./frps_darwin_amd64 ./cmd/frps
|
||||
env CGO_ENABLED=0 GOOS=linux GOARCH=386 go build -ldflags "$(LDFLAGS)" -o ./frpc_linux_386 ./cmd/frpc
|
||||
env CGO_ENABLED=0 GOOS=linux GOARCH=386 go build -ldflags "$(LDFLAGS)" -o ./frps_linux_386 ./cmd/frps
|
||||
env CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags "$(LDFLAGS)" -o ./frpc_linux_amd64 ./cmd/frpc
|
||||
env CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags "$(LDFLAGS)" -o ./frps_linux_amd64 ./cmd/frps
|
||||
env CGO_ENABLED=0 GOOS=linux GOARCH=arm go build -ldflags "$(LDFLAGS)" -o ./frpc_linux_arm ./cmd/frpc
|
||||
env CGO_ENABLED=0 GOOS=linux GOARCH=arm go build -ldflags "$(LDFLAGS)" -o ./frps_linux_arm ./cmd/frps
|
||||
env CGO_ENABLED=0 GOOS=windows GOARCH=386 go build -ldflags "$(LDFLAGS)" -o ./frpc_windows_386.exe ./cmd/frpc
|
||||
env CGO_ENABLED=0 GOOS=windows GOARCH=386 go build -ldflags "$(LDFLAGS)" -o ./frps_windows_386.exe ./cmd/frps
|
||||
env CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -ldflags "$(LDFLAGS)" -o ./frpc_windows_amd64.exe ./cmd/frpc
|
||||
env CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -ldflags "$(LDFLAGS)" -o ./frps_windows_amd64.exe ./cmd/frps
|
||||
env CGO_ENABLED=0 GOOS=linux GOARCH=mips64 go build -ldflags "$(LDFLAGS)" -o ./frpc_linux_mips64 ./cmd/frpc
|
||||
env CGO_ENABLED=0 GOOS=linux GOARCH=mips64 go build -ldflags "$(LDFLAGS)" -o ./frps_linux_mips64 ./cmd/frps
|
||||
env CGO_ENABLED=0 GOOS=linux GOARCH=mips64le go build -ldflags "$(LDFLAGS)" -o ./frpc_linux_mips64le ./cmd/frpc
|
||||
env CGO_ENABLED=0 GOOS=linux GOARCH=mips64le go build -ldflags "$(LDFLAGS)" -o ./frps_linux_mips64le ./cmd/frps
|
||||
env CGO_ENABLED=0 GOOS=linux GOARCH=mips go build -o -ldflags "$(LDFLAGS)" ./frpc_linux_mips ./cmd/frpc
|
||||
env CGO_ENABLED=0 GOOS=linux GOARCH=mips go build -o -ldflags "$(LDFLAGS)" ./frps_linux_mips ./cmd/frps
|
||||
env CGO_ENABLED=0 GOOS=linux GOARCH=mipsle go build -o -ldflags "$(LDFLAGS)" ./frpc_linux_mipsle ./cmd/frpc
|
||||
env CGO_ENABLED=0 GOOS=linux GOARCH=mipsle go build -o -ldflags "$(LDFLAGS)" ./frps_linux_mipsle ./cmd/frps
|
||||
|
||||
temp:
|
||||
env CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags "$(LDFLAGS)" -o ./frps_linux_amd64 ./cmd/frps
|
||||
|
@ -25,7 +25,7 @@ import (
|
||||
|
||||
"github.com/rakyll/statik/fs"
|
||||
|
||||
_ "github.com/fatedier/frp/src/assets/statik"
|
||||
_ "github.com/fatedier/frp/assets/statik"
|
||||
)
|
||||
|
||||
var (
|
BIN
assets/static/b02bdc1b846fd65473922f5f62832108.ttf
Normal file
BIN
assets/static/b02bdc1b846fd65473922f5f62832108.ttf
Normal file
Binary file not shown.
BIN
assets/static/favicon.ico
Normal file
BIN
assets/static/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 9.4 KiB |
1
assets/static/index.html
Normal file
1
assets/static/index.html
Normal file
@ -0,0 +1 @@
|
||||
<!DOCTYPE html> <html lang=en> <head> <meta charset=utf-8> <title>frps dashboard</title> <link rel="shortcut icon" href="favicon.ico"></head> <body> <div id=app></div> <script type="text/javascript" src="manifest.js?b99c1346247a42f912f8"></script><script type="text/javascript" src="vendor.js?66dfcf2d1c500e900413"></script><script type="text/javascript" src="index.js?ba80138d6369555bbd4e"></script></body> </html>
|
25
assets/static/index.js
Normal file
25
assets/static/index.js
Normal file
File diff suppressed because one or more lines are too long
1
assets/static/manifest.js
Normal file
1
assets/static/manifest.js
Normal file
@ -0,0 +1 @@
|
||||
!function(e){function r(n){if(t[n])return t[n].exports;var o=t[n]={i:n,l:!1,exports:{}};return e[n].call(o.exports,o,o.exports,r),o.l=!0,o.exports}var n=window.webpackJsonp;window.webpackJsonp=function(t,c,u){for(var i,a,f,l=0,s=[];l<t.length;l++)a=t[l],o[a]&&s.push(o[a][0]),o[a]=0;for(i in c)Object.prototype.hasOwnProperty.call(c,i)&&(e[i]=c[i]);for(n&&n(t,c,u);s.length;)s.shift()();if(u)for(l=0;l<u.length;l++)f=r(r.s=u[l]);return f};var t={},o={2:0};r.e=function(e){function n(){u.onerror=u.onload=null,clearTimeout(i);var r=o[e];0!==r&&(r&&r[1](new Error("Loading chunk "+e+" failed.")),o[e]=void 0)}if(0===o[e])return Promise.resolve();if(o[e])return o[e][2];var t=new Promise(function(r,n){o[e]=[r,n]});o[e][2]=t;var c=document.getElementsByTagName("head")[0],u=document.createElement("script");u.type="text/javascript",u.charset="utf-8",u.async=!0,u.timeout=12e4,r.nc&&u.setAttribute("nonce",r.nc),u.src=r.p+""+e+".js?"+{0:"ba80138d6369555bbd4e",1:"66dfcf2d1c500e900413"}[e];var i=setTimeout(n,12e4);return u.onerror=u.onload=n,c.appendChild(u),t},r.m=e,r.c=t,r.i=function(e){return e},r.d=function(e,n,t){r.o(e,n)||Object.defineProperty(e,n,{configurable:!1,enumerable:!0,get:t})},r.n=function(e){var n=e&&e.__esModule?function(){return e.default}:function(){return e};return r.d(n,"a",n),n},r.o=function(e,r){return Object.prototype.hasOwnProperty.call(e,r)},r.p="",r.oe=function(e){throw console.error(e),e}}([]);
|
6
assets/static/vendor.js
Normal file
6
assets/static/vendor.js
Normal file
File diff suppressed because one or more lines are too long
10
assets/statik/statik.go
Normal file
10
assets/statik/statik.go
Normal file
File diff suppressed because one or more lines are too long
396
client/control.go
Normal file
396
client/control.go
Normal file
@ -0,0 +1,396 @@
|
||||
// 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/crypto"
|
||||
"github.com/fatedier/frp/utils/log"
|
||||
"github.com/fatedier/frp/utils/net"
|
||||
"github.com/fatedier/frp/utils/util"
|
||||
"github.com/fatedier/frp/utils/version"
|
||||
)
|
||||
|
||||
const (
|
||||
connReadTimeout time.Duration = 10 * time.Second
|
||||
)
|
||||
|
||||
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, %v", err)
|
||||
workConn.Close()
|
||||
return
|
||||
}
|
||||
workConn.AddLogPrefix(startMsg.ProxyName)
|
||||
|
||||
// dispatch this work connection to related proxy
|
||||
if pxy, ok := ctl.proxies[startMsg.ProxyName]; ok {
|
||||
workConn.Info("start a new work connection, localAddr: %s remoteAddr: %s", workConn.LocalAddr().String(), workConn.RemoteAddr().String())
|
||||
go pxy.InWorkConn(workConn)
|
||||
} else {
|
||||
workConn.Close()
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if err != nil {
|
||||
conn.Close()
|
||||
}
|
||||
}()
|
||||
|
||||
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
|
||||
conn.SetReadDeadline(time.Now().Add(connReadTimeout))
|
||||
if err = msg.ReadMsgInto(conn, &loginRespMsg); err != nil {
|
||||
return err
|
||||
}
|
||||
conn.SetReadDeadline(time.Time{})
|
||||
|
||||
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)
|
||||
}
|
||||
}()
|
||||
defer close(ctl.closedCh)
|
||||
|
||||
encReader := crypto.NewReader(ctl.conn, []byte(config.ClientCommonCfg.PrivilegeToken))
|
||||
for {
|
||||
if m, err := msg.ReadMsg(encReader); err != nil {
|
||||
if err == io.EOF {
|
||||
ctl.Debug("read from control connection EOF")
|
||||
return
|
||||
} else {
|
||||
ctl.Warn("read error: %v", err)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
ctl.readCh <- m
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (ctl *Control) writer() {
|
||||
encWriter, err := crypto.NewWriter(ctl.conn, []byte(config.ClientCommonCfg.PrivilegeToken))
|
||||
if err != nil {
|
||||
ctl.conn.Error("crypto new writer error: %v", err)
|
||||
ctl.conn.Close()
|
||||
return
|
||||
}
|
||||
for {
|
||||
if m, ok := <-ctl.sendCh; !ok {
|
||||
ctl.Info("control writer is closing")
|
||||
return
|
||||
} else {
|
||||
if err := msg.WriteMsg(encWriter, 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.Debug("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")
|
||||
// let reader() stop
|
||||
ctl.conn.Close()
|
||||
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
|
||||
}
|
||||
cfg, ok := ctl.pxyCfgs[m.ProxyName]
|
||||
if !ok {
|
||||
// it will never go to this branch now
|
||||
ctl.Warn("[%s] no proxy conf found", m.ProxyName)
|
||||
continue
|
||||
}
|
||||
oldPxy, ok := ctl.proxies[m.ProxyName]
|
||||
if ok {
|
||||
oldPxy.Close()
|
||||
}
|
||||
pxy := NewProxy(ctl, cfg)
|
||||
if err := pxy.Run(); err != nil {
|
||||
ctl.Warn("[%s] proxy start running error: %v", m.ProxyName, err)
|
||||
continue
|
||||
}
|
||||
ctl.proxies[m.ProxyName] = pxy
|
||||
ctl.Info("[%s] start proxy success", m.ProxyName)
|
||||
case *msg.Pong:
|
||||
ctl.lastPong = time.Now()
|
||||
ctl.Debug("receive heartbeat from server")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
|
||||
checkInterval := 30 * time.Second
|
||||
checkProxyTicker := time.NewTicker(checkInterval)
|
||||
for {
|
||||
select {
|
||||
case <-checkProxyTicker.C:
|
||||
// Every 30 seconds, check which proxy registered failed and reregister it to server.
|
||||
for _, cfg := range ctl.pxyCfgs {
|
||||
if _, exist := ctl.proxies[cfg.GetName()]; !exist {
|
||||
ctl.Info("try to reregister proxy [%s]", cfg.GetName())
|
||||
var newProxyMsg msg.NewProxy
|
||||
cfg.UnMarshalToMsg(&newProxyMsg)
|
||||
ctl.sendCh <- &newProxyMsg
|
||||
}
|
||||
}
|
||||
case _, ok := <-ctl.closedCh:
|
||||
// we won't get any variable from this channel
|
||||
if !ok {
|
||||
// close related channels
|
||||
close(ctl.readCh)
|
||||
close(ctl.sendCh)
|
||||
|
||||
for _, pxy := range ctl.proxies {
|
||||
pxy.Close()
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
checkProxyTicker.Stop()
|
||||
checkProxyTicker = time.NewTicker(checkInterval)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
265
client/proxy.go
Normal file
265
client/proxy.go
Normal file
@ -0,0 +1,265 @@
|
||||
// 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"
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/fatedier/frp/models/config"
|
||||
"github.com/fatedier/frp/models/msg"
|
||||
"github.com/fatedier/frp/models/proto/tcp"
|
||||
"github.com/fatedier/frp/models/proto/udp"
|
||||
"github.com/fatedier/frp/utils/errors"
|
||||
"github.com/fatedier/frp/utils/log"
|
||||
frpNet "github.com/fatedier/frp/utils/net"
|
||||
)
|
||||
|
||||
// Proxy defines how to work for different proxy type.
|
||||
type Proxy interface {
|
||||
Run() error
|
||||
|
||||
// InWorkConn accept work connections registered to server.
|
||||
InWorkConn(conn frpNet.Conn)
|
||||
Close()
|
||||
log.Logger
|
||||
}
|
||||
|
||||
func NewProxy(ctl *Control, pxyConf config.ProxyConf) (pxy Proxy) {
|
||||
baseProxy := BaseProxy{
|
||||
ctl: ctl,
|
||||
Logger: log.NewPrefixLogger(pxyConf.GetName()),
|
||||
}
|
||||
switch cfg := pxyConf.(type) {
|
||||
case *config.TcpProxyConf:
|
||||
pxy = &TcpProxy{
|
||||
BaseProxy: baseProxy,
|
||||
cfg: cfg,
|
||||
}
|
||||
case *config.UdpProxyConf:
|
||||
pxy = &UdpProxy{
|
||||
BaseProxy: baseProxy,
|
||||
cfg: cfg,
|
||||
}
|
||||
case *config.HttpProxyConf:
|
||||
pxy = &HttpProxy{
|
||||
BaseProxy: baseProxy,
|
||||
cfg: cfg,
|
||||
}
|
||||
case *config.HttpsProxyConf:
|
||||
pxy = &HttpsProxy{
|
||||
BaseProxy: baseProxy,
|
||||
cfg: cfg,
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
type BaseProxy struct {
|
||||
ctl *Control
|
||||
closed bool
|
||||
mu sync.RWMutex
|
||||
log.Logger
|
||||
}
|
||||
|
||||
// TCP
|
||||
type TcpProxy struct {
|
||||
BaseProxy
|
||||
|
||||
cfg *config.TcpProxyConf
|
||||
}
|
||||
|
||||
func (pxy *TcpProxy) Run() (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func (pxy *TcpProxy) Close() {
|
||||
}
|
||||
|
||||
func (pxy *TcpProxy) InWorkConn(conn frpNet.Conn) {
|
||||
defer conn.Close()
|
||||
HandleTcpWorkConnection(&pxy.cfg.LocalSvrConf, &pxy.cfg.BaseProxyConf, conn)
|
||||
}
|
||||
|
||||
// HTTP
|
||||
type HttpProxy struct {
|
||||
BaseProxy
|
||||
|
||||
cfg *config.HttpProxyConf
|
||||
}
|
||||
|
||||
func (pxy *HttpProxy) Run() (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func (pxy *HttpProxy) Close() {
|
||||
}
|
||||
|
||||
func (pxy *HttpProxy) InWorkConn(conn frpNet.Conn) {
|
||||
defer conn.Close()
|
||||
HandleTcpWorkConnection(&pxy.cfg.LocalSvrConf, &pxy.cfg.BaseProxyConf, conn)
|
||||
}
|
||||
|
||||
// HTTPS
|
||||
type HttpsProxy struct {
|
||||
BaseProxy
|
||||
|
||||
cfg *config.HttpsProxyConf
|
||||
}
|
||||
|
||||
func (pxy *HttpsProxy) Run() (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func (pxy *HttpsProxy) Close() {
|
||||
}
|
||||
|
||||
func (pxy *HttpsProxy) InWorkConn(conn frpNet.Conn) {
|
||||
defer conn.Close()
|
||||
HandleTcpWorkConnection(&pxy.cfg.LocalSvrConf, &pxy.cfg.BaseProxyConf, conn)
|
||||
}
|
||||
|
||||
// UDP
|
||||
type UdpProxy struct {
|
||||
BaseProxy
|
||||
|
||||
cfg *config.UdpProxyConf
|
||||
|
||||
localAddr *net.UDPAddr
|
||||
readCh chan *msg.UdpPacket
|
||||
|
||||
// include msg.UdpPacket and msg.Ping
|
||||
sendCh chan msg.Message
|
||||
workConn frpNet.Conn
|
||||
}
|
||||
|
||||
func (pxy *UdpProxy) Run() (err error) {
|
||||
pxy.localAddr, err = net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", pxy.cfg.LocalIp, pxy.cfg.LocalPort))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (pxy *UdpProxy) Close() {
|
||||
pxy.mu.Lock()
|
||||
defer pxy.mu.Unlock()
|
||||
|
||||
if !pxy.closed {
|
||||
pxy.closed = true
|
||||
if pxy.workConn != nil {
|
||||
pxy.workConn.Close()
|
||||
}
|
||||
if pxy.readCh != nil {
|
||||
close(pxy.readCh)
|
||||
}
|
||||
if pxy.sendCh != nil {
|
||||
close(pxy.sendCh)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (pxy *UdpProxy) InWorkConn(conn frpNet.Conn) {
|
||||
pxy.Info("incoming a new work connection for udp proxy, %s", conn.RemoteAddr().String())
|
||||
// close resources releated with old workConn
|
||||
pxy.Close()
|
||||
|
||||
pxy.mu.Lock()
|
||||
pxy.workConn = conn
|
||||
pxy.readCh = make(chan *msg.UdpPacket, 1024)
|
||||
pxy.sendCh = make(chan msg.Message, 1024)
|
||||
pxy.closed = false
|
||||
pxy.mu.Unlock()
|
||||
|
||||
workConnReaderFn := func(conn net.Conn, readCh chan *msg.UdpPacket) {
|
||||
for {
|
||||
var udpMsg msg.UdpPacket
|
||||
if errRet := msg.ReadMsgInto(conn, &udpMsg); errRet != nil {
|
||||
pxy.Warn("read from workConn for udp error: %v", errRet)
|
||||
return
|
||||
}
|
||||
if errRet := errors.PanicToError(func() {
|
||||
pxy.Trace("get udp package from workConn: %s", udpMsg.Content)
|
||||
readCh <- &udpMsg
|
||||
}); errRet != nil {
|
||||
pxy.Info("reader goroutine for udp work connection closed: %v", errRet)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
workConnSenderFn := func(conn net.Conn, sendCh chan msg.Message) {
|
||||
defer func() {
|
||||
pxy.Info("writer goroutine for udp work connection closed")
|
||||
}()
|
||||
var errRet error
|
||||
for rawMsg := range sendCh {
|
||||
switch m := rawMsg.(type) {
|
||||
case *msg.UdpPacket:
|
||||
pxy.Trace("send udp package to workConn: %s", m.Content)
|
||||
case *msg.Ping:
|
||||
pxy.Trace("send ping message to udp workConn")
|
||||
}
|
||||
if errRet = msg.WriteMsg(conn, rawMsg); errRet != nil {
|
||||
pxy.Error("udp work write error: %v", errRet)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
heartbeatFn := func(conn net.Conn, sendCh chan msg.Message) {
|
||||
var errRet error
|
||||
for {
|
||||
time.Sleep(time.Duration(30) * time.Second)
|
||||
if errRet = errors.PanicToError(func() {
|
||||
sendCh <- &msg.Ping{}
|
||||
}); errRet != nil {
|
||||
pxy.Trace("heartbeat goroutine for udp work connection closed")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
go workConnSenderFn(pxy.workConn, pxy.sendCh)
|
||||
go workConnReaderFn(pxy.workConn, pxy.readCh)
|
||||
go heartbeatFn(pxy.workConn, pxy.sendCh)
|
||||
udp.Forwarder(pxy.localAddr, pxy.readCh, pxy.sendCh)
|
||||
}
|
||||
|
||||
// Common handler for tcp work connections.
|
||||
func HandleTcpWorkConnection(localInfo *config.LocalSvrConf, baseInfo *config.BaseProxyConf, workConn frpNet.Conn) {
|
||||
localConn, err := frpNet.ConnectTcpServer(fmt.Sprintf("%s:%d", localInfo.LocalIp, localInfo.LocalPort))
|
||||
if err != nil {
|
||||
workConn.Error("connect to local service [%s:%d] error: %v", localInfo.LocalIp, localInfo.LocalPort, err)
|
||||
return
|
||||
}
|
||||
|
||||
var remote io.ReadWriteCloser
|
||||
remote = workConn
|
||||
if baseInfo.UseEncryption {
|
||||
remote, err = tcp.WithEncryption(remote, []byte(config.ClientCommonCfg.PrivilegeToken))
|
||||
if err != nil {
|
||||
workConn.Error("create encryption stream error: %v", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
if baseInfo.UseCompression {
|
||||
remote = tcp.WithCompression(remote)
|
||||
}
|
||||
workConn.Debug("join connections, localConn(l[%s] r[%s]) workConn(l[%s] r[%s])", localConn.LocalAddr().String(),
|
||||
localConn.RemoteAddr().String(), workConn.LocalAddr().String(), workConn.RemoteAddr().String())
|
||||
tcp.Join(localConn, remote)
|
||||
workConn.Debug("join connections closed")
|
||||
}
|
43
client/service.go
Normal file
43
client/service.go
Normal file
@ -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
|
||||
}
|
@ -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)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
config.ClientCommonCfg, err = config.LoadClientCommonConf(conf)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
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)
|
||||
}
|
||||
}
|
118
cmd/frps/main.go
Normal file
118
cmd/frps/main.go
Normal file
@ -0,0 +1,118 @@
|
||||
// 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")
|
||||
}
|
||||
server.ServerService = svr
|
||||
svr.Run()
|
||||
}
|
@ -11,76 +11,64 @@ server_port = 7000
|
||||
# console or real logFile path like ./frpc.log
|
||||
log_file = ./frpc.log
|
||||
|
||||
# debug, info, warn, error
|
||||
# trace, debug, info, warn, error
|
||||
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
|
||||
# the default value of heartbeat_interval is 10 and heartbeat_timeout is 90
|
||||
# heartbeat_interval = 30
|
||||
# heartbeat_timeout = 90
|
||||
|
||||
# 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
|
||||
|
@ -21,24 +21,23 @@ dashboard_pwd = admin
|
||||
# console or real logFile path like ./frps.log
|
||||
log_file = ./frps.log
|
||||
|
||||
# debug, info, warn, error
|
||||
# trace, debug, info, warn, error
|
||||
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
|
||||
# the default value of heartbeat_timeout is 30
|
||||
# heartbeat_timeout = 30
|
||||
# the default value of heartbeat_timeout is 90
|
||||
# heartbeat_timeout = 90
|
||||
|
||||
# only allow frpc to bind ports you list, if you set nothing, there won't be any limit
|
||||
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
Normal file
160
models/config/client_common.go
Normal file
@ -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: 30,
|
||||
HeartBeatTimeout: 90,
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
456
models/config/proxy.go
Normal file
456
models/config/proxy.go
Normal file
@ -0,0 +1,456 @@
|
||||
// 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"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/fatedier/frp/models/consts"
|
||||
"github.com/fatedier/frp/models/msg"
|
||||
|
||||
ini "github.com/vaughan0/go-ini"
|
||||
)
|
||||
|
||||
var proxyConfTypeMap map[string]reflect.Type
|
||||
|
||||
func init() {
|
||||
proxyConfTypeMap = make(map[string]reflect.Type)
|
||||
proxyConfTypeMap[consts.TcpProxy] = reflect.TypeOf(TcpProxyConf{})
|
||||
proxyConfTypeMap[consts.UdpProxy] = reflect.TypeOf(UdpProxyConf{})
|
||||
proxyConfTypeMap[consts.HttpProxy] = reflect.TypeOf(HttpProxyConf{})
|
||||
proxyConfTypeMap[consts.HttpsProxy] = reflect.TypeOf(HttpsProxyConf{})
|
||||
}
|
||||
|
||||
// NewConfByType creates a empty ProxyConf object by proxyType.
|
||||
// If proxyType isn't exist, return nil.
|
||||
func NewConfByType(proxyType string) ProxyConf {
|
||||
v, ok := proxyConfTypeMap[proxyType]
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
cfg := reflect.New(v).Interface().(ProxyConf)
|
||||
return cfg
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
cfg = NewConfByType(pMsg.ProxyType)
|
||||
if cfg == nil {
|
||||
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
|
||||
}
|
||||
cfg = NewConfByType(proxyType)
|
||||
if cfg == nil {
|
||||
err = fmt.Errorf("proxy [%s] type [%s] error", name, proxyType)
|
||||
return
|
||||
}
|
||||
err = cfg.LoadFromFile(name, section)
|
||||
return
|
||||
}
|
||||
|
||||
// BaseProxy info
|
||||
type BaseProxyConf struct {
|
||||
ProxyName string `json:"proxy_name"`
|
||||
ProxyType string `json:"proxy_type"`
|
||||
|
||||
UseEncryption bool `json:"use_encryption"`
|
||||
UseCompression bool `json:"use_compression"`
|
||||
}
|
||||
|
||||
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
|
||||
)
|
||||
if ClientCommonCfg.User != "" {
|
||||
cfg.ProxyName = ClientCommonCfg.User + "." + name
|
||||
} else {
|
||||
cfg.ProxyName = 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 `json:"bind_addr"`
|
||||
RemotePort int64 `json:"remote_port"`
|
||||
}
|
||||
|
||||
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 `json:"custom_domains"`
|
||||
SubDomain string `json:"sub_domain"`
|
||||
}
|
||||
|
||||
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")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type LocalSvrConf struct {
|
||||
LocalIp string `json:"-"`
|
||||
LocalPort int `json:"-"`
|
||||
}
|
||||
|
||||
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 `json:"locations"`
|
||||
HostHeaderRewrite string `json:"host_header_rewrite"`
|
||||
HttpUser string `json:"-"`
|
||||
HttpPwd string `json:"-"`
|
||||
}
|
||||
|
||||
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
Normal file
279
models/config/server_common.go
Normal file
@ -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: 90,
|
||||
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
|
||||
}
|
30
models/consts/consts.go
Normal file
30
models/consts/consts.go
Normal file
@ -0,0 +1,30 @@
|
||||
// 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 consts
|
||||
|
||||
var (
|
||||
// proxy status
|
||||
Idle string = "idle"
|
||||
Working string = "working"
|
||||
Closed string = "closed"
|
||||
Online string = "online"
|
||||
Offline string = "offline"
|
||||
|
||||
// proxy type
|
||||
TcpProxy string = "tcp"
|
||||
UdpProxy string = "udp"
|
||||
HttpProxy string = "http"
|
||||
HttpsProxy string = "https"
|
||||
)
|
@ -12,30 +12,10 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package consts
|
||||
package errors
|
||||
|
||||
// server status
|
||||
const (
|
||||
Idle = iota
|
||||
Working
|
||||
Closed
|
||||
)
|
||||
import "errors"
|
||||
|
||||
var (
|
||||
StatusStr = []string{
|
||||
"idle",
|
||||
"working",
|
||||
"closed",
|
||||
}
|
||||
)
|
||||
|
||||
// msg type
|
||||
const (
|
||||
NewCtlConn = iota
|
||||
NewWorkConn
|
||||
NoticeUserConn
|
||||
NewCtlConnRes
|
||||
HeartbeatReq
|
||||
HeartbeatRes
|
||||
NewWorkConnUdp
|
||||
ErrMsgType = errors.New("message type error")
|
||||
)
|
129
models/msg/msg.go
Normal file
129
models/msg/msg.go
Normal file
@ -0,0 +1,129 @@
|
||||
// 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 (
|
||||
"net"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
const (
|
||||
TypeLogin = 'o'
|
||||
TypeLoginResp = '1'
|
||||
TypeNewProxy = 'p'
|
||||
TypeNewProxyResp = '2'
|
||||
TypeNewWorkConn = 'w'
|
||||
TypeReqWorkConn = 'r'
|
||||
TypeStartWorkConn = 's'
|
||||
TypePing = 'h'
|
||||
TypePong = '4'
|
||||
TypeUdpPacket = 'u'
|
||||
)
|
||||
|
||||
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] = reflect.TypeOf(Login{})
|
||||
TypeMap[TypeLoginResp] = reflect.TypeOf(LoginResp{})
|
||||
TypeMap[TypeNewProxy] = reflect.TypeOf(NewProxy{})
|
||||
TypeMap[TypeNewProxyResp] = reflect.TypeOf(NewProxyResp{})
|
||||
TypeMap[TypeNewWorkConn] = reflect.TypeOf(NewWorkConn{})
|
||||
TypeMap[TypeReqWorkConn] = reflect.TypeOf(ReqWorkConn{})
|
||||
TypeMap[TypeStartWorkConn] = reflect.TypeOf(StartWorkConn{})
|
||||
TypeMap[TypePing] = reflect.TypeOf(Ping{})
|
||||
TypeMap[TypePong] = reflect.TypeOf(Pong{})
|
||||
TypeMap[TypeUdpPacket] = reflect.TypeOf(UdpPacket{})
|
||||
|
||||
for k, v := range TypeMap {
|
||||
TypeStringMap[v] = k
|
||||
}
|
||||
}
|
||||
|
||||
// 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 {
|
||||
}
|
||||
|
||||
type UdpPacket struct {
|
||||
Content string `json:"c"`
|
||||
LocalAddr *net.UDPAddr `json:"l"`
|
||||
RemoteAddr *net.UDPAddr `json:"r"`
|
||||
}
|
69
models/msg/pack.go
Normal file
69
models/msg/pack.go
Normal file
@ -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
|
||||
}
|
87
models/msg/pack_test.go
Normal file
87
models/msg/pack_test.go
Normal file
@ -0,0 +1,87 @@
|
||||
// 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"
|
||||
|
||||
"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(reflect.TypeOf(msg).Elem(), reflect.TypeOf(Pong{}))
|
||||
}
|
||||
|
||||
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
Normal file
88
models/msg/process.go
Normal file
@ -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
Normal file
97
models/msg/process_test.go
Normal file
@ -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
Normal file
38
models/proto/tcp/process.go
Normal file
@ -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
|
||||
}
|
67
models/proto/tcp/process_test.go
Normal file
67
models/proto/tcp/process_test.go
Normal file
@ -0,0 +1,67 @@
|
||||
// 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 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()
|
||||
}
|
73
models/proto/tcp/tcp.go
Normal file
73
models/proto/tcp/tcp.go
Normal file
@ -0,0 +1,73 @@
|
||||
// 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) (io.ReadWriteCloser, error) {
|
||||
w, err := crypto.NewWriter(rwc, key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return WrapReadWriteCloser(crypto.NewReader(rwc, key), w), nil
|
||||
}
|
||||
|
||||
func WithCompression(rwc io.ReadWriteCloser) io.ReadWriteCloser {
|
||||
return WrapReadWriteCloser(snappy.NewReader(rwc), snappy.NewWriter(rwc))
|
||||
}
|
||||
|
||||
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
Normal file
100
models/proto/tcp/tcp_test.go
Normal file
@ -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)
|
||||
}
|
135
models/proto/udp/udp.go
Normal file
135
models/proto/udp/udp.go
Normal file
@ -0,0 +1,135 @@
|
||||
// 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 udp
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/fatedier/frp/models/msg"
|
||||
"github.com/fatedier/frp/utils/errors"
|
||||
"github.com/fatedier/frp/utils/pool"
|
||||
)
|
||||
|
||||
func NewUdpPacket(buf []byte, laddr, raddr *net.UDPAddr) *msg.UdpPacket {
|
||||
return &msg.UdpPacket{
|
||||
Content: base64.StdEncoding.EncodeToString(buf),
|
||||
LocalAddr: laddr,
|
||||
RemoteAddr: raddr,
|
||||
}
|
||||
}
|
||||
|
||||
func GetContent(m *msg.UdpPacket) (buf []byte, err error) {
|
||||
buf, err = base64.StdEncoding.DecodeString(m.Content)
|
||||
return
|
||||
}
|
||||
|
||||
func ForwardUserConn(udpConn *net.UDPConn, readCh <-chan *msg.UdpPacket, sendCh chan<- *msg.UdpPacket) {
|
||||
// read
|
||||
go func() {
|
||||
for udpMsg := range readCh {
|
||||
buf, err := GetContent(udpMsg)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
udpConn.WriteToUDP(buf, udpMsg.RemoteAddr)
|
||||
}
|
||||
}()
|
||||
|
||||
// write
|
||||
buf := pool.GetBuf(1500)
|
||||
defer pool.PutBuf(buf)
|
||||
for {
|
||||
n, remoteAddr, err := udpConn.ReadFromUDP(buf)
|
||||
if err != nil {
|
||||
udpConn.Close()
|
||||
return
|
||||
}
|
||||
// buf[:n] will be encoded to string, so the bytes can be reused
|
||||
udpMsg := NewUdpPacket(buf[:n], nil, remoteAddr)
|
||||
select {
|
||||
case sendCh <- udpMsg:
|
||||
default:
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func Forwarder(dstAddr *net.UDPAddr, readCh <-chan *msg.UdpPacket, sendCh chan<- msg.Message) {
|
||||
var (
|
||||
mu sync.RWMutex
|
||||
)
|
||||
udpConnMap := make(map[string]*net.UDPConn)
|
||||
|
||||
// read from dstAddr and write to sendCh
|
||||
writerFn := func(raddr *net.UDPAddr, udpConn *net.UDPConn) {
|
||||
addr := raddr.String()
|
||||
defer func() {
|
||||
mu.Lock()
|
||||
delete(udpConnMap, addr)
|
||||
mu.Unlock()
|
||||
}()
|
||||
|
||||
buf := pool.GetBuf(1500)
|
||||
for {
|
||||
udpConn.SetReadDeadline(time.Now().Add(30 * time.Second))
|
||||
n, _, err := udpConn.ReadFromUDP(buf)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
udpMsg := NewUdpPacket(buf[:n], nil, raddr)
|
||||
if err = errors.PanicToError(func() {
|
||||
select {
|
||||
case sendCh <- udpMsg:
|
||||
default:
|
||||
}
|
||||
}); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// read from readCh
|
||||
go func() {
|
||||
for udpMsg := range readCh {
|
||||
buf, err := GetContent(udpMsg)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
mu.Lock()
|
||||
udpConn, ok := udpConnMap[udpMsg.RemoteAddr.String()]
|
||||
if !ok {
|
||||
udpConn, err = net.DialUDP("udp", nil, dstAddr)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
udpConnMap[udpMsg.RemoteAddr.String()] = udpConn
|
||||
}
|
||||
mu.Unlock()
|
||||
|
||||
_, err = udpConn.Write(buf)
|
||||
if err != nil {
|
||||
udpConn.Close()
|
||||
}
|
||||
|
||||
if !ok {
|
||||
go writerFn(udpMsg.RemoteAddr, udpConn)
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
18
models/proto/udp/udp_test.go
Normal file
18
models/proto/udp/udp_test.go
Normal file
@ -0,0 +1,18 @@
|
||||
package udp
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestUdpPacket(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
buf := []byte("hello world")
|
||||
udpMsg := NewUdpPacket(buf, nil, nil)
|
||||
|
||||
newBuf, err := GetContent(udpMsg)
|
||||
assert.NoError(err)
|
||||
assert.EqualValues(buf, newBuf)
|
||||
}
|
360
server/control.go
Normal file
360
server/control.go
Normal file
@ -0,0 +1,360 @@
|
||||
// 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 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/crypto"
|
||||
"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() {
|
||||
loginRespMsg := &msg.LoginResp{
|
||||
Version: version.Full(),
|
||||
RunId: ctl.runId,
|
||||
Error: "",
|
||||
}
|
||||
msg.WriteMsg(ctl.conn, loginRespMsg)
|
||||
|
||||
go ctl.writer()
|
||||
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 = errors.ErrCtlClosed
|
||||
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 = errors.ErrCtlClosed
|
||||
ctl.conn.Warn("no work connections avaiable, %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()
|
||||
|
||||
encWriter, err := crypto.NewWriter(ctl.conn, []byte(config.ServerCommonCfg.PrivilegeToken))
|
||||
if err != nil {
|
||||
ctl.conn.Error("crypto new writer error: %v", err)
|
||||
ctl.allShutdown.Start()
|
||||
return
|
||||
}
|
||||
for {
|
||||
if m, ok := <-ctl.sendCh; !ok {
|
||||
ctl.conn.Info("control writer is closing")
|
||||
return
|
||||
} else {
|
||||
if err := msg.WriteMsg(encWriter, 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()
|
||||
|
||||
encReader := crypto.NewReader(ctl.conn, []byte(config.ServerCommonCfg.PrivilegeToken))
|
||||
for {
|
||||
if m, err := msg.ReadMsg(encReader); 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()
|
||||
ctl.readerShutdown.WaitDown()
|
||||
|
||||
close(ctl.workConnCh)
|
||||
for workConn := range ctl.workConnCh {
|
||||
workConn.Close()
|
||||
}
|
||||
|
||||
for _, pxy := range ctl.proxies {
|
||||
pxy.Close()
|
||||
ctl.svr.DelProxy(pxy.GetName())
|
||||
StatsCloseProxy(pxy.GetConf().GetBaseInfo().ProxyType)
|
||||
}
|
||||
|
||||
ctl.allShutdown.Done()
|
||||
ctl.conn.Info("client exit success")
|
||||
|
||||
StatsCloseClient()
|
||||
}
|
||||
|
||||
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)
|
||||
StatsNewProxy(m.ProxyName, m.ProxyType)
|
||||
}
|
||||
ctl.sendCh <- resp
|
||||
case *msg.Ping:
|
||||
ctl.lastPing = time.Now()
|
||||
ctl.conn.Debug("receive heartbeat")
|
||||
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
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
// Copyright 2016 fatedier, fatedier@gmail.com
|
||||
// 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.
|
||||
@ -15,14 +15,15 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/fatedier/frp/src/assets"
|
||||
"github.com/fatedier/frp/assets"
|
||||
"github.com/fatedier/frp/models/config"
|
||||
|
||||
"github.com/julienschmidt/httprouter"
|
||||
)
|
||||
|
||||
var (
|
||||
@ -32,20 +33,28 @@ var (
|
||||
|
||||
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)
|
||||
router := httprouter.New()
|
||||
|
||||
// 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))
|
||||
// api, see dashboard_api.go
|
||||
//mux.HandleFunc("/api/reload", use(apiReload, basicAuth))
|
||||
router.GET("/api/serverinfo", apiServerInfo)
|
||||
router.GET("/api/proxy/tcp", apiProxyTcp)
|
||||
router.GET("/api/proxy/udp", apiProxyUdp)
|
||||
router.GET("/api/proxy/http", apiProxyHttp)
|
||||
router.GET("/api/proxy/https", apiProxyHttps)
|
||||
router.GET("/api/proxy/traffic/:name", apiProxyTraffic)
|
||||
|
||||
// view
|
||||
router.Handler("GET", "/favicon.ico", http.FileServer(assets.FileSystem))
|
||||
router.Handler("GET", "/static/*filepath", http.StripPrefix("/static/", http.FileServer(assets.FileSystem)))
|
||||
router.HandlerFunc("GET", "/", func(w http.ResponseWriter, r *http.Request) {
|
||||
http.Redirect(w, r, "/static/", http.StatusMovedPermanently)
|
||||
})
|
||||
|
||||
address := fmt.Sprintf("%s:%d", addr, port)
|
||||
server := &http.Server{
|
||||
Addr: address,
|
||||
Handler: mux,
|
||||
Handler: router,
|
||||
ReadTimeout: httpServerReadTimeout,
|
||||
WriteTimeout: httpServerWriteTimeout,
|
||||
}
|
||||
@ -65,7 +74,6 @@ func use(h http.HandlerFunc, middleware ...func(http.HandlerFunc) http.HandlerFu
|
||||
for _, m := range middleware {
|
||||
h = m(h)
|
||||
}
|
||||
|
||||
return h
|
||||
}
|
||||
|
||||
@ -74,25 +82,13 @@ func basicAuth(h http.HandlerFunc) http.HandlerFunc {
|
||||
|
||||
w.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`)
|
||||
|
||||
s := strings.SplitN(r.Header.Get("Authorization"), " ", 2)
|
||||
if len(s) != 2 {
|
||||
username, passwd, ok := r.BasicAuth()
|
||||
if !ok {
|
||||
http.Error(w, "Not authorized", 401)
|
||||
return
|
||||
}
|
||||
|
||||
b, err := base64.StdEncoding.DecodeString(s[1])
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), 401)
|
||||
return
|
||||
}
|
||||
|
||||
pair := strings.SplitN(string(b), ":", 2)
|
||||
if len(pair) != 2 {
|
||||
http.Error(w, "Not authorized", 401)
|
||||
return
|
||||
}
|
||||
|
||||
if pair[0] != DashboardUsername || pair[1] != DashboardPassword {
|
||||
if username != config.ServerCommonCfg.DashboardUser || passwd != config.ServerCommonCfg.DashboardPwd {
|
||||
http.Error(w, "Not authorized", 401)
|
||||
return
|
||||
}
|
218
server/dashboard_api.go
Normal file
218
server/dashboard_api.go
Normal file
@ -0,0 +1,218 @@
|
||||
// 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 server
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
|
||||
"github.com/fatedier/frp/models/config"
|
||||
"github.com/fatedier/frp/models/consts"
|
||||
"github.com/fatedier/frp/utils/log"
|
||||
|
||||
"github.com/julienschmidt/httprouter"
|
||||
)
|
||||
|
||||
type GeneralResponse struct {
|
||||
Code int64 `json:"code"`
|
||||
Msg string `json:"msg"`
|
||||
}
|
||||
|
||||
// api/serverinfo
|
||||
type ServerInfoResp struct {
|
||||
GeneralResponse
|
||||
|
||||
VhostHttpPort int64 `json:"vhost_http_port"`
|
||||
VhostHttpsPort int64 `json:"vhost_https_port"`
|
||||
AuthTimeout int64 `json:"auth_timeout"`
|
||||
SubdomainHost string `json:"subdomain_host"`
|
||||
MaxPoolCount int64 `json:"max_pool_count"`
|
||||
HeartBeatTimeout int64 `json:"heart_beat_timeout"`
|
||||
|
||||
TotalTrafficIn int64 `json:"total_traffic_in"`
|
||||
TotalTrafficOut int64 `json:"total_traffic_out"`
|
||||
CurConns int64 `json:"cur_conns"`
|
||||
ClientCounts int64 `json:"client_counts"`
|
||||
ProxyTypeCounts map[string]int64 `json:"proxy_type_count"`
|
||||
}
|
||||
|
||||
func apiServerInfo(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
|
||||
var (
|
||||
buf []byte
|
||||
res ServerInfoResp
|
||||
)
|
||||
defer func() {
|
||||
log.Info("Http response [/api/serverinfo]: code [%d]", res.Code)
|
||||
}()
|
||||
|
||||
log.Info("Http request: [/api/serverinfo]")
|
||||
cfg := config.ServerCommonCfg
|
||||
serverStats := StatsGetServer()
|
||||
res = ServerInfoResp{
|
||||
VhostHttpPort: cfg.VhostHttpPort,
|
||||
VhostHttpsPort: cfg.VhostHttpsPort,
|
||||
AuthTimeout: cfg.AuthTimeout,
|
||||
SubdomainHost: cfg.SubDomainHost,
|
||||
MaxPoolCount: cfg.MaxPoolCount,
|
||||
HeartBeatTimeout: cfg.HeartBeatTimeout,
|
||||
|
||||
TotalTrafficIn: serverStats.TotalTrafficIn,
|
||||
TotalTrafficOut: serverStats.TotalTrafficOut,
|
||||
CurConns: serverStats.CurConns,
|
||||
ClientCounts: serverStats.ClientCounts,
|
||||
ProxyTypeCounts: serverStats.ProxyTypeCounts,
|
||||
}
|
||||
|
||||
buf, _ = json.Marshal(&res)
|
||||
w.Write(buf)
|
||||
}
|
||||
|
||||
// Get proxy info.
|
||||
type ProxyStatsInfo struct {
|
||||
Name string `json:"name"`
|
||||
Conf config.ProxyConf `json:"conf"`
|
||||
TodayTrafficIn int64 `json:"today_traffic_in"`
|
||||
TodayTrafficOut int64 `json:"today_traffic_out"`
|
||||
CurConns int64 `json:"cur_conns"`
|
||||
Status string `json:"status"`
|
||||
}
|
||||
|
||||
type GetProxyInfoResp struct {
|
||||
GeneralResponse
|
||||
Proxies []*ProxyStatsInfo `json:"proxies"`
|
||||
}
|
||||
|
||||
// api/proxy/tcp
|
||||
func apiProxyTcp(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
|
||||
var (
|
||||
buf []byte
|
||||
res GetProxyInfoResp
|
||||
)
|
||||
defer func() {
|
||||
log.Info("Http response [/api/proxy/tcp]: code [%d]", res.Code)
|
||||
}()
|
||||
log.Info("Http request: [/api/proxy/tcp]")
|
||||
|
||||
res.Proxies = getProxyStatsByType(consts.TcpProxy)
|
||||
|
||||
buf, _ = json.Marshal(&res)
|
||||
w.Write(buf)
|
||||
}
|
||||
|
||||
// api/proxy/udp
|
||||
func apiProxyUdp(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
|
||||
var (
|
||||
buf []byte
|
||||
res GetProxyInfoResp
|
||||
)
|
||||
defer func() {
|
||||
log.Info("Http response [/api/proxy/udp]: code [%d]", res.Code)
|
||||
}()
|
||||
log.Info("Http request: [/api/proxy/udp]")
|
||||
|
||||
res.Proxies = getProxyStatsByType(consts.UdpProxy)
|
||||
|
||||
buf, _ = json.Marshal(&res)
|
||||
w.Write(buf)
|
||||
}
|
||||
|
||||
// api/proxy/http
|
||||
func apiProxyHttp(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
|
||||
var (
|
||||
buf []byte
|
||||
res GetProxyInfoResp
|
||||
)
|
||||
defer func() {
|
||||
log.Info("Http response [/api/proxy/http]: code [%d]", res.Code)
|
||||
}()
|
||||
log.Info("Http request: [/api/proxy/http]")
|
||||
|
||||
res.Proxies = getProxyStatsByType(consts.HttpProxy)
|
||||
|
||||
buf, _ = json.Marshal(&res)
|
||||
w.Write(buf)
|
||||
}
|
||||
|
||||
// api/proxy/https
|
||||
func apiProxyHttps(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
|
||||
var (
|
||||
buf []byte
|
||||
res GetProxyInfoResp
|
||||
)
|
||||
defer func() {
|
||||
log.Info("Http response [/api/proxy/https]: code [%d]", res.Code)
|
||||
}()
|
||||
log.Info("Http request: [/api/proxy/https]")
|
||||
|
||||
res.Proxies = getProxyStatsByType(consts.HttpsProxy)
|
||||
|
||||
buf, _ = json.Marshal(&res)
|
||||
w.Write(buf)
|
||||
}
|
||||
|
||||
func getProxyStatsByType(proxyType string) (proxyInfos []*ProxyStatsInfo) {
|
||||
proxyStats := StatsGetProxiesByType(proxyType)
|
||||
proxyInfos = make([]*ProxyStatsInfo, 0, len(proxyStats))
|
||||
for _, ps := range proxyStats {
|
||||
proxyInfo := &ProxyStatsInfo{}
|
||||
if pxy, ok := ServerService.pxyManager.GetByName(ps.Name); ok {
|
||||
proxyInfo.Conf = pxy.GetConf()
|
||||
proxyInfo.Status = consts.Online
|
||||
} else {
|
||||
proxyInfo.Status = consts.Offline
|
||||
}
|
||||
proxyInfo.TodayTrafficIn = ps.TodayTrafficIn
|
||||
proxyInfo.TodayTrafficOut = ps.TodayTrafficOut
|
||||
proxyInfo.CurConns = ps.CurConns
|
||||
proxyInfo.Name = ps.Name
|
||||
proxyInfos = append(proxyInfos, proxyInfo)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// api/proxy/traffic/:name
|
||||
type GetProxyTrafficResp struct {
|
||||
GeneralResponse
|
||||
|
||||
Name string `json:"name"`
|
||||
TrafficIn []int64 `json:"traffic_in"`
|
||||
TrafficOut []int64 `json:"traffic_out"`
|
||||
}
|
||||
|
||||
func apiProxyTraffic(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
|
||||
var (
|
||||
buf []byte
|
||||
res GetProxyTrafficResp
|
||||
)
|
||||
name := params.ByName("name")
|
||||
|
||||
defer func() {
|
||||
log.Info("Http response [/api/proxy/traffic/:name]: code [%d]", res.Code)
|
||||
}()
|
||||
log.Info("Http request: [/api/proxy/traffic/:name]")
|
||||
|
||||
res.Name = name
|
||||
proxyTrafficInfo := StatsGetProxyTraffic(name)
|
||||
if proxyTrafficInfo == nil {
|
||||
res.Code = 1
|
||||
res.Msg = "no proxy info found"
|
||||
} else {
|
||||
res.TrafficIn = proxyTrafficInfo.TrafficIn
|
||||
res.TrafficOut = proxyTrafficInfo.TrafficOut
|
||||
}
|
||||
|
||||
buf, _ = json.Marshal(&res)
|
||||
w.Write(buf)
|
||||
}
|
89
server/manager.go
Normal file
89
server/manager.go
Normal file
@ -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 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
|
||||
}
|
241
server/metric.go
Normal file
241
server/metric.go
Normal file
@ -0,0 +1,241 @@
|
||||
// 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 server
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/fatedier/frp/models/config"
|
||||
"github.com/fatedier/frp/utils/metric"
|
||||
)
|
||||
|
||||
const (
|
||||
ReserveDays = 7
|
||||
)
|
||||
|
||||
var globalStats *ServerStatistics
|
||||
|
||||
type ServerStatistics struct {
|
||||
TotalTrafficIn metric.DateCounter
|
||||
TotalTrafficOut metric.DateCounter
|
||||
CurConns metric.Counter
|
||||
|
||||
ClientCounts metric.Counter
|
||||
ProxyTypeCounts map[string]metric.Counter
|
||||
|
||||
ProxyStatistics map[string]*ProxyStatistics
|
||||
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
type ProxyStatistics struct {
|
||||
ProxyType string
|
||||
TrafficIn metric.DateCounter
|
||||
TrafficOut metric.DateCounter
|
||||
CurConns metric.Counter
|
||||
}
|
||||
|
||||
func init() {
|
||||
globalStats = &ServerStatistics{
|
||||
TotalTrafficIn: metric.NewDateCounter(ReserveDays),
|
||||
TotalTrafficOut: metric.NewDateCounter(ReserveDays),
|
||||
CurConns: metric.NewCounter(),
|
||||
|
||||
ClientCounts: metric.NewCounter(),
|
||||
ProxyTypeCounts: make(map[string]metric.Counter),
|
||||
|
||||
ProxyStatistics: make(map[string]*ProxyStatistics),
|
||||
}
|
||||
}
|
||||
|
||||
func StatsNewClient() {
|
||||
if config.ServerCommonCfg.DashboardPort != 0 {
|
||||
globalStats.ClientCounts.Inc(1)
|
||||
}
|
||||
}
|
||||
|
||||
func StatsCloseClient() {
|
||||
if config.ServerCommonCfg.DashboardPort != 0 {
|
||||
globalStats.ClientCounts.Dec(1)
|
||||
}
|
||||
}
|
||||
|
||||
func StatsNewProxy(name string, proxyType string) {
|
||||
if config.ServerCommonCfg.DashboardPort != 0 {
|
||||
globalStats.mu.Lock()
|
||||
defer globalStats.mu.Unlock()
|
||||
counter, ok := globalStats.ProxyTypeCounts[proxyType]
|
||||
if !ok {
|
||||
counter = metric.NewCounter()
|
||||
}
|
||||
counter.Inc(1)
|
||||
globalStats.ProxyTypeCounts[proxyType] = counter
|
||||
|
||||
proxyStats, ok := globalStats.ProxyStatistics[name]
|
||||
if !ok {
|
||||
proxyStats = &ProxyStatistics{
|
||||
ProxyType: proxyType,
|
||||
CurConns: metric.NewCounter(),
|
||||
TrafficIn: metric.NewDateCounter(ReserveDays),
|
||||
TrafficOut: metric.NewDateCounter(ReserveDays),
|
||||
}
|
||||
globalStats.ProxyStatistics[name] = proxyStats
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func StatsCloseProxy(proxyType string) {
|
||||
if config.ServerCommonCfg.DashboardPort != 0 {
|
||||
globalStats.mu.Lock()
|
||||
defer globalStats.mu.Unlock()
|
||||
if counter, ok := globalStats.ProxyTypeCounts[proxyType]; ok {
|
||||
counter.Dec(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func StatsOpenConnection(name string) {
|
||||
if config.ServerCommonCfg.DashboardPort != 0 {
|
||||
globalStats.CurConns.Inc(1)
|
||||
|
||||
globalStats.mu.Lock()
|
||||
defer globalStats.mu.Unlock()
|
||||
proxyStats, ok := globalStats.ProxyStatistics[name]
|
||||
if ok {
|
||||
proxyStats.CurConns.Inc(1)
|
||||
globalStats.ProxyStatistics[name] = proxyStats
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func StatsCloseConnection(name string) {
|
||||
if config.ServerCommonCfg.DashboardPort != 0 {
|
||||
globalStats.CurConns.Dec(1)
|
||||
|
||||
globalStats.mu.Lock()
|
||||
defer globalStats.mu.Unlock()
|
||||
proxyStats, ok := globalStats.ProxyStatistics[name]
|
||||
if ok {
|
||||
proxyStats.CurConns.Dec(1)
|
||||
globalStats.ProxyStatistics[name] = proxyStats
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func StatsAddTrafficIn(name string, trafficIn int64) {
|
||||
if config.ServerCommonCfg.DashboardPort != 0 {
|
||||
globalStats.TotalTrafficIn.Inc(trafficIn)
|
||||
|
||||
globalStats.mu.Lock()
|
||||
defer globalStats.mu.Unlock()
|
||||
|
||||
proxyStats, ok := globalStats.ProxyStatistics[name]
|
||||
if ok {
|
||||
proxyStats.TrafficIn.Inc(trafficIn)
|
||||
globalStats.ProxyStatistics[name] = proxyStats
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func StatsAddTrafficOut(name string, trafficOut int64) {
|
||||
if config.ServerCommonCfg.DashboardPort != 0 {
|
||||
globalStats.TotalTrafficOut.Inc(trafficOut)
|
||||
|
||||
globalStats.mu.Lock()
|
||||
defer globalStats.mu.Unlock()
|
||||
|
||||
proxyStats, ok := globalStats.ProxyStatistics[name]
|
||||
if ok {
|
||||
proxyStats.TrafficOut.Inc(trafficOut)
|
||||
globalStats.ProxyStatistics[name] = proxyStats
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Functions for getting server stats.
|
||||
type ServerStats struct {
|
||||
TotalTrafficIn int64
|
||||
TotalTrafficOut int64
|
||||
CurConns int64
|
||||
ClientCounts int64
|
||||
ProxyTypeCounts map[string]int64
|
||||
}
|
||||
|
||||
func StatsGetServer() *ServerStats {
|
||||
globalStats.mu.Lock()
|
||||
defer globalStats.mu.Unlock()
|
||||
s := &ServerStats{
|
||||
TotalTrafficIn: globalStats.TotalTrafficIn.TodayCount(),
|
||||
TotalTrafficOut: globalStats.TotalTrafficOut.TodayCount(),
|
||||
CurConns: globalStats.CurConns.Count(),
|
||||
ClientCounts: globalStats.ClientCounts.Count(),
|
||||
ProxyTypeCounts: make(map[string]int64),
|
||||
}
|
||||
for k, v := range globalStats.ProxyTypeCounts {
|
||||
s.ProxyTypeCounts[k] = v.Count()
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
type ProxyStats struct {
|
||||
Name string
|
||||
Type string
|
||||
TodayTrafficIn int64
|
||||
TodayTrafficOut int64
|
||||
CurConns int64
|
||||
}
|
||||
|
||||
func StatsGetProxiesByType(proxyType string) []*ProxyStats {
|
||||
res := make([]*ProxyStats, 0)
|
||||
globalStats.mu.Lock()
|
||||
defer globalStats.mu.Unlock()
|
||||
|
||||
for name, proxyStats := range globalStats.ProxyStatistics {
|
||||
if proxyStats.ProxyType != proxyType {
|
||||
continue
|
||||
}
|
||||
|
||||
ps := &ProxyStats{
|
||||
Name: name,
|
||||
Type: proxyStats.ProxyType,
|
||||
TodayTrafficIn: proxyStats.TrafficIn.TodayCount(),
|
||||
TodayTrafficOut: proxyStats.TrafficOut.TodayCount(),
|
||||
CurConns: proxyStats.CurConns.Count(),
|
||||
}
|
||||
res = append(res, ps)
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
type ProxyTrafficInfo struct {
|
||||
Name string
|
||||
TrafficIn []int64
|
||||
TrafficOut []int64
|
||||
}
|
||||
|
||||
func StatsGetProxyTraffic(name string) (res *ProxyTrafficInfo) {
|
||||
globalStats.mu.Lock()
|
||||
defer globalStats.mu.Unlock()
|
||||
|
||||
proxyStats, ok := globalStats.ProxyStatistics[name]
|
||||
if ok {
|
||||
res = &ProxyTrafficInfo{
|
||||
Name: name,
|
||||
}
|
||||
res.TrafficIn = proxyStats.TrafficIn.GetLastDaysCount(ReserveDays)
|
||||
res.TrafficOut = proxyStats.TrafficOut.GetLastDaysCount(ReserveDays)
|
||||
}
|
||||
return
|
||||
}
|
480
server/proxy.go
Normal file
480
server/proxy.go
Normal file
@ -0,0 +1,480 @@
|
||||
// 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 server
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/fatedier/frp/models/config"
|
||||
"github.com/fatedier/frp/models/msg"
|
||||
"github.com/fatedier/frp/models/proto/tcp"
|
||||
"github.com/fatedier/frp/models/proto/udp"
|
||||
"github.com/fatedier/frp/utils/errors"
|
||||
"github.com/fatedier/frp/utils/log"
|
||||
frpNet "github.com/fatedier/frp/utils/net"
|
||||
"github.com/fatedier/frp/utils/vhost"
|
||||
)
|
||||
|
||||
type Proxy interface {
|
||||
Run() error
|
||||
GetControl() *Control
|
||||
GetName() string
|
||||
GetConf() config.ProxyConf
|
||||
GetWorkConnFromPool() (workConn frpNet.Conn, err error)
|
||||
Close()
|
||||
log.Logger
|
||||
}
|
||||
|
||||
type BaseProxy struct {
|
||||
name string
|
||||
ctl *Control
|
||||
listeners []frpNet.Listener
|
||||
mu sync.RWMutex
|
||||
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 (pxy *BaseProxy) GetWorkConnFromPool() (workConn frpNet.Conn, err error) {
|
||||
ctl := pxy.GetControl()
|
||||
// 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
|
||||
}
|
||||
pxy.Info("get a 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
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// startListenHandler start a goroutine handler for each listener.
|
||||
// p: p will just be passed to handler(Proxy, frpNet.Conn).
|
||||
// handler: each proxy type can set different handler function to deal with connections accepted from listeners.
|
||||
func (pxy *BaseProxy) startListenHandler(p Proxy, handler func(Proxy, frpNet.Conn)) {
|
||||
for _, listener := range pxy.listeners {
|
||||
go func(l frpNet.Listener) {
|
||||
for {
|
||||
// block
|
||||
// if listener is closed, err returned
|
||||
c, err := l.Accept()
|
||||
if err != nil {
|
||||
pxy.Info("listener is closed")
|
||||
return
|
||||
}
|
||||
pxy.Debug("get a user connection [%s]", c.RemoteAddr().String())
|
||||
go handler(p, c)
|
||||
}
|
||||
}(listener)
|
||||
}
|
||||
}
|
||||
|
||||
func NewProxy(ctl *Control, pxyConf config.ProxyConf) (pxy Proxy, err error) {
|
||||
basePxy := BaseProxy{
|
||||
name: pxyConf.GetName(),
|
||||
ctl: ctl,
|
||||
listeners: make([]frpNet.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 := frpNet.ListenTcp(config.ServerCommonCfg.BindAddr, pxy.cfg.RemotePort)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
listener.AddLogPrefix(pxy.name)
|
||||
pxy.listeners = append(pxy.listeners, listener)
|
||||
pxy.Info("tcp proxy listen port [%d]", pxy.cfg.RemotePort)
|
||||
|
||||
pxy.startListenHandler(pxy, HandleUserTcpConnection)
|
||||
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) {
|
||||
routeConfig := &vhost.VhostRouteConfig{
|
||||
RewriteHost: pxy.cfg.HostHeaderRewrite,
|
||||
Username: pxy.cfg.HttpUser,
|
||||
Password: pxy.cfg.HttpPwd,
|
||||
}
|
||||
|
||||
locations := pxy.cfg.Locations
|
||||
if len(locations) == 0 {
|
||||
locations = []string{""}
|
||||
}
|
||||
for _, domain := range pxy.cfg.CustomDomains {
|
||||
routeConfig.Domain = domain
|
||||
for _, location := range locations {
|
||||
routeConfig.Location = location
|
||||
l, err := pxy.ctl.svr.VhostHttpMuxer.Listen(routeConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
l.AddLogPrefix(pxy.name)
|
||||
pxy.Info("http proxy listen for host [%s] location [%s]", routeConfig.Domain, routeConfig.Location)
|
||||
pxy.listeners = append(pxy.listeners, l)
|
||||
}
|
||||
}
|
||||
|
||||
if pxy.cfg.SubDomain != "" {
|
||||
routeConfig.Domain = pxy.cfg.SubDomain + "." + config.ServerCommonCfg.SubDomainHost
|
||||
for _, location := range locations {
|
||||
routeConfig.Location = location
|
||||
l, err := pxy.ctl.svr.VhostHttpMuxer.Listen(routeConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
l.AddLogPrefix(pxy.name)
|
||||
pxy.Info("http proxy listen for host [%s] location [%s]", routeConfig.Domain, routeConfig.Location)
|
||||
pxy.listeners = append(pxy.listeners, l)
|
||||
}
|
||||
}
|
||||
|
||||
pxy.startListenHandler(pxy, HandleUserTcpConnection)
|
||||
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) {
|
||||
routeConfig := &vhost.VhostRouteConfig{}
|
||||
|
||||
for _, domain := range pxy.cfg.CustomDomains {
|
||||
routeConfig.Domain = domain
|
||||
l, err := pxy.ctl.svr.VhostHttpsMuxer.Listen(routeConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
l.AddLogPrefix(pxy.name)
|
||||
pxy.Info("https proxy listen for host [%s]", routeConfig.Domain)
|
||||
pxy.listeners = append(pxy.listeners, l)
|
||||
}
|
||||
|
||||
if pxy.cfg.SubDomain != "" {
|
||||
routeConfig.Domain = pxy.cfg.SubDomain + "." + config.ServerCommonCfg.SubDomainHost
|
||||
l, err := pxy.ctl.svr.VhostHttpsMuxer.Listen(routeConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
l.AddLogPrefix(pxy.name)
|
||||
pxy.Info("https proxy listen for host [%s]", routeConfig.Domain)
|
||||
pxy.listeners = append(pxy.listeners, l)
|
||||
}
|
||||
|
||||
pxy.startListenHandler(pxy, HandleUserTcpConnection)
|
||||
return
|
||||
}
|
||||
|
||||
func (pxy *HttpsProxy) GetConf() config.ProxyConf {
|
||||
return pxy.cfg
|
||||
}
|
||||
|
||||
func (pxy *HttpsProxy) Close() {
|
||||
pxy.BaseProxy.Close()
|
||||
}
|
||||
|
||||
type UdpProxy struct {
|
||||
BaseProxy
|
||||
cfg *config.UdpProxyConf
|
||||
|
||||
// udpConn is the listener of udp packages
|
||||
udpConn *net.UDPConn
|
||||
|
||||
// there are always only one workConn at the same time
|
||||
// get another one if it closed
|
||||
workConn net.Conn
|
||||
|
||||
// sendCh is used for sending packages to workConn
|
||||
sendCh chan *msg.UdpPacket
|
||||
|
||||
// readCh is used for reading packages from workConn
|
||||
readCh chan *msg.UdpPacket
|
||||
|
||||
// checkCloseCh is used for watching if workConn is closed
|
||||
checkCloseCh chan int
|
||||
|
||||
isClosed bool
|
||||
}
|
||||
|
||||
func (pxy *UdpProxy) Run() (err error) {
|
||||
addr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", config.ServerCommonCfg.BindAddr, pxy.cfg.RemotePort))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
udpConn, err := net.ListenUDP("udp", addr)
|
||||
if err != nil {
|
||||
pxy.Warn("listen udp port error: %v", err)
|
||||
return err
|
||||
}
|
||||
pxy.Info("udp proxy listen port [%d]", pxy.cfg.RemotePort)
|
||||
|
||||
pxy.udpConn = udpConn
|
||||
pxy.sendCh = make(chan *msg.UdpPacket, 1024)
|
||||
pxy.readCh = make(chan *msg.UdpPacket, 1024)
|
||||
pxy.checkCloseCh = make(chan int)
|
||||
|
||||
// read message from workConn, if it returns any error, notify proxy to start a new workConn
|
||||
workConnReaderFn := func(conn net.Conn) {
|
||||
for {
|
||||
var (
|
||||
rawMsg msg.Message
|
||||
errRet error
|
||||
)
|
||||
pxy.Trace("loop waiting message from udp workConn")
|
||||
// client will send heartbeat in workConn for keeping alive
|
||||
conn.SetReadDeadline(time.Now().Add(time.Duration(60) * time.Second))
|
||||
if rawMsg, errRet = msg.ReadMsg(conn); errRet != nil {
|
||||
pxy.Warn("read from workConn for udp error: %v", errRet)
|
||||
conn.Close()
|
||||
// notify proxy to start a new work connection
|
||||
// ignore error here, it means the proxy is closed
|
||||
errors.PanicToError(func() {
|
||||
pxy.checkCloseCh <- 1
|
||||
})
|
||||
return
|
||||
}
|
||||
conn.SetReadDeadline(time.Time{})
|
||||
switch m := rawMsg.(type) {
|
||||
case *msg.Ping:
|
||||
pxy.Trace("udp work conn get ping message")
|
||||
continue
|
||||
case *msg.UdpPacket:
|
||||
if errRet := errors.PanicToError(func() {
|
||||
pxy.Trace("get udp message from workConn: %s", m.Content)
|
||||
pxy.readCh <- m
|
||||
StatsAddTrafficOut(pxy.GetName(), int64(len(m.Content)))
|
||||
}); errRet != nil {
|
||||
conn.Close()
|
||||
pxy.Info("reader goroutine for udp work connection closed")
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// send message to workConn
|
||||
workConnSenderFn := func(conn net.Conn, ctx context.Context) {
|
||||
var errRet error
|
||||
for {
|
||||
select {
|
||||
case udpMsg, ok := <-pxy.sendCh:
|
||||
if !ok {
|
||||
pxy.Info("sender goroutine for udp work connection closed")
|
||||
return
|
||||
}
|
||||
if errRet = msg.WriteMsg(conn, udpMsg); errRet != nil {
|
||||
pxy.Info("sender goroutine for udp work connection closed: %v", errRet)
|
||||
conn.Close()
|
||||
return
|
||||
} else {
|
||||
pxy.Trace("send message to udp workConn: %s", udpMsg.Content)
|
||||
StatsAddTrafficIn(pxy.GetName(), int64(len(udpMsg.Content)))
|
||||
continue
|
||||
}
|
||||
case <-ctx.Done():
|
||||
pxy.Info("sender goroutine for udp work connection closed")
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
go func() {
|
||||
// Sleep a while for waiting control send the NewProxyResp to client.
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
for {
|
||||
workConn, err := pxy.GetWorkConnFromPool()
|
||||
if err != nil {
|
||||
time.Sleep(1 * time.Second)
|
||||
// check if proxy is closed
|
||||
select {
|
||||
case _, ok := <-pxy.checkCloseCh:
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
default:
|
||||
}
|
||||
continue
|
||||
}
|
||||
// close the old workConn and replac it with a new one
|
||||
if pxy.workConn != nil {
|
||||
pxy.workConn.Close()
|
||||
}
|
||||
pxy.workConn = workConn
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
go workConnReaderFn(workConn)
|
||||
go workConnSenderFn(workConn, ctx)
|
||||
_, ok := <-pxy.checkCloseCh
|
||||
cancel()
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
// Read from user connections and send wrapped udp message to sendCh (forwarded by workConn).
|
||||
// Client will transfor udp message to local udp service and waiting for response for a while.
|
||||
// Response will be wrapped to be forwarded by work connection to server.
|
||||
// Close readCh and sendCh at the end.
|
||||
go func() {
|
||||
udp.ForwardUserConn(udpConn, pxy.readCh, pxy.sendCh)
|
||||
pxy.Close()
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (pxy *UdpProxy) GetConf() config.ProxyConf {
|
||||
return pxy.cfg
|
||||
}
|
||||
|
||||
func (pxy *UdpProxy) Close() {
|
||||
pxy.mu.Lock()
|
||||
defer pxy.mu.Unlock()
|
||||
if !pxy.isClosed {
|
||||
pxy.isClosed = true
|
||||
|
||||
pxy.BaseProxy.Close()
|
||||
pxy.workConn.Close()
|
||||
pxy.udpConn.Close()
|
||||
|
||||
// all channels only closed here
|
||||
close(pxy.checkCloseCh)
|
||||
close(pxy.readCh)
|
||||
close(pxy.sendCh)
|
||||
}
|
||||
}
|
||||
|
||||
// HandleUserTcpConnection is used for incoming tcp user connections.
|
||||
// It can be used for tcp, http, https type.
|
||||
func HandleUserTcpConnection(pxy Proxy, userConn frpNet.Conn) {
|
||||
defer userConn.Close()
|
||||
|
||||
// try all connections from the pool
|
||||
workConn, err := pxy.GetWorkConnFromPool()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer workConn.Close()
|
||||
|
||||
var local io.ReadWriteCloser = workConn
|
||||
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)
|
||||
}
|
||||
pxy.Debug("join connections, workConn(l[%s] r[%s]) userConn(l[%s] r[%s])", workConn.LocalAddr().String(),
|
||||
workConn.RemoteAddr().String(), userConn.LocalAddr().String(), userConn.RemoteAddr().String())
|
||||
|
||||
StatsOpenConnection(pxy.GetName())
|
||||
inCount, outCount := tcp.Join(local, userConn)
|
||||
StatsCloseConnection(pxy.GetName())
|
||||
StatsAddTrafficIn(pxy.GetName(), inCount)
|
||||
StatsAddTrafficOut(pxy.GetName(), outCount)
|
||||
pxy.Debug("join connections closed")
|
||||
}
|
221
server/service.go
Normal file
221
server/service.go
Normal file
@ -0,0 +1,221 @@
|
||||
// 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 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"
|
||||
)
|
||||
|
||||
const (
|
||||
connReadTimeout time.Duration = 10 * time.Second
|
||||
)
|
||||
|
||||
var ServerService *Service
|
||||
|
||||
// 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
|
||||
frpConn.SetReadDeadline(time.Now().Add(connReadTimeout))
|
||||
if rawMsg, err = msg.ReadMsg(frpConn); err != nil {
|
||||
log.Warn("Failed to read message: %v", err)
|
||||
frpConn.Close()
|
||||
return
|
||||
}
|
||||
frpConn.SetReadDeadline(time.Time{})
|
||||
|
||||
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()
|
||||
|
||||
// for statistics
|
||||
StatsNewClient()
|
||||
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)
|
||||
}
|
5
src/assets/static/css/bootstrap.min.css
vendored
5
src/assets/static/css/bootstrap.min.css
vendored
File diff suppressed because one or more lines are too long
4
src/assets/static/css/font-awesome.min.css
vendored
4
src/assets/static/css/font-awesome.min.css
vendored
File diff suppressed because one or more lines are too long
@ -1,18 +0,0 @@
|
||||
|
||||
@font-face {font-family: "iconfont";
|
||||
src: url('/static/font/iconfont.eot'); /* IE9*/
|
||||
src: url('/static/font/iconfont.eot#iefix') format('embedded-opentype'), /* IE6-IE8 */
|
||||
url('/static/font/iconfont.woff') format('woff'), /* chrome, firefox */
|
||||
url('/static/font/iconfont.ttf') format('truetype'), /* chrome, firefox, opera, Safari, Android, iOS 4.2+*/
|
||||
url('/static/font/iconfont.svg#iconfont') format('svg'); /* iOS 4.1- */
|
||||
}
|
||||
|
||||
.iconfont {
|
||||
font-family:"iconfont" !important;
|
||||
font-size:16px;
|
||||
font-style:normal;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-webkit-text-stroke-width: 0.2px;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
.icon-sort:before { content: "\e66d"; }
|
Binary file not shown.
Before Width: | Height: | Size: 4.2 KiB |
Binary file not shown.
@ -1,39 +0,0 @@
|
||||
<?xml version="1.0" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" >
|
||||
<svg xmlns="http://www.w3.org/2000/svg">
|
||||
<metadata>
|
||||
Created by FontForge 20120731 at Thu Aug 4 18:45:29 2016
|
||||
By admin
|
||||
</metadata>
|
||||
<defs>
|
||||
<font id="iconfont" horiz-adv-x="374" >
|
||||
<font-face
|
||||
font-family="iconfont"
|
||||
font-weight="500"
|
||||
font-stretch="normal"
|
||||
units-per-em="1024"
|
||||
panose-1="2 0 6 3 0 0 0 0 0 0"
|
||||
ascent="896"
|
||||
descent="-128"
|
||||
x-height="792"
|
||||
bbox="34 -83 956 792"
|
||||
underline-thickness="50"
|
||||
underline-position="-100"
|
||||
unicode-range="U+0078-E66D"
|
||||
/>
|
||||
<missing-glyph
|
||||
d="M34 0v682h272v-682h-272zM68 34h204v614h-204v-614z" />
|
||||
<glyph glyph-name=".notdef"
|
||||
d="M34 0v682h272v-682h-272zM68 34h204v614h-204v-614z" />
|
||||
<glyph glyph-name=".null" horiz-adv-x="0"
|
||||
/>
|
||||
<glyph glyph-name="nonmarkingreturn" horiz-adv-x="341"
|
||||
/>
|
||||
<glyph glyph-name="x" unicode="x" horiz-adv-x="1001"
|
||||
d="M281 543q-27 -1 -53 -1h-83q-18 0 -36.5 -6t-32.5 -18.5t-23 -32t-9 -45.5v-76h912v41q0 16 -0.5 30t-0.5 18q0 13 -5 29t-17 29.5t-31.5 22.5t-49.5 9h-133v-97h-438v97zM955 310v-52q0 -23 0.5 -52t0.5 -58t-10.5 -47.5t-26 -30t-33 -16t-31.5 -4.5q-14 -1 -29.5 -0.5
|
||||
t-29.5 0.5h-32l-45 128h-439l-44 -128h-29h-34q-20 0 -45 1q-25 0 -41 9.5t-25.5 23t-13.5 29.5t-4 30v167h911zM163 247q-12 0 -21 -8.5t-9 -21.5t9 -21.5t21 -8.5q13 0 22 8.5t9 21.5t-9 21.5t-22 8.5zM316 123q-8 -26 -14 -48q-5 -19 -10.5 -37t-7.5 -25t-3 -15t1 -14.5
|
||||
t9.5 -10.5t21.5 -4h37h67h81h80h64h36q23 0 34 12t2 38q-5 13 -9.5 30.5t-9.5 34.5q-5 19 -11 39h-368zM336 498v228q0 11 2.5 23t10 21.5t20.5 15.5t34 6h188q31 0 51.5 -14.5t20.5 -52.5v-227h-327z" />
|
||||
<glyph glyph-name="uniE66D" unicode="" horiz-adv-x="1024"
|
||||
d="M922 440l-39 -39l-207 200v-684h-55v766h54zM349 -83l-247 243l39 39l207 -200v684h55v-766h-54z" />
|
||||
</font>
|
||||
</defs></svg>
|
Before Width: | Height: | Size: 1.8 KiB |
Binary file not shown.
Binary file not shown.
@ -1,310 +0,0 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<title>frp</title>
|
||||
<link href="static/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link href="static/css/iconfont.css" rel="stylesheet">
|
||||
<script src="static/js/jquery.min.js"></script>
|
||||
<script src="static/js/bootstrap.min.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="container" style="margin-top: 80px">
|
||||
<div class="row">
|
||||
<div class="col-md-7">
|
||||
<div class="panel panel-default">
|
||||
<div ng-app="myTable" class="panel-body">
|
||||
<table class="table table-bordered" ng-app="myTable" ng-controller="myCtrl">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="tab_info" ng-click="col='name';desc=!desc">Server<i class="iconfont pull-right"></i></th>
|
||||
<th class="tab_info" ng-click="col='type';desc=!desc">Type<i class="iconfont pull-right"></i></th>
|
||||
<th class="tab_info" ng-click="col='listen_port';desc=!desc">Port<i class="iconfont pull-right"></i></th>
|
||||
<th class="tab_info" ng-click="col='status';desc=!desc">Status<i class="iconfont pull-right"></i></th>
|
||||
<th class="tab_info" ng-click="col='current_conns';desc=!desc">CurCon<i class="iconfont pull-right"></i></th>
|
||||
<th class="tab_info" ng-click="col='daily[daily.length-1].flow_out';desc=!desc">FlowOut<i class="iconfont pull-right"></i></th>
|
||||
<th class="tab_info" ng-click="col='daily[daily.length-1].flow_in';desc=!desc">FlowIn<i class="iconfont pull-right"></i></th>
|
||||
<th class="tab_info" ng-click="col='daily[daily.length-1].total_accept_conns';desc=!desc">TotalAcceptConns<i class="iconfont pull-right"></i></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="tab_body">
|
||||
<tr ng-repeat="x in proxies|orderBy:col:desc">
|
||||
<td>
|
||||
<button class="btn btn-xs btn-block btn-success center-block" onclick="changeit(this)"><span ng-bind="x.name"></span></button>
|
||||
</td>
|
||||
<td><span ng-bind="x.type"></span></td>
|
||||
<td><span ng-bind="x.listen_port"></span></td>
|
||||
<td><span ng-bind="x.status"></span></td>
|
||||
<td><span ng-bind="x.current_conns"></span></td>
|
||||
<td><span ng-bind="x.daily[x.daily.length-1].flow_out"></span></td>
|
||||
<td><span ng-bind="x.daily[x.daily.length-1].flow_in"></span></td>
|
||||
<td><span ng-bind="x.daily[x.daily.length-1].total_accept_conns"></span></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-5">
|
||||
<div id="view1" style="height: 300pt"></div>
|
||||
<div id="view2" style="height: 300pt"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script src="static/js/angular.min.js"></script>
|
||||
<script type="text/javascript" src="static/js/echarts.min.js"></script>
|
||||
<script>
|
||||
var alldata = new Array();
|
||||
var index = null;
|
||||
<<< range .>>>
|
||||
alldata["<<< .Name >>>"] = {
|
||||
name: "<<< .Name >>>",
|
||||
type: "<<< .Type >>>",
|
||||
bind_addr: "<<< .BindAddr >>>",
|
||||
listen_port: "<<< .ListenPort >>>",
|
||||
current_conns: <<< .CurrentConns >>> ,
|
||||
domains: [ <<< range.CustomDomains >>> "<<< . >>>", <<< end >>> ],
|
||||
locations: [ <<< range.Locations >>> "<<< . >>>", <<< end >>> ],
|
||||
stat: "<<< .Status>>>",
|
||||
use_encryption: "<<< .UseEncryption >>>",
|
||||
use_gzip: "<<< .UseGzip >>>",
|
||||
privilege_mode: "<<< .PrivilegeMode >>>",
|
||||
times: [],
|
||||
ins: [],
|
||||
outs: [],
|
||||
conns: [],
|
||||
};
|
||||
<<< end >>>
|
||||
|
||||
var newproxies = <<< . >>>;
|
||||
var dom1 = document.getElementById("view1");
|
||||
var dom2 = document.getElementById("view2");
|
||||
var myChart1 = echarts.init(dom1);
|
||||
var myChart2 = echarts.init(dom2);
|
||||
var option1 = null;
|
||||
var option2 = null;
|
||||
var maxval = 0;
|
||||
var dw = " B";
|
||||
var step = 1;
|
||||
|
||||
function reloadview() {
|
||||
window.maxval = 0;
|
||||
window.dw = " B";
|
||||
window.step = 1;
|
||||
for (var val1 in alldata[index].ins) {
|
||||
if (maxval < alldata[index].ins[val1]) {
|
||||
window.maxval = alldata[index].outs[val1];
|
||||
}
|
||||
}
|
||||
|
||||
for (var val2 in alldata[index].outs) {
|
||||
if (maxval < alldata[index].outs[val2]) {
|
||||
window.maxval = alldata[index].outs[val2]
|
||||
}
|
||||
}
|
||||
|
||||
if (maxval > 1024 * 1024) {
|
||||
window.dw = " MB";
|
||||
window.step = 1024 * 1024;
|
||||
} else if (maxval > 1024) {
|
||||
window.dw = " KB";
|
||||
window.step = 1024;
|
||||
}
|
||||
|
||||
window.option1 = {
|
||||
title: {
|
||||
text: alldata[index].name
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'axis'
|
||||
},
|
||||
legend: {
|
||||
data: ['flow_in', 'flow_out']
|
||||
},
|
||||
grid: {
|
||||
left: '3%',
|
||||
right: '6%',
|
||||
bottom: '3%',
|
||||
containLabel: true
|
||||
},
|
||||
toolbox: {
|
||||
feature: {
|
||||
saveAsImage: {}
|
||||
}
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
data: alldata[index].times
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
axisLabel: {
|
||||
formatter: function(value) {
|
||||
var data = (value / step).toFixed(2);
|
||||
return data + dw;
|
||||
}
|
||||
}
|
||||
},
|
||||
series: [{
|
||||
name: 'flow_in',
|
||||
type: 'bar',
|
||||
stack: '总量',
|
||||
data: alldata[index].ins
|
||||
}, {
|
||||
name: 'flow_out',
|
||||
type: 'bar',
|
||||
stack: '总量',
|
||||
data: alldata[index].outs
|
||||
}]
|
||||
};
|
||||
|
||||
window.option2 = {
|
||||
title: {
|
||||
text: ""
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'axis'
|
||||
},
|
||||
legend: {
|
||||
data: ['total_accept_conns']
|
||||
},
|
||||
grid: {
|
||||
left: '3%',
|
||||
right: '6%',
|
||||
bottom: '3%',
|
||||
containLabel: true
|
||||
},
|
||||
toolbox: {
|
||||
feature: {
|
||||
saveAsImage: {}
|
||||
}
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
data: alldata[index].times
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value'
|
||||
},
|
||||
series: [{
|
||||
name: 'total_accept_conns',
|
||||
type: 'bar',
|
||||
stack: '总量',
|
||||
data: alldata[index].conns
|
||||
}]
|
||||
};
|
||||
|
||||
myChart1.setOption(option1, true);
|
||||
myChart2.setOption(option2, true);
|
||||
};;
|
||||
|
||||
var showdetail = false;
|
||||
var newindex = 0;
|
||||
|
||||
function cleandetail() {
|
||||
$(".info_detail").remove();
|
||||
showdetail = false;
|
||||
};
|
||||
|
||||
function changeit(id) {
|
||||
newindex = id.firstElementChild.innerHTML;
|
||||
if (index == newindex) {
|
||||
if (showdetail) {
|
||||
cleandetail();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
index = newindex;
|
||||
cleandetail();
|
||||
window.showdetail = true;
|
||||
|
||||
var newrow = "<tr class='info_detail'><th colspan='4'>Key</th><th colspan='4'>Val</th><tr>";
|
||||
for (var domainindex in alldata[index].domains) {
|
||||
newrow += "<tr class='info_detail'><td colspan='4'>Domains</td><td colspan='4'>" +
|
||||
alldata[index].domains[domainindex] + "</td><tr>";
|
||||
}
|
||||
for (var locindex in alldata[index].locations) {
|
||||
newrow += "<tr class='info_detail'><td colspan='4'>Locations</td><td colspan='4'>" +
|
||||
alldata[index].locations[locindex] + "</td><tr>";
|
||||
}
|
||||
newrow += "<tr class='info_detail'><td colspan='4'>Ip</td><td colspan='4'>" + alldata[index].bind_addr + "</td><tr>";
|
||||
newrow += "<tr class='info_detail'><td colspan='4'>Status</td><td colspan='4'>" + alldata[index].stat + "</td><tr>";
|
||||
newrow += "<tr class='info_detail'><td colspan='4'>Encryption</td><td colspan='4'>" + alldata[index].use_encryption + "</td><tr>";
|
||||
newrow += "<tr class='info_detail'><td colspan='4'>Gzip</td><td colspan='4'>" + alldata[index].use_gzip + "</td><tr>";
|
||||
newrow += "<tr class='info_detail'><td colspan='4'>Privilege</td><td colspan='4'>" + alldata[index].privilege_mode + "</td><tr>";
|
||||
|
||||
var hehe = $(id.parentNode.parentNode);
|
||||
|
||||
$(hehe).after(newrow);
|
||||
reloadview();
|
||||
};
|
||||
|
||||
// add somedata
|
||||
{
|
||||
var ttdy = new Date();
|
||||
var today = ttdy.getFullYear() * 10000 + (1 + ttdy.getMonth()) * 100 + ttdy.getDate();
|
||||
for (var inx in newproxies) {
|
||||
if (newproxies[inx].current_conns == undefined) {
|
||||
newproxies[inx].current_conns = 0;
|
||||
alldata[newproxies[inx].name].current_conns = 0;
|
||||
}
|
||||
|
||||
if (newproxies[inx].daily == undefined ) {
|
||||
newproxies[inx].daily = [];
|
||||
}
|
||||
|
||||
newproxies[inx].daily.sort(function (a, b) {
|
||||
return a.time > b.time;
|
||||
});
|
||||
|
||||
for (var iinnx in newproxies[inx].daily) {
|
||||
alldata[newproxies[inx].name].times.push(newproxies[inx].daily[iinnx].time);
|
||||
alldata[newproxies[inx].name].ins.push(newproxies[inx].daily[iinnx].flow_in);
|
||||
alldata[newproxies[inx].name].outs.push(newproxies[inx].daily[iinnx].flow_out);
|
||||
alldata[newproxies[inx].name].conns.push(newproxies[inx].daily[iinnx].total_accept_conns);
|
||||
}
|
||||
|
||||
if (newproxies[inx].daily.length == 0 || newproxies[inx].daily[newproxies[inx].daily.length-1].time != today) {
|
||||
alldata[newproxies[inx].name].times.push(today);
|
||||
alldata[newproxies[inx].name].ins.push(0);
|
||||
alldata[newproxies[inx].name].outs.push(0);
|
||||
alldata[newproxies[inx].name].conns.push(0);
|
||||
newproxies[inx].daily.push({
|
||||
time: today,
|
||||
flow_in: 0,
|
||||
flow_out: 0,
|
||||
total_accept_conns: 0
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var app = angular.module('myTable', []);
|
||||
|
||||
app.controller('myCtrl', function($scope) {
|
||||
$scope.col = 'name';
|
||||
$scope.desc = 0;
|
||||
$scope.proxies = newproxies;
|
||||
});
|
||||
|
||||
$(".tab_info").hover(
|
||||
function() {
|
||||
$(this).css("color", "orange");
|
||||
},
|
||||
function() {
|
||||
$(this).css("color", "black");
|
||||
});
|
||||
// set default index
|
||||
for (var inx in alldata) {
|
||||
if (window.index == null || window.index > inx) {
|
||||
window.index = inx;
|
||||
}
|
||||
}
|
||||
|
||||
reloadview();
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
317
src/assets/static/js/angular.min.js
vendored
317
src/assets/static/js/angular.min.js
vendored
@ -1,317 +0,0 @@
|
||||
/*
|
||||
AngularJS v1.5.8
|
||||
(c) 2010-2016 Google, Inc. http://angularjs.org
|
||||
License: MIT
|
||||
*/
|
||||
(function(C){'use strict';function N(a){return function(){var b=arguments[0],d;d="["+(a?a+":":"")+b+"] http://errors.angularjs.org/1.5.8/"+(a?a+"/":"")+b;for(b=1;b<arguments.length;b++){d=d+(1==b?"?":"&")+"p"+(b-1)+"=";var c=encodeURIComponent,e;e=arguments[b];e="function"==typeof e?e.toString().replace(/ \{[\s\S]*$/,""):"undefined"==typeof e?"undefined":"string"!=typeof e?JSON.stringify(e):e;d+=c(e)}return Error(d)}}function ta(a){if(null==a||Va(a))return!1;if(L(a)||G(a)||F&&a instanceof F)return!0;
|
||||
var b="length"in Object(a)&&a.length;return T(b)&&(0<=b&&(b-1 in a||a instanceof Array)||"function"==typeof a.item)}function q(a,b,d){var c,e;if(a)if(z(a))for(c in a)"prototype"==c||"length"==c||"name"==c||a.hasOwnProperty&&!a.hasOwnProperty(c)||b.call(d,a[c],c,a);else if(L(a)||ta(a)){var f="object"!==typeof a;c=0;for(e=a.length;c<e;c++)(f||c in a)&&b.call(d,a[c],c,a)}else if(a.forEach&&a.forEach!==q)a.forEach(b,d,a);else if(sc(a))for(c in a)b.call(d,a[c],c,a);else if("function"===typeof a.hasOwnProperty)for(c in a)a.hasOwnProperty(c)&&
|
||||
b.call(d,a[c],c,a);else for(c in a)ua.call(a,c)&&b.call(d,a[c],c,a);return a}function tc(a,b,d){for(var c=Object.keys(a).sort(),e=0;e<c.length;e++)b.call(d,a[c[e]],c[e]);return c}function uc(a){return function(b,d){a(d,b)}}function Yd(){return++pb}function Pb(a,b,d){for(var c=a.$$hashKey,e=0,f=b.length;e<f;++e){var g=b[e];if(D(g)||z(g))for(var h=Object.keys(g),k=0,l=h.length;k<l;k++){var m=h[k],n=g[m];d&&D(n)?da(n)?a[m]=new Date(n.valueOf()):Wa(n)?a[m]=new RegExp(n):n.nodeName?a[m]=n.cloneNode(!0):
|
||||
Qb(n)?a[m]=n.clone():(D(a[m])||(a[m]=L(n)?[]:{}),Pb(a[m],[n],!0)):a[m]=n}}c?a.$$hashKey=c:delete a.$$hashKey;return a}function S(a){return Pb(a,va.call(arguments,1),!1)}function Zd(a){return Pb(a,va.call(arguments,1),!0)}function Z(a){return parseInt(a,10)}function Rb(a,b){return S(Object.create(a),b)}function A(){}function Xa(a){return a}function ha(a){return function(){return a}}function vc(a){return z(a.toString)&&a.toString!==ma}function y(a){return"undefined"===typeof a}function w(a){return"undefined"!==
|
||||
typeof a}function D(a){return null!==a&&"object"===typeof a}function sc(a){return null!==a&&"object"===typeof a&&!wc(a)}function G(a){return"string"===typeof a}function T(a){return"number"===typeof a}function da(a){return"[object Date]"===ma.call(a)}function z(a){return"function"===typeof a}function Wa(a){return"[object RegExp]"===ma.call(a)}function Va(a){return a&&a.window===a}function Ya(a){return a&&a.$evalAsync&&a.$watch}function Ga(a){return"boolean"===typeof a}function $d(a){return a&&T(a.length)&&
|
||||
ae.test(ma.call(a))}function Qb(a){return!(!a||!(a.nodeName||a.prop&&a.attr&&a.find))}function be(a){var b={};a=a.split(",");var d;for(d=0;d<a.length;d++)b[a[d]]=!0;return b}function wa(a){return Q(a.nodeName||a[0]&&a[0].nodeName)}function Za(a,b){var d=a.indexOf(b);0<=d&&a.splice(d,1);return d}function pa(a,b){function d(a,b){var d=b.$$hashKey,e;if(L(a)){e=0;for(var f=a.length;e<f;e++)b.push(c(a[e]))}else if(sc(a))for(e in a)b[e]=c(a[e]);else if(a&&"function"===typeof a.hasOwnProperty)for(e in a)a.hasOwnProperty(e)&&
|
||||
(b[e]=c(a[e]));else for(e in a)ua.call(a,e)&&(b[e]=c(a[e]));d?b.$$hashKey=d:delete b.$$hashKey;return b}function c(a){if(!D(a))return a;var b=f.indexOf(a);if(-1!==b)return g[b];if(Va(a)||Ya(a))throw xa("cpws");var b=!1,c=e(a);void 0===c&&(c=L(a)?[]:Object.create(wc(a)),b=!0);f.push(a);g.push(c);return b?d(a,c):c}function e(a){switch(ma.call(a)){case "[object Int8Array]":case "[object Int16Array]":case "[object Int32Array]":case "[object Float32Array]":case "[object Float64Array]":case "[object Uint8Array]":case "[object Uint8ClampedArray]":case "[object Uint16Array]":case "[object Uint32Array]":return new a.constructor(c(a.buffer),
|
||||
a.byteOffset,a.length);case "[object ArrayBuffer]":if(!a.slice){var b=new ArrayBuffer(a.byteLength);(new Uint8Array(b)).set(new Uint8Array(a));return b}return a.slice(0);case "[object Boolean]":case "[object Number]":case "[object String]":case "[object Date]":return new a.constructor(a.valueOf());case "[object RegExp]":return b=new RegExp(a.source,a.toString().match(/[^\/]*$/)[0]),b.lastIndex=a.lastIndex,b;case "[object Blob]":return new a.constructor([a],{type:a.type})}if(z(a.cloneNode))return a.cloneNode(!0)}
|
||||
var f=[],g=[];if(b){if($d(b)||"[object ArrayBuffer]"===ma.call(b))throw xa("cpta");if(a===b)throw xa("cpi");L(b)?b.length=0:q(b,function(a,d){"$$hashKey"!==d&&delete b[d]});f.push(a);g.push(b);return d(a,b)}return c(a)}function na(a,b){if(a===b)return!0;if(null===a||null===b)return!1;if(a!==a&&b!==b)return!0;var d=typeof a,c;if(d==typeof b&&"object"==d)if(L(a)){if(!L(b))return!1;if((d=a.length)==b.length){for(c=0;c<d;c++)if(!na(a[c],b[c]))return!1;return!0}}else{if(da(a))return da(b)?na(a.getTime(),
|
||||
b.getTime()):!1;if(Wa(a))return Wa(b)?a.toString()==b.toString():!1;if(Ya(a)||Ya(b)||Va(a)||Va(b)||L(b)||da(b)||Wa(b))return!1;d=U();for(c in a)if("$"!==c.charAt(0)&&!z(a[c])){if(!na(a[c],b[c]))return!1;d[c]=!0}for(c in b)if(!(c in d)&&"$"!==c.charAt(0)&&w(b[c])&&!z(b[c]))return!1;return!0}return!1}function $a(a,b,d){return a.concat(va.call(b,d))}function ab(a,b){var d=2<arguments.length?va.call(arguments,2):[];return!z(b)||b instanceof RegExp?b:d.length?function(){return arguments.length?b.apply(a,
|
||||
$a(d,arguments,0)):b.apply(a,d)}:function(){return arguments.length?b.apply(a,arguments):b.call(a)}}function ce(a,b){var d=b;"string"===typeof a&&"$"===a.charAt(0)&&"$"===a.charAt(1)?d=void 0:Va(b)?d="$WINDOW":b&&C.document===b?d="$DOCUMENT":Ya(b)&&(d="$SCOPE");return d}function bb(a,b){if(!y(a))return T(b)||(b=b?2:null),JSON.stringify(a,ce,b)}function xc(a){return G(a)?JSON.parse(a):a}function yc(a,b){a=a.replace(de,"");var d=Date.parse("Jan 01, 1970 00:00:00 "+a)/6E4;return isNaN(d)?b:d}function Sb(a,
|
||||
b,d){d=d?-1:1;var c=a.getTimezoneOffset();b=yc(b,c);d*=b-c;a=new Date(a.getTime());a.setMinutes(a.getMinutes()+d);return a}function ya(a){a=F(a).clone();try{a.empty()}catch(b){}var d=F("<div>").append(a).html();try{return a[0].nodeType===Ma?Q(d):d.match(/^(<[^>]+>)/)[1].replace(/^<([\w\-]+)/,function(a,b){return"<"+Q(b)})}catch(c){return Q(d)}}function zc(a){try{return decodeURIComponent(a)}catch(b){}}function Ac(a){var b={};q((a||"").split("&"),function(a){var c,e,f;a&&(e=a=a.replace(/\+/g,"%20"),
|
||||
c=a.indexOf("="),-1!==c&&(e=a.substring(0,c),f=a.substring(c+1)),e=zc(e),w(e)&&(f=w(f)?zc(f):!0,ua.call(b,e)?L(b[e])?b[e].push(f):b[e]=[b[e],f]:b[e]=f))});return b}function Tb(a){var b=[];q(a,function(a,c){L(a)?q(a,function(a){b.push(ea(c,!0)+(!0===a?"":"="+ea(a,!0)))}):b.push(ea(c,!0)+(!0===a?"":"="+ea(a,!0)))});return b.length?b.join("&"):""}function qb(a){return ea(a,!0).replace(/%26/gi,"&").replace(/%3D/gi,"=").replace(/%2B/gi,"+")}function ea(a,b){return encodeURIComponent(a).replace(/%40/gi,
|
||||
"@").replace(/%3A/gi,":").replace(/%24/g,"$").replace(/%2C/gi,",").replace(/%3B/gi,";").replace(/%20/g,b?"%20":"+")}function ee(a,b){var d,c,e=Na.length;for(c=0;c<e;++c)if(d=Na[c]+b,G(d=a.getAttribute(d)))return d;return null}function fe(a,b){var d,c,e={};q(Na,function(b){b+="app";!d&&a.hasAttribute&&a.hasAttribute(b)&&(d=a,c=a.getAttribute(b))});q(Na,function(b){b+="app";var e;!d&&(e=a.querySelector("["+b.replace(":","\\:")+"]"))&&(d=e,c=e.getAttribute(b))});d&&(e.strictDi=null!==ee(d,"strict-di"),
|
||||
b(d,c?[c]:[],e))}function Bc(a,b,d){D(d)||(d={});d=S({strictDi:!1},d);var c=function(){a=F(a);if(a.injector()){var c=a[0]===C.document?"document":ya(a);throw xa("btstrpd",c.replace(/</,"<").replace(/>/,">"));}b=b||[];b.unshift(["$provide",function(b){b.value("$rootElement",a)}]);d.debugInfoEnabled&&b.push(["$compileProvider",function(a){a.debugInfoEnabled(!0)}]);b.unshift("ng");c=cb(b,d.strictDi);c.invoke(["$rootScope","$rootElement","$compile","$injector",function(a,b,c,d){a.$apply(function(){b.data("$injector",
|
||||
d);c(b)(a)})}]);return c},e=/^NG_ENABLE_DEBUG_INFO!/,f=/^NG_DEFER_BOOTSTRAP!/;C&&e.test(C.name)&&(d.debugInfoEnabled=!0,C.name=C.name.replace(e,""));if(C&&!f.test(C.name))return c();C.name=C.name.replace(f,"");ca.resumeBootstrap=function(a){q(a,function(a){b.push(a)});return c()};z(ca.resumeDeferredBootstrap)&&ca.resumeDeferredBootstrap()}function ge(){C.name="NG_ENABLE_DEBUG_INFO!"+C.name;C.location.reload()}function he(a){a=ca.element(a).injector();if(!a)throw xa("test");return a.get("$$testability")}
|
||||
function Cc(a,b){b=b||"_";return a.replace(ie,function(a,c){return(c?b:"")+a.toLowerCase()})}function je(){var a;if(!Dc){var b=rb();(qa=y(b)?C.jQuery:b?C[b]:void 0)&&qa.fn.on?(F=qa,S(qa.fn,{scope:Oa.scope,isolateScope:Oa.isolateScope,controller:Oa.controller,injector:Oa.injector,inheritedData:Oa.inheritedData}),a=qa.cleanData,qa.cleanData=function(b){for(var c,e=0,f;null!=(f=b[e]);e++)(c=qa._data(f,"events"))&&c.$destroy&&qa(f).triggerHandler("$destroy");a(b)}):F=O;ca.element=F;Dc=!0}}function sb(a,
|
||||
b,d){if(!a)throw xa("areq",b||"?",d||"required");return a}function Pa(a,b,d){d&&L(a)&&(a=a[a.length-1]);sb(z(a),b,"not a function, got "+(a&&"object"===typeof a?a.constructor.name||"Object":typeof a));return a}function Qa(a,b){if("hasOwnProperty"===a)throw xa("badname",b);}function Ec(a,b,d){if(!b)return a;b=b.split(".");for(var c,e=a,f=b.length,g=0;g<f;g++)c=b[g],a&&(a=(e=a)[c]);return!d&&z(a)?ab(e,a):a}function tb(a){for(var b=a[0],d=a[a.length-1],c,e=1;b!==d&&(b=b.nextSibling);e++)if(c||a[e]!==
|
||||
b)c||(c=F(va.call(a,0,e))),c.push(b);return c||a}function U(){return Object.create(null)}function ke(a){function b(a,b,c){return a[b]||(a[b]=c())}var d=N("$injector"),c=N("ng");a=b(a,"angular",Object);a.$$minErr=a.$$minErr||N;return b(a,"module",function(){var a={};return function(f,g,h){if("hasOwnProperty"===f)throw c("badname","module");g&&a.hasOwnProperty(f)&&(a[f]=null);return b(a,f,function(){function a(b,d,e,f){f||(f=c);return function(){f[e||"push"]([b,d,arguments]);return R}}function b(a,
|
||||
d){return function(b,e){e&&z(e)&&(e.$$moduleName=f);c.push([a,d,arguments]);return R}}if(!g)throw d("nomod",f);var c=[],e=[],p=[],u=a("$injector","invoke","push",e),R={_invokeQueue:c,_configBlocks:e,_runBlocks:p,requires:g,name:f,provider:b("$provide","provider"),factory:b("$provide","factory"),service:b("$provide","service"),value:a("$provide","value"),constant:a("$provide","constant","unshift"),decorator:b("$provide","decorator"),animation:b("$animateProvider","register"),filter:b("$filterProvider",
|
||||
"register"),controller:b("$controllerProvider","register"),directive:b("$compileProvider","directive"),component:b("$compileProvider","component"),config:u,run:function(a){p.push(a);return this}};h&&u(h);return R})}})}function ia(a,b){if(L(a)){b=b||[];for(var d=0,c=a.length;d<c;d++)b[d]=a[d]}else if(D(a))for(d in b=b||{},a)if("$"!==d.charAt(0)||"$"!==d.charAt(1))b[d]=a[d];return b||a}function le(a){S(a,{bootstrap:Bc,copy:pa,extend:S,merge:Zd,equals:na,element:F,forEach:q,injector:cb,noop:A,bind:ab,
|
||||
toJson:bb,fromJson:xc,identity:Xa,isUndefined:y,isDefined:w,isString:G,isFunction:z,isObject:D,isNumber:T,isElement:Qb,isArray:L,version:me,isDate:da,lowercase:Q,uppercase:ub,callbacks:{$$counter:0},getTestability:he,$$minErr:N,$$csp:Ba,reloadWithDebugInfo:ge});Ub=ke(C);Ub("ng",["ngLocale"],["$provide",function(a){a.provider({$$sanitizeUri:ne});a.provider("$compile",Fc).directive({a:oe,input:Gc,textarea:Gc,form:pe,script:qe,select:re,style:se,option:te,ngBind:ue,ngBindHtml:ve,ngBindTemplate:we,ngClass:xe,
|
||||
ngClassEven:ye,ngClassOdd:ze,ngCloak:Ae,ngController:Be,ngForm:Ce,ngHide:De,ngIf:Ee,ngInclude:Fe,ngInit:Ge,ngNonBindable:He,ngPluralize:Ie,ngRepeat:Je,ngShow:Ke,ngStyle:Le,ngSwitch:Me,ngSwitchWhen:Ne,ngSwitchDefault:Oe,ngOptions:Pe,ngTransclude:Qe,ngModel:Re,ngList:Se,ngChange:Te,pattern:Hc,ngPattern:Hc,required:Ic,ngRequired:Ic,minlength:Jc,ngMinlength:Jc,maxlength:Kc,ngMaxlength:Kc,ngValue:Ue,ngModelOptions:Ve}).directive({ngInclude:We}).directive(vb).directive(Lc);a.provider({$anchorScroll:Xe,
|
||||
$animate:Ye,$animateCss:Ze,$$animateJs:$e,$$animateQueue:af,$$AnimateRunner:bf,$$animateAsyncRun:cf,$browser:df,$cacheFactory:ef,$controller:ff,$document:gf,$exceptionHandler:hf,$filter:Mc,$$forceReflow:jf,$interpolate:kf,$interval:lf,$http:mf,$httpParamSerializer:nf,$httpParamSerializerJQLike:of,$httpBackend:pf,$xhrFactory:qf,$jsonpCallbacks:rf,$location:sf,$log:tf,$parse:uf,$rootScope:vf,$q:wf,$$q:xf,$sce:yf,$sceDelegate:zf,$sniffer:Af,$templateCache:Bf,$templateRequest:Cf,$$testability:Df,$timeout:Ef,
|
||||
$window:Ff,$$rAF:Gf,$$jqLite:Hf,$$HashMap:If,$$cookieReader:Jf})}])}function db(a){return a.replace(Kf,function(a,d,c,e){return e?c.toUpperCase():c}).replace(Lf,"Moz$1")}function Nc(a){a=a.nodeType;return 1===a||!a||9===a}function Oc(a,b){var d,c,e=b.createDocumentFragment(),f=[];if(Vb.test(a)){d=e.appendChild(b.createElement("div"));c=(Mf.exec(a)||["",""])[1].toLowerCase();c=ja[c]||ja._default;d.innerHTML=c[1]+a.replace(Nf,"<$1></$2>")+c[2];for(c=c[0];c--;)d=d.lastChild;f=$a(f,d.childNodes);d=e.firstChild;
|
||||
d.textContent=""}else f.push(b.createTextNode(a));e.textContent="";e.innerHTML="";q(f,function(a){e.appendChild(a)});return e}function Pc(a,b){var d=a.parentNode;d&&d.replaceChild(b,a);b.appendChild(a)}function O(a){if(a instanceof O)return a;var b;G(a)&&(a=W(a),b=!0);if(!(this instanceof O)){if(b&&"<"!=a.charAt(0))throw Wb("nosel");return new O(a)}if(b){b=C.document;var d;a=(d=Of.exec(a))?[b.createElement(d[1])]:(d=Oc(a,b))?d.childNodes:[]}Qc(this,a)}function Xb(a){return a.cloneNode(!0)}function wb(a,
|
||||
b){b||eb(a);if(a.querySelectorAll)for(var d=a.querySelectorAll("*"),c=0,e=d.length;c<e;c++)eb(d[c])}function Rc(a,b,d,c){if(w(c))throw Wb("offargs");var e=(c=xb(a))&&c.events,f=c&&c.handle;if(f)if(b){var g=function(b){var c=e[b];w(d)&&Za(c||[],d);w(d)&&c&&0<c.length||(a.removeEventListener(b,f,!1),delete e[b])};q(b.split(" "),function(a){g(a);yb[a]&&g(yb[a])})}else for(b in e)"$destroy"!==b&&a.removeEventListener(b,f,!1),delete e[b]}function eb(a,b){var d=a.ng339,c=d&&fb[d];c&&(b?delete c.data[b]:
|
||||
(c.handle&&(c.events.$destroy&&c.handle({},"$destroy"),Rc(a)),delete fb[d],a.ng339=void 0))}function xb(a,b){var d=a.ng339,d=d&&fb[d];b&&!d&&(a.ng339=d=++Pf,d=fb[d]={events:{},data:{},handle:void 0});return d}function Yb(a,b,d){if(Nc(a)){var c=w(d),e=!c&&b&&!D(b),f=!b;a=(a=xb(a,!e))&&a.data;if(c)a[b]=d;else{if(f)return a;if(e)return a&&a[b];S(a,b)}}}function zb(a,b){return a.getAttribute?-1<(" "+(a.getAttribute("class")||"")+" ").replace(/[\n\t]/g," ").indexOf(" "+b+" "):!1}function Ab(a,b){b&&a.setAttribute&&
|
||||
q(b.split(" "),function(b){a.setAttribute("class",W((" "+(a.getAttribute("class")||"")+" ").replace(/[\n\t]/g," ").replace(" "+W(b)+" "," ")))})}function Bb(a,b){if(b&&a.setAttribute){var d=(" "+(a.getAttribute("class")||"")+" ").replace(/[\n\t]/g," ");q(b.split(" "),function(a){a=W(a);-1===d.indexOf(" "+a+" ")&&(d+=a+" ")});a.setAttribute("class",W(d))}}function Qc(a,b){if(b)if(b.nodeType)a[a.length++]=b;else{var d=b.length;if("number"===typeof d&&b.window!==b){if(d)for(var c=0;c<d;c++)a[a.length++]=
|
||||
b[c]}else a[a.length++]=b}}function Sc(a,b){return Cb(a,"$"+(b||"ngController")+"Controller")}function Cb(a,b,d){9==a.nodeType&&(a=a.documentElement);for(b=L(b)?b:[b];a;){for(var c=0,e=b.length;c<e;c++)if(w(d=F.data(a,b[c])))return d;a=a.parentNode||11===a.nodeType&&a.host}}function Tc(a){for(wb(a,!0);a.firstChild;)a.removeChild(a.firstChild)}function Db(a,b){b||wb(a);var d=a.parentNode;d&&d.removeChild(a)}function Qf(a,b){b=b||C;if("complete"===b.document.readyState)b.setTimeout(a);else F(b).on("load",
|
||||
a)}function Uc(a,b){var d=Eb[b.toLowerCase()];return d&&Vc[wa(a)]&&d}function Rf(a,b){var d=function(c,d){c.isDefaultPrevented=function(){return c.defaultPrevented};var f=b[d||c.type],g=f?f.length:0;if(g){if(y(c.immediatePropagationStopped)){var h=c.stopImmediatePropagation;c.stopImmediatePropagation=function(){c.immediatePropagationStopped=!0;c.stopPropagation&&c.stopPropagation();h&&h.call(c)}}c.isImmediatePropagationStopped=function(){return!0===c.immediatePropagationStopped};var k=f.specialHandlerWrapper||
|
||||
Sf;1<g&&(f=ia(f));for(var l=0;l<g;l++)c.isImmediatePropagationStopped()||k(a,c,f[l])}};d.elem=a;return d}function Sf(a,b,d){d.call(a,b)}function Tf(a,b,d){var c=b.relatedTarget;c&&(c===a||Uf.call(a,c))||d.call(a,b)}function Hf(){this.$get=function(){return S(O,{hasClass:function(a,b){a.attr&&(a=a[0]);return zb(a,b)},addClass:function(a,b){a.attr&&(a=a[0]);return Bb(a,b)},removeClass:function(a,b){a.attr&&(a=a[0]);return Ab(a,b)}})}}function Ca(a,b){var d=a&&a.$$hashKey;if(d)return"function"===typeof d&&
|
||||
(d=a.$$hashKey()),d;d=typeof a;return d="function"==d||"object"==d&&null!==a?a.$$hashKey=d+":"+(b||Yd)():d+":"+a}function Ra(a,b){if(b){var d=0;this.nextUid=function(){return++d}}q(a,this.put,this)}function Wc(a){a=(Function.prototype.toString.call(a)+" ").replace(Vf,"");return a.match(Wf)||a.match(Xf)}function Yf(a){return(a=Wc(a))?"function("+(a[1]||"").replace(/[\s\r\n]+/," ")+")":"fn"}function cb(a,b){function d(a){return function(b,c){if(D(b))q(b,uc(a));else return a(b,c)}}function c(a,b){Qa(a,
|
||||
"service");if(z(b)||L(b))b=p.instantiate(b);if(!b.$get)throw Ha("pget",a);return n[a+"Provider"]=b}function e(a,b){return function(){var c=B.invoke(b,this);if(y(c))throw Ha("undef",a);return c}}function f(a,b,d){return c(a,{$get:!1!==d?e(a,b):b})}function g(a){sb(y(a)||L(a),"modulesToLoad","not an array");var b=[],c;q(a,function(a){function d(a){var b,c;b=0;for(c=a.length;b<c;b++){var e=a[b],f=p.get(e[0]);f[e[1]].apply(f,e[2])}}if(!m.get(a)){m.put(a,!0);try{G(a)?(c=Ub(a),b=b.concat(g(c.requires)).concat(c._runBlocks),
|
||||
d(c._invokeQueue),d(c._configBlocks)):z(a)?b.push(p.invoke(a)):L(a)?b.push(p.invoke(a)):Pa(a,"module")}catch(e){throw L(a)&&(a=a[a.length-1]),e.message&&e.stack&&-1==e.stack.indexOf(e.message)&&(e=e.message+"\n"+e.stack),Ha("modulerr",a,e.stack||e.message||e);}}});return b}function h(a,c){function d(b,e){if(a.hasOwnProperty(b)){if(a[b]===k)throw Ha("cdep",b+" <- "+l.join(" <- "));return a[b]}try{return l.unshift(b),a[b]=k,a[b]=c(b,e)}catch(f){throw a[b]===k&&delete a[b],f;}finally{l.shift()}}function e(a,
|
||||
c,f){var g=[];a=cb.$$annotate(a,b,f);for(var h=0,k=a.length;h<k;h++){var l=a[h];if("string"!==typeof l)throw Ha("itkn",l);g.push(c&&c.hasOwnProperty(l)?c[l]:d(l,f))}return g}return{invoke:function(a,b,c,d){"string"===typeof c&&(d=c,c=null);c=e(a,c,d);L(a)&&(a=a[a.length-1]);d=11>=Ea?!1:"function"===typeof a&&/^(?:class\b|constructor\()/.test(Function.prototype.toString.call(a)+" ");return d?(c.unshift(null),new (Function.prototype.bind.apply(a,c))):a.apply(b,c)},instantiate:function(a,b,c){var d=
|
||||
L(a)?a[a.length-1]:a;a=e(a,b,c);a.unshift(null);return new (Function.prototype.bind.apply(d,a))},get:d,annotate:cb.$$annotate,has:function(b){return n.hasOwnProperty(b+"Provider")||a.hasOwnProperty(b)}}}b=!0===b;var k={},l=[],m=new Ra([],!0),n={$provide:{provider:d(c),factory:d(f),service:d(function(a,b){return f(a,["$injector",function(a){return a.instantiate(b)}])}),value:d(function(a,b){return f(a,ha(b),!1)}),constant:d(function(a,b){Qa(a,"constant");n[a]=b;u[a]=b}),decorator:function(a,b){var c=
|
||||
p.get(a+"Provider"),d=c.$get;c.$get=function(){var a=B.invoke(d,c);return B.invoke(b,null,{$delegate:a})}}}},p=n.$injector=h(n,function(a,b){ca.isString(b)&&l.push(b);throw Ha("unpr",l.join(" <- "));}),u={},R=h(u,function(a,b){var c=p.get(a+"Provider",b);return B.invoke(c.$get,c,void 0,a)}),B=R;n.$injectorProvider={$get:ha(R)};var r=g(a),B=R.get("$injector");B.strictDi=b;q(r,function(a){a&&B.invoke(a)});return B}function Xe(){var a=!0;this.disableAutoScrolling=function(){a=!1};this.$get=["$window",
|
||||
"$location","$rootScope",function(b,d,c){function e(a){var b=null;Array.prototype.some.call(a,function(a){if("a"===wa(a))return b=a,!0});return b}function f(a){if(a){a.scrollIntoView();var c;c=g.yOffset;z(c)?c=c():Qb(c)?(c=c[0],c="fixed"!==b.getComputedStyle(c).position?0:c.getBoundingClientRect().bottom):T(c)||(c=0);c&&(a=a.getBoundingClientRect().top,b.scrollBy(0,a-c))}else b.scrollTo(0,0)}function g(a){a=G(a)?a:d.hash();var b;a?(b=h.getElementById(a))?f(b):(b=e(h.getElementsByName(a)))?f(b):"top"===
|
||||
a&&f(null):f(null)}var h=b.document;a&&c.$watch(function(){return d.hash()},function(a,b){a===b&&""===a||Qf(function(){c.$evalAsync(g)})});return g}]}function gb(a,b){if(!a&&!b)return"";if(!a)return b;if(!b)return a;L(a)&&(a=a.join(" "));L(b)&&(b=b.join(" "));return a+" "+b}function Zf(a){G(a)&&(a=a.split(" "));var b=U();q(a,function(a){a.length&&(b[a]=!0)});return b}function Ia(a){return D(a)?a:{}}function $f(a,b,d,c){function e(a){try{a.apply(null,va.call(arguments,1))}finally{if(R--,0===R)for(;B.length;)try{B.pop()()}catch(b){d.error(b)}}}
|
||||
function f(){t=null;g();h()}function g(){r=K();r=y(r)?null:r;na(r,E)&&(r=E);E=r}function h(){if(v!==k.url()||J!==r)v=k.url(),J=r,q(M,function(a){a(k.url(),r)})}var k=this,l=a.location,m=a.history,n=a.setTimeout,p=a.clearTimeout,u={};k.isMock=!1;var R=0,B=[];k.$$completeOutstandingRequest=e;k.$$incOutstandingRequestCount=function(){R++};k.notifyWhenNoOutstandingRequests=function(a){0===R?a():B.push(a)};var r,J,v=l.href,fa=b.find("base"),t=null,K=c.history?function(){try{return m.state}catch(a){}}:
|
||||
A;g();J=r;k.url=function(b,d,e){y(e)&&(e=null);l!==a.location&&(l=a.location);m!==a.history&&(m=a.history);if(b){var f=J===e;if(v===b&&(!c.history||f))return k;var h=v&&Ja(v)===Ja(b);v=b;J=e;!c.history||h&&f?(h||(t=b),d?l.replace(b):h?(d=l,e=b.indexOf("#"),e=-1===e?"":b.substr(e),d.hash=e):l.href=b,l.href!==b&&(t=b)):(m[d?"replaceState":"pushState"](e,"",b),g(),J=r);t&&(t=b);return k}return t||l.href.replace(/%27/g,"'")};k.state=function(){return r};var M=[],H=!1,E=null;k.onUrlChange=function(b){if(!H){if(c.history)F(a).on("popstate",
|
||||
f);F(a).on("hashchange",f);H=!0}M.push(b);return b};k.$$applicationDestroyed=function(){F(a).off("hashchange popstate",f)};k.$$checkUrlChange=h;k.baseHref=function(){var a=fa.attr("href");return a?a.replace(/^(https?\:)?\/\/[^\/]*/,""):""};k.defer=function(a,b){var c;R++;c=n(function(){delete u[c];e(a)},b||0);u[c]=!0;return c};k.defer.cancel=function(a){return u[a]?(delete u[a],p(a),e(A),!0):!1}}function df(){this.$get=["$window","$log","$sniffer","$document",function(a,b,d,c){return new $f(a,c,b,
|
||||
d)}]}function ef(){this.$get=function(){function a(a,c){function e(a){a!=n&&(p?p==a&&(p=a.n):p=a,f(a.n,a.p),f(a,n),n=a,n.n=null)}function f(a,b){a!=b&&(a&&(a.p=b),b&&(b.n=a))}if(a in b)throw N("$cacheFactory")("iid",a);var g=0,h=S({},c,{id:a}),k=U(),l=c&&c.capacity||Number.MAX_VALUE,m=U(),n=null,p=null;return b[a]={put:function(a,b){if(!y(b)){if(l<Number.MAX_VALUE){var c=m[a]||(m[a]={key:a});e(c)}a in k||g++;k[a]=b;g>l&&this.remove(p.key);return b}},get:function(a){if(l<Number.MAX_VALUE){var b=m[a];
|
||||
if(!b)return;e(b)}return k[a]},remove:function(a){if(l<Number.MAX_VALUE){var b=m[a];if(!b)return;b==n&&(n=b.p);b==p&&(p=b.n);f(b.n,b.p);delete m[a]}a in k&&(delete k[a],g--)},removeAll:function(){k=U();g=0;m=U();n=p=null},destroy:function(){m=h=k=null;delete b[a]},info:function(){return S({},h,{size:g})}}}var b={};a.info=function(){var a={};q(b,function(b,e){a[e]=b.info()});return a};a.get=function(a){return b[a]};return a}}function Bf(){this.$get=["$cacheFactory",function(a){return a("templates")}]}
|
||||
function Fc(a,b){function d(a,b,c){var d=/^\s*([@&<]|=(\*?))(\??)\s*(\w*)\s*$/,e=U();q(a,function(a,f){if(a in n)e[f]=n[a];else{var g=a.match(d);if(!g)throw ga("iscp",b,f,a,c?"controller bindings definition":"isolate scope definition");e[f]={mode:g[1][0],collection:"*"===g[2],optional:"?"===g[3],attrName:g[4]||f};g[4]&&(n[a]=e[f])}});return e}function c(a){var b=a.charAt(0);if(!b||b!==Q(b))throw ga("baddir",a);if(a!==a.trim())throw ga("baddir",a);}function e(a){var b=a.require||a.controller&&a.name;
|
||||
!L(b)&&D(b)&&q(b,function(a,c){var d=a.match(l);a.substring(d[0].length)||(b[c]=d[0]+c)});return b}var f={},g=/^\s*directive\:\s*([\w\-]+)\s+(.*)$/,h=/(([\w\-]+)(?:\:([^;]+))?;?)/,k=be("ngSrc,ngSrcset,src,srcset"),l=/^(?:(\^\^?)?(\?)?(\^\^?)?)?/,m=/^(on[a-z]+|formaction)$/,n=U();this.directive=function B(b,d){Qa(b,"directive");G(b)?(c(b),sb(d,"directiveFactory"),f.hasOwnProperty(b)||(f[b]=[],a.factory(b+"Directive",["$injector","$exceptionHandler",function(a,c){var d=[];q(f[b],function(f,g){try{var h=
|
||||
a.invoke(f);z(h)?h={compile:ha(h)}:!h.compile&&h.link&&(h.compile=ha(h.link));h.priority=h.priority||0;h.index=g;h.name=h.name||b;h.require=e(h);h.restrict=h.restrict||"EA";h.$$moduleName=f.$$moduleName;d.push(h)}catch(k){c(k)}});return d}])),f[b].push(d)):q(b,uc(B));return this};this.component=function(a,b){function c(a){function e(b){return z(b)||L(b)?function(c,d){return a.invoke(b,this,{$element:c,$attrs:d})}:b}var f=b.template||b.templateUrl?b.template:"",g={controller:d,controllerAs:Xc(b.controller)||
|
||||
b.controllerAs||"$ctrl",template:e(f),templateUrl:e(b.templateUrl),transclude:b.transclude,scope:{},bindToController:b.bindings||{},restrict:"E",require:b.require};q(b,function(a,b){"$"===b.charAt(0)&&(g[b]=a)});return g}var d=b.controller||function(){};q(b,function(a,b){"$"===b.charAt(0)&&(c[b]=a,z(d)&&(d[b]=a))});c.$inject=["$injector"];return this.directive(a,c)};this.aHrefSanitizationWhitelist=function(a){return w(a)?(b.aHrefSanitizationWhitelist(a),this):b.aHrefSanitizationWhitelist()};this.imgSrcSanitizationWhitelist=
|
||||
function(a){return w(a)?(b.imgSrcSanitizationWhitelist(a),this):b.imgSrcSanitizationWhitelist()};var p=!0;this.debugInfoEnabled=function(a){return w(a)?(p=a,this):p};var u=10;this.onChangesTtl=function(a){return arguments.length?(u=a,this):u};this.$get=["$injector","$interpolate","$exceptionHandler","$templateRequest","$parse","$controller","$rootScope","$sce","$animate","$$sanitizeUri",function(a,b,c,e,n,t,K,M,H,E){function I(){try{if(!--qa)throw Y=void 0,ga("infchng",u);K.$apply(function(){for(var a=
|
||||
[],b=0,c=Y.length;b<c;++b)try{Y[b]()}catch(d){a.push(d)}Y=void 0;if(a.length)throw a;})}finally{qa++}}function Da(a,b){if(b){var c=Object.keys(b),d,e,f;d=0;for(e=c.length;d<e;d++)f=c[d],this[f]=b[f]}else this.$attr={};this.$$element=a}function P(a,b,c){pa.innerHTML="<span "+b+">";b=pa.firstChild.attributes;var d=b[0];b.removeNamedItem(d.name);d.value=c;a.attributes.setNamedItem(d)}function x(a,b){try{a.addClass(b)}catch(c){}}function aa(a,b,c,d,e){a instanceof F||(a=F(a));for(var f=/\S+/,g=0,h=a.length;g<
|
||||
h;g++){var k=a[g];k.nodeType===Ma&&k.nodeValue.match(f)&&Pc(k,a[g]=C.document.createElement("span"))}var l=s(a,b,a,c,d,e);aa.$$addScopeClass(a);var m=null;return function(b,c,d){sb(b,"scope");e&&e.needsNewScope&&(b=b.$parent.$new());d=d||{};var f=d.parentBoundTranscludeFn,g=d.transcludeControllers;d=d.futureParentElement;f&&f.$$boundTransclude&&(f=f.$$boundTransclude);m||(m=(d=d&&d[0])?"foreignobject"!==wa(d)&&ma.call(d).match(/SVG/)?"svg":"html":"html");d="html"!==m?F(da(m,F("<div>").append(a).html())):
|
||||
c?Oa.clone.call(a):a;if(g)for(var h in g)d.data("$"+h+"Controller",g[h].instance);aa.$$addScopeInfo(d,b);c&&c(d,b);l&&l(b,d,d,f);return d}}function s(a,b,c,d,e,f){function g(a,c,d,e){var f,k,l,m,p,r,v;if(n)for(v=Array(c.length),m=0;m<h.length;m+=3)f=h[m],v[f]=c[f];else v=c;m=0;for(p=h.length;m<p;)k=v[h[m++]],c=h[m++],f=h[m++],c?(c.scope?(l=a.$new(),aa.$$addScopeInfo(F(k),l)):l=a,r=c.transcludeOnThisElement?za(a,c.transclude,e):!c.templateOnThisElement&&e?e:!e&&b?za(a,b):null,c(f,l,k,d,r)):f&&f(a,
|
||||
k.childNodes,void 0,e)}for(var h=[],k,l,m,p,n,r=0;r<a.length;r++){k=new Da;l=$b(a[r],[],k,0===r?d:void 0,e);(f=l.length?oa(l,a[r],k,b,c,null,[],[],f):null)&&f.scope&&aa.$$addScopeClass(k.$$element);k=f&&f.terminal||!(m=a[r].childNodes)||!m.length?null:s(m,f?(f.transcludeOnThisElement||!f.templateOnThisElement)&&f.transclude:b);if(f||k)h.push(r,f,k),p=!0,n=n||f;f=null}return p?g:null}function za(a,b,c){function d(e,f,g,h,k){e||(e=a.$new(!1,k),e.$$transcluded=!0);return b(e,f,{parentBoundTranscludeFn:c,
|
||||
transcludeControllers:g,futureParentElement:h})}var e=d.$$slots=U(),f;for(f in b.$$slots)e[f]=b.$$slots[f]?za(a,b.$$slots[f],c):null;return d}function $b(a,b,c,d,e){var f=c.$attr;switch(a.nodeType){case 1:O(b,Aa(wa(a)),"E",d,e);for(var g,k,l,m,p=a.attributes,n=0,r=p&&p.length;n<r;n++){var v=!1,u=!1;g=p[n];k=g.name;l=W(g.value);g=Aa(k);if(m=Ba.test(g))k=k.replace(Yc,"").substr(8).replace(/_(.)/g,function(a,b){return b.toUpperCase()});(g=g.match(Ca))&&V(g[1])&&(v=k,u=k.substr(0,k.length-5)+"end",k=
|
||||
k.substr(0,k.length-6));g=Aa(k.toLowerCase());f[g]=k;if(m||!c.hasOwnProperty(g))c[g]=l,Uc(a,g)&&(c[g]=!0);ia(a,b,l,g,m);O(b,g,"A",d,e,v,u)}f=a.className;D(f)&&(f=f.animVal);if(G(f)&&""!==f)for(;a=h.exec(f);)g=Aa(a[2]),O(b,g,"C",d,e)&&(c[g]=W(a[3])),f=f.substr(a.index+a[0].length);break;case Ma:if(11===Ea)for(;a.parentNode&&a.nextSibling&&a.nextSibling.nodeType===Ma;)a.nodeValue+=a.nextSibling.nodeValue,a.parentNode.removeChild(a.nextSibling);ca(b,a.nodeValue);break;case 8:hb(a,b,c,d,e)}b.sort(Z);
|
||||
return b}function hb(a,b,c,d,e){try{var f=g.exec(a.nodeValue);if(f){var h=Aa(f[1]);O(b,h,"M",d,e)&&(c[h]=W(f[2]))}}catch(k){}}function N(a,b,c){var d=[],e=0;if(b&&a.hasAttribute&&a.hasAttribute(b)){do{if(!a)throw ga("uterdir",b,c);1==a.nodeType&&(a.hasAttribute(b)&&e++,a.hasAttribute(c)&&e--);d.push(a);a=a.nextSibling}while(0<e)}else d.push(a);return F(d)}function Zc(a,b,c){return function(d,e,f,g,h){e=N(e[0],b,c);return a(d,e,f,g,h)}}function ac(a,b,c,d,e,f){var g;return a?aa(b,c,d,e,f):function(){g||
|
||||
(g=aa(b,c,d,e,f),b=c=f=null);return g.apply(this,arguments)}}function oa(a,b,d,e,f,g,h,k,l){function m(a,b,c,d){if(a){c&&(a=Zc(a,c,d));a.require=x.require;a.directiveName=I;if(u===x||x.$$isolateScope)a=ja(a,{isolateScope:!0});h.push(a)}if(b){c&&(b=Zc(b,c,d));b.require=x.require;b.directiveName=I;if(u===x||x.$$isolateScope)b=ja(b,{isolateScope:!0});k.push(b)}}function p(a,e,f,g,l){function m(a,b,c,d){var e;Ya(a)||(d=c,c=b,b=a,a=void 0);fa&&(e=t);c||(c=fa?I.parent():I);if(d){var f=l.$$slots[d];if(f)return f(a,
|
||||
b,e,c,s);if(y(f))throw ga("noslot",d,ya(I));}else return l(a,b,e,c,s)}var n,E,x,M,B,t,P,I;b===f?(g=d,I=d.$$element):(I=F(f),g=new Da(I,d));B=e;u?M=e.$new(!0):r&&(B=e.$parent);l&&(P=m,P.$$boundTransclude=l,P.isSlotFilled=function(a){return!!l.$$slots[a]});v&&(t=ag(I,g,P,v,M,e,u));u&&(aa.$$addScopeInfo(I,M,!0,!(H&&(H===u||H===u.$$originalDirective))),aa.$$addScopeClass(I,!0),M.$$isolateBindings=u.$$isolateBindings,E=ka(e,g,M,M.$$isolateBindings,u),E.removeWatches&&M.$on("$destroy",E.removeWatches));
|
||||
for(n in t){E=v[n];x=t[n];var Zb=E.$$bindings.bindToController;x.bindingInfo=x.identifier&&Zb?ka(B,g,x.instance,Zb,E):{};var K=x();K!==x.instance&&(x.instance=K,I.data("$"+E.name+"Controller",K),x.bindingInfo.removeWatches&&x.bindingInfo.removeWatches(),x.bindingInfo=ka(B,g,x.instance,Zb,E))}q(v,function(a,b){var c=a.require;a.bindToController&&!L(c)&&D(c)&&S(t[b].instance,ib(b,c,I,t))});q(t,function(a){var b=a.instance;if(z(b.$onChanges))try{b.$onChanges(a.bindingInfo.initialChanges)}catch(d){c(d)}if(z(b.$onInit))try{b.$onInit()}catch(e){c(e)}z(b.$doCheck)&&
|
||||
(B.$watch(function(){b.$doCheck()}),b.$doCheck());z(b.$onDestroy)&&B.$on("$destroy",function(){b.$onDestroy()})});n=0;for(E=h.length;n<E;n++)x=h[n],la(x,x.isolateScope?M:e,I,g,x.require&&ib(x.directiveName,x.require,I,t),P);var s=e;u&&(u.template||null===u.templateUrl)&&(s=M);a&&a(s,f.childNodes,void 0,l);for(n=k.length-1;0<=n;n--)x=k[n],la(x,x.isolateScope?M:e,I,g,x.require&&ib(x.directiveName,x.require,I,t),P);q(t,function(a){a=a.instance;z(a.$postLink)&&a.$postLink()})}l=l||{};for(var n=-Number.MAX_VALUE,
|
||||
r=l.newScopeDirective,v=l.controllerDirectives,u=l.newIsolateScopeDirective,H=l.templateDirective,E=l.nonTlbTranscludeDirective,M=!1,B=!1,fa=l.hasElementTranscludeDirective,t=d.$$element=F(b),x,I,P,K=e,s,Fa=!1,za=!1,w,A=0,C=a.length;A<C;A++){x=a[A];var G=x.$$start,hb=x.$$end;G&&(t=N(b,G,hb));P=void 0;if(n>x.priority)break;if(w=x.scope)x.templateUrl||(D(w)?(X("new/isolated scope",u||r,x,t),u=x):X("new/isolated scope",u,x,t)),r=r||x;I=x.name;if(!Fa&&(x.replace&&(x.templateUrl||x.template)||x.transclude&&
|
||||
!x.$$tlb)){for(w=A+1;Fa=a[w++];)if(Fa.transclude&&!Fa.$$tlb||Fa.replace&&(Fa.templateUrl||Fa.template)){za=!0;break}Fa=!0}!x.templateUrl&&x.controller&&(w=x.controller,v=v||U(),X("'"+I+"' controller",v[I],x,t),v[I]=x);if(w=x.transclude)if(M=!0,x.$$tlb||(X("transclusion",E,x,t),E=x),"element"==w)fa=!0,n=x.priority,P=t,t=d.$$element=F(aa.$$createComment(I,d[I])),b=t[0],ea(f,va.call(P,0),b),P[0].$$parentNode=P[0].parentNode,K=ac(za,P,e,n,g&&g.name,{nonTlbTranscludeDirective:E});else{var oa=U();P=F(Xb(b)).contents();
|
||||
if(D(w)){P=[];var Q=U(),O=U();q(w,function(a,b){var c="?"===a.charAt(0);a=c?a.substring(1):a;Q[a]=b;oa[b]=null;O[b]=c});q(t.contents(),function(a){var b=Q[Aa(wa(a))];b?(O[b]=!0,oa[b]=oa[b]||[],oa[b].push(a)):P.push(a)});q(O,function(a,b){if(!a)throw ga("reqslot",b);});for(var V in oa)oa[V]&&(oa[V]=ac(za,oa[V],e))}t.empty();K=ac(za,P,e,void 0,void 0,{needsNewScope:x.$$isolateScope||x.$$newScope});K.$$slots=oa}if(x.template)if(B=!0,X("template",H,x,t),H=x,w=z(x.template)?x.template(t,d):x.template,
|
||||
w=xa(w),x.replace){g=x;P=Vb.test(w)?$c(da(x.templateNamespace,W(w))):[];b=P[0];if(1!=P.length||1!==b.nodeType)throw ga("tplrt",I,"");ea(f,t,b);C={$attr:{}};w=$b(b,[],C);var Z=a.splice(A+1,a.length-(A+1));(u||r)&&T(w,u,r);a=a.concat(w).concat(Z);$(d,C);C=a.length}else t.html(w);if(x.templateUrl)B=!0,X("template",H,x,t),H=x,x.replace&&(g=x),p=ba(a.splice(A,a.length-A),t,d,f,M&&K,h,k,{controllerDirectives:v,newScopeDirective:r!==x&&r,newIsolateScopeDirective:u,templateDirective:H,nonTlbTranscludeDirective:E}),
|
||||
C=a.length;else if(x.compile)try{s=x.compile(t,d,K);var Y=x.$$originalDirective||x;z(s)?m(null,ab(Y,s),G,hb):s&&m(ab(Y,s.pre),ab(Y,s.post),G,hb)}catch(ca){c(ca,ya(t))}x.terminal&&(p.terminal=!0,n=Math.max(n,x.priority))}p.scope=r&&!0===r.scope;p.transcludeOnThisElement=M;p.templateOnThisElement=B;p.transclude=K;l.hasElementTranscludeDirective=fa;return p}function ib(a,b,c,d){var e;if(G(b)){var f=b.match(l);b=b.substring(f[0].length);var g=f[1]||f[3],f="?"===f[2];"^^"===g?c=c.parent():e=(e=d&&d[b])&&
|
||||
e.instance;if(!e){var h="$"+b+"Controller";e=g?c.inheritedData(h):c.data(h)}if(!e&&!f)throw ga("ctreq",b,a);}else if(L(b))for(e=[],g=0,f=b.length;g<f;g++)e[g]=ib(a,b[g],c,d);else D(b)&&(e={},q(b,function(b,f){e[f]=ib(a,b,c,d)}));return e||null}function ag(a,b,c,d,e,f,g){var h=U(),k;for(k in d){var l=d[k],m={$scope:l===g||l.$$isolateScope?e:f,$element:a,$attrs:b,$transclude:c},p=l.controller;"@"==p&&(p=b[l.name]);m=t(p,m,!0,l.controllerAs);h[l.name]=m;a.data("$"+l.name+"Controller",m.instance)}return h}
|
||||
function T(a,b,c){for(var d=0,e=a.length;d<e;d++)a[d]=Rb(a[d],{$$isolateScope:b,$$newScope:c})}function O(b,e,g,h,k,l,m){if(e===k)return null;k=null;if(f.hasOwnProperty(e)){var p;e=a.get(e+"Directive");for(var n=0,r=e.length;n<r;n++)try{if(p=e[n],(y(h)||h>p.priority)&&-1!=p.restrict.indexOf(g)){l&&(p=Rb(p,{$$start:l,$$end:m}));if(!p.$$bindings){var u=p,v=p,x=p.name,H={isolateScope:null,bindToController:null};D(v.scope)&&(!0===v.bindToController?(H.bindToController=d(v.scope,x,!0),H.isolateScope={}):
|
||||
H.isolateScope=d(v.scope,x,!1));D(v.bindToController)&&(H.bindToController=d(v.bindToController,x,!0));if(D(H.bindToController)){var E=v.controller,M=v.controllerAs;if(!E)throw ga("noctrl",x);if(!Xc(E,M))throw ga("noident",x);}var t=u.$$bindings=H;D(t.isolateScope)&&(p.$$isolateBindings=t.isolateScope)}b.push(p);k=p}}catch(I){c(I)}}return k}function V(b){if(f.hasOwnProperty(b))for(var c=a.get(b+"Directive"),d=0,e=c.length;d<e;d++)if(b=c[d],b.multiElement)return!0;return!1}function $(a,b){var c=b.$attr,
|
||||
d=a.$attr;q(a,function(d,e){"$"!=e.charAt(0)&&(b[e]&&b[e]!==d&&(d+=("style"===e?";":" ")+b[e]),a.$set(e,d,!0,c[e]))});q(b,function(b,e){a.hasOwnProperty(e)||"$"===e.charAt(0)||(a[e]=b,"class"!==e&&"style"!==e&&(d[e]=c[e]))})}function ba(a,b,c,d,f,g,h,k){var l=[],m,p,n=b[0],r=a.shift(),u=Rb(r,{templateUrl:null,transclude:null,replace:null,$$originalDirective:r}),H=z(r.templateUrl)?r.templateUrl(b,c):r.templateUrl,E=r.templateNamespace;b.empty();e(H).then(function(e){var v,M;e=xa(e);if(r.replace){e=
|
||||
Vb.test(e)?$c(da(E,W(e))):[];v=e[0];if(1!=e.length||1!==v.nodeType)throw ga("tplrt",r.name,H);e={$attr:{}};ea(d,b,v);var B=$b(v,[],e);D(r.scope)&&T(B,!0);a=B.concat(a);$(c,e)}else v=n,b.html(e);a.unshift(u);m=oa(a,v,c,f,b,r,g,h,k);q(d,function(a,c){a==v&&(d[c]=b[0])});for(p=s(b[0].childNodes,f);l.length;){e=l.shift();M=l.shift();var t=l.shift(),I=l.shift(),B=b[0];if(!e.$$destroyed){if(M!==n){var P=M.className;k.hasElementTranscludeDirective&&r.replace||(B=Xb(v));ea(t,F(M),B);x(F(B),P)}M=m.transcludeOnThisElement?
|
||||
za(e,m.transclude,I):I;m(p,e,B,d,M)}}l=null});return function(a,b,c,d,e){a=e;b.$$destroyed||(l?l.push(b,c,d,a):(m.transcludeOnThisElement&&(a=za(b,m.transclude,e)),m(p,b,c,d,a)))}}function Z(a,b){var c=b.priority-a.priority;return 0!==c?c:a.name!==b.name?a.name<b.name?-1:1:a.index-b.index}function X(a,b,c,d){function e(a){return a?" (module: "+a+")":""}if(b)throw ga("multidir",b.name,e(b.$$moduleName),c.name,e(c.$$moduleName),a,ya(d));}function ca(a,c){var d=b(c,!0);d&&a.push({priority:0,compile:function(a){a=
|
||||
a.parent();var b=!!a.length;b&&aa.$$addBindingClass(a);return function(a,c){var e=c.parent();b||aa.$$addBindingClass(e);aa.$$addBindingInfo(e,d.expressions);a.$watch(d,function(a){c[0].nodeValue=a})}}})}function da(a,b){a=Q(a||"html");switch(a){case "svg":case "math":var c=C.document.createElement("div");c.innerHTML="<"+a+">"+b+"</"+a+">";return c.childNodes[0].childNodes;default:return b}}function ha(a,b){if("srcdoc"==b)return M.HTML;var c=wa(a);if("xlinkHref"==b||"form"==c&&"action"==b||"img"!=
|
||||
c&&("src"==b||"ngSrc"==b))return M.RESOURCE_URL}function ia(a,c,d,e,f){var g=ha(a,e);f=k[e]||f;var h=b(d,!0,g,f);if(h){if("multiple"===e&&"select"===wa(a))throw ga("selmulti",ya(a));c.push({priority:100,compile:function(){return{pre:function(a,c,k){c=k.$$observers||(k.$$observers=U());if(m.test(e))throw ga("nodomevents");var l=k[e];l!==d&&(h=l&&b(l,!0,g,f),d=l);h&&(k[e]=h(a),(c[e]||(c[e]=[])).$$inter=!0,(k.$$observers&&k.$$observers[e].$$scope||a).$watch(h,function(a,b){"class"===e&&a!=b?k.$updateClass(a,
|
||||
b):k.$set(e,a)}))}}}})}}function ea(a,b,c){var d=b[0],e=b.length,f=d.parentNode,g,h;if(a)for(g=0,h=a.length;g<h;g++)if(a[g]==d){a[g++]=c;h=g+e-1;for(var k=a.length;g<k;g++,h++)h<k?a[g]=a[h]:delete a[g];a.length-=e-1;a.context===d&&(a.context=c);break}f&&f.replaceChild(c,d);a=C.document.createDocumentFragment();for(g=0;g<e;g++)a.appendChild(b[g]);F.hasData(d)&&(F.data(c,F.data(d)),F(d).off("$destroy"));F.cleanData(a.querySelectorAll("*"));for(g=1;g<e;g++)delete b[g];b[0]=c;b.length=1}function ja(a,
|
||||
b){return S(function(){return a.apply(null,arguments)},a,b)}function la(a,b,d,e,f,g){try{a(b,d,e,f,g)}catch(h){c(h,ya(d))}}function ka(a,c,d,e,f){function g(b,c,e){z(d.$onChanges)&&c!==e&&(Y||(a.$$postDigest(I),Y=[]),m||(m={},Y.push(h)),m[b]&&(e=m[b].previousValue),m[b]=new Fb(e,c))}function h(){d.$onChanges(m);m=void 0}var k=[],l={},m;q(e,function(e,h){var m=e.attrName,p=e.optional,v,u,x,H;switch(e.mode){case "@":p||ua.call(c,m)||(d[h]=c[m]=void 0);c.$observe(m,function(a){if(G(a)||Ga(a))g(h,a,d[h]),
|
||||
d[h]=a});c.$$observers[m].$$scope=a;v=c[m];G(v)?d[h]=b(v)(a):Ga(v)&&(d[h]=v);l[h]=new Fb(bc,d[h]);break;case "=":if(!ua.call(c,m)){if(p)break;c[m]=void 0}if(p&&!c[m])break;u=n(c[m]);H=u.literal?na:function(a,b){return a===b||a!==a&&b!==b};x=u.assign||function(){v=d[h]=u(a);throw ga("nonassign",c[m],m,f.name);};v=d[h]=u(a);p=function(b){H(b,d[h])||(H(b,v)?x(a,b=d[h]):d[h]=b);return v=b};p.$stateful=!0;p=e.collection?a.$watchCollection(c[m],p):a.$watch(n(c[m],p),null,u.literal);k.push(p);break;case "<":if(!ua.call(c,
|
||||
m)){if(p)break;c[m]=void 0}if(p&&!c[m])break;u=n(c[m]);var E=d[h]=u(a);l[h]=new Fb(bc,d[h]);p=a.$watch(u,function(a,b){if(b===a){if(b===E)return;b=E}g(h,a,b);d[h]=a},u.literal);k.push(p);break;case "&":u=c.hasOwnProperty(m)?n(c[m]):A;if(u===A&&p)break;d[h]=function(b){return u(a,b)}}});return{initialChanges:l,removeWatches:k.length&&function(){for(var a=0,b=k.length;a<b;++a)k[a]()}}}var ta=/^\w/,pa=C.document.createElement("div"),qa=u,Y;Da.prototype={$normalize:Aa,$addClass:function(a){a&&0<a.length&&
|
||||
H.addClass(this.$$element,a)},$removeClass:function(a){a&&0<a.length&&H.removeClass(this.$$element,a)},$updateClass:function(a,b){var c=ad(a,b);c&&c.length&&H.addClass(this.$$element,c);(c=ad(b,a))&&c.length&&H.removeClass(this.$$element,c)},$set:function(a,b,d,e){var f=Uc(this.$$element[0],a),g=bd[a],h=a;f?(this.$$element.prop(a,b),e=f):g&&(this[g]=b,h=g);this[a]=b;e?this.$attr[a]=e:(e=this.$attr[a])||(this.$attr[a]=e=Cc(a,"-"));f=wa(this.$$element);if("a"===f&&("href"===a||"xlinkHref"===a)||"img"===
|
||||
f&&"src"===a)this[a]=b=E(b,"src"===a);else if("img"===f&&"srcset"===a&&w(b)){for(var f="",g=W(b),k=/(\s+\d+x\s*,|\s+\d+w\s*,|\s+,|,\s+)/,k=/\s/.test(g)?k:/(,)/,g=g.split(k),k=Math.floor(g.length/2),l=0;l<k;l++)var m=2*l,f=f+E(W(g[m]),!0),f=f+(" "+W(g[m+1]));g=W(g[2*l]).split(/\s/);f+=E(W(g[0]),!0);2===g.length&&(f+=" "+W(g[1]));this[a]=b=f}!1!==d&&(null===b||y(b)?this.$$element.removeAttr(e):ta.test(e)?this.$$element.attr(e,b):P(this.$$element[0],e,b));(a=this.$$observers)&&q(a[h],function(a){try{a(b)}catch(d){c(d)}})},
|
||||
$observe:function(a,b){var c=this,d=c.$$observers||(c.$$observers=U()),e=d[a]||(d[a]=[]);e.push(b);K.$evalAsync(function(){e.$$inter||!c.hasOwnProperty(a)||y(c[a])||b(c[a])});return function(){Za(e,b)}}};var ra=b.startSymbol(),sa=b.endSymbol(),xa="{{"==ra&&"}}"==sa?Xa:function(a){return a.replace(/\{\{/g,ra).replace(/}}/g,sa)},Ba=/^ngAttr[A-Z]/,Ca=/^(.+)Start$/;aa.$$addBindingInfo=p?function(a,b){var c=a.data("$binding")||[];L(b)?c=c.concat(b):c.push(b);a.data("$binding",c)}:A;aa.$$addBindingClass=
|
||||
p?function(a){x(a,"ng-binding")}:A;aa.$$addScopeInfo=p?function(a,b,c,d){a.data(c?d?"$isolateScopeNoTemplate":"$isolateScope":"$scope",b)}:A;aa.$$addScopeClass=p?function(a,b){x(a,b?"ng-isolate-scope":"ng-scope")}:A;aa.$$createComment=function(a,b){var c="";p&&(c=" "+(a||"")+": ",b&&(c+=b+" "));return C.document.createComment(c)};return aa}]}function Fb(a,b){this.previousValue=a;this.currentValue=b}function Aa(a){return db(a.replace(Yc,""))}function ad(a,b){var d="",c=a.split(/\s+/),e=b.split(/\s+/),
|
||||
f=0;a:for(;f<c.length;f++){for(var g=c[f],h=0;h<e.length;h++)if(g==e[h])continue a;d+=(0<d.length?" ":"")+g}return d}function $c(a){a=F(a);var b=a.length;if(1>=b)return a;for(;b--;)8===a[b].nodeType&&bg.call(a,b,1);return a}function Xc(a,b){if(b&&G(b))return b;if(G(a)){var d=cd.exec(a);if(d)return d[3]}}function ff(){var a={},b=!1;this.has=function(b){return a.hasOwnProperty(b)};this.register=function(b,c){Qa(b,"controller");D(b)?S(a,b):a[b]=c};this.allowGlobals=function(){b=!0};this.$get=["$injector",
|
||||
"$window",function(d,c){function e(a,b,c,d){if(!a||!D(a.$scope))throw N("$controller")("noscp",d,b);a.$scope[b]=c}return function(f,g,h,k){var l,m,n;h=!0===h;k&&G(k)&&(n=k);if(G(f)){k=f.match(cd);if(!k)throw cg("ctrlfmt",f);m=k[1];n=n||k[3];f=a.hasOwnProperty(m)?a[m]:Ec(g.$scope,m,!0)||(b?Ec(c,m,!0):void 0);Pa(f,m,!0)}if(h)return h=(L(f)?f[f.length-1]:f).prototype,l=Object.create(h||null),n&&e(g,n,l,m||f.name),S(function(){var a=d.invoke(f,l,g,m);a!==l&&(D(a)||z(a))&&(l=a,n&&e(g,n,l,m||f.name));return l},
|
||||
{instance:l,identifier:n});l=d.instantiate(f,g,m);n&&e(g,n,l,m||f.name);return l}}]}function gf(){this.$get=["$window",function(a){return F(a.document)}]}function hf(){this.$get=["$log",function(a){return function(b,d){a.error.apply(a,arguments)}}]}function cc(a){return D(a)?da(a)?a.toISOString():bb(a):a}function nf(){this.$get=function(){return function(a){if(!a)return"";var b=[];tc(a,function(a,c){null===a||y(a)||(L(a)?q(a,function(a){b.push(ea(c)+"="+ea(cc(a)))}):b.push(ea(c)+"="+ea(cc(a))))});
|
||||
return b.join("&")}}}function of(){this.$get=function(){return function(a){function b(a,e,f){null===a||y(a)||(L(a)?q(a,function(a,c){b(a,e+"["+(D(a)?c:"")+"]")}):D(a)&&!da(a)?tc(a,function(a,c){b(a,e+(f?"":"[")+c+(f?"":"]"))}):d.push(ea(e)+"="+ea(cc(a))))}if(!a)return"";var d=[];b(a,"",!0);return d.join("&")}}}function dc(a,b){if(G(a)){var d=a.replace(dg,"").trim();if(d){var c=b("Content-Type");(c=c&&0===c.indexOf(dd))||(c=(c=d.match(eg))&&fg[c[0]].test(d));c&&(a=xc(d))}}return a}function ed(a){var b=
|
||||
U(),d;G(a)?q(a.split("\n"),function(a){d=a.indexOf(":");var e=Q(W(a.substr(0,d)));a=W(a.substr(d+1));e&&(b[e]=b[e]?b[e]+", "+a:a)}):D(a)&&q(a,function(a,d){var f=Q(d),g=W(a);f&&(b[f]=b[f]?b[f]+", "+g:g)});return b}function fd(a){var b;return function(d){b||(b=ed(a));return d?(d=b[Q(d)],void 0===d&&(d=null),d):b}}function gd(a,b,d,c){if(z(c))return c(a,b,d);q(c,function(c){a=c(a,b,d)});return a}function mf(){var a=this.defaults={transformResponse:[dc],transformRequest:[function(a){return D(a)&&"[object File]"!==
|
||||
ma.call(a)&&"[object Blob]"!==ma.call(a)&&"[object FormData]"!==ma.call(a)?bb(a):a}],headers:{common:{Accept:"application/json, text/plain, */*"},post:ia(ec),put:ia(ec),patch:ia(ec)},xsrfCookieName:"XSRF-TOKEN",xsrfHeaderName:"X-XSRF-TOKEN",paramSerializer:"$httpParamSerializer"},b=!1;this.useApplyAsync=function(a){return w(a)?(b=!!a,this):b};var d=!0;this.useLegacyPromiseExtensions=function(a){return w(a)?(d=!!a,this):d};var c=this.interceptors=[];this.$get=["$httpBackend","$$cookieReader","$cacheFactory",
|
||||
"$rootScope","$q","$injector",function(e,f,g,h,k,l){function m(b){function c(a,b){for(var d=0,e=b.length;d<e;){var f=b[d++],g=b[d++];a=a.then(f,g)}b.length=0;return a}function e(a,b){var c,d={};q(a,function(a,e){z(a)?(c=a(b),null!=c&&(d[e]=c)):d[e]=a});return d}function f(a){var b=S({},a);b.data=gd(a.data,a.headers,a.status,g.transformResponse);a=a.status;return 200<=a&&300>a?b:k.reject(b)}if(!D(b))throw N("$http")("badreq",b);if(!G(b.url))throw N("$http")("badreq",b.url);var g=S({method:"get",transformRequest:a.transformRequest,
|
||||
transformResponse:a.transformResponse,paramSerializer:a.paramSerializer},b);g.headers=function(b){var c=a.headers,d=S({},b.headers),f,g,h,c=S({},c.common,c[Q(b.method)]);a:for(f in c){g=Q(f);for(h in d)if(Q(h)===g)continue a;d[f]=c[f]}return e(d,ia(b))}(b);g.method=ub(g.method);g.paramSerializer=G(g.paramSerializer)?l.get(g.paramSerializer):g.paramSerializer;var h=[],m=[],p=k.when(g);q(R,function(a){(a.request||a.requestError)&&h.unshift(a.request,a.requestError);(a.response||a.responseError)&&m.push(a.response,
|
||||
a.responseError)});p=c(p,h);p=p.then(function(b){var c=b.headers,d=gd(b.data,fd(c),void 0,b.transformRequest);y(d)&&q(c,function(a,b){"content-type"===Q(b)&&delete c[b]});y(b.withCredentials)&&!y(a.withCredentials)&&(b.withCredentials=a.withCredentials);return n(b,d).then(f,f)});p=c(p,m);d?(p.success=function(a){Pa(a,"fn");p.then(function(b){a(b.data,b.status,b.headers,g)});return p},p.error=function(a){Pa(a,"fn");p.then(null,function(b){a(b.data,b.status,b.headers,g)});return p}):(p.success=hd("success"),
|
||||
p.error=hd("error"));return p}function n(c,d){function g(a){if(a){var c={};q(a,function(a,d){c[d]=function(c){function d(){a(c)}b?h.$applyAsync(d):h.$$phase?d():h.$apply(d)}});return c}}function l(a,c,d,e){function f(){n(c,a,d,e)}E&&(200<=a&&300>a?E.put(P,[a,c,ed(d),e]):E.remove(P));b?h.$applyAsync(f):(f(),h.$$phase||h.$apply())}function n(a,b,d,e){b=-1<=b?b:0;(200<=b&&300>b?M.resolve:M.reject)({data:a,status:b,headers:fd(d),config:c,statusText:e})}function t(a){n(a.data,a.status,ia(a.headers()),
|
||||
a.statusText)}function R(){var a=m.pendingRequests.indexOf(c);-1!==a&&m.pendingRequests.splice(a,1)}var M=k.defer(),H=M.promise,E,I,Da=c.headers,P=p(c.url,c.paramSerializer(c.params));m.pendingRequests.push(c);H.then(R,R);!c.cache&&!a.cache||!1===c.cache||"GET"!==c.method&&"JSONP"!==c.method||(E=D(c.cache)?c.cache:D(a.cache)?a.cache:u);E&&(I=E.get(P),w(I)?I&&z(I.then)?I.then(t,t):L(I)?n(I[1],I[0],ia(I[2]),I[3]):n(I,200,{},"OK"):E.put(P,H));y(I)&&((I=id(c.url)?f()[c.xsrfCookieName||a.xsrfCookieName]:
|
||||
void 0)&&(Da[c.xsrfHeaderName||a.xsrfHeaderName]=I),e(c.method,P,d,l,Da,c.timeout,c.withCredentials,c.responseType,g(c.eventHandlers),g(c.uploadEventHandlers)));return H}function p(a,b){0<b.length&&(a+=(-1==a.indexOf("?")?"?":"&")+b);return a}var u=g("$http");a.paramSerializer=G(a.paramSerializer)?l.get(a.paramSerializer):a.paramSerializer;var R=[];q(c,function(a){R.unshift(G(a)?l.get(a):l.invoke(a))});m.pendingRequests=[];(function(a){q(arguments,function(a){m[a]=function(b,c){return m(S({},c||{},
|
||||
{method:a,url:b}))}})})("get","delete","head","jsonp");(function(a){q(arguments,function(a){m[a]=function(b,c,d){return m(S({},d||{},{method:a,url:b,data:c}))}})})("post","put","patch");m.defaults=a;return m}]}function qf(){this.$get=function(){return function(){return new C.XMLHttpRequest}}}function pf(){this.$get=["$browser","$jsonpCallbacks","$document","$xhrFactory",function(a,b,d,c){return gg(a,c,a.defer,b,d[0])}]}function gg(a,b,d,c,e){function f(a,b,d){a=a.replace("JSON_CALLBACK",b);var f=
|
||||
e.createElement("script"),m=null;f.type="text/javascript";f.src=a;f.async=!0;m=function(a){f.removeEventListener("load",m,!1);f.removeEventListener("error",m,!1);e.body.removeChild(f);f=null;var g=-1,u="unknown";a&&("load"!==a.type||c.wasCalled(b)||(a={type:"error"}),u=a.type,g="error"===a.type?404:200);d&&d(g,u)};f.addEventListener("load",m,!1);f.addEventListener("error",m,!1);e.body.appendChild(f);return m}return function(e,h,k,l,m,n,p,u,R,B){function r(){fa&&fa();t&&t.abort()}function J(b,c,e,
|
||||
f,g){w(M)&&d.cancel(M);fa=t=null;b(c,e,f,g);a.$$completeOutstandingRequest(A)}a.$$incOutstandingRequestCount();h=h||a.url();if("jsonp"===Q(e))var v=c.createCallback(h),fa=f(h,v,function(a,b){var d=200===a&&c.getResponse(v);J(l,a,d,"",b);c.removeCallback(v)});else{var t=b(e,h);t.open(e,h,!0);q(m,function(a,b){w(a)&&t.setRequestHeader(b,a)});t.onload=function(){var a=t.statusText||"",b="response"in t?t.response:t.responseText,c=1223===t.status?204:t.status;0===c&&(c=b?200:"file"==Y(h).protocol?404:
|
||||
0);J(l,c,b,t.getAllResponseHeaders(),a)};e=function(){J(l,-1,null,null,"")};t.onerror=e;t.onabort=e;q(R,function(a,b){t.addEventListener(b,a)});q(B,function(a,b){t.upload.addEventListener(b,a)});p&&(t.withCredentials=!0);if(u)try{t.responseType=u}catch(K){if("json"!==u)throw K;}t.send(y(k)?null:k)}if(0<n)var M=d(r,n);else n&&z(n.then)&&n.then(r)}}function kf(){var a="{{",b="}}";this.startSymbol=function(b){return b?(a=b,this):a};this.endSymbol=function(a){return a?(b=a,this):b};this.$get=["$parse",
|
||||
"$exceptionHandler","$sce",function(d,c,e){function f(a){return"\\\\\\"+a}function g(c){return c.replace(n,a).replace(p,b)}function h(a,b,c,d){var e;return e=a.$watch(function(a){e();return d(a)},b,c)}function k(f,k,p,n){function J(a){try{var b=a;a=p?e.getTrusted(p,b):e.valueOf(b);var d;if(n&&!w(a))d=a;else if(null==a)d="";else{switch(typeof a){case "string":break;case "number":a=""+a;break;default:a=bb(a)}d=a}return d}catch(g){c(Ka.interr(f,g))}}if(!f.length||-1===f.indexOf(a)){var v;k||(k=g(f),
|
||||
v=ha(k),v.exp=f,v.expressions=[],v.$$watchDelegate=h);return v}n=!!n;var q,t,K=0,M=[],H=[];v=f.length;for(var E=[],I=[];K<v;)if(-1!=(q=f.indexOf(a,K))&&-1!=(t=f.indexOf(b,q+l)))K!==q&&E.push(g(f.substring(K,q))),K=f.substring(q+l,t),M.push(K),H.push(d(K,J)),K=t+m,I.push(E.length),E.push("");else{K!==v&&E.push(g(f.substring(K)));break}p&&1<E.length&&Ka.throwNoconcat(f);if(!k||M.length){var Da=function(a){for(var b=0,c=M.length;b<c;b++){if(n&&y(a[b]))return;E[I[b]]=a[b]}return E.join("")};return S(function(a){var b=
|
||||
0,d=M.length,e=Array(d);try{for(;b<d;b++)e[b]=H[b](a);return Da(e)}catch(g){c(Ka.interr(f,g))}},{exp:f,expressions:M,$$watchDelegate:function(a,b){var c;return a.$watchGroup(H,function(d,e){var f=Da(d);z(b)&&b.call(this,f,d!==e?c:f,a);c=f})}})}}var l=a.length,m=b.length,n=new RegExp(a.replace(/./g,f),"g"),p=new RegExp(b.replace(/./g,f),"g");k.startSymbol=function(){return a};k.endSymbol=function(){return b};return k}]}function lf(){this.$get=["$rootScope","$window","$q","$$q","$browser",function(a,
|
||||
b,d,c,e){function f(f,k,l,m){function n(){p?f.apply(null,u):f(r)}var p=4<arguments.length,u=p?va.call(arguments,4):[],R=b.setInterval,q=b.clearInterval,r=0,J=w(m)&&!m,v=(J?c:d).defer(),fa=v.promise;l=w(l)?l:0;fa.$$intervalId=R(function(){J?e.defer(n):a.$evalAsync(n);v.notify(r++);0<l&&r>=l&&(v.resolve(r),q(fa.$$intervalId),delete g[fa.$$intervalId]);J||a.$apply()},k);g[fa.$$intervalId]=v;return fa}var g={};f.cancel=function(a){return a&&a.$$intervalId in g?(g[a.$$intervalId].reject("canceled"),b.clearInterval(a.$$intervalId),
|
||||
delete g[a.$$intervalId],!0):!1};return f}]}function fc(a){a=a.split("/");for(var b=a.length;b--;)a[b]=qb(a[b]);return a.join("/")}function jd(a,b){var d=Y(a);b.$$protocol=d.protocol;b.$$host=d.hostname;b.$$port=Z(d.port)||hg[d.protocol]||null}function kd(a,b){var d="/"!==a.charAt(0);d&&(a="/"+a);var c=Y(a);b.$$path=decodeURIComponent(d&&"/"===c.pathname.charAt(0)?c.pathname.substring(1):c.pathname);b.$$search=Ac(c.search);b.$$hash=decodeURIComponent(c.hash);b.$$path&&"/"!=b.$$path.charAt(0)&&(b.$$path=
|
||||
"/"+b.$$path)}function ka(a,b){if(0===b.lastIndexOf(a,0))return b.substr(a.length)}function Ja(a){var b=a.indexOf("#");return-1==b?a:a.substr(0,b)}function jb(a){return a.replace(/(#.+)|#$/,"$1")}function gc(a,b,d){this.$$html5=!0;d=d||"";jd(a,this);this.$$parse=function(a){var d=ka(b,a);if(!G(d))throw Gb("ipthprfx",a,b);kd(d,this);this.$$path||(this.$$path="/");this.$$compose()};this.$$compose=function(){var a=Tb(this.$$search),d=this.$$hash?"#"+qb(this.$$hash):"";this.$$url=fc(this.$$path)+(a?"?"+
|
||||
a:"")+d;this.$$absUrl=b+this.$$url.substr(1)};this.$$parseLinkUrl=function(c,e){if(e&&"#"===e[0])return this.hash(e.slice(1)),!0;var f,g;w(f=ka(a,c))?(g=f,g=w(f=ka(d,f))?b+(ka("/",f)||f):a+g):w(f=ka(b,c))?g=b+f:b==c+"/"&&(g=b);g&&this.$$parse(g);return!!g}}function hc(a,b,d){jd(a,this);this.$$parse=function(c){var e=ka(a,c)||ka(b,c),f;y(e)||"#"!==e.charAt(0)?this.$$html5?f=e:(f="",y(e)&&(a=c,this.replace())):(f=ka(d,e),y(f)&&(f=e));kd(f,this);c=this.$$path;var e=a,g=/^\/[A-Z]:(\/.*)/;0===f.lastIndexOf(e,
|
||||
0)&&(f=f.replace(e,""));g.exec(f)||(c=(f=g.exec(c))?f[1]:c);this.$$path=c;this.$$compose()};this.$$compose=function(){var b=Tb(this.$$search),e=this.$$hash?"#"+qb(this.$$hash):"";this.$$url=fc(this.$$path)+(b?"?"+b:"")+e;this.$$absUrl=a+(this.$$url?d+this.$$url:"")};this.$$parseLinkUrl=function(b,d){return Ja(a)==Ja(b)?(this.$$parse(b),!0):!1}}function ld(a,b,d){this.$$html5=!0;hc.apply(this,arguments);this.$$parseLinkUrl=function(c,e){if(e&&"#"===e[0])return this.hash(e.slice(1)),!0;var f,g;a==Ja(c)?
|
||||
f=c:(g=ka(b,c))?f=a+d+g:b===c+"/"&&(f=b);f&&this.$$parse(f);return!!f};this.$$compose=function(){var b=Tb(this.$$search),e=this.$$hash?"#"+qb(this.$$hash):"";this.$$url=fc(this.$$path)+(b?"?"+b:"")+e;this.$$absUrl=a+d+this.$$url}}function Hb(a){return function(){return this[a]}}function md(a,b){return function(d){if(y(d))return this[a];this[a]=b(d);this.$$compose();return this}}function sf(){var a="",b={enabled:!1,requireBase:!0,rewriteLinks:!0};this.hashPrefix=function(b){return w(b)?(a=b,this):
|
||||
a};this.html5Mode=function(a){return Ga(a)?(b.enabled=a,this):D(a)?(Ga(a.enabled)&&(b.enabled=a.enabled),Ga(a.requireBase)&&(b.requireBase=a.requireBase),Ga(a.rewriteLinks)&&(b.rewriteLinks=a.rewriteLinks),this):b};this.$get=["$rootScope","$browser","$sniffer","$rootElement","$window",function(d,c,e,f,g){function h(a,b,d){var e=l.url(),f=l.$$state;try{c.url(a,b,d),l.$$state=c.state()}catch(g){throw l.url(e),l.$$state=f,g;}}function k(a,b){d.$broadcast("$locationChangeSuccess",l.absUrl(),a,l.$$state,
|
||||
b)}var l,m;m=c.baseHref();var n=c.url(),p;if(b.enabled){if(!m&&b.requireBase)throw Gb("nobase");p=n.substring(0,n.indexOf("/",n.indexOf("//")+2))+(m||"/");m=e.history?gc:ld}else p=Ja(n),m=hc;var u=p.substr(0,Ja(p).lastIndexOf("/")+1);l=new m(p,u,"#"+a);l.$$parseLinkUrl(n,n);l.$$state=c.state();var R=/^\s*(javascript|mailto):/i;f.on("click",function(a){if(b.rewriteLinks&&!a.ctrlKey&&!a.metaKey&&!a.shiftKey&&2!=a.which&&2!=a.button){for(var e=F(a.target);"a"!==wa(e[0]);)if(e[0]===f[0]||!(e=e.parent())[0])return;
|
||||
var h=e.prop("href"),k=e.attr("href")||e.attr("xlink:href");D(h)&&"[object SVGAnimatedString]"===h.toString()&&(h=Y(h.animVal).href);R.test(h)||!h||e.attr("target")||a.isDefaultPrevented()||!l.$$parseLinkUrl(h,k)||(a.preventDefault(),l.absUrl()!=c.url()&&(d.$apply(),g.angular["ff-684208-preventDefault"]=!0))}});jb(l.absUrl())!=jb(n)&&c.url(l.absUrl(),!0);var q=!0;c.onUrlChange(function(a,b){y(ka(u,a))?g.location.href=a:(d.$evalAsync(function(){var c=l.absUrl(),e=l.$$state,f;a=jb(a);l.$$parse(a);l.$$state=
|
||||
b;f=d.$broadcast("$locationChangeStart",a,c,b,e).defaultPrevented;l.absUrl()===a&&(f?(l.$$parse(c),l.$$state=e,h(c,!1,e)):(q=!1,k(c,e)))}),d.$$phase||d.$digest())});d.$watch(function(){var a=jb(c.url()),b=jb(l.absUrl()),f=c.state(),g=l.$$replace,m=a!==b||l.$$html5&&e.history&&f!==l.$$state;if(q||m)q=!1,d.$evalAsync(function(){var b=l.absUrl(),c=d.$broadcast("$locationChangeStart",b,a,l.$$state,f).defaultPrevented;l.absUrl()===b&&(c?(l.$$parse(a),l.$$state=f):(m&&h(b,g,f===l.$$state?null:l.$$state),
|
||||
k(a,f)))});l.$$replace=!1});return l}]}function tf(){var a=!0,b=this;this.debugEnabled=function(b){return w(b)?(a=b,this):a};this.$get=["$window",function(d){function c(a){a instanceof Error&&(a.stack?a=a.message&&-1===a.stack.indexOf(a.message)?"Error: "+a.message+"\n"+a.stack:a.stack:a.sourceURL&&(a=a.message+"\n"+a.sourceURL+":"+a.line));return a}function e(a){var b=d.console||{},e=b[a]||b.log||A;a=!1;try{a=!!e.apply}catch(k){}return a?function(){var a=[];q(arguments,function(b){a.push(c(b))});
|
||||
return e.apply(b,a)}:function(a,b){e(a,null==b?"":b)}}return{log:e("log"),info:e("info"),warn:e("warn"),error:e("error"),debug:function(){var c=e("debug");return function(){a&&c.apply(b,arguments)}}()}}]}function Sa(a,b){if("__defineGetter__"===a||"__defineSetter__"===a||"__lookupGetter__"===a||"__lookupSetter__"===a||"__proto__"===a)throw X("isecfld",b);return a}function ig(a){return a+""}function ra(a,b){if(a){if(a.constructor===a)throw X("isecfn",b);if(a.window===a)throw X("isecwindow",b);if(a.children&&
|
||||
(a.nodeName||a.prop&&a.attr&&a.find))throw X("isecdom",b);if(a===Object)throw X("isecobj",b);}return a}function nd(a,b){if(a){if(a.constructor===a)throw X("isecfn",b);if(a===jg||a===kg||a===lg)throw X("isecff",b);}}function Ib(a,b){if(a&&(a===(0).constructor||a===(!1).constructor||a==="".constructor||a==={}.constructor||a===[].constructor||a===Function.constructor))throw X("isecaf",b);}function mg(a,b){return"undefined"!==typeof a?a:b}function od(a,b){return"undefined"===typeof a?b:"undefined"===
|
||||
typeof b?a:a+b}function V(a,b){var d,c;switch(a.type){case s.Program:d=!0;q(a.body,function(a){V(a.expression,b);d=d&&a.expression.constant});a.constant=d;break;case s.Literal:a.constant=!0;a.toWatch=[];break;case s.UnaryExpression:V(a.argument,b);a.constant=a.argument.constant;a.toWatch=a.argument.toWatch;break;case s.BinaryExpression:V(a.left,b);V(a.right,b);a.constant=a.left.constant&&a.right.constant;a.toWatch=a.left.toWatch.concat(a.right.toWatch);break;case s.LogicalExpression:V(a.left,b);V(a.right,
|
||||
b);a.constant=a.left.constant&&a.right.constant;a.toWatch=a.constant?[]:[a];break;case s.ConditionalExpression:V(a.test,b);V(a.alternate,b);V(a.consequent,b);a.constant=a.test.constant&&a.alternate.constant&&a.consequent.constant;a.toWatch=a.constant?[]:[a];break;case s.Identifier:a.constant=!1;a.toWatch=[a];break;case s.MemberExpression:V(a.object,b);a.computed&&V(a.property,b);a.constant=a.object.constant&&(!a.computed||a.property.constant);a.toWatch=[a];break;case s.CallExpression:d=a.filter?!b(a.callee.name).$stateful:
|
||||
!1;c=[];q(a.arguments,function(a){V(a,b);d=d&&a.constant;a.constant||c.push.apply(c,a.toWatch)});a.constant=d;a.toWatch=a.filter&&!b(a.callee.name).$stateful?c:[a];break;case s.AssignmentExpression:V(a.left,b);V(a.right,b);a.constant=a.left.constant&&a.right.constant;a.toWatch=[a];break;case s.ArrayExpression:d=!0;c=[];q(a.elements,function(a){V(a,b);d=d&&a.constant;a.constant||c.push.apply(c,a.toWatch)});a.constant=d;a.toWatch=c;break;case s.ObjectExpression:d=!0;c=[];q(a.properties,function(a){V(a.value,
|
||||
b);d=d&&a.value.constant&&!a.computed;a.value.constant||c.push.apply(c,a.value.toWatch)});a.constant=d;a.toWatch=c;break;case s.ThisExpression:a.constant=!1;a.toWatch=[];break;case s.LocalsExpression:a.constant=!1,a.toWatch=[]}}function pd(a){if(1==a.length){a=a[0].expression;var b=a.toWatch;return 1!==b.length?b:b[0]!==a?b:void 0}}function qd(a){return a.type===s.Identifier||a.type===s.MemberExpression}function rd(a){if(1===a.body.length&&qd(a.body[0].expression))return{type:s.AssignmentExpression,
|
||||
left:a.body[0].expression,right:{type:s.NGValueParameter},operator:"="}}function sd(a){return 0===a.body.length||1===a.body.length&&(a.body[0].expression.type===s.Literal||a.body[0].expression.type===s.ArrayExpression||a.body[0].expression.type===s.ObjectExpression)}function td(a,b){this.astBuilder=a;this.$filter=b}function ud(a,b){this.astBuilder=a;this.$filter=b}function Jb(a){return"constructor"==a}function ic(a){return z(a.valueOf)?a.valueOf():ng.call(a)}function uf(){var a=U(),b=U(),d={"true":!0,
|
||||
"false":!1,"null":null,undefined:void 0},c,e;this.addLiteral=function(a,b){d[a]=b};this.setIdentifierFns=function(a,b){c=a;e=b;return this};this.$get=["$filter",function(f){function g(c,d,e){var g,k,H;e=e||J;switch(typeof c){case "string":H=c=c.trim();var E=e?b:a;g=E[H];if(!g){":"===c.charAt(0)&&":"===c.charAt(1)&&(k=!0,c=c.substring(2));g=e?r:B;var q=new jc(g);g=(new kc(q,f,g)).parse(c);g.constant?g.$$watchDelegate=p:k?g.$$watchDelegate=g.literal?n:m:g.inputs&&(g.$$watchDelegate=l);e&&(g=h(g));E[H]=
|
||||
g}return u(g,d);case "function":return u(c,d);default:return u(A,d)}}function h(a){function b(c,d,e,f){var g=J;J=!0;try{return a(c,d,e,f)}finally{J=g}}if(!a)return a;b.$$watchDelegate=a.$$watchDelegate;b.assign=h(a.assign);b.constant=a.constant;b.literal=a.literal;for(var c=0;a.inputs&&c<a.inputs.length;++c)a.inputs[c]=h(a.inputs[c]);b.inputs=a.inputs;return b}function k(a,b){return null==a||null==b?a===b:"object"===typeof a&&(a=ic(a),"object"===typeof a)?!1:a===b||a!==a&&b!==b}function l(a,b,c,d,
|
||||
e){var f=d.inputs,g;if(1===f.length){var h=k,f=f[0];return a.$watch(function(a){var b=f(a);k(b,h)||(g=d(a,void 0,void 0,[b]),h=b&&ic(b));return g},b,c,e)}for(var l=[],m=[],p=0,n=f.length;p<n;p++)l[p]=k,m[p]=null;return a.$watch(function(a){for(var b=!1,c=0,e=f.length;c<e;c++){var h=f[c](a);if(b||(b=!k(h,l[c])))m[c]=h,l[c]=h&&ic(h)}b&&(g=d(a,void 0,void 0,m));return g},b,c,e)}function m(a,b,c,d){var e,f;return e=a.$watch(function(a){return d(a)},function(a,c,d){f=a;z(b)&&b.apply(this,arguments);w(a)&&
|
||||
d.$$postDigest(function(){w(f)&&e()})},c)}function n(a,b,c,d){function e(a){var b=!0;q(a,function(a){w(a)||(b=!1)});return b}var f,g;return f=a.$watch(function(a){return d(a)},function(a,c,d){g=a;z(b)&&b.call(this,a,c,d);e(a)&&d.$$postDigest(function(){e(g)&&f()})},c)}function p(a,b,c,d){var e;return e=a.$watch(function(a){e();return d(a)},b,c)}function u(a,b){if(!b)return a;var c=a.$$watchDelegate,d=!1,c=c!==n&&c!==m?function(c,e,f,g){f=d&&g?g[0]:a(c,e,f,g);return b(f,c,e)}:function(c,d,e,f){e=a(c,
|
||||
d,e,f);c=b(e,c,d);return w(e)?c:e};a.$$watchDelegate&&a.$$watchDelegate!==l?c.$$watchDelegate=a.$$watchDelegate:b.$stateful||(c.$$watchDelegate=l,d=!a.inputs,c.inputs=a.inputs?a.inputs:[a]);return c}var R=Ba().noUnsafeEval,B={csp:R,expensiveChecks:!1,literals:pa(d),isIdentifierStart:z(c)&&c,isIdentifierContinue:z(e)&&e},r={csp:R,expensiveChecks:!0,literals:pa(d),isIdentifierStart:z(c)&&c,isIdentifierContinue:z(e)&&e},J=!1;g.$$runningExpensiveChecks=function(){return J};return g}]}function wf(){this.$get=
|
||||
["$rootScope","$exceptionHandler",function(a,b){return vd(function(b){a.$evalAsync(b)},b)}]}function xf(){this.$get=["$browser","$exceptionHandler",function(a,b){return vd(function(b){a.defer(b)},b)}]}function vd(a,b){function d(){this.$$state={status:0}}function c(a,b){return function(c){b.call(a,c)}}function e(c){!c.processScheduled&&c.pending&&(c.processScheduled=!0,a(function(){var a,d,e;e=c.pending;c.processScheduled=!1;c.pending=void 0;for(var f=0,g=e.length;f<g;++f){d=e[f][0];a=e[f][c.status];
|
||||
try{z(a)?d.resolve(a(c.value)):1===c.status?d.resolve(c.value):d.reject(c.value)}catch(h){d.reject(h),b(h)}}}))}function f(){this.promise=new d}var g=N("$q",TypeError),h=function(){var a=new f;a.resolve=c(a,a.resolve);a.reject=c(a,a.reject);a.notify=c(a,a.notify);return a};S(d.prototype,{then:function(a,b,c){if(y(a)&&y(b)&&y(c))return this;var d=new f;this.$$state.pending=this.$$state.pending||[];this.$$state.pending.push([d,a,b,c]);0<this.$$state.status&&e(this.$$state);return d.promise},"catch":function(a){return this.then(null,
|
||||
a)},"finally":function(a,b){return this.then(function(b){return l(b,!0,a)},function(b){return l(b,!1,a)},b)}});S(f.prototype,{resolve:function(a){this.promise.$$state.status||(a===this.promise?this.$$reject(g("qcycle",a)):this.$$resolve(a))},$$resolve:function(a){function d(a){k||(k=!0,h.$$resolve(a))}function f(a){k||(k=!0,h.$$reject(a))}var g,h=this,k=!1;try{if(D(a)||z(a))g=a&&a.then;z(g)?(this.promise.$$state.status=-1,g.call(a,d,f,c(this,this.notify))):(this.promise.$$state.value=a,this.promise.$$state.status=
|
||||
1,e(this.promise.$$state))}catch(l){f(l),b(l)}},reject:function(a){this.promise.$$state.status||this.$$reject(a)},$$reject:function(a){this.promise.$$state.value=a;this.promise.$$state.status=2;e(this.promise.$$state)},notify:function(c){var d=this.promise.$$state.pending;0>=this.promise.$$state.status&&d&&d.length&&a(function(){for(var a,e,f=0,g=d.length;f<g;f++){e=d[f][0];a=d[f][3];try{e.notify(z(a)?a(c):c)}catch(h){b(h)}}})}});var k=function(a,b){var c=new f;b?c.resolve(a):c.reject(a);return c.promise},
|
||||
l=function(a,b,c){var d=null;try{z(c)&&(d=c())}catch(e){return k(e,!1)}return d&&z(d.then)?d.then(function(){return k(a,b)},function(a){return k(a,!1)}):k(a,b)},m=function(a,b,c,d){var e=new f;e.resolve(a);return e.promise.then(b,c,d)},n=function(a){if(!z(a))throw g("norslvr",a);var b=new f;a(function(a){b.resolve(a)},function(a){b.reject(a)});return b.promise};n.prototype=d.prototype;n.defer=h;n.reject=function(a){var b=new f;b.reject(a);return b.promise};n.when=m;n.resolve=m;n.all=function(a){var b=
|
||||
new f,c=0,d=L(a)?[]:{};q(a,function(a,e){c++;m(a).then(function(a){d.hasOwnProperty(e)||(d[e]=a,--c||b.resolve(d))},function(a){d.hasOwnProperty(e)||b.reject(a)})});0===c&&b.resolve(d);return b.promise};n.race=function(a){var b=h();q(a,function(a){m(a).then(b.resolve,b.reject)});return b.promise};return n}function Gf(){this.$get=["$window","$timeout",function(a,b){var d=a.requestAnimationFrame||a.webkitRequestAnimationFrame,c=a.cancelAnimationFrame||a.webkitCancelAnimationFrame||a.webkitCancelRequestAnimationFrame,
|
||||
e=!!d,f=e?function(a){var b=d(a);return function(){c(b)}}:function(a){var c=b(a,16.66,!1);return function(){b.cancel(c)}};f.supported=e;return f}]}function vf(){function a(a){function b(){this.$$watchers=this.$$nextSibling=this.$$childHead=this.$$childTail=null;this.$$listeners={};this.$$listenerCount={};this.$$watchersCount=0;this.$id=++pb;this.$$ChildScope=null}b.prototype=a;return b}var b=10,d=N("$rootScope"),c=null,e=null;this.digestTtl=function(a){arguments.length&&(b=a);return b};this.$get=
|
||||
["$exceptionHandler","$parse","$browser",function(f,g,h){function k(a){a.currentScope.$$destroyed=!0}function l(a){9===Ea&&(a.$$childHead&&l(a.$$childHead),a.$$nextSibling&&l(a.$$nextSibling));a.$parent=a.$$nextSibling=a.$$prevSibling=a.$$childHead=a.$$childTail=a.$root=a.$$watchers=null}function m(){this.$id=++pb;this.$$phase=this.$parent=this.$$watchers=this.$$nextSibling=this.$$prevSibling=this.$$childHead=this.$$childTail=null;this.$root=this;this.$$destroyed=!1;this.$$listeners={};this.$$listenerCount=
|
||||
{};this.$$watchersCount=0;this.$$isolateBindings=null}function n(a){if(J.$$phase)throw d("inprog",J.$$phase);J.$$phase=a}function p(a,b){do a.$$watchersCount+=b;while(a=a.$parent)}function u(a,b,c){do a.$$listenerCount[c]-=b,0===a.$$listenerCount[c]&&delete a.$$listenerCount[c];while(a=a.$parent)}function s(){}function B(){for(;t.length;)try{t.shift()()}catch(a){f(a)}e=null}function r(){null===e&&(e=h.defer(function(){J.$apply(B)}))}m.prototype={constructor:m,$new:function(b,c){var d;c=c||this;b?
|
||||
(d=new m,d.$root=this.$root):(this.$$ChildScope||(this.$$ChildScope=a(this)),d=new this.$$ChildScope);d.$parent=c;d.$$prevSibling=c.$$childTail;c.$$childHead?(c.$$childTail.$$nextSibling=d,c.$$childTail=d):c.$$childHead=c.$$childTail=d;(b||c!=this)&&d.$on("$destroy",k);return d},$watch:function(a,b,d,e){var f=g(a);if(f.$$watchDelegate)return f.$$watchDelegate(this,b,d,f,a);var h=this,k=h.$$watchers,l={fn:b,last:s,get:f,exp:e||a,eq:!!d};c=null;z(b)||(l.fn=A);k||(k=h.$$watchers=[]);k.unshift(l);p(this,
|
||||
1);return function(){0<=Za(k,l)&&p(h,-1);c=null}},$watchGroup:function(a,b){function c(){h=!1;k?(k=!1,b(e,e,g)):b(e,d,g)}var d=Array(a.length),e=Array(a.length),f=[],g=this,h=!1,k=!0;if(!a.length){var l=!0;g.$evalAsync(function(){l&&b(e,e,g)});return function(){l=!1}}if(1===a.length)return this.$watch(a[0],function(a,c,f){e[0]=a;d[0]=c;b(e,a===c?e:d,f)});q(a,function(a,b){var k=g.$watch(a,function(a,f){e[b]=a;d[b]=f;h||(h=!0,g.$evalAsync(c))});f.push(k)});return function(){for(;f.length;)f.shift()()}},
|
||||
$watchCollection:function(a,b){function c(a){e=a;var b,d,g,h;if(!y(e)){if(D(e))if(ta(e))for(f!==n&&(f=n,u=f.length=0,l++),a=e.length,u!==a&&(l++,f.length=u=a),b=0;b<a;b++)h=f[b],g=e[b],d=h!==h&&g!==g,d||h===g||(l++,f[b]=g);else{f!==p&&(f=p={},u=0,l++);a=0;for(b in e)ua.call(e,b)&&(a++,g=e[b],h=f[b],b in f?(d=h!==h&&g!==g,d||h===g||(l++,f[b]=g)):(u++,f[b]=g,l++));if(u>a)for(b in l++,f)ua.call(e,b)||(u--,delete f[b])}else f!==e&&(f=e,l++);return l}}c.$stateful=!0;var d=this,e,f,h,k=1<b.length,l=0,m=
|
||||
g(a,c),n=[],p={},r=!0,u=0;return this.$watch(m,function(){r?(r=!1,b(e,e,d)):b(e,h,d);if(k)if(D(e))if(ta(e)){h=Array(e.length);for(var a=0;a<e.length;a++)h[a]=e[a]}else for(a in h={},e)ua.call(e,a)&&(h[a]=e[a]);else h=e})},$digest:function(){var a,g,k,l,m,p,u,r,q=b,t,y=[],A,C;n("$digest");h.$$checkUrlChange();this===J&&null!==e&&(h.defer.cancel(e),B());c=null;do{r=!1;t=this;for(p=0;p<v.length;p++){try{C=v[p],C.scope.$eval(C.expression,C.locals)}catch(F){f(F)}c=null}v.length=0;a:do{if(p=t.$$watchers)for(u=
|
||||
p.length;u--;)try{if(a=p[u])if(m=a.get,(g=m(t))!==(k=a.last)&&!(a.eq?na(g,k):"number"===typeof g&&"number"===typeof k&&isNaN(g)&&isNaN(k)))r=!0,c=a,a.last=a.eq?pa(g,null):g,l=a.fn,l(g,k===s?g:k,t),5>q&&(A=4-q,y[A]||(y[A]=[]),y[A].push({msg:z(a.exp)?"fn: "+(a.exp.name||a.exp.toString()):a.exp,newVal:g,oldVal:k}));else if(a===c){r=!1;break a}}catch(G){f(G)}if(!(p=t.$$watchersCount&&t.$$childHead||t!==this&&t.$$nextSibling))for(;t!==this&&!(p=t.$$nextSibling);)t=t.$parent}while(t=p);if((r||v.length)&&
|
||||
!q--)throw J.$$phase=null,d("infdig",b,y);}while(r||v.length);for(J.$$phase=null;K<w.length;)try{w[K++]()}catch(D){f(D)}w.length=K=0},$destroy:function(){if(!this.$$destroyed){var a=this.$parent;this.$broadcast("$destroy");this.$$destroyed=!0;this===J&&h.$$applicationDestroyed();p(this,-this.$$watchersCount);for(var b in this.$$listenerCount)u(this,this.$$listenerCount[b],b);a&&a.$$childHead==this&&(a.$$childHead=this.$$nextSibling);a&&a.$$childTail==this&&(a.$$childTail=this.$$prevSibling);this.$$prevSibling&&
|
||||
(this.$$prevSibling.$$nextSibling=this.$$nextSibling);this.$$nextSibling&&(this.$$nextSibling.$$prevSibling=this.$$prevSibling);this.$destroy=this.$digest=this.$apply=this.$evalAsync=this.$applyAsync=A;this.$on=this.$watch=this.$watchGroup=function(){return A};this.$$listeners={};this.$$nextSibling=null;l(this)}},$eval:function(a,b){return g(a)(this,b)},$evalAsync:function(a,b){J.$$phase||v.length||h.defer(function(){v.length&&J.$digest()});v.push({scope:this,expression:g(a),locals:b})},$$postDigest:function(a){w.push(a)},
|
||||
$apply:function(a){try{n("$apply");try{return this.$eval(a)}finally{J.$$phase=null}}catch(b){f(b)}finally{try{J.$digest()}catch(c){throw f(c),c;}}},$applyAsync:function(a){function b(){c.$eval(a)}var c=this;a&&t.push(b);a=g(a);r()},$on:function(a,b){var c=this.$$listeners[a];c||(this.$$listeners[a]=c=[]);c.push(b);var d=this;do d.$$listenerCount[a]||(d.$$listenerCount[a]=0),d.$$listenerCount[a]++;while(d=d.$parent);var e=this;return function(){var d=c.indexOf(b);-1!==d&&(c[d]=null,u(e,1,a))}},$emit:function(a,
|
||||
b){var c=[],d,e=this,g=!1,h={name:a,targetScope:e,stopPropagation:function(){g=!0},preventDefault:function(){h.defaultPrevented=!0},defaultPrevented:!1},k=$a([h],arguments,1),l,m;do{d=e.$$listeners[a]||c;h.currentScope=e;l=0;for(m=d.length;l<m;l++)if(d[l])try{d[l].apply(null,k)}catch(n){f(n)}else d.splice(l,1),l--,m--;if(g)return h.currentScope=null,h;e=e.$parent}while(e);h.currentScope=null;return h},$broadcast:function(a,b){var c=this,d=this,e={name:a,targetScope:this,preventDefault:function(){e.defaultPrevented=
|
||||
!0},defaultPrevented:!1};if(!this.$$listenerCount[a])return e;for(var g=$a([e],arguments,1),h,k;c=d;){e.currentScope=c;d=c.$$listeners[a]||[];h=0;for(k=d.length;h<k;h++)if(d[h])try{d[h].apply(null,g)}catch(l){f(l)}else d.splice(h,1),h--,k--;if(!(d=c.$$listenerCount[a]&&c.$$childHead||c!==this&&c.$$nextSibling))for(;c!==this&&!(d=c.$$nextSibling);)c=c.$parent}e.currentScope=null;return e}};var J=new m,v=J.$$asyncQueue=[],w=J.$$postDigestQueue=[],t=J.$$applyAsyncQueue=[],K=0;return J}]}function ne(){var a=
|
||||
/^\s*(https?|ftp|mailto|tel|file):/,b=/^\s*((https?|ftp|file|blob):|data:image\/)/;this.aHrefSanitizationWhitelist=function(b){return w(b)?(a=b,this):a};this.imgSrcSanitizationWhitelist=function(a){return w(a)?(b=a,this):b};this.$get=function(){return function(d,c){var e=c?b:a,f;f=Y(d).href;return""===f||f.match(e)?d:"unsafe:"+f}}}function og(a){if("self"===a)return a;if(G(a)){if(-1<a.indexOf("***"))throw sa("iwcard",a);a=wd(a).replace("\\*\\*",".*").replace("\\*","[^:/.?&;]*");return new RegExp("^"+
|
||||
a+"$")}if(Wa(a))return new RegExp("^"+a.source+"$");throw sa("imatcher");}function xd(a){var b=[];w(a)&&q(a,function(a){b.push(og(a))});return b}function zf(){this.SCE_CONTEXTS=la;var a=["self"],b=[];this.resourceUrlWhitelist=function(b){arguments.length&&(a=xd(b));return a};this.resourceUrlBlacklist=function(a){arguments.length&&(b=xd(a));return b};this.$get=["$injector",function(d){function c(a,b){return"self"===a?id(b):!!a.exec(b.href)}function e(a){var b=function(a){this.$$unwrapTrustedValue=
|
||||
function(){return a}};a&&(b.prototype=new a);b.prototype.valueOf=function(){return this.$$unwrapTrustedValue()};b.prototype.toString=function(){return this.$$unwrapTrustedValue().toString()};return b}var f=function(a){throw sa("unsafe");};d.has("$sanitize")&&(f=d.get("$sanitize"));var g=e(),h={};h[la.HTML]=e(g);h[la.CSS]=e(g);h[la.URL]=e(g);h[la.JS]=e(g);h[la.RESOURCE_URL]=e(h[la.URL]);return{trustAs:function(a,b){var c=h.hasOwnProperty(a)?h[a]:null;if(!c)throw sa("icontext",a,b);if(null===b||y(b)||
|
||||
""===b)return b;if("string"!==typeof b)throw sa("itype",a);return new c(b)},getTrusted:function(d,e){if(null===e||y(e)||""===e)return e;var g=h.hasOwnProperty(d)?h[d]:null;if(g&&e instanceof g)return e.$$unwrapTrustedValue();if(d===la.RESOURCE_URL){var g=Y(e.toString()),n,p,u=!1;n=0;for(p=a.length;n<p;n++)if(c(a[n],g)){u=!0;break}if(u)for(n=0,p=b.length;n<p;n++)if(c(b[n],g)){u=!1;break}if(u)return e;throw sa("insecurl",e.toString());}if(d===la.HTML)return f(e);throw sa("unsafe");},valueOf:function(a){return a instanceof
|
||||
g?a.$$unwrapTrustedValue():a}}}]}function yf(){var a=!0;this.enabled=function(b){arguments.length&&(a=!!b);return a};this.$get=["$parse","$sceDelegate",function(b,d){if(a&&8>Ea)throw sa("iequirks");var c=ia(la);c.isEnabled=function(){return a};c.trustAs=d.trustAs;c.getTrusted=d.getTrusted;c.valueOf=d.valueOf;a||(c.trustAs=c.getTrusted=function(a,b){return b},c.valueOf=Xa);c.parseAs=function(a,d){var e=b(d);return e.literal&&e.constant?e:b(d,function(b){return c.getTrusted(a,b)})};var e=c.parseAs,
|
||||
f=c.getTrusted,g=c.trustAs;q(la,function(a,b){var d=Q(b);c[db("parse_as_"+d)]=function(b){return e(a,b)};c[db("get_trusted_"+d)]=function(b){return f(a,b)};c[db("trust_as_"+d)]=function(b){return g(a,b)}});return c}]}function Af(){this.$get=["$window","$document",function(a,b){var d={},c=!(a.chrome&&a.chrome.app&&a.chrome.app.runtime)&&a.history&&a.history.pushState,e=Z((/android (\d+)/.exec(Q((a.navigator||{}).userAgent))||[])[1]),f=/Boxee/i.test((a.navigator||{}).userAgent),g=b[0]||{},h,k=/^(Moz|webkit|ms)(?=[A-Z])/,
|
||||
l=g.body&&g.body.style,m=!1,n=!1;if(l){for(var p in l)if(m=k.exec(p)){h=m[0];h=h[0].toUpperCase()+h.substr(1);break}h||(h="WebkitOpacity"in l&&"webkit");m=!!("transition"in l||h+"Transition"in l);n=!!("animation"in l||h+"Animation"in l);!e||m&&n||(m=G(l.webkitTransition),n=G(l.webkitAnimation))}return{history:!(!c||4>e||f),hasEvent:function(a){if("input"===a&&11>=Ea)return!1;if(y(d[a])){var b=g.createElement("div");d[a]="on"+a in b}return d[a]},csp:Ba(),vendorPrefix:h,transitions:m,animations:n,android:e}}]}
|
||||
function Cf(){var a;this.httpOptions=function(b){return b?(a=b,this):a};this.$get=["$templateCache","$http","$q","$sce",function(b,d,c,e){function f(g,h){f.totalPendingRequests++;if(!G(g)||y(b.get(g)))g=e.getTrustedResourceUrl(g);var k=d.defaults&&d.defaults.transformResponse;L(k)?k=k.filter(function(a){return a!==dc}):k===dc&&(k=null);return d.get(g,S({cache:b,transformResponse:k},a))["finally"](function(){f.totalPendingRequests--}).then(function(a){b.put(g,a.data);return a.data},function(a){if(!h)throw pg("tpload",
|
||||
g,a.status,a.statusText);return c.reject(a)})}f.totalPendingRequests=0;return f}]}function Df(){this.$get=["$rootScope","$browser","$location",function(a,b,d){return{findBindings:function(a,b,d){a=a.getElementsByClassName("ng-binding");var g=[];q(a,function(a){var c=ca.element(a).data("$binding");c&&q(c,function(c){d?(new RegExp("(^|\\s)"+wd(b)+"(\\s|\\||$)")).test(c)&&g.push(a):-1!=c.indexOf(b)&&g.push(a)})});return g},findModels:function(a,b,d){for(var g=["ng-","data-ng-","ng\\:"],h=0;h<g.length;++h){var k=
|
||||
a.querySelectorAll("["+g[h]+"model"+(d?"=":"*=")+'"'+b+'"]');if(k.length)return k}},getLocation:function(){return d.url()},setLocation:function(b){b!==d.url()&&(d.url(b),a.$digest())},whenStable:function(a){b.notifyWhenNoOutstandingRequests(a)}}}]}function Ef(){this.$get=["$rootScope","$browser","$q","$$q","$exceptionHandler",function(a,b,d,c,e){function f(f,k,l){z(f)||(l=k,k=f,f=A);var m=va.call(arguments,3),n=w(l)&&!l,p=(n?c:d).defer(),u=p.promise,q;q=b.defer(function(){try{p.resolve(f.apply(null,
|
||||
m))}catch(b){p.reject(b),e(b)}finally{delete g[u.$$timeoutId]}n||a.$apply()},k);u.$$timeoutId=q;g[q]=p;return u}var g={};f.cancel=function(a){return a&&a.$$timeoutId in g?(g[a.$$timeoutId].reject("canceled"),delete g[a.$$timeoutId],b.defer.cancel(a.$$timeoutId)):!1};return f}]}function Y(a){Ea&&($.setAttribute("href",a),a=$.href);$.setAttribute("href",a);return{href:$.href,protocol:$.protocol?$.protocol.replace(/:$/,""):"",host:$.host,search:$.search?$.search.replace(/^\?/,""):"",hash:$.hash?$.hash.replace(/^#/,
|
||||
""):"",hostname:$.hostname,port:$.port,pathname:"/"===$.pathname.charAt(0)?$.pathname:"/"+$.pathname}}function id(a){a=G(a)?Y(a):a;return a.protocol===yd.protocol&&a.host===yd.host}function Ff(){this.$get=ha(C)}function zd(a){function b(a){try{return decodeURIComponent(a)}catch(b){return a}}var d=a[0]||{},c={},e="";return function(){var a,g,h,k,l;a=d.cookie||"";if(a!==e)for(e=a,a=e.split("; "),c={},h=0;h<a.length;h++)g=a[h],k=g.indexOf("="),0<k&&(l=b(g.substring(0,k)),y(c[l])&&(c[l]=b(g.substring(k+
|
||||
1))));return c}}function Jf(){this.$get=zd}function Mc(a){function b(d,c){if(D(d)){var e={};q(d,function(a,c){e[c]=b(c,a)});return e}return a.factory(d+"Filter",c)}this.register=b;this.$get=["$injector",function(a){return function(b){return a.get(b+"Filter")}}];b("currency",Ad);b("date",Bd);b("filter",qg);b("json",rg);b("limitTo",sg);b("lowercase",tg);b("number",Cd);b("orderBy",Dd);b("uppercase",ug)}function qg(){return function(a,b,d,c){if(!ta(a)){if(null==a)return a;throw N("filter")("notarray",
|
||||
a);}c=c||"$";var e;switch(lc(b)){case "function":break;case "boolean":case "null":case "number":case "string":e=!0;case "object":b=vg(b,d,c,e);break;default:return a}return Array.prototype.filter.call(a,b)}}function vg(a,b,d,c){var e=D(a)&&d in a;!0===b?b=na:z(b)||(b=function(a,b){if(y(a))return!1;if(null===a||null===b)return a===b;if(D(b)||D(a)&&!vc(a))return!1;a=Q(""+a);b=Q(""+b);return-1!==a.indexOf(b)});return function(f){return e&&!D(f)?La(f,a[d],b,d,!1):La(f,a,b,d,c)}}function La(a,b,d,c,e,
|
||||
f){var g=lc(a),h=lc(b);if("string"===h&&"!"===b.charAt(0))return!La(a,b.substring(1),d,c,e);if(L(a))return a.some(function(a){return La(a,b,d,c,e)});switch(g){case "object":var k;if(e){for(k in a)if("$"!==k.charAt(0)&&La(a[k],b,d,c,!0))return!0;return f?!1:La(a,b,d,c,!1)}if("object"===h){for(k in b)if(f=b[k],!z(f)&&!y(f)&&(g=k===c,!La(g?a:a[k],f,d,c,g,g)))return!1;return!0}return d(a,b);case "function":return!1;default:return d(a,b)}}function lc(a){return null===a?"null":typeof a}function Ad(a){var b=
|
||||
a.NUMBER_FORMATS;return function(a,c,e){y(c)&&(c=b.CURRENCY_SYM);y(e)&&(e=b.PATTERNS[1].maxFrac);return null==a?a:Ed(a,b.PATTERNS[1],b.GROUP_SEP,b.DECIMAL_SEP,e).replace(/\u00A4/g,c)}}function Cd(a){var b=a.NUMBER_FORMATS;return function(a,c){return null==a?a:Ed(a,b.PATTERNS[0],b.GROUP_SEP,b.DECIMAL_SEP,c)}}function wg(a){var b=0,d,c,e,f,g;-1<(c=a.indexOf(Fd))&&(a=a.replace(Fd,""));0<(e=a.search(/e/i))?(0>c&&(c=e),c+=+a.slice(e+1),a=a.substring(0,e)):0>c&&(c=a.length);for(e=0;a.charAt(e)==mc;e++);
|
||||
if(e==(g=a.length))d=[0],c=1;else{for(g--;a.charAt(g)==mc;)g--;c-=e;d=[];for(f=0;e<=g;e++,f++)d[f]=+a.charAt(e)}c>Gd&&(d=d.splice(0,Gd-1),b=c-1,c=1);return{d:d,e:b,i:c}}function xg(a,b,d,c){var e=a.d,f=e.length-a.i;b=y(b)?Math.min(Math.max(d,f),c):+b;d=b+a.i;c=e[d];if(0<d){e.splice(Math.max(a.i,d));for(var g=d;g<e.length;g++)e[g]=0}else for(f=Math.max(0,f),a.i=1,e.length=Math.max(1,d=b+1),e[0]=0,g=1;g<d;g++)e[g]=0;if(5<=c)if(0>d-1){for(c=0;c>d;c--)e.unshift(0),a.i++;e.unshift(1);a.i++}else e[d-1]++;
|
||||
for(;f<Math.max(0,b);f++)e.push(0);if(b=e.reduceRight(function(a,b,c,d){b+=a;d[c]=b%10;return Math.floor(b/10)},0))e.unshift(b),a.i++}function Ed(a,b,d,c,e){if(!G(a)&&!T(a)||isNaN(a))return"";var f=!isFinite(a),g=!1,h=Math.abs(a)+"",k="";if(f)k="\u221e";else{g=wg(h);xg(g,e,b.minFrac,b.maxFrac);k=g.d;h=g.i;e=g.e;f=[];for(g=k.reduce(function(a,b){return a&&!b},!0);0>h;)k.unshift(0),h++;0<h?f=k.splice(h,k.length):(f=k,k=[0]);h=[];for(k.length>=b.lgSize&&h.unshift(k.splice(-b.lgSize,k.length).join(""));k.length>
|
||||
b.gSize;)h.unshift(k.splice(-b.gSize,k.length).join(""));k.length&&h.unshift(k.join(""));k=h.join(d);f.length&&(k+=c+f.join(""));e&&(k+="e+"+e)}return 0>a&&!g?b.negPre+k+b.negSuf:b.posPre+k+b.posSuf}function Kb(a,b,d,c){var e="";if(0>a||c&&0>=a)c?a=-a+1:(a=-a,e="-");for(a=""+a;a.length<b;)a=mc+a;d&&(a=a.substr(a.length-b));return e+a}function ba(a,b,d,c,e){d=d||0;return function(f){f=f["get"+a]();if(0<d||f>-d)f+=d;0===f&&-12==d&&(f=12);return Kb(f,b,c,e)}}function kb(a,b,d){return function(c,e){var f=
|
||||
c["get"+a](),g=ub((d?"STANDALONE":"")+(b?"SHORT":"")+a);return e[g][f]}}function Hd(a){var b=(new Date(a,0,1)).getDay();return new Date(a,0,(4>=b?5:12)-b)}function Id(a){return function(b){var d=Hd(b.getFullYear());b=+new Date(b.getFullYear(),b.getMonth(),b.getDate()+(4-b.getDay()))-+d;b=1+Math.round(b/6048E5);return Kb(b,a)}}function nc(a,b){return 0>=a.getFullYear()?b.ERAS[0]:b.ERAS[1]}function Bd(a){function b(a){var b;if(b=a.match(d)){a=new Date(0);var f=0,g=0,h=b[8]?a.setUTCFullYear:a.setFullYear,
|
||||
k=b[8]?a.setUTCHours:a.setHours;b[9]&&(f=Z(b[9]+b[10]),g=Z(b[9]+b[11]));h.call(a,Z(b[1]),Z(b[2])-1,Z(b[3]));f=Z(b[4]||0)-f;g=Z(b[5]||0)-g;h=Z(b[6]||0);b=Math.round(1E3*parseFloat("0."+(b[7]||0)));k.call(a,f,g,h,b)}return a}var d=/^(\d{4})-?(\d\d)-?(\d\d)(?:T(\d\d)(?::?(\d\d)(?::?(\d\d)(?:\.(\d+))?)?)?(Z|([+-])(\d\d):?(\d\d))?)?$/;return function(c,d,f){var g="",h=[],k,l;d=d||"mediumDate";d=a.DATETIME_FORMATS[d]||d;G(c)&&(c=yg.test(c)?Z(c):b(c));T(c)&&(c=new Date(c));if(!da(c)||!isFinite(c.getTime()))return c;
|
||||
for(;d;)(l=zg.exec(d))?(h=$a(h,l,1),d=h.pop()):(h.push(d),d=null);var m=c.getTimezoneOffset();f&&(m=yc(f,m),c=Sb(c,f,!0));q(h,function(b){k=Ag[b];g+=k?k(c,a.DATETIME_FORMATS,m):"''"===b?"'":b.replace(/(^'|'$)/g,"").replace(/''/g,"'")});return g}}function rg(){return function(a,b){y(b)&&(b=2);return bb(a,b)}}function sg(){return function(a,b,d){b=Infinity===Math.abs(Number(b))?Number(b):Z(b);if(isNaN(b))return a;T(a)&&(a=a.toString());if(!ta(a))return a;d=!d||isNaN(d)?0:Z(d);d=0>d?Math.max(0,a.length+
|
||||
d):d;return 0<=b?oc(a,d,d+b):0===d?oc(a,b,a.length):oc(a,Math.max(0,d+b),d)}}function oc(a,b,d){return G(a)?a.slice(b,d):va.call(a,b,d)}function Dd(a){function b(b){return b.map(function(b){var c=1,d=Xa;if(z(b))d=b;else if(G(b)){if("+"==b.charAt(0)||"-"==b.charAt(0))c="-"==b.charAt(0)?-1:1,b=b.substring(1);if(""!==b&&(d=a(b),d.constant))var e=d(),d=function(a){return a[e]}}return{get:d,descending:c}})}function d(a){switch(typeof a){case "number":case "boolean":case "string":return!0;default:return!1}}
|
||||
function c(a,b){var c=0,d=a.type,k=b.type;if(d===k){var k=a.value,l=b.value;"string"===d?(k=k.toLowerCase(),l=l.toLowerCase()):"object"===d&&(D(k)&&(k=a.index),D(l)&&(l=b.index));k!==l&&(c=k<l?-1:1)}else c=d<k?-1:1;return c}return function(a,f,g,h){if(null==a)return a;if(!ta(a))throw N("orderBy")("notarray",a);L(f)||(f=[f]);0===f.length&&(f=["+"]);var k=b(f),l=g?-1:1,m=z(h)?h:c;a=Array.prototype.map.call(a,function(a,b){return{value:a,tieBreaker:{value:b,type:"number",index:b},predicateValues:k.map(function(c){var e=
|
||||
c.get(a);c=typeof e;if(null===e)c="string",e="null";else if("object"===c)a:{if(z(e.valueOf)&&(e=e.valueOf(),d(e)))break a;vc(e)&&(e=e.toString(),d(e))}return{value:e,type:c,index:b}})}});a.sort(function(a,b){for(var c=0,d=k.length;c<d;c++){var e=m(a.predicateValues[c],b.predicateValues[c]);if(e)return e*k[c].descending*l}return m(a.tieBreaker,b.tieBreaker)*l});return a=a.map(function(a){return a.value})}}function Ta(a){z(a)&&(a={link:a});a.restrict=a.restrict||"AC";return ha(a)}function Jd(a,b,d,
|
||||
c,e){var f=this,g=[];f.$error={};f.$$success={};f.$pending=void 0;f.$name=e(b.name||b.ngForm||"")(d);f.$dirty=!1;f.$pristine=!0;f.$valid=!0;f.$invalid=!1;f.$submitted=!1;f.$$parentForm=Lb;f.$rollbackViewValue=function(){q(g,function(a){a.$rollbackViewValue()})};f.$commitViewValue=function(){q(g,function(a){a.$commitViewValue()})};f.$addControl=function(a){Qa(a.$name,"input");g.push(a);a.$name&&(f[a.$name]=a);a.$$parentForm=f};f.$$renameControl=function(a,b){var c=a.$name;f[c]===a&&delete f[c];f[b]=
|
||||
a;a.$name=b};f.$removeControl=function(a){a.$name&&f[a.$name]===a&&delete f[a.$name];q(f.$pending,function(b,c){f.$setValidity(c,null,a)});q(f.$error,function(b,c){f.$setValidity(c,null,a)});q(f.$$success,function(b,c){f.$setValidity(c,null,a)});Za(g,a);a.$$parentForm=Lb};Kd({ctrl:this,$element:a,set:function(a,b,c){var d=a[b];d?-1===d.indexOf(c)&&d.push(c):a[b]=[c]},unset:function(a,b,c){var d=a[b];d&&(Za(d,c),0===d.length&&delete a[b])},$animate:c});f.$setDirty=function(){c.removeClass(a,Ua);c.addClass(a,
|
||||
Mb);f.$dirty=!0;f.$pristine=!1;f.$$parentForm.$setDirty()};f.$setPristine=function(){c.setClass(a,Ua,Mb+" ng-submitted");f.$dirty=!1;f.$pristine=!0;f.$submitted=!1;q(g,function(a){a.$setPristine()})};f.$setUntouched=function(){q(g,function(a){a.$setUntouched()})};f.$setSubmitted=function(){c.addClass(a,"ng-submitted");f.$submitted=!0;f.$$parentForm.$setSubmitted()}}function pc(a){a.$formatters.push(function(b){return a.$isEmpty(b)?b:b.toString()})}function lb(a,b,d,c,e,f){var g=Q(b[0].type);if(!e.android){var h=
|
||||
!1;b.on("compositionstart",function(){h=!0});b.on("compositionend",function(){h=!1;l()})}var k,l=function(a){k&&(f.defer.cancel(k),k=null);if(!h){var e=b.val();a=a&&a.type;"password"===g||d.ngTrim&&"false"===d.ngTrim||(e=W(e));(c.$viewValue!==e||""===e&&c.$$hasNativeValidators)&&c.$setViewValue(e,a)}};if(e.hasEvent("input"))b.on("input",l);else{var m=function(a,b,c){k||(k=f.defer(function(){k=null;b&&b.value===c||l(a)}))};b.on("keydown",function(a){var b=a.keyCode;91===b||15<b&&19>b||37<=b&&40>=b||
|
||||
m(a,this,this.value)});if(e.hasEvent("paste"))b.on("paste cut",m)}b.on("change",l);if(Ld[g]&&c.$$hasNativeValidators&&g===d.type)b.on("keydown wheel mousedown",function(a){if(!k){var b=this.validity,c=b.badInput,d=b.typeMismatch;k=f.defer(function(){k=null;b.badInput===c&&b.typeMismatch===d||l(a)})}});c.$render=function(){var a=c.$isEmpty(c.$viewValue)?"":c.$viewValue;b.val()!==a&&b.val(a)}}function Nb(a,b){return function(d,c){var e,f;if(da(d))return d;if(G(d)){'"'==d.charAt(0)&&'"'==d.charAt(d.length-
|
||||
1)&&(d=d.substring(1,d.length-1));if(Bg.test(d))return new Date(d);a.lastIndex=0;if(e=a.exec(d))return e.shift(),f=c?{yyyy:c.getFullYear(),MM:c.getMonth()+1,dd:c.getDate(),HH:c.getHours(),mm:c.getMinutes(),ss:c.getSeconds(),sss:c.getMilliseconds()/1E3}:{yyyy:1970,MM:1,dd:1,HH:0,mm:0,ss:0,sss:0},q(e,function(a,c){c<b.length&&(f[b[c]]=+a)}),new Date(f.yyyy,f.MM-1,f.dd,f.HH,f.mm,f.ss||0,1E3*f.sss||0)}return NaN}}function mb(a,b,d,c){return function(e,f,g,h,k,l,m){function n(a){return a&&!(a.getTime&&
|
||||
a.getTime()!==a.getTime())}function p(a){return w(a)&&!da(a)?d(a)||void 0:a}Md(e,f,g,h);lb(e,f,g,h,k,l);var u=h&&h.$options&&h.$options.timezone,q;h.$$parserName=a;h.$parsers.push(function(a){if(h.$isEmpty(a))return null;if(b.test(a))return a=d(a,q),u&&(a=Sb(a,u)),a});h.$formatters.push(function(a){if(a&&!da(a))throw nb("datefmt",a);if(n(a))return(q=a)&&u&&(q=Sb(q,u,!0)),m("date")(a,c,u);q=null;return""});if(w(g.min)||g.ngMin){var s;h.$validators.min=function(a){return!n(a)||y(s)||d(a)>=s};g.$observe("min",
|
||||
function(a){s=p(a);h.$validate()})}if(w(g.max)||g.ngMax){var r;h.$validators.max=function(a){return!n(a)||y(r)||d(a)<=r};g.$observe("max",function(a){r=p(a);h.$validate()})}}}function Md(a,b,d,c){(c.$$hasNativeValidators=D(b[0].validity))&&c.$parsers.push(function(a){var c=b.prop("validity")||{};return c.badInput||c.typeMismatch?void 0:a})}function Nd(a,b,d,c,e){if(w(c)){a=a(c);if(!a.constant)throw nb("constexpr",d,c);return a(b)}return e}function qc(a,b){a="ngClass"+a;return["$animate",function(d){function c(a,
|
||||
b){var c=[],d=0;a:for(;d<a.length;d++){for(var e=a[d],m=0;m<b.length;m++)if(e==b[m])continue a;c.push(e)}return c}function e(a){var b=[];return L(a)?(q(a,function(a){b=b.concat(e(a))}),b):G(a)?a.split(" "):D(a)?(q(a,function(a,c){a&&(b=b.concat(c.split(" ")))}),b):a}return{restrict:"AC",link:function(f,g,h){function k(a){a=l(a,1);h.$addClass(a)}function l(a,b){var c=g.data("$classCounts")||U(),d=[];q(a,function(a){if(0<b||c[a])c[a]=(c[a]||0)+b,c[a]===+(0<b)&&d.push(a)});g.data("$classCounts",c);return d.join(" ")}
|
||||
function m(a,b){var e=c(b,a),f=c(a,b),e=l(e,1),f=l(f,-1);e&&e.length&&d.addClass(g,e);f&&f.length&&d.removeClass(g,f)}function n(a){if(!0===b||(f.$index&1)===b){var c=e(a||[]);if(!p)k(c);else if(!na(a,p)){var d=e(p);m(d,c)}}p=L(a)?a.map(function(a){return ia(a)}):ia(a)}var p;f.$watch(h[a],n,!0);h.$observe("class",function(b){n(f.$eval(h[a]))});"ngClass"!==a&&f.$watch("$index",function(c,d){var g=c&1;if(g!==(d&1)){var m=e(f.$eval(h[a]));g===b?k(m):(g=l(m,-1),h.$removeClass(g))}})}}}]}function Kd(a){function b(a,
|
||||
b){b&&!f[a]?(k.addClass(e,a),f[a]=!0):!b&&f[a]&&(k.removeClass(e,a),f[a]=!1)}function d(a,c){a=a?"-"+Cc(a,"-"):"";b(ob+a,!0===c);b(Od+a,!1===c)}var c=a.ctrl,e=a.$element,f={},g=a.set,h=a.unset,k=a.$animate;f[Od]=!(f[ob]=e.hasClass(ob));c.$setValidity=function(a,e,f){y(e)?(c.$pending||(c.$pending={}),g(c.$pending,a,f)):(c.$pending&&h(c.$pending,a,f),Pd(c.$pending)&&(c.$pending=void 0));Ga(e)?e?(h(c.$error,a,f),g(c.$$success,a,f)):(g(c.$error,a,f),h(c.$$success,a,f)):(h(c.$error,a,f),h(c.$$success,
|
||||
a,f));c.$pending?(b(Qd,!0),c.$valid=c.$invalid=void 0,d("",null)):(b(Qd,!1),c.$valid=Pd(c.$error),c.$invalid=!c.$valid,d("",c.$valid));e=c.$pending&&c.$pending[a]?void 0:c.$error[a]?!1:c.$$success[a]?!0:null;d(a,e);c.$$parentForm.$setValidity(a,e,c)}}function Pd(a){if(a)for(var b in a)if(a.hasOwnProperty(b))return!1;return!0}var Cg=/^\/(.+)\/([a-z]*)$/,ua=Object.prototype.hasOwnProperty,Q=function(a){return G(a)?a.toLowerCase():a},ub=function(a){return G(a)?a.toUpperCase():a},Ea,F,qa,va=[].slice,
|
||||
bg=[].splice,Dg=[].push,ma=Object.prototype.toString,wc=Object.getPrototypeOf,xa=N("ng"),ca=C.angular||(C.angular={}),Ub,pb=0;Ea=C.document.documentMode;A.$inject=[];Xa.$inject=[];var L=Array.isArray,ae=/^\[object (?:Uint8|Uint8Clamped|Uint16|Uint32|Int8|Int16|Int32|Float32|Float64)Array\]$/,W=function(a){return G(a)?a.trim():a},wd=function(a){return a.replace(/([-()\[\]{}+?*.$\^|,:#<!\\])/g,"\\$1").replace(/\x08/g,"\\x08")},Ba=function(){if(!w(Ba.rules)){var a=C.document.querySelector("[ng-csp]")||
|
||||
C.document.querySelector("[data-ng-csp]");if(a){var b=a.getAttribute("ng-csp")||a.getAttribute("data-ng-csp");Ba.rules={noUnsafeEval:!b||-1!==b.indexOf("no-unsafe-eval"),noInlineStyle:!b||-1!==b.indexOf("no-inline-style")}}else{a=Ba;try{new Function(""),b=!1}catch(d){b=!0}a.rules={noUnsafeEval:b,noInlineStyle:!1}}}return Ba.rules},rb=function(){if(w(rb.name_))return rb.name_;var a,b,d=Na.length,c,e;for(b=0;b<d;++b)if(c=Na[b],a=C.document.querySelector("["+c.replace(":","\\:")+"jq]")){e=a.getAttribute(c+
|
||||
"jq");break}return rb.name_=e},de=/:/g,Na=["ng-","data-ng-","ng:","x-ng-"],ie=/[A-Z]/g,Dc=!1,Ma=3,me={full:"1.5.8",major:1,minor:5,dot:8,codeName:"arbitrary-fallbacks"};O.expando="ng339";var fb=O.cache={},Pf=1;O._data=function(a){return this.cache[a[this.expando]]||{}};var Kf=/([\:\-\_]+(.))/g,Lf=/^moz([A-Z])/,yb={mouseleave:"mouseout",mouseenter:"mouseover"},Wb=N("jqLite"),Of=/^<([\w-]+)\s*\/?>(?:<\/\1>|)$/,Vb=/<|&#?\w+;/,Mf=/<([\w:-]+)/,Nf=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:-]+)[^>]*)\/>/gi,
|
||||
ja={option:[1,'<select multiple="multiple">',"</select>"],thead:[1,"<table>","</table>"],col:[2,"<table><colgroup>","</colgroup></table>"],tr:[2,"<table><tbody>","</tbody></table>"],td:[3,"<table><tbody><tr>","</tr></tbody></table>"],_default:[0,"",""]};ja.optgroup=ja.option;ja.tbody=ja.tfoot=ja.colgroup=ja.caption=ja.thead;ja.th=ja.td;var Uf=C.Node.prototype.contains||function(a){return!!(this.compareDocumentPosition(a)&16)},Oa=O.prototype={ready:function(a){function b(){d||(d=!0,a())}var d=!1;"complete"===
|
||||
C.document.readyState?C.setTimeout(b):(this.on("DOMContentLoaded",b),O(C).on("load",b))},toString:function(){var a=[];q(this,function(b){a.push(""+b)});return"["+a.join(", ")+"]"},eq:function(a){return 0<=a?F(this[a]):F(this[this.length+a])},length:0,push:Dg,sort:[].sort,splice:[].splice},Eb={};q("multiple selected checked disabled readOnly required open".split(" "),function(a){Eb[Q(a)]=a});var Vc={};q("input select option textarea button form details".split(" "),function(a){Vc[a]=!0});var bd={ngMinlength:"minlength",
|
||||
ngMaxlength:"maxlength",ngMin:"min",ngMax:"max",ngPattern:"pattern"};q({data:Yb,removeData:eb,hasData:function(a){for(var b in fb[a.ng339])return!0;return!1},cleanData:function(a){for(var b=0,d=a.length;b<d;b++)eb(a[b])}},function(a,b){O[b]=a});q({data:Yb,inheritedData:Cb,scope:function(a){return F.data(a,"$scope")||Cb(a.parentNode||a,["$isolateScope","$scope"])},isolateScope:function(a){return F.data(a,"$isolateScope")||F.data(a,"$isolateScopeNoTemplate")},controller:Sc,injector:function(a){return Cb(a,
|
||||
"$injector")},removeAttr:function(a,b){a.removeAttribute(b)},hasClass:zb,css:function(a,b,d){b=db(b);if(w(d))a.style[b]=d;else return a.style[b]},attr:function(a,b,d){var c=a.nodeType;if(c!==Ma&&2!==c&&8!==c)if(c=Q(b),Eb[c])if(w(d))d?(a[b]=!0,a.setAttribute(b,c)):(a[b]=!1,a.removeAttribute(c));else return a[b]||(a.attributes.getNamedItem(b)||A).specified?c:void 0;else if(w(d))a.setAttribute(b,d);else if(a.getAttribute)return a=a.getAttribute(b,2),null===a?void 0:a},prop:function(a,b,d){if(w(d))a[b]=
|
||||
d;else return a[b]},text:function(){function a(a,d){if(y(d)){var c=a.nodeType;return 1===c||c===Ma?a.textContent:""}a.textContent=d}a.$dv="";return a}(),val:function(a,b){if(y(b)){if(a.multiple&&"select"===wa(a)){var d=[];q(a.options,function(a){a.selected&&d.push(a.value||a.text)});return 0===d.length?null:d}return a.value}a.value=b},html:function(a,b){if(y(b))return a.innerHTML;wb(a,!0);a.innerHTML=b},empty:Tc},function(a,b){O.prototype[b]=function(b,c){var e,f,g=this.length;if(a!==Tc&&y(2==a.length&&
|
||||
a!==zb&&a!==Sc?b:c)){if(D(b)){for(e=0;e<g;e++)if(a===Yb)a(this[e],b);else for(f in b)a(this[e],f,b[f]);return this}e=a.$dv;g=y(e)?Math.min(g,1):g;for(f=0;f<g;f++){var h=a(this[f],b,c);e=e?e+h:h}return e}for(e=0;e<g;e++)a(this[e],b,c);return this}});q({removeData:eb,on:function(a,b,d,c){if(w(c))throw Wb("onargs");if(Nc(a)){c=xb(a,!0);var e=c.events,f=c.handle;f||(f=c.handle=Rf(a,e));c=0<=b.indexOf(" ")?b.split(" "):[b];for(var g=c.length,h=function(b,c,g){var h=e[b];h||(h=e[b]=[],h.specialHandlerWrapper=
|
||||
c,"$destroy"===b||g||a.addEventListener(b,f,!1));h.push(d)};g--;)b=c[g],yb[b]?(h(yb[b],Tf),h(b,void 0,!0)):h(b)}},off:Rc,one:function(a,b,d){a=F(a);a.on(b,function e(){a.off(b,d);a.off(b,e)});a.on(b,d)},replaceWith:function(a,b){var d,c=a.parentNode;wb(a);q(new O(b),function(b){d?c.insertBefore(b,d.nextSibling):c.replaceChild(b,a);d=b})},children:function(a){var b=[];q(a.childNodes,function(a){1===a.nodeType&&b.push(a)});return b},contents:function(a){return a.contentDocument||a.childNodes||[]},append:function(a,
|
||||
b){var d=a.nodeType;if(1===d||11===d){b=new O(b);for(var d=0,c=b.length;d<c;d++)a.appendChild(b[d])}},prepend:function(a,b){if(1===a.nodeType){var d=a.firstChild;q(new O(b),function(b){a.insertBefore(b,d)})}},wrap:function(a,b){Pc(a,F(b).eq(0).clone()[0])},remove:Db,detach:function(a){Db(a,!0)},after:function(a,b){var d=a,c=a.parentNode;b=new O(b);for(var e=0,f=b.length;e<f;e++){var g=b[e];c.insertBefore(g,d.nextSibling);d=g}},addClass:Bb,removeClass:Ab,toggleClass:function(a,b,d){b&&q(b.split(" "),
|
||||
function(b){var e=d;y(e)&&(e=!zb(a,b));(e?Bb:Ab)(a,b)})},parent:function(a){return(a=a.parentNode)&&11!==a.nodeType?a:null},next:function(a){return a.nextElementSibling},find:function(a,b){return a.getElementsByTagName?a.getElementsByTagName(b):[]},clone:Xb,triggerHandler:function(a,b,d){var c,e,f=b.type||b,g=xb(a);if(g=(g=g&&g.events)&&g[f])c={preventDefault:function(){this.defaultPrevented=!0},isDefaultPrevented:function(){return!0===this.defaultPrevented},stopImmediatePropagation:function(){this.immediatePropagationStopped=
|
||||
!0},isImmediatePropagationStopped:function(){return!0===this.immediatePropagationStopped},stopPropagation:A,type:f,target:a},b.type&&(c=S(c,b)),b=ia(g),e=d?[c].concat(d):[c],q(b,function(b){c.isImmediatePropagationStopped()||b.apply(a,e)})}},function(a,b){O.prototype[b]=function(b,c,e){for(var f,g=0,h=this.length;g<h;g++)y(f)?(f=a(this[g],b,c,e),w(f)&&(f=F(f))):Qc(f,a(this[g],b,c,e));return w(f)?f:this};O.prototype.bind=O.prototype.on;O.prototype.unbind=O.prototype.off});Ra.prototype={put:function(a,
|
||||
b){this[Ca(a,this.nextUid)]=b},get:function(a){return this[Ca(a,this.nextUid)]},remove:function(a){var b=this[a=Ca(a,this.nextUid)];delete this[a];return b}};var If=[function(){this.$get=[function(){return Ra}]}],Wf=/^([^\(]+?)=>/,Xf=/^[^\(]*\(\s*([^\)]*)\)/m,Eg=/,/,Fg=/^\s*(_?)(\S+?)\1\s*$/,Vf=/((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg,Ha=N("$injector");cb.$$annotate=function(a,b,d){var c;if("function"===typeof a){if(!(c=a.$inject)){c=[];if(a.length){if(b)throw G(d)&&d||(d=a.name||Yf(a)),Ha("strictdi",d);
|
||||
b=Wc(a);q(b[1].split(Eg),function(a){a.replace(Fg,function(a,b,d){c.push(d)})})}a.$inject=c}}else L(a)?(b=a.length-1,Pa(a[b],"fn"),c=a.slice(0,b)):Pa(a,"fn",!0);return c};var Rd=N("$animate"),$e=function(){this.$get=A},af=function(){var a=new Ra,b=[];this.$get=["$$AnimateRunner","$rootScope",function(d,c){function e(a,b,c){var d=!1;b&&(b=G(b)?b.split(" "):L(b)?b:[],q(b,function(b){b&&(d=!0,a[b]=c)}));return d}function f(){q(b,function(b){var c=a.get(b);if(c){var d=Zf(b.attr("class")),e="",f="";q(c,
|
||||
function(a,b){a!==!!d[b]&&(a?e+=(e.length?" ":"")+b:f+=(f.length?" ":"")+b)});q(b,function(a){e&&Bb(a,e);f&&Ab(a,f)});a.remove(b)}});b.length=0}return{enabled:A,on:A,off:A,pin:A,push:function(g,h,k,l){l&&l();k=k||{};k.from&&g.css(k.from);k.to&&g.css(k.to);if(k.addClass||k.removeClass)if(h=k.addClass,l=k.removeClass,k=a.get(g)||{},h=e(k,h,!0),l=e(k,l,!1),h||l)a.put(g,k),b.push(g),1===b.length&&c.$$postDigest(f);g=new d;g.complete();return g}}}]},Ye=["$provide",function(a){var b=this;this.$$registeredAnimations=
|
||||
Object.create(null);this.register=function(d,c){if(d&&"."!==d.charAt(0))throw Rd("notcsel",d);var e=d+"-animation";b.$$registeredAnimations[d.substr(1)]=e;a.factory(e,c)};this.classNameFilter=function(a){if(1===arguments.length&&(this.$$classNameFilter=a instanceof RegExp?a:null)&&/(\s+|\/)ng-animate(\s+|\/)/.test(this.$$classNameFilter.toString()))throw Rd("nongcls","ng-animate");return this.$$classNameFilter};this.$get=["$$animateQueue",function(a){function b(a,c,d){if(d){var h;a:{for(h=0;h<d.length;h++){var k=
|
||||
d[h];if(1===k.nodeType){h=k;break a}}h=void 0}!h||h.parentNode||h.previousElementSibling||(d=null)}d?d.after(a):c.prepend(a)}return{on:a.on,off:a.off,pin:a.pin,enabled:a.enabled,cancel:function(a){a.end&&a.end()},enter:function(e,f,g,h){f=f&&F(f);g=g&&F(g);f=f||g.parent();b(e,f,g);return a.push(e,"enter",Ia(h))},move:function(e,f,g,h){f=f&&F(f);g=g&&F(g);f=f||g.parent();b(e,f,g);return a.push(e,"move",Ia(h))},leave:function(b,c){return a.push(b,"leave",Ia(c),function(){b.remove()})},addClass:function(b,
|
||||
c,g){g=Ia(g);g.addClass=gb(g.addclass,c);return a.push(b,"addClass",g)},removeClass:function(b,c,g){g=Ia(g);g.removeClass=gb(g.removeClass,c);return a.push(b,"removeClass",g)},setClass:function(b,c,g,h){h=Ia(h);h.addClass=gb(h.addClass,c);h.removeClass=gb(h.removeClass,g);return a.push(b,"setClass",h)},animate:function(b,c,g,h,k){k=Ia(k);k.from=k.from?S(k.from,c):c;k.to=k.to?S(k.to,g):g;k.tempClasses=gb(k.tempClasses,h||"ng-inline-animate");return a.push(b,"animate",k)}}}]}],cf=function(){this.$get=
|
||||
["$$rAF",function(a){function b(b){d.push(b);1<d.length||a(function(){for(var a=0;a<d.length;a++)d[a]();d=[]})}var d=[];return function(){var a=!1;b(function(){a=!0});return function(d){a?d():b(d)}}}]},bf=function(){this.$get=["$q","$sniffer","$$animateAsyncRun","$document","$timeout",function(a,b,d,c,e){function f(a){this.setHost(a);var b=d();this._doneCallbacks=[];this._tick=function(a){var d=c[0];d&&d.hidden?e(a,0,!1):b(a)};this._state=0}f.chain=function(a,b){function c(){if(d===a.length)b(!0);
|
||||
else a[d](function(a){!1===a?b(!1):(d++,c())})}var d=0;c()};f.all=function(a,b){function c(f){e=e&&f;++d===a.length&&b(e)}var d=0,e=!0;q(a,function(a){a.done(c)})};f.prototype={setHost:function(a){this.host=a||{}},done:function(a){2===this._state?a():this._doneCallbacks.push(a)},progress:A,getPromise:function(){if(!this.promise){var b=this;this.promise=a(function(a,c){b.done(function(b){!1===b?c():a()})})}return this.promise},then:function(a,b){return this.getPromise().then(a,b)},"catch":function(a){return this.getPromise()["catch"](a)},
|
||||
"finally":function(a){return this.getPromise()["finally"](a)},pause:function(){this.host.pause&&this.host.pause()},resume:function(){this.host.resume&&this.host.resume()},end:function(){this.host.end&&this.host.end();this._resolve(!0)},cancel:function(){this.host.cancel&&this.host.cancel();this._resolve(!1)},complete:function(a){var b=this;0===b._state&&(b._state=1,b._tick(function(){b._resolve(a)}))},_resolve:function(a){2!==this._state&&(q(this._doneCallbacks,function(b){b(a)}),this._doneCallbacks.length=
|
||||
0,this._state=2)}};return f}]},Ze=function(){this.$get=["$$rAF","$q","$$AnimateRunner",function(a,b,d){return function(b,e){function f(){a(function(){g.addClass&&(b.addClass(g.addClass),g.addClass=null);g.removeClass&&(b.removeClass(g.removeClass),g.removeClass=null);g.to&&(b.css(g.to),g.to=null);h||k.complete();h=!0});return k}var g=e||{};g.$$prepared||(g=pa(g));g.cleanupStyles&&(g.from=g.to=null);g.from&&(b.css(g.from),g.from=null);var h,k=new d;return{start:f,end:f}}}]},ga=N("$compile"),bc=new function(){};
|
||||
Fc.$inject=["$provide","$$sanitizeUriProvider"];Fb.prototype.isFirstChange=function(){return this.previousValue===bc};var Yc=/^((?:x|data)[\:\-_])/i,cg=N("$controller"),cd=/^(\S+)(\s+as\s+([\w$]+))?$/,jf=function(){this.$get=["$document",function(a){return function(b){b?!b.nodeType&&b instanceof F&&(b=b[0]):b=a[0].body;return b.offsetWidth+1}}]},dd="application/json",ec={"Content-Type":dd+";charset=utf-8"},eg=/^\[|^\{(?!\{)/,fg={"[":/]$/,"{":/}$/},dg=/^\)\]\}',?\n/,Gg=N("$http"),hd=function(a){return function(){throw Gg("legacy",
|
||||
a);}},Ka=ca.$interpolateMinErr=N("$interpolate");Ka.throwNoconcat=function(a){throw Ka("noconcat",a);};Ka.interr=function(a,b){return Ka("interr",a,b.toString())};var rf=function(){this.$get=["$window",function(a){function b(a){var b=function(a){b.data=a;b.called=!0};b.id=a;return b}var d=a.angular.callbacks,c={};return{createCallback:function(a){a="_"+(d.$$counter++).toString(36);var f="angular.callbacks."+a,g=b(a);c[f]=d[a]=g;return f},wasCalled:function(a){return c[a].called},getResponse:function(a){return c[a].data},
|
||||
removeCallback:function(a){delete d[c[a].id];delete c[a]}}}]},Hg=/^([^\?#]*)(\?([^#]*))?(#(.*))?$/,hg={http:80,https:443,ftp:21},Gb=N("$location"),Ig={$$absUrl:"",$$html5:!1,$$replace:!1,absUrl:Hb("$$absUrl"),url:function(a){if(y(a))return this.$$url;var b=Hg.exec(a);(b[1]||""===a)&&this.path(decodeURIComponent(b[1]));(b[2]||b[1]||""===a)&&this.search(b[3]||"");this.hash(b[5]||"");return this},protocol:Hb("$$protocol"),host:Hb("$$host"),port:Hb("$$port"),path:md("$$path",function(a){a=null!==a?a.toString():
|
||||
"";return"/"==a.charAt(0)?a:"/"+a}),search:function(a,b){switch(arguments.length){case 0:return this.$$search;case 1:if(G(a)||T(a))a=a.toString(),this.$$search=Ac(a);else if(D(a))a=pa(a,{}),q(a,function(b,c){null==b&&delete a[c]}),this.$$search=a;else throw Gb("isrcharg");break;default:y(b)||null===b?delete this.$$search[a]:this.$$search[a]=b}this.$$compose();return this},hash:md("$$hash",function(a){return null!==a?a.toString():""}),replace:function(){this.$$replace=!0;return this}};q([ld,hc,gc],
|
||||
function(a){a.prototype=Object.create(Ig);a.prototype.state=function(b){if(!arguments.length)return this.$$state;if(a!==gc||!this.$$html5)throw Gb("nostate");this.$$state=y(b)?null:b;return this}});var X=N("$parse"),jg=Function.prototype.call,kg=Function.prototype.apply,lg=Function.prototype.bind,Ob=U();q("+ - * / % === !== == != < > <= >= && || ! = |".split(" "),function(a){Ob[a]=!0});var Jg={n:"\n",f:"\f",r:"\r",t:"\t",v:"\v","'":"'",'"':'"'},jc=function(a){this.options=a};jc.prototype={constructor:jc,
|
||||
lex:function(a){this.text=a;this.index=0;for(this.tokens=[];this.index<this.text.length;)if(a=this.text.charAt(this.index),'"'===a||"'"===a)this.readString(a);else if(this.isNumber(a)||"."===a&&this.isNumber(this.peek()))this.readNumber();else if(this.isIdentifierStart(this.peekMultichar()))this.readIdent();else if(this.is(a,"(){}[].,;:?"))this.tokens.push({index:this.index,text:a}),this.index++;else if(this.isWhitespace(a))this.index++;else{var b=a+this.peek(),d=b+this.peek(2),c=Ob[b],e=Ob[d];Ob[a]||
|
||||
c||e?(a=e?d:c?b:a,this.tokens.push({index:this.index,text:a,operator:!0}),this.index+=a.length):this.throwError("Unexpected next character ",this.index,this.index+1)}return this.tokens},is:function(a,b){return-1!==b.indexOf(a)},peek:function(a){a=a||1;return this.index+a<this.text.length?this.text.charAt(this.index+a):!1},isNumber:function(a){return"0"<=a&&"9">=a&&"string"===typeof a},isWhitespace:function(a){return" "===a||"\r"===a||"\t"===a||"\n"===a||"\v"===a||"\u00a0"===a},isIdentifierStart:function(a){return this.options.isIdentifierStart?
|
||||
this.options.isIdentifierStart(a,this.codePointAt(a)):this.isValidIdentifierStart(a)},isValidIdentifierStart:function(a){return"a"<=a&&"z">=a||"A"<=a&&"Z">=a||"_"===a||"$"===a},isIdentifierContinue:function(a){return this.options.isIdentifierContinue?this.options.isIdentifierContinue(a,this.codePointAt(a)):this.isValidIdentifierContinue(a)},isValidIdentifierContinue:function(a,b){return this.isValidIdentifierStart(a,b)||this.isNumber(a)},codePointAt:function(a){return 1===a.length?a.charCodeAt(0):
|
||||
(a.charCodeAt(0)<<10)+a.charCodeAt(1)-56613888},peekMultichar:function(){var a=this.text.charAt(this.index),b=this.peek();if(!b)return a;var d=a.charCodeAt(0),c=b.charCodeAt(0);return 55296<=d&&56319>=d&&56320<=c&&57343>=c?a+b:a},isExpOperator:function(a){return"-"===a||"+"===a||this.isNumber(a)},throwError:function(a,b,d){d=d||this.index;b=w(b)?"s "+b+"-"+this.index+" ["+this.text.substring(b,d)+"]":" "+d;throw X("lexerr",a,b,this.text);},readNumber:function(){for(var a="",b=this.index;this.index<
|
||||
this.text.length;){var d=Q(this.text.charAt(this.index));if("."==d||this.isNumber(d))a+=d;else{var c=this.peek();if("e"==d&&this.isExpOperator(c))a+=d;else if(this.isExpOperator(d)&&c&&this.isNumber(c)&&"e"==a.charAt(a.length-1))a+=d;else if(!this.isExpOperator(d)||c&&this.isNumber(c)||"e"!=a.charAt(a.length-1))break;else this.throwError("Invalid exponent")}this.index++}this.tokens.push({index:b,text:a,constant:!0,value:Number(a)})},readIdent:function(){var a=this.index;for(this.index+=this.peekMultichar().length;this.index<
|
||||
this.text.length;){var b=this.peekMultichar();if(!this.isIdentifierContinue(b))break;this.index+=b.length}this.tokens.push({index:a,text:this.text.slice(a,this.index),identifier:!0})},readString:function(a){var b=this.index;this.index++;for(var d="",c=a,e=!1;this.index<this.text.length;){var f=this.text.charAt(this.index),c=c+f;if(e)"u"===f?(e=this.text.substring(this.index+1,this.index+5),e.match(/[\da-f]{4}/i)||this.throwError("Invalid unicode escape [\\u"+e+"]"),this.index+=4,d+=String.fromCharCode(parseInt(e,
|
||||
16))):d+=Jg[f]||f,e=!1;else if("\\"===f)e=!0;else{if(f===a){this.index++;this.tokens.push({index:b,text:c,constant:!0,value:d});return}d+=f}this.index++}this.throwError("Unterminated quote",b)}};var s=function(a,b){this.lexer=a;this.options=b};s.Program="Program";s.ExpressionStatement="ExpressionStatement";s.AssignmentExpression="AssignmentExpression";s.ConditionalExpression="ConditionalExpression";s.LogicalExpression="LogicalExpression";s.BinaryExpression="BinaryExpression";s.UnaryExpression="UnaryExpression";
|
||||
s.CallExpression="CallExpression";s.MemberExpression="MemberExpression";s.Identifier="Identifier";s.Literal="Literal";s.ArrayExpression="ArrayExpression";s.Property="Property";s.ObjectExpression="ObjectExpression";s.ThisExpression="ThisExpression";s.LocalsExpression="LocalsExpression";s.NGValueParameter="NGValueParameter";s.prototype={ast:function(a){this.text=a;this.tokens=this.lexer.lex(a);a=this.program();0!==this.tokens.length&&this.throwError("is an unexpected token",this.tokens[0]);return a},
|
||||
program:function(){for(var a=[];;)if(0<this.tokens.length&&!this.peek("}",")",";","]")&&a.push(this.expressionStatement()),!this.expect(";"))return{type:s.Program,body:a}},expressionStatement:function(){return{type:s.ExpressionStatement,expression:this.filterChain()}},filterChain:function(){for(var a=this.expression();this.expect("|");)a=this.filter(a);return a},expression:function(){return this.assignment()},assignment:function(){var a=this.ternary();this.expect("=")&&(a={type:s.AssignmentExpression,
|
||||
left:a,right:this.assignment(),operator:"="});return a},ternary:function(){var a=this.logicalOR(),b,d;return this.expect("?")&&(b=this.expression(),this.consume(":"))?(d=this.expression(),{type:s.ConditionalExpression,test:a,alternate:b,consequent:d}):a},logicalOR:function(){for(var a=this.logicalAND();this.expect("||");)a={type:s.LogicalExpression,operator:"||",left:a,right:this.logicalAND()};return a},logicalAND:function(){for(var a=this.equality();this.expect("&&");)a={type:s.LogicalExpression,
|
||||
operator:"&&",left:a,right:this.equality()};return a},equality:function(){for(var a=this.relational(),b;b=this.expect("==","!=","===","!==");)a={type:s.BinaryExpression,operator:b.text,left:a,right:this.relational()};return a},relational:function(){for(var a=this.additive(),b;b=this.expect("<",">","<=",">=");)a={type:s.BinaryExpression,operator:b.text,left:a,right:this.additive()};return a},additive:function(){for(var a=this.multiplicative(),b;b=this.expect("+","-");)a={type:s.BinaryExpression,operator:b.text,
|
||||
left:a,right:this.multiplicative()};return a},multiplicative:function(){for(var a=this.unary(),b;b=this.expect("*","/","%");)a={type:s.BinaryExpression,operator:b.text,left:a,right:this.unary()};return a},unary:function(){var a;return(a=this.expect("+","-","!"))?{type:s.UnaryExpression,operator:a.text,prefix:!0,argument:this.unary()}:this.primary()},primary:function(){var a;this.expect("(")?(a=this.filterChain(),this.consume(")")):this.expect("[")?a=this.arrayDeclaration():this.expect("{")?a=this.object():
|
||||
this.selfReferential.hasOwnProperty(this.peek().text)?a=pa(this.selfReferential[this.consume().text]):this.options.literals.hasOwnProperty(this.peek().text)?a={type:s.Literal,value:this.options.literals[this.consume().text]}:this.peek().identifier?a=this.identifier():this.peek().constant?a=this.constant():this.throwError("not a primary expression",this.peek());for(var b;b=this.expect("(","[",".");)"("===b.text?(a={type:s.CallExpression,callee:a,arguments:this.parseArguments()},this.consume(")")):
|
||||
"["===b.text?(a={type:s.MemberExpression,object:a,property:this.expression(),computed:!0},this.consume("]")):"."===b.text?a={type:s.MemberExpression,object:a,property:this.identifier(),computed:!1}:this.throwError("IMPOSSIBLE");return a},filter:function(a){a=[a];for(var b={type:s.CallExpression,callee:this.identifier(),arguments:a,filter:!0};this.expect(":");)a.push(this.expression());return b},parseArguments:function(){var a=[];if(")"!==this.peekToken().text){do a.push(this.filterChain());while(this.expect(","))
|
||||
}return a},identifier:function(){var a=this.consume();a.identifier||this.throwError("is not a valid identifier",a);return{type:s.Identifier,name:a.text}},constant:function(){return{type:s.Literal,value:this.consume().value}},arrayDeclaration:function(){var a=[];if("]"!==this.peekToken().text){do{if(this.peek("]"))break;a.push(this.expression())}while(this.expect(","))}this.consume("]");return{type:s.ArrayExpression,elements:a}},object:function(){var a=[],b;if("}"!==this.peekToken().text){do{if(this.peek("}"))break;
|
||||
b={type:s.Property,kind:"init"};this.peek().constant?(b.key=this.constant(),b.computed=!1,this.consume(":"),b.value=this.expression()):this.peek().identifier?(b.key=this.identifier(),b.computed=!1,this.peek(":")?(this.consume(":"),b.value=this.expression()):b.value=b.key):this.peek("[")?(this.consume("["),b.key=this.expression(),this.consume("]"),b.computed=!0,this.consume(":"),b.value=this.expression()):this.throwError("invalid key",this.peek());a.push(b)}while(this.expect(","))}this.consume("}");
|
||||
return{type:s.ObjectExpression,properties:a}},throwError:function(a,b){throw X("syntax",b.text,a,b.index+1,this.text,this.text.substring(b.index));},consume:function(a){if(0===this.tokens.length)throw X("ueoe",this.text);var b=this.expect(a);b||this.throwError("is unexpected, expecting ["+a+"]",this.peek());return b},peekToken:function(){if(0===this.tokens.length)throw X("ueoe",this.text);return this.tokens[0]},peek:function(a,b,d,c){return this.peekAhead(0,a,b,d,c)},peekAhead:function(a,b,d,c,e){if(this.tokens.length>
|
||||
a){a=this.tokens[a];var f=a.text;if(f===b||f===d||f===c||f===e||!(b||d||c||e))return a}return!1},expect:function(a,b,d,c){return(a=this.peek(a,b,d,c))?(this.tokens.shift(),a):!1},selfReferential:{"this":{type:s.ThisExpression},$locals:{type:s.LocalsExpression}}};td.prototype={compile:function(a,b){var d=this,c=this.astBuilder.ast(a);this.state={nextId:0,filters:{},expensiveChecks:b,fn:{vars:[],body:[],own:{}},assign:{vars:[],body:[],own:{}},inputs:[]};V(c,d.$filter);var e="",f;this.stage="assign";
|
||||
if(f=rd(c))this.state.computing="assign",e=this.nextId(),this.recurse(f,e),this.return_(e),e="fn.assign="+this.generateFunction("assign","s,v,l");f=pd(c.body);d.stage="inputs";q(f,function(a,b){var c="fn"+b;d.state[c]={vars:[],body:[],own:{}};d.state.computing=c;var e=d.nextId();d.recurse(a,e);d.return_(e);d.state.inputs.push(c);a.watchId=b});this.state.computing="fn";this.stage="main";this.recurse(c);e='"'+this.USE+" "+this.STRICT+'";\n'+this.filterPrefix()+"var fn="+this.generateFunction("fn","s,l,a,i")+
|
||||
e+this.watchFns()+"return fn;";e=(new Function("$filter","ensureSafeMemberName","ensureSafeObject","ensureSafeFunction","getStringValue","ensureSafeAssignContext","ifDefined","plus","text",e))(this.$filter,Sa,ra,nd,ig,Ib,mg,od,a);this.state=this.stage=void 0;e.literal=sd(c);e.constant=c.constant;return e},USE:"use",STRICT:"strict",watchFns:function(){var a=[],b=this.state.inputs,d=this;q(b,function(b){a.push("var "+b+"="+d.generateFunction(b,"s"))});b.length&&a.push("fn.inputs=["+b.join(",")+"];");
|
||||
return a.join("")},generateFunction:function(a,b){return"function("+b+"){"+this.varsPrefix(a)+this.body(a)+"};"},filterPrefix:function(){var a=[],b=this;q(this.state.filters,function(d,c){a.push(d+"=$filter("+b.escape(c)+")")});return a.length?"var "+a.join(",")+";":""},varsPrefix:function(a){return this.state[a].vars.length?"var "+this.state[a].vars.join(",")+";":""},body:function(a){return this.state[a].body.join("")},recurse:function(a,b,d,c,e,f){var g,h,k=this,l,m,n;c=c||A;if(!f&&w(a.watchId))b=
|
||||
b||this.nextId(),this.if_("i",this.lazyAssign(b,this.computedMember("i",a.watchId)),this.lazyRecurse(a,b,d,c,e,!0));else switch(a.type){case s.Program:q(a.body,function(b,c){k.recurse(b.expression,void 0,void 0,function(a){h=a});c!==a.body.length-1?k.current().body.push(h,";"):k.return_(h)});break;case s.Literal:m=this.escape(a.value);this.assign(b,m);c(m);break;case s.UnaryExpression:this.recurse(a.argument,void 0,void 0,function(a){h=a});m=a.operator+"("+this.ifDefined(h,0)+")";this.assign(b,m);
|
||||
c(m);break;case s.BinaryExpression:this.recurse(a.left,void 0,void 0,function(a){g=a});this.recurse(a.right,void 0,void 0,function(a){h=a});m="+"===a.operator?this.plus(g,h):"-"===a.operator?this.ifDefined(g,0)+a.operator+this.ifDefined(h,0):"("+g+")"+a.operator+"("+h+")";this.assign(b,m);c(m);break;case s.LogicalExpression:b=b||this.nextId();k.recurse(a.left,b);k.if_("&&"===a.operator?b:k.not(b),k.lazyRecurse(a.right,b));c(b);break;case s.ConditionalExpression:b=b||this.nextId();k.recurse(a.test,
|
||||
b);k.if_(b,k.lazyRecurse(a.alternate,b),k.lazyRecurse(a.consequent,b));c(b);break;case s.Identifier:b=b||this.nextId();d&&(d.context="inputs"===k.stage?"s":this.assign(this.nextId(),this.getHasOwnProperty("l",a.name)+"?l:s"),d.computed=!1,d.name=a.name);Sa(a.name);k.if_("inputs"===k.stage||k.not(k.getHasOwnProperty("l",a.name)),function(){k.if_("inputs"===k.stage||"s",function(){e&&1!==e&&k.if_(k.not(k.nonComputedMember("s",a.name)),k.lazyAssign(k.nonComputedMember("s",a.name),"{}"));k.assign(b,k.nonComputedMember("s",
|
||||
a.name))})},b&&k.lazyAssign(b,k.nonComputedMember("l",a.name)));(k.state.expensiveChecks||Jb(a.name))&&k.addEnsureSafeObject(b);c(b);break;case s.MemberExpression:g=d&&(d.context=this.nextId())||this.nextId();b=b||this.nextId();k.recurse(a.object,g,void 0,function(){k.if_(k.notNull(g),function(){e&&1!==e&&k.addEnsureSafeAssignContext(g);if(a.computed)h=k.nextId(),k.recurse(a.property,h),k.getStringValue(h),k.addEnsureSafeMemberName(h),e&&1!==e&&k.if_(k.not(k.computedMember(g,h)),k.lazyAssign(k.computedMember(g,
|
||||
h),"{}")),m=k.ensureSafeObject(k.computedMember(g,h)),k.assign(b,m),d&&(d.computed=!0,d.name=h);else{Sa(a.property.name);e&&1!==e&&k.if_(k.not(k.nonComputedMember(g,a.property.name)),k.lazyAssign(k.nonComputedMember(g,a.property.name),"{}"));m=k.nonComputedMember(g,a.property.name);if(k.state.expensiveChecks||Jb(a.property.name))m=k.ensureSafeObject(m);k.assign(b,m);d&&(d.computed=!1,d.name=a.property.name)}},function(){k.assign(b,"undefined")});c(b)},!!e);break;case s.CallExpression:b=b||this.nextId();
|
||||
a.filter?(h=k.filter(a.callee.name),l=[],q(a.arguments,function(a){var b=k.nextId();k.recurse(a,b);l.push(b)}),m=h+"("+l.join(",")+")",k.assign(b,m),c(b)):(h=k.nextId(),g={},l=[],k.recurse(a.callee,h,g,function(){k.if_(k.notNull(h),function(){k.addEnsureSafeFunction(h);q(a.arguments,function(a){k.recurse(a,k.nextId(),void 0,function(a){l.push(k.ensureSafeObject(a))})});g.name?(k.state.expensiveChecks||k.addEnsureSafeObject(g.context),m=k.member(g.context,g.name,g.computed)+"("+l.join(",")+")"):m=
|
||||
h+"("+l.join(",")+")";m=k.ensureSafeObject(m);k.assign(b,m)},function(){k.assign(b,"undefined")});c(b)}));break;case s.AssignmentExpression:h=this.nextId();g={};if(!qd(a.left))throw X("lval");this.recurse(a.left,void 0,g,function(){k.if_(k.notNull(g.context),function(){k.recurse(a.right,h);k.addEnsureSafeObject(k.member(g.context,g.name,g.computed));k.addEnsureSafeAssignContext(g.context);m=k.member(g.context,g.name,g.computed)+a.operator+h;k.assign(b,m);c(b||m)})},1);break;case s.ArrayExpression:l=
|
||||
[];q(a.elements,function(a){k.recurse(a,k.nextId(),void 0,function(a){l.push(a)})});m="["+l.join(",")+"]";this.assign(b,m);c(m);break;case s.ObjectExpression:l=[];n=!1;q(a.properties,function(a){a.computed&&(n=!0)});n?(b=b||this.nextId(),this.assign(b,"{}"),q(a.properties,function(a){a.computed?(g=k.nextId(),k.recurse(a.key,g)):g=a.key.type===s.Identifier?a.key.name:""+a.key.value;h=k.nextId();k.recurse(a.value,h);k.assign(k.member(b,g,a.computed),h)})):(q(a.properties,function(b){k.recurse(b.value,
|
||||
a.constant?void 0:k.nextId(),void 0,function(a){l.push(k.escape(b.key.type===s.Identifier?b.key.name:""+b.key.value)+":"+a)})}),m="{"+l.join(",")+"}",this.assign(b,m));c(b||m);break;case s.ThisExpression:this.assign(b,"s");c("s");break;case s.LocalsExpression:this.assign(b,"l");c("l");break;case s.NGValueParameter:this.assign(b,"v"),c("v")}},getHasOwnProperty:function(a,b){var d=a+"."+b,c=this.current().own;c.hasOwnProperty(d)||(c[d]=this.nextId(!1,a+"&&("+this.escape(b)+" in "+a+")"));return c[d]},
|
||||
assign:function(a,b){if(a)return this.current().body.push(a,"=",b,";"),a},filter:function(a){this.state.filters.hasOwnProperty(a)||(this.state.filters[a]=this.nextId(!0));return this.state.filters[a]},ifDefined:function(a,b){return"ifDefined("+a+","+this.escape(b)+")"},plus:function(a,b){return"plus("+a+","+b+")"},return_:function(a){this.current().body.push("return ",a,";")},if_:function(a,b,d){if(!0===a)b();else{var c=this.current().body;c.push("if(",a,"){");b();c.push("}");d&&(c.push("else{"),
|
||||
d(),c.push("}"))}},not:function(a){return"!("+a+")"},notNull:function(a){return a+"!=null"},nonComputedMember:function(a,b){var d=/[^$_a-zA-Z0-9]/g;return/[$_a-zA-Z][$_a-zA-Z0-9]*/.test(b)?a+"."+b:a+'["'+b.replace(d,this.stringEscapeFn)+'"]'},computedMember:function(a,b){return a+"["+b+"]"},member:function(a,b,d){return d?this.computedMember(a,b):this.nonComputedMember(a,b)},addEnsureSafeObject:function(a){this.current().body.push(this.ensureSafeObject(a),";")},addEnsureSafeMemberName:function(a){this.current().body.push(this.ensureSafeMemberName(a),
|
||||
";")},addEnsureSafeFunction:function(a){this.current().body.push(this.ensureSafeFunction(a),";")},addEnsureSafeAssignContext:function(a){this.current().body.push(this.ensureSafeAssignContext(a),";")},ensureSafeObject:function(a){return"ensureSafeObject("+a+",text)"},ensureSafeMemberName:function(a){return"ensureSafeMemberName("+a+",text)"},ensureSafeFunction:function(a){return"ensureSafeFunction("+a+",text)"},getStringValue:function(a){this.assign(a,"getStringValue("+a+")")},ensureSafeAssignContext:function(a){return"ensureSafeAssignContext("+
|
||||
a+",text)"},lazyRecurse:function(a,b,d,c,e,f){var g=this;return function(){g.recurse(a,b,d,c,e,f)}},lazyAssign:function(a,b){var d=this;return function(){d.assign(a,b)}},stringEscapeRegex:/[^ a-zA-Z0-9]/g,stringEscapeFn:function(a){return"\\u"+("0000"+a.charCodeAt(0).toString(16)).slice(-4)},escape:function(a){if(G(a))return"'"+a.replace(this.stringEscapeRegex,this.stringEscapeFn)+"'";if(T(a))return a.toString();if(!0===a)return"true";if(!1===a)return"false";if(null===a)return"null";if("undefined"===
|
||||
typeof a)return"undefined";throw X("esc");},nextId:function(a,b){var d="v"+this.state.nextId++;a||this.current().vars.push(d+(b?"="+b:""));return d},current:function(){return this.state[this.state.computing]}};ud.prototype={compile:function(a,b){var d=this,c=this.astBuilder.ast(a);this.expression=a;this.expensiveChecks=b;V(c,d.$filter);var e,f;if(e=rd(c))f=this.recurse(e);e=pd(c.body);var g;e&&(g=[],q(e,function(a,b){var c=d.recurse(a);a.input=c;g.push(c);a.watchId=b}));var h=[];q(c.body,function(a){h.push(d.recurse(a.expression))});
|
||||
e=0===c.body.length?A:1===c.body.length?h[0]:function(a,b){var c;q(h,function(d){c=d(a,b)});return c};f&&(e.assign=function(a,b,c){return f(a,c,b)});g&&(e.inputs=g);e.literal=sd(c);e.constant=c.constant;return e},recurse:function(a,b,d){var c,e,f=this,g;if(a.input)return this.inputs(a.input,a.watchId);switch(a.type){case s.Literal:return this.value(a.value,b);case s.UnaryExpression:return e=this.recurse(a.argument),this["unary"+a.operator](e,b);case s.BinaryExpression:return c=this.recurse(a.left),
|
||||
e=this.recurse(a.right),this["binary"+a.operator](c,e,b);case s.LogicalExpression:return c=this.recurse(a.left),e=this.recurse(a.right),this["binary"+a.operator](c,e,b);case s.ConditionalExpression:return this["ternary?:"](this.recurse(a.test),this.recurse(a.alternate),this.recurse(a.consequent),b);case s.Identifier:return Sa(a.name,f.expression),f.identifier(a.name,f.expensiveChecks||Jb(a.name),b,d,f.expression);case s.MemberExpression:return c=this.recurse(a.object,!1,!!d),a.computed||(Sa(a.property.name,
|
||||
f.expression),e=a.property.name),a.computed&&(e=this.recurse(a.property)),a.computed?this.computedMember(c,e,b,d,f.expression):this.nonComputedMember(c,e,f.expensiveChecks,b,d,f.expression);case s.CallExpression:return g=[],q(a.arguments,function(a){g.push(f.recurse(a))}),a.filter&&(e=this.$filter(a.callee.name)),a.filter||(e=this.recurse(a.callee,!0)),a.filter?function(a,c,d,f){for(var n=[],p=0;p<g.length;++p)n.push(g[p](a,c,d,f));a=e.apply(void 0,n,f);return b?{context:void 0,name:void 0,value:a}:
|
||||
a}:function(a,c,d,m){var n=e(a,c,d,m),p;if(null!=n.value){ra(n.context,f.expression);nd(n.value,f.expression);p=[];for(var q=0;q<g.length;++q)p.push(ra(g[q](a,c,d,m),f.expression));p=ra(n.value.apply(n.context,p),f.expression)}return b?{value:p}:p};case s.AssignmentExpression:return c=this.recurse(a.left,!0,1),e=this.recurse(a.right),function(a,d,g,m){var n=c(a,d,g,m);a=e(a,d,g,m);ra(n.value,f.expression);Ib(n.context);n.context[n.name]=a;return b?{value:a}:a};case s.ArrayExpression:return g=[],q(a.elements,
|
||||
function(a){g.push(f.recurse(a))}),function(a,c,d,e){for(var f=[],p=0;p<g.length;++p)f.push(g[p](a,c,d,e));return b?{value:f}:f};case s.ObjectExpression:return g=[],q(a.properties,function(a){a.computed?g.push({key:f.recurse(a.key),computed:!0,value:f.recurse(a.value)}):g.push({key:a.key.type===s.Identifier?a.key.name:""+a.key.value,computed:!1,value:f.recurse(a.value)})}),function(a,c,d,e){for(var f={},p=0;p<g.length;++p)g[p].computed?f[g[p].key(a,c,d,e)]=g[p].value(a,c,d,e):f[g[p].key]=g[p].value(a,
|
||||
c,d,e);return b?{value:f}:f};case s.ThisExpression:return function(a){return b?{value:a}:a};case s.LocalsExpression:return function(a,c){return b?{value:c}:c};case s.NGValueParameter:return function(a,c,d){return b?{value:d}:d}}},"unary+":function(a,b){return function(d,c,e,f){d=a(d,c,e,f);d=w(d)?+d:0;return b?{value:d}:d}},"unary-":function(a,b){return function(d,c,e,f){d=a(d,c,e,f);d=w(d)?-d:0;return b?{value:d}:d}},"unary!":function(a,b){return function(d,c,e,f){d=!a(d,c,e,f);return b?{value:d}:
|
||||
d}},"binary+":function(a,b,d){return function(c,e,f,g){var h=a(c,e,f,g);c=b(c,e,f,g);h=od(h,c);return d?{value:h}:h}},"binary-":function(a,b,d){return function(c,e,f,g){var h=a(c,e,f,g);c=b(c,e,f,g);h=(w(h)?h:0)-(w(c)?c:0);return d?{value:h}:h}},"binary*":function(a,b,d){return function(c,e,f,g){c=a(c,e,f,g)*b(c,e,f,g);return d?{value:c}:c}},"binary/":function(a,b,d){return function(c,e,f,g){c=a(c,e,f,g)/b(c,e,f,g);return d?{value:c}:c}},"binary%":function(a,b,d){return function(c,e,f,g){c=a(c,e,
|
||||
f,g)%b(c,e,f,g);return d?{value:c}:c}},"binary===":function(a,b,d){return function(c,e,f,g){c=a(c,e,f,g)===b(c,e,f,g);return d?{value:c}:c}},"binary!==":function(a,b,d){return function(c,e,f,g){c=a(c,e,f,g)!==b(c,e,f,g);return d?{value:c}:c}},"binary==":function(a,b,d){return function(c,e,f,g){c=a(c,e,f,g)==b(c,e,f,g);return d?{value:c}:c}},"binary!=":function(a,b,d){return function(c,e,f,g){c=a(c,e,f,g)!=b(c,e,f,g);return d?{value:c}:c}},"binary<":function(a,b,d){return function(c,e,f,g){c=a(c,e,
|
||||
f,g)<b(c,e,f,g);return d?{value:c}:c}},"binary>":function(a,b,d){return function(c,e,f,g){c=a(c,e,f,g)>b(c,e,f,g);return d?{value:c}:c}},"binary<=":function(a,b,d){return function(c,e,f,g){c=a(c,e,f,g)<=b(c,e,f,g);return d?{value:c}:c}},"binary>=":function(a,b,d){return function(c,e,f,g){c=a(c,e,f,g)>=b(c,e,f,g);return d?{value:c}:c}},"binary&&":function(a,b,d){return function(c,e,f,g){c=a(c,e,f,g)&&b(c,e,f,g);return d?{value:c}:c}},"binary||":function(a,b,d){return function(c,e,f,g){c=a(c,e,f,g)||
|
||||
b(c,e,f,g);return d?{value:c}:c}},"ternary?:":function(a,b,d,c){return function(e,f,g,h){e=a(e,f,g,h)?b(e,f,g,h):d(e,f,g,h);return c?{value:e}:e}},value:function(a,b){return function(){return b?{context:void 0,name:void 0,value:a}:a}},identifier:function(a,b,d,c,e){return function(f,g,h,k){f=g&&a in g?g:f;c&&1!==c&&f&&!f[a]&&(f[a]={});g=f?f[a]:void 0;b&&ra(g,e);return d?{context:f,name:a,value:g}:g}},computedMember:function(a,b,d,c,e){return function(f,g,h,k){var l=a(f,g,h,k),m,n;null!=l&&(m=b(f,
|
||||
g,h,k),m+="",Sa(m,e),c&&1!==c&&(Ib(l),l&&!l[m]&&(l[m]={})),n=l[m],ra(n,e));return d?{context:l,name:m,value:n}:n}},nonComputedMember:function(a,b,d,c,e,f){return function(g,h,k,l){g=a(g,h,k,l);e&&1!==e&&(Ib(g),g&&!g[b]&&(g[b]={}));h=null!=g?g[b]:void 0;(d||Jb(b))&&ra(h,f);return c?{context:g,name:b,value:h}:h}},inputs:function(a,b){return function(d,c,e,f){return f?f[b]:a(d,c,e)}}};var kc=function(a,b,d){this.lexer=a;this.$filter=b;this.options=d;this.ast=new s(a,d);this.astCompiler=d.csp?new ud(this.ast,
|
||||
b):new td(this.ast,b)};kc.prototype={constructor:kc,parse:function(a){return this.astCompiler.compile(a,this.options.expensiveChecks)}};var ng=Object.prototype.valueOf,sa=N("$sce"),la={HTML:"html",CSS:"css",URL:"url",RESOURCE_URL:"resourceUrl",JS:"js"},pg=N("$compile"),$=C.document.createElement("a"),yd=Y(C.location.href);zd.$inject=["$document"];Mc.$inject=["$provide"];var Gd=22,Fd=".",mc="0";Ad.$inject=["$locale"];Cd.$inject=["$locale"];var Ag={yyyy:ba("FullYear",4,0,!1,!0),yy:ba("FullYear",2,0,
|
||||
!0,!0),y:ba("FullYear",1,0,!1,!0),MMMM:kb("Month"),MMM:kb("Month",!0),MM:ba("Month",2,1),M:ba("Month",1,1),LLLL:kb("Month",!1,!0),dd:ba("Date",2),d:ba("Date",1),HH:ba("Hours",2),H:ba("Hours",1),hh:ba("Hours",2,-12),h:ba("Hours",1,-12),mm:ba("Minutes",2),m:ba("Minutes",1),ss:ba("Seconds",2),s:ba("Seconds",1),sss:ba("Milliseconds",3),EEEE:kb("Day"),EEE:kb("Day",!0),a:function(a,b){return 12>a.getHours()?b.AMPMS[0]:b.AMPMS[1]},Z:function(a,b,d){a=-1*d;return a=(0<=a?"+":"")+(Kb(Math[0<a?"floor":"ceil"](a/
|
||||
60),2)+Kb(Math.abs(a%60),2))},ww:Id(2),w:Id(1),G:nc,GG:nc,GGG:nc,GGGG:function(a,b){return 0>=a.getFullYear()?b.ERANAMES[0]:b.ERANAMES[1]}},zg=/((?:[^yMLdHhmsaZEwG']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|L+|d+|H+|h+|m+|s+|a|Z|G+|w+))(.*)/,yg=/^\-?\d+$/;Bd.$inject=["$locale"];var tg=ha(Q),ug=ha(ub);Dd.$inject=["$parse"];var oe=ha({restrict:"E",compile:function(a,b){if(!b.href&&!b.xlinkHref)return function(a,b){if("a"===b[0].nodeName.toLowerCase()){var e="[object SVGAnimatedString]"===ma.call(b.prop("href"))?
|
||||
"xlink:href":"href";b.on("click",function(a){b.attr(e)||a.preventDefault()})}}}}),vb={};q(Eb,function(a,b){function d(a,d,e){a.$watch(e[c],function(a){e.$set(b,!!a)})}if("multiple"!=a){var c=Aa("ng-"+b),e=d;"checked"===a&&(e=function(a,b,e){e.ngModel!==e[c]&&d(a,b,e)});vb[c]=function(){return{restrict:"A",priority:100,link:e}}}});q(bd,function(a,b){vb[b]=function(){return{priority:100,link:function(a,c,e){if("ngPattern"===b&&"/"==e.ngPattern.charAt(0)&&(c=e.ngPattern.match(Cg))){e.$set("ngPattern",
|
||||
new RegExp(c[1],c[2]));return}a.$watch(e[b],function(a){e.$set(b,a)})}}}});q(["src","srcset","href"],function(a){var b=Aa("ng-"+a);vb[b]=function(){return{priority:99,link:function(d,c,e){var f=a,g=a;"href"===a&&"[object SVGAnimatedString]"===ma.call(c.prop("href"))&&(g="xlinkHref",e.$attr[g]="xlink:href",f=null);e.$observe(b,function(b){b?(e.$set(g,b),Ea&&f&&c.prop(f,e[g])):"href"===a&&e.$set(g,null)})}}}});var Lb={$addControl:A,$$renameControl:function(a,b){a.$name=b},$removeControl:A,$setValidity:A,
|
||||
$setDirty:A,$setPristine:A,$setSubmitted:A};Jd.$inject=["$element","$attrs","$scope","$animate","$interpolate"];var Sd=function(a){return["$timeout","$parse",function(b,d){function c(a){return""===a?d('this[""]').assign:d(a).assign||A}return{name:"form",restrict:a?"EAC":"E",require:["form","^^?form"],controller:Jd,compile:function(d,f){d.addClass(Ua).addClass(ob);var g=f.name?"name":a&&f.ngForm?"ngForm":!1;return{pre:function(a,d,e,f){var n=f[0];if(!("action"in e)){var p=function(b){a.$apply(function(){n.$commitViewValue();
|
||||
n.$setSubmitted()});b.preventDefault()};d[0].addEventListener("submit",p,!1);d.on("$destroy",function(){b(function(){d[0].removeEventListener("submit",p,!1)},0,!1)})}(f[1]||n.$$parentForm).$addControl(n);var q=g?c(n.$name):A;g&&(q(a,n),e.$observe(g,function(b){n.$name!==b&&(q(a,void 0),n.$$parentForm.$$renameControl(n,b),q=c(n.$name),q(a,n))}));d.on("$destroy",function(){n.$$parentForm.$removeControl(n);q(a,void 0);S(n,Lb)})}}}}}]},pe=Sd(),Ce=Sd(!0),Bg=/^\d{4,}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d\.\d+(?:[+-][0-2]\d:[0-5]\d|Z)$/,
|
||||
Kg=/^[a-z][a-z\d.+-]*:\/*(?:[^:@]+(?::[^@]+)?@)?(?:[^\s:/?#]+|\[[a-f\d:]+\])(?::\d+)?(?:\/[^?#]*)?(?:\?[^#]*)?(?:#.*)?$/i,Lg=/^(?=.{1,254}$)(?=.{1,64}@)[-!#$%&'*+\/0-9=?A-Z^_`a-z{|}~]+(\.[-!#$%&'*+\/0-9=?A-Z^_`a-z{|}~]+)*@[A-Za-z0-9]([A-Za-z0-9-]{0,61}[A-Za-z0-9])?(\.[A-Za-z0-9]([A-Za-z0-9-]{0,61}[A-Za-z0-9])?)*$/,Mg=/^\s*(\-|\+)?(\d+|(\d*(\.\d*)))([eE][+-]?\d+)?\s*$/,Td=/^(\d{4,})-(\d{2})-(\d{2})$/,Ud=/^(\d{4,})-(\d\d)-(\d\d)T(\d\d):(\d\d)(?::(\d\d)(\.\d{1,3})?)?$/,rc=/^(\d{4,})-W(\d\d)$/,Vd=/^(\d{4,})-(\d\d)$/,
|
||||
Wd=/^(\d\d):(\d\d)(?::(\d\d)(\.\d{1,3})?)?$/,Ld=U();q(["date","datetime-local","month","time","week"],function(a){Ld[a]=!0});var Xd={text:function(a,b,d,c,e,f){lb(a,b,d,c,e,f);pc(c)},date:mb("date",Td,Nb(Td,["yyyy","MM","dd"]),"yyyy-MM-dd"),"datetime-local":mb("datetimelocal",Ud,Nb(Ud,"yyyy MM dd HH mm ss sss".split(" ")),"yyyy-MM-ddTHH:mm:ss.sss"),time:mb("time",Wd,Nb(Wd,["HH","mm","ss","sss"]),"HH:mm:ss.sss"),week:mb("week",rc,function(a,b){if(da(a))return a;if(G(a)){rc.lastIndex=0;var d=rc.exec(a);
|
||||
if(d){var c=+d[1],e=+d[2],f=d=0,g=0,h=0,k=Hd(c),e=7*(e-1);b&&(d=b.getHours(),f=b.getMinutes(),g=b.getSeconds(),h=b.getMilliseconds());return new Date(c,0,k.getDate()+e,d,f,g,h)}}return NaN},"yyyy-Www"),month:mb("month",Vd,Nb(Vd,["yyyy","MM"]),"yyyy-MM"),number:function(a,b,d,c,e,f){Md(a,b,d,c);lb(a,b,d,c,e,f);c.$$parserName="number";c.$parsers.push(function(a){if(c.$isEmpty(a))return null;if(Mg.test(a))return parseFloat(a)});c.$formatters.push(function(a){if(!c.$isEmpty(a)){if(!T(a))throw nb("numfmt",
|
||||
a);a=a.toString()}return a});if(w(d.min)||d.ngMin){var g;c.$validators.min=function(a){return c.$isEmpty(a)||y(g)||a>=g};d.$observe("min",function(a){w(a)&&!T(a)&&(a=parseFloat(a));g=T(a)&&!isNaN(a)?a:void 0;c.$validate()})}if(w(d.max)||d.ngMax){var h;c.$validators.max=function(a){return c.$isEmpty(a)||y(h)||a<=h};d.$observe("max",function(a){w(a)&&!T(a)&&(a=parseFloat(a));h=T(a)&&!isNaN(a)?a:void 0;c.$validate()})}},url:function(a,b,d,c,e,f){lb(a,b,d,c,e,f);pc(c);c.$$parserName="url";c.$validators.url=
|
||||
function(a,b){var d=a||b;return c.$isEmpty(d)||Kg.test(d)}},email:function(a,b,d,c,e,f){lb(a,b,d,c,e,f);pc(c);c.$$parserName="email";c.$validators.email=function(a,b){var d=a||b;return c.$isEmpty(d)||Lg.test(d)}},radio:function(a,b,d,c){y(d.name)&&b.attr("name",++pb);b.on("click",function(a){b[0].checked&&c.$setViewValue(d.value,a&&a.type)});c.$render=function(){b[0].checked=d.value==c.$viewValue};d.$observe("value",c.$render)},checkbox:function(a,b,d,c,e,f,g,h){var k=Nd(h,a,"ngTrueValue",d.ngTrueValue,
|
||||
!0),l=Nd(h,a,"ngFalseValue",d.ngFalseValue,!1);b.on("click",function(a){c.$setViewValue(b[0].checked,a&&a.type)});c.$render=function(){b[0].checked=c.$viewValue};c.$isEmpty=function(a){return!1===a};c.$formatters.push(function(a){return na(a,k)});c.$parsers.push(function(a){return a?k:l})},hidden:A,button:A,submit:A,reset:A,file:A},Gc=["$browser","$sniffer","$filter","$parse",function(a,b,d,c){return{restrict:"E",require:["?ngModel"],link:{pre:function(e,f,g,h){h[0]&&(Xd[Q(g.type)]||Xd.text)(e,f,
|
||||
g,h[0],b,a,d,c)}}}}],Ng=/^(true|false|\d+)$/,Ue=function(){return{restrict:"A",priority:100,compile:function(a,b){return Ng.test(b.ngValue)?function(a,b,e){e.$set("value",a.$eval(e.ngValue))}:function(a,b,e){a.$watch(e.ngValue,function(a){e.$set("value",a)})}}}},ue=["$compile",function(a){return{restrict:"AC",compile:function(b){a.$$addBindingClass(b);return function(b,c,e){a.$$addBindingInfo(c,e.ngBind);c=c[0];b.$watch(e.ngBind,function(a){c.textContent=y(a)?"":a})}}}}],we=["$interpolate","$compile",
|
||||
function(a,b){return{compile:function(d){b.$$addBindingClass(d);return function(c,d,f){c=a(d.attr(f.$attr.ngBindTemplate));b.$$addBindingInfo(d,c.expressions);d=d[0];f.$observe("ngBindTemplate",function(a){d.textContent=y(a)?"":a})}}}}],ve=["$sce","$parse","$compile",function(a,b,d){return{restrict:"A",compile:function(c,e){var f=b(e.ngBindHtml),g=b(e.ngBindHtml,function(b){return a.valueOf(b)});d.$$addBindingClass(c);return function(b,c,e){d.$$addBindingInfo(c,e.ngBindHtml);b.$watch(g,function(){var d=
|
||||
f(b);c.html(a.getTrustedHtml(d)||"")})}}}}],Te=ha({restrict:"A",require:"ngModel",link:function(a,b,d,c){c.$viewChangeListeners.push(function(){a.$eval(d.ngChange)})}}),xe=qc("",!0),ze=qc("Odd",0),ye=qc("Even",1),Ae=Ta({compile:function(a,b){b.$set("ngCloak",void 0);a.removeClass("ng-cloak")}}),Be=[function(){return{restrict:"A",scope:!0,controller:"@",priority:500}}],Lc={},Og={blur:!0,focus:!0};q("click dblclick mousedown mouseup mouseover mouseout mousemove mouseenter mouseleave keydown keyup keypress submit focus blur copy cut paste".split(" "),
|
||||
function(a){var b=Aa("ng-"+a);Lc[b]=["$parse","$rootScope",function(d,c){return{restrict:"A",compile:function(e,f){var g=d(f[b],null,!0);return function(b,d){d.on(a,function(d){var e=function(){g(b,{$event:d})};Og[a]&&c.$$phase?b.$evalAsync(e):b.$apply(e)})}}}}]});var Ee=["$animate","$compile",function(a,b){return{multiElement:!0,transclude:"element",priority:600,terminal:!0,restrict:"A",$$tlb:!0,link:function(d,c,e,f,g){var h,k,l;d.$watch(e.ngIf,function(d){d?k||g(function(d,f){k=f;d[d.length++]=
|
||||
b.$$createComment("end ngIf",e.ngIf);h={clone:d};a.enter(d,c.parent(),c)}):(l&&(l.remove(),l=null),k&&(k.$destroy(),k=null),h&&(l=tb(h.clone),a.leave(l).then(function(){l=null}),h=null))})}}}],Fe=["$templateRequest","$anchorScroll","$animate",function(a,b,d){return{restrict:"ECA",priority:400,terminal:!0,transclude:"element",controller:ca.noop,compile:function(c,e){var f=e.ngInclude||e.src,g=e.onload||"",h=e.autoscroll;return function(c,e,m,n,p){var q=0,s,B,r,y=function(){B&&(B.remove(),B=null);s&&
|
||||
(s.$destroy(),s=null);r&&(d.leave(r).then(function(){B=null}),B=r,r=null)};c.$watch(f,function(f){var m=function(){!w(h)||h&&!c.$eval(h)||b()},t=++q;f?(a(f,!0).then(function(a){if(!c.$$destroyed&&t===q){var b=c.$new();n.template=a;a=p(b,function(a){y();d.enter(a,null,e).then(m)});s=b;r=a;s.$emit("$includeContentLoaded",f);c.$eval(g)}},function(){c.$$destroyed||t!==q||(y(),c.$emit("$includeContentError",f))}),c.$emit("$includeContentRequested",f)):(y(),n.template=null)})}}}}],We=["$compile",function(a){return{restrict:"ECA",
|
||||
priority:-400,require:"ngInclude",link:function(b,d,c,e){ma.call(d[0]).match(/SVG/)?(d.empty(),a(Oc(e.template,C.document).childNodes)(b,function(a){d.append(a)},{futureParentElement:d})):(d.html(e.template),a(d.contents())(b))}}}],Ge=Ta({priority:450,compile:function(){return{pre:function(a,b,d){a.$eval(d.ngInit)}}}}),Se=function(){return{restrict:"A",priority:100,require:"ngModel",link:function(a,b,d,c){var e=b.attr(d.$attr.ngList)||", ",f="false"!==d.ngTrim,g=f?W(e):e;c.$parsers.push(function(a){if(!y(a)){var b=
|
||||
[];a&&q(a.split(g),function(a){a&&b.push(f?W(a):a)});return b}});c.$formatters.push(function(a){if(L(a))return a.join(e)});c.$isEmpty=function(a){return!a||!a.length}}}},ob="ng-valid",Od="ng-invalid",Ua="ng-pristine",Mb="ng-dirty",Qd="ng-pending",nb=N("ngModel"),Pg=["$scope","$exceptionHandler","$attrs","$element","$parse","$animate","$timeout","$rootScope","$q","$interpolate",function(a,b,d,c,e,f,g,h,k,l){this.$modelValue=this.$viewValue=Number.NaN;this.$$rawModelValue=void 0;this.$validators={};
|
||||
this.$asyncValidators={};this.$parsers=[];this.$formatters=[];this.$viewChangeListeners=[];this.$untouched=!0;this.$touched=!1;this.$pristine=!0;this.$dirty=!1;this.$valid=!0;this.$invalid=!1;this.$error={};this.$$success={};this.$pending=void 0;this.$name=l(d.name||"",!1)(a);this.$$parentForm=Lb;var m=e(d.ngModel),n=m.assign,p=m,u=n,s=null,B,r=this;this.$$setOptions=function(a){if((r.$options=a)&&a.getterSetter){var b=e(d.ngModel+"()"),f=e(d.ngModel+"($$$p)");p=function(a){var c=m(a);z(c)&&(c=b(a));
|
||||
return c};u=function(a,b){z(m(a))?f(a,{$$$p:b}):n(a,b)}}else if(!m.assign)throw nb("nonassign",d.ngModel,ya(c));};this.$render=A;this.$isEmpty=function(a){return y(a)||""===a||null===a||a!==a};this.$$updateEmptyClasses=function(a){r.$isEmpty(a)?(f.removeClass(c,"ng-not-empty"),f.addClass(c,"ng-empty")):(f.removeClass(c,"ng-empty"),f.addClass(c,"ng-not-empty"))};var J=0;Kd({ctrl:this,$element:c,set:function(a,b){a[b]=!0},unset:function(a,b){delete a[b]},$animate:f});this.$setPristine=function(){r.$dirty=
|
||||
!1;r.$pristine=!0;f.removeClass(c,Mb);f.addClass(c,Ua)};this.$setDirty=function(){r.$dirty=!0;r.$pristine=!1;f.removeClass(c,Ua);f.addClass(c,Mb);r.$$parentForm.$setDirty()};this.$setUntouched=function(){r.$touched=!1;r.$untouched=!0;f.setClass(c,"ng-untouched","ng-touched")};this.$setTouched=function(){r.$touched=!0;r.$untouched=!1;f.setClass(c,"ng-touched","ng-untouched")};this.$rollbackViewValue=function(){g.cancel(s);r.$viewValue=r.$$lastCommittedViewValue;r.$render()};this.$validate=function(){if(!T(r.$modelValue)||
|
||||
!isNaN(r.$modelValue)){var a=r.$$rawModelValue,b=r.$valid,c=r.$modelValue,d=r.$options&&r.$options.allowInvalid;r.$$runValidators(a,r.$$lastCommittedViewValue,function(e){d||b===e||(r.$modelValue=e?a:void 0,r.$modelValue!==c&&r.$$writeModelToScope())})}};this.$$runValidators=function(a,b,c){function d(){var c=!0;q(r.$validators,function(d,e){var g=d(a,b);c=c&&g;f(e,g)});return c?!0:(q(r.$asyncValidators,function(a,b){f(b,null)}),!1)}function e(){var c=[],d=!0;q(r.$asyncValidators,function(e,g){var h=
|
||||
e(a,b);if(!h||!z(h.then))throw nb("nopromise",h);f(g,void 0);c.push(h.then(function(){f(g,!0)},function(){d=!1;f(g,!1)}))});c.length?k.all(c).then(function(){g(d)},A):g(!0)}function f(a,b){h===J&&r.$setValidity(a,b)}function g(a){h===J&&c(a)}J++;var h=J;(function(){var a=r.$$parserName||"parse";if(y(B))f(a,null);else return B||(q(r.$validators,function(a,b){f(b,null)}),q(r.$asyncValidators,function(a,b){f(b,null)})),f(a,B),B;return!0})()?d()?e():g(!1):g(!1)};this.$commitViewValue=function(){var a=
|
||||
r.$viewValue;g.cancel(s);if(r.$$lastCommittedViewValue!==a||""===a&&r.$$hasNativeValidators)r.$$updateEmptyClasses(a),r.$$lastCommittedViewValue=a,r.$pristine&&this.$setDirty(),this.$$parseAndValidate()};this.$$parseAndValidate=function(){var b=r.$$lastCommittedViewValue;if(B=y(b)?void 0:!0)for(var c=0;c<r.$parsers.length;c++)if(b=r.$parsers[c](b),y(b)){B=!1;break}T(r.$modelValue)&&isNaN(r.$modelValue)&&(r.$modelValue=p(a));var d=r.$modelValue,e=r.$options&&r.$options.allowInvalid;r.$$rawModelValue=
|
||||
b;e&&(r.$modelValue=b,r.$modelValue!==d&&r.$$writeModelToScope());r.$$runValidators(b,r.$$lastCommittedViewValue,function(a){e||(r.$modelValue=a?b:void 0,r.$modelValue!==d&&r.$$writeModelToScope())})};this.$$writeModelToScope=function(){u(a,r.$modelValue);q(r.$viewChangeListeners,function(a){try{a()}catch(c){b(c)}})};this.$setViewValue=function(a,b){r.$viewValue=a;r.$options&&!r.$options.updateOnDefault||r.$$debounceViewValueCommit(b)};this.$$debounceViewValueCommit=function(b){var c=0,d=r.$options;
|
||||
d&&w(d.debounce)&&(d=d.debounce,T(d)?c=d:T(d[b])?c=d[b]:T(d["default"])&&(c=d["default"]));g.cancel(s);c?s=g(function(){r.$commitViewValue()},c):h.$$phase?r.$commitViewValue():a.$apply(function(){r.$commitViewValue()})};a.$watch(function(){var b=p(a);if(b!==r.$modelValue&&(r.$modelValue===r.$modelValue||b===b)){r.$modelValue=r.$$rawModelValue=b;B=void 0;for(var c=r.$formatters,d=c.length,e=b;d--;)e=c[d](e);r.$viewValue!==e&&(r.$$updateEmptyClasses(e),r.$viewValue=r.$$lastCommittedViewValue=e,r.$render(),
|
||||
r.$$runValidators(b,e,A))}return b})}],Re=["$rootScope",function(a){return{restrict:"A",require:["ngModel","^?form","^?ngModelOptions"],controller:Pg,priority:1,compile:function(b){b.addClass(Ua).addClass("ng-untouched").addClass(ob);return{pre:function(a,b,e,f){var g=f[0];b=f[1]||g.$$parentForm;g.$$setOptions(f[2]&&f[2].$options);b.$addControl(g);e.$observe("name",function(a){g.$name!==a&&g.$$parentForm.$$renameControl(g,a)});a.$on("$destroy",function(){g.$$parentForm.$removeControl(g)})},post:function(b,
|
||||
c,e,f){var g=f[0];if(g.$options&&g.$options.updateOn)c.on(g.$options.updateOn,function(a){g.$$debounceViewValueCommit(a&&a.type)});c.on("blur",function(){g.$touched||(a.$$phase?b.$evalAsync(g.$setTouched):b.$apply(g.$setTouched))})}}}}}],Qg=/(\s+|^)default(\s+|$)/,Ve=function(){return{restrict:"A",controller:["$scope","$attrs",function(a,b){var d=this;this.$options=pa(a.$eval(b.ngModelOptions));w(this.$options.updateOn)?(this.$options.updateOnDefault=!1,this.$options.updateOn=W(this.$options.updateOn.replace(Qg,
|
||||
function(){d.$options.updateOnDefault=!0;return" "}))):this.$options.updateOnDefault=!0}]}},He=Ta({terminal:!0,priority:1E3}),Rg=N("ngOptions"),Sg=/^\s*([\s\S]+?)(?:\s+as\s+([\s\S]+?))?(?:\s+group\s+by\s+([\s\S]+?))?(?:\s+disable\s+when\s+([\s\S]+?))?\s+for\s+(?:([\$\w][\$\w]*)|(?:\(\s*([\$\w][\$\w]*)\s*,\s*([\$\w][\$\w]*)\s*\)))\s+in\s+([\s\S]+?)(?:\s+track\s+by\s+([\s\S]+?))?$/,Pe=["$compile","$document","$parse",function(a,b,d){function c(a,b,c){function e(a,b,c,d,f){this.selectValue=a;this.viewValue=
|
||||
b;this.label=c;this.group=d;this.disabled=f}function f(a){var b;if(!q&&ta(a))b=a;else{b=[];for(var c in a)a.hasOwnProperty(c)&&"$"!==c.charAt(0)&&b.push(c)}return b}var n=a.match(Sg);if(!n)throw Rg("iexp",a,ya(b));var p=n[5]||n[7],q=n[6];a=/ as /.test(n[0])&&n[1];var s=n[9];b=d(n[2]?n[1]:p);var w=a&&d(a)||b,r=s&&d(s),y=s?function(a,b){return r(c,b)}:function(a){return Ca(a)},v=function(a,b){return y(a,E(a,b))},A=d(n[2]||n[1]),t=d(n[3]||""),K=d(n[4]||""),z=d(n[8]),H={},E=q?function(a,b){H[q]=b;H[p]=
|
||||
a;return H}:function(a){H[p]=a;return H};return{trackBy:s,getTrackByValue:v,getWatchables:d(z,function(a){var b=[];a=a||[];for(var d=f(a),e=d.length,g=0;g<e;g++){var h=a===d?g:d[g],l=a[h],h=E(l,h),l=y(l,h);b.push(l);if(n[2]||n[1])l=A(c,h),b.push(l);n[4]&&(h=K(c,h),b.push(h))}return b}),getOptions:function(){for(var a=[],b={},d=z(c)||[],g=f(d),h=g.length,n=0;n<h;n++){var p=d===g?n:g[n],q=E(d[p],p),r=w(c,q),p=y(r,q),u=A(c,q),H=t(c,q),q=K(c,q),r=new e(p,r,u,H,q);a.push(r);b[p]=r}return{items:a,selectValueMap:b,
|
||||
getOptionFromViewValue:function(a){return b[v(a)]},getViewValueFromOption:function(a){return s?ca.copy(a.viewValue):a.viewValue}}}}}var e=C.document.createElement("option"),f=C.document.createElement("optgroup");return{restrict:"A",terminal:!0,require:["select","ngModel"],link:{pre:function(a,b,c,d){d[0].registerOption=A},post:function(d,h,k,l){function m(a,b){a.element=b;b.disabled=a.disabled;a.label!==b.label&&(b.label=a.label,b.textContent=a.label);a.value!==b.value&&(b.value=a.selectValue)}function n(){var a=
|
||||
t&&p.readValue();if(t)for(var b=t.items.length-1;0<=b;b--){var c=t.items[b];w(c.group)?Db(c.element.parentNode):Db(c.element)}t=K.getOptions();var d={};v&&h.prepend(B);t.items.forEach(function(a){var b;if(w(a.group)){b=d[a.group];b||(b=f.cloneNode(!1),C.appendChild(b),b.label=null===a.group?"null":a.group,d[a.group]=b);var c=e.cloneNode(!1)}else b=C,c=e.cloneNode(!1);b.appendChild(c);m(a,c)});h[0].appendChild(C);s.$render();s.$isEmpty(a)||(b=p.readValue(),(K.trackBy||y?na(a,b):a===b)||(s.$setViewValue(b),
|
||||
s.$render()))}var p=l[0],s=l[1],y=k.multiple,B;l=0;for(var r=h.children(),A=r.length;l<A;l++)if(""===r[l].value){B=r.eq(l);break}var v=!!B,z=F(e.cloneNode(!1));z.val("?");var t,K=c(k.ngOptions,h,d),C=b[0].createDocumentFragment();y?(s.$isEmpty=function(a){return!a||0===a.length},p.writeValue=function(a){t.items.forEach(function(a){a.element.selected=!1});a&&a.forEach(function(a){if(a=t.getOptionFromViewValue(a))a.element.selected=!0})},p.readValue=function(){var a=h.val()||[],b=[];q(a,function(a){(a=
|
||||
t.selectValueMap[a])&&!a.disabled&&b.push(t.getViewValueFromOption(a))});return b},K.trackBy&&d.$watchCollection(function(){if(L(s.$viewValue))return s.$viewValue.map(function(a){return K.getTrackByValue(a)})},function(){s.$render()})):(p.writeValue=function(a){var b=t.getOptionFromViewValue(a);b?(h[0].value!==b.selectValue&&(z.remove(),v||B.remove(),h[0].value=b.selectValue,b.element.selected=!0),b.element.setAttribute("selected","selected")):null===a||v?(z.remove(),v||h.prepend(B),h.val(""),B.prop("selected",
|
||||
!0),B.attr("selected",!0)):(v||B.remove(),h.prepend(z),h.val("?"),z.prop("selected",!0),z.attr("selected",!0))},p.readValue=function(){var a=t.selectValueMap[h.val()];return a&&!a.disabled?(v||B.remove(),z.remove(),t.getViewValueFromOption(a)):null},K.trackBy&&d.$watch(function(){return K.getTrackByValue(s.$viewValue)},function(){s.$render()}));v?(B.remove(),a(B)(d),B.removeClass("ng-scope")):B=F(e.cloneNode(!1));h.empty();n();d.$watchCollection(K.getWatchables,n)}}}}],Ie=["$locale","$interpolate",
|
||||
"$log",function(a,b,d){var c=/{}/g,e=/^when(Minus)?(.+)$/;return{link:function(f,g,h){function k(a){g.text(a||"")}var l=h.count,m=h.$attr.when&&g.attr(h.$attr.when),n=h.offset||0,p=f.$eval(m)||{},s={},w=b.startSymbol(),B=b.endSymbol(),r=w+l+"-"+n+B,z=ca.noop,v;q(h,function(a,b){var c=e.exec(b);c&&(c=(c[1]?"-":"")+Q(c[2]),p[c]=g.attr(h.$attr[b]))});q(p,function(a,d){s[d]=b(a.replace(c,r))});f.$watch(l,function(b){var c=parseFloat(b),e=isNaN(c);e||c in p||(c=a.pluralCat(c-n));c===v||e&&T(v)&&isNaN(v)||
|
||||
(z(),e=s[c],y(e)?(null!=b&&d.debug("ngPluralize: no rule defined for '"+c+"' in "+m),z=A,k()):z=f.$watch(e,k),v=c)})}}}],Je=["$parse","$animate","$compile",function(a,b,d){var c=N("ngRepeat"),e=function(a,b,c,d,e,m,n){a[c]=d;e&&(a[e]=m);a.$index=b;a.$first=0===b;a.$last=b===n-1;a.$middle=!(a.$first||a.$last);a.$odd=!(a.$even=0===(b&1))};return{restrict:"A",multiElement:!0,transclude:"element",priority:1E3,terminal:!0,$$tlb:!0,compile:function(f,g){var h=g.ngRepeat,k=d.$$createComment("end ngRepeat",
|
||||
h),l=h.match(/^\s*([\s\S]+?)\s+in\s+([\s\S]+?)(?:\s+as\s+([\s\S]+?))?(?:\s+track\s+by\s+([\s\S]+?))?\s*$/);if(!l)throw c("iexp",h);var m=l[1],n=l[2],p=l[3],s=l[4],l=m.match(/^(?:(\s*[\$\w]+)|\(\s*([\$\w]+)\s*,\s*([\$\w]+)\s*\))$/);if(!l)throw c("iidexp",m);var w=l[3]||l[1],y=l[2];if(p&&(!/^[$a-zA-Z_][$a-zA-Z0-9_]*$/.test(p)||/^(null|undefined|this|\$index|\$first|\$middle|\$last|\$even|\$odd|\$parent|\$root|\$id)$/.test(p)))throw c("badident",p);var r,z,v,A,t={$id:Ca};s?r=a(s):(v=function(a,b){return Ca(b)},
|
||||
A=function(a){return a});return function(a,d,f,g,l){r&&(z=function(b,c,d){y&&(t[y]=b);t[w]=c;t.$index=d;return r(a,t)});var m=U();a.$watchCollection(n,function(f){var g,n,r=d[0],s,u=U(),t,C,F,E,G,D,H;p&&(a[p]=f);if(ta(f))G=f,n=z||v;else for(H in n=z||A,G=[],f)ua.call(f,H)&&"$"!==H.charAt(0)&&G.push(H);t=G.length;H=Array(t);for(g=0;g<t;g++)if(C=f===G?g:G[g],F=f[C],E=n(C,F,g),m[E])D=m[E],delete m[E],u[E]=D,H[g]=D;else{if(u[E])throw q(H,function(a){a&&a.scope&&(m[a.id]=a)}),c("dupes",h,E,F);H[g]={id:E,
|
||||
scope:void 0,clone:void 0};u[E]=!0}for(s in m){D=m[s];E=tb(D.clone);b.leave(E);if(E[0].parentNode)for(g=0,n=E.length;g<n;g++)E[g].$$NG_REMOVED=!0;D.scope.$destroy()}for(g=0;g<t;g++)if(C=f===G?g:G[g],F=f[C],D=H[g],D.scope){s=r;do s=s.nextSibling;while(s&&s.$$NG_REMOVED);D.clone[0]!=s&&b.move(tb(D.clone),null,r);r=D.clone[D.clone.length-1];e(D.scope,g,w,F,y,C,t)}else l(function(a,c){D.scope=c;var d=k.cloneNode(!1);a[a.length++]=d;b.enter(a,null,r);r=d;D.clone=a;u[D.id]=D;e(D.scope,g,w,F,y,C,t)});m=
|
||||
u})}}}}],Ke=["$animate",function(a){return{restrict:"A",multiElement:!0,link:function(b,d,c){b.$watch(c.ngShow,function(b){a[b?"removeClass":"addClass"](d,"ng-hide",{tempClasses:"ng-hide-animate"})})}}}],De=["$animate",function(a){return{restrict:"A",multiElement:!0,link:function(b,d,c){b.$watch(c.ngHide,function(b){a[b?"addClass":"removeClass"](d,"ng-hide",{tempClasses:"ng-hide-animate"})})}}}],Le=Ta(function(a,b,d){a.$watch(d.ngStyle,function(a,d){d&&a!==d&&q(d,function(a,c){b.css(c,"")});a&&b.css(a)},
|
||||
!0)}),Me=["$animate","$compile",function(a,b){return{require:"ngSwitch",controller:["$scope",function(){this.cases={}}],link:function(d,c,e,f){var g=[],h=[],k=[],l=[],m=function(a,b){return function(){a.splice(b,1)}};d.$watch(e.ngSwitch||e.on,function(c){var d,e;d=0;for(e=k.length;d<e;++d)a.cancel(k[d]);d=k.length=0;for(e=l.length;d<e;++d){var s=tb(h[d].clone);l[d].$destroy();(k[d]=a.leave(s)).then(m(k,d))}h.length=0;l.length=0;(g=f.cases["!"+c]||f.cases["?"])&&q(g,function(c){c.transclude(function(d,
|
||||
e){l.push(e);var f=c.element;d[d.length++]=b.$$createComment("end ngSwitchWhen");h.push({clone:d});a.enter(d,f.parent(),f)})})})}}}],Ne=Ta({transclude:"element",priority:1200,require:"^ngSwitch",multiElement:!0,link:function(a,b,d,c,e){c.cases["!"+d.ngSwitchWhen]=c.cases["!"+d.ngSwitchWhen]||[];c.cases["!"+d.ngSwitchWhen].push({transclude:e,element:b})}}),Oe=Ta({transclude:"element",priority:1200,require:"^ngSwitch",multiElement:!0,link:function(a,b,d,c,e){c.cases["?"]=c.cases["?"]||[];c.cases["?"].push({transclude:e,
|
||||
element:b})}}),Tg=N("ngTransclude"),Qe=["$compile",function(a){return{restrict:"EAC",terminal:!0,compile:function(b){var d=a(b.contents());b.empty();return function(a,b,f,g,h){function k(){d(a,function(a){b.append(a)})}if(!h)throw Tg("orphan",ya(b));f.ngTransclude===f.$attr.ngTransclude&&(f.ngTransclude="");f=f.ngTransclude||f.ngTranscludeSlot;h(function(a,c){a.length?b.append(a):(k(),c.$destroy())},null,f);f&&!h.isSlotFilled(f)&&k()}}}}],qe=["$templateCache",function(a){return{restrict:"E",terminal:!0,
|
||||
compile:function(b,d){"text/ng-template"==d.type&&a.put(d.id,b[0].text)}}}],Ug={$setViewValue:A,$render:A},Vg=["$element","$scope",function(a,b){var d=this,c=new Ra;d.ngModelCtrl=Ug;d.unknownOption=F(C.document.createElement("option"));d.renderUnknownOption=function(b){b="? "+Ca(b)+" ?";d.unknownOption.val(b);a.prepend(d.unknownOption);a.val(b)};b.$on("$destroy",function(){d.renderUnknownOption=A});d.removeUnknownOption=function(){d.unknownOption.parent()&&d.unknownOption.remove()};d.readValue=function(){d.removeUnknownOption();
|
||||
return a.val()};d.writeValue=function(b){d.hasOption(b)?(d.removeUnknownOption(),a.val(b),""===b&&d.emptyOption.prop("selected",!0)):null==b&&d.emptyOption?(d.removeUnknownOption(),a.val("")):d.renderUnknownOption(b)};d.addOption=function(a,b){if(8!==b[0].nodeType){Qa(a,'"option value"');""===a&&(d.emptyOption=b);var g=c.get(a)||0;c.put(a,g+1);d.ngModelCtrl.$render();b[0].hasAttribute("selected")&&(b[0].selected=!0)}};d.removeOption=function(a){var b=c.get(a);b&&(1===b?(c.remove(a),""===a&&(d.emptyOption=
|
||||
void 0)):c.put(a,b-1))};d.hasOption=function(a){return!!c.get(a)};d.registerOption=function(a,b,c,h,k){if(h){var l;c.$observe("value",function(a){w(l)&&d.removeOption(l);l=a;d.addOption(a,b)})}else k?a.$watch(k,function(a,e){c.$set("value",a);e!==a&&d.removeOption(e);d.addOption(a,b)}):d.addOption(c.value,b);b.on("$destroy",function(){d.removeOption(c.value);d.ngModelCtrl.$render()})}}],re=function(){return{restrict:"E",require:["select","?ngModel"],controller:Vg,priority:1,link:{pre:function(a,b,
|
||||
d,c){var e=c[1];if(e){var f=c[0];f.ngModelCtrl=e;b.on("change",function(){a.$apply(function(){e.$setViewValue(f.readValue())})});if(d.multiple){f.readValue=function(){var a=[];q(b.find("option"),function(b){b.selected&&a.push(b.value)});return a};f.writeValue=function(a){var c=new Ra(a);q(b.find("option"),function(a){a.selected=w(c.get(a.value))})};var g,h=NaN;a.$watch(function(){h!==e.$viewValue||na(g,e.$viewValue)||(g=ia(e.$viewValue),e.$render());h=e.$viewValue});e.$isEmpty=function(a){return!a||
|
||||
0===a.length}}}},post:function(a,b,d,c){var e=c[1];if(e){var f=c[0];e.$render=function(){f.writeValue(e.$viewValue)}}}}}},te=["$interpolate",function(a){return{restrict:"E",priority:100,compile:function(b,d){if(w(d.value))var c=a(d.value,!0);else{var e=a(b.text(),!0);e||d.$set("value",b.text())}return function(a,b,d){var k=b.parent();(k=k.data("$selectController")||k.parent().data("$selectController"))&&k.registerOption(a,b,d,c,e)}}}}],se=ha({restrict:"E",terminal:!1}),Ic=function(){return{restrict:"A",
|
||||
require:"?ngModel",link:function(a,b,d,c){c&&(d.required=!0,c.$validators.required=function(a,b){return!d.required||!c.$isEmpty(b)},d.$observe("required",function(){c.$validate()}))}}},Hc=function(){return{restrict:"A",require:"?ngModel",link:function(a,b,d,c){if(c){var e,f=d.ngPattern||d.pattern;d.$observe("pattern",function(a){G(a)&&0<a.length&&(a=new RegExp("^"+a+"$"));if(a&&!a.test)throw N("ngPattern")("noregexp",f,a,ya(b));e=a||void 0;c.$validate()});c.$validators.pattern=function(a,b){return c.$isEmpty(b)||
|
||||
y(e)||e.test(b)}}}}},Kc=function(){return{restrict:"A",require:"?ngModel",link:function(a,b,d,c){if(c){var e=-1;d.$observe("maxlength",function(a){a=Z(a);e=isNaN(a)?-1:a;c.$validate()});c.$validators.maxlength=function(a,b){return 0>e||c.$isEmpty(b)||b.length<=e}}}}},Jc=function(){return{restrict:"A",require:"?ngModel",link:function(a,b,d,c){if(c){var e=0;d.$observe("minlength",function(a){e=Z(a)||0;c.$validate()});c.$validators.minlength=function(a,b){return c.$isEmpty(b)||b.length>=e}}}}};C.angular.bootstrap?
|
||||
C.console&&console.log("WARNING: Tried to load angular more than once."):(je(),le(ca),ca.module("ngLocale",[],["$provide",function(a){function b(a){a+="";var b=a.indexOf(".");return-1==b?0:a.length-b-1}a.value("$locale",{DATETIME_FORMATS:{AMPMS:["AM","PM"],DAY:"Sunday Monday Tuesday Wednesday Thursday Friday Saturday".split(" "),ERANAMES:["Before Christ","Anno Domini"],ERAS:["BC","AD"],FIRSTDAYOFWEEK:6,MONTH:"January February March April May June July August September October November December".split(" "),
|
||||
SHORTDAY:"Sun Mon Tue Wed Thu Fri Sat".split(" "),SHORTMONTH:"Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec".split(" "),STANDALONEMONTH:"January February March April May June July August September October November December".split(" "),WEEKENDRANGE:[5,6],fullDate:"EEEE, MMMM d, y",longDate:"MMMM d, y",medium:"MMM d, y h:mm:ss a",mediumDate:"MMM d, y",mediumTime:"h:mm:ss a","short":"M/d/yy h:mm a",shortDate:"M/d/yy",shortTime:"h:mm a"},NUMBER_FORMATS:{CURRENCY_SYM:"$",DECIMAL_SEP:".",GROUP_SEP:",",
|
||||
PATTERNS:[{gSize:3,lgSize:3,maxFrac:3,minFrac:0,minInt:1,negPre:"-",negSuf:"",posPre:"",posSuf:""},{gSize:3,lgSize:3,maxFrac:2,minFrac:2,minInt:1,negPre:"-\u00a4",negSuf:"",posPre:"\u00a4",posSuf:""}]},id:"en-us",localeID:"en_US",pluralCat:function(a,c){var e=a|0,f=c;void 0===f&&(f=Math.min(b(a),3));Math.pow(10,f);return 1==e&&0==f?"one":"other"}})}]),F(C.document).ready(function(){fe(C.document,Bc)}))})(window);!window.angular.$$csp().noInlineStyle&&window.angular.element(document.head).prepend('<style type="text/css">@charset "UTF-8";[ng\\:cloak],[ng-cloak],[data-ng-cloak],[x-ng-cloak],.ng-cloak,.x-ng-cloak,.ng-hide:not(.ng-hide-animate){display:none !important;}ng\\:form{display:block;}.ng-animate-shim{visibility:hidden;}.ng-anchor{position:absolute;}</style>');
|
7
src/assets/static/js/bootstrap.min.js
vendored
7
src/assets/static/js/bootstrap.min.js
vendored
File diff suppressed because one or more lines are too long
35
src/assets/static/js/echarts.min.js
vendored
35
src/assets/static/js/echarts.min.js
vendored
File diff suppressed because one or more lines are too long
4
src/assets/static/js/jquery.min.js
vendored
4
src/assets/static/js/jquery.min.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -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")
|
||||
}
|
@ -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
|
||||
}
|
@ -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)
|
||||
}
|
@ -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
|
||||
}
|
@ -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
|
||||
}
|
@ -1,153 +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"
|
||||
"io"
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/fatedier/frp/src/models/msg"
|
||||
"github.com/fatedier/frp/src/utils/conn"
|
||||
"github.com/fatedier/frp/src/utils/pool"
|
||||
)
|
||||
|
||||
type UdpProcesser struct {
|
||||
tcpConn *conn.Conn
|
||||
closeCh chan struct{}
|
||||
|
||||
localAddr string
|
||||
|
||||
// cache local udp connections
|
||||
// key is remoteAddr
|
||||
localUdpConns map[string]*net.UDPConn
|
||||
mutex sync.RWMutex
|
||||
tcpConnMutex sync.RWMutex
|
||||
}
|
||||
|
||||
func NewUdpProcesser(c *conn.Conn, localIp string, localPort int64) *UdpProcesser {
|
||||
return &UdpProcesser{
|
||||
tcpConn: c,
|
||||
closeCh: make(chan struct{}),
|
||||
localAddr: fmt.Sprintf("%s:%d", localIp, localPort),
|
||||
localUdpConns: make(map[string]*net.UDPConn),
|
||||
}
|
||||
}
|
||||
|
||||
func (up *UdpProcesser) UpdateTcpConn(c *conn.Conn) {
|
||||
up.tcpConnMutex.Lock()
|
||||
defer up.tcpConnMutex.Unlock()
|
||||
up.tcpConn = c
|
||||
}
|
||||
|
||||
func (up *UdpProcesser) Run() {
|
||||
go up.ReadLoop()
|
||||
}
|
||||
|
||||
func (up *UdpProcesser) ReadLoop() {
|
||||
var (
|
||||
buf string
|
||||
err error
|
||||
)
|
||||
for {
|
||||
udpPacket := &msg.UdpPacket{}
|
||||
|
||||
// read udp package from frps
|
||||
buf, err = up.tcpConn.ReadLine()
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
return
|
||||
} else {
|
||||
continue
|
||||
}
|
||||
}
|
||||
err = udpPacket.UnPack([]byte(buf))
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// write to local udp port
|
||||
sendConn, ok := up.GetUdpConn(udpPacket.SrcStr)
|
||||
if !ok {
|
||||
dstAddr, err := net.ResolveUDPAddr("udp", up.localAddr)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
sendConn, err = net.DialUDP("udp", nil, dstAddr)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
up.SetUdpConn(udpPacket.SrcStr, sendConn)
|
||||
}
|
||||
|
||||
_, err = sendConn.Write(udpPacket.Content)
|
||||
if err != nil {
|
||||
sendConn.Close()
|
||||
continue
|
||||
}
|
||||
|
||||
if !ok {
|
||||
go up.Forward(udpPacket, sendConn)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (up *UdpProcesser) Forward(udpPacket *msg.UdpPacket, singleConn *net.UDPConn) {
|
||||
addr := udpPacket.SrcStr
|
||||
defer up.RemoveUdpConn(addr)
|
||||
|
||||
buf := pool.GetBuf(2048)
|
||||
for {
|
||||
singleConn.SetReadDeadline(time.Now().Add(120 * time.Second))
|
||||
n, remoteAddr, err := singleConn.ReadFromUDP(buf)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// forward to frps
|
||||
forwardPacket := msg.NewUdpPacket(buf[0:n], remoteAddr, udpPacket.Src)
|
||||
up.tcpConnMutex.RLock()
|
||||
err = up.tcpConn.WriteString(string(forwardPacket.Pack()) + "\n")
|
||||
up.tcpConnMutex.RUnlock()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (up *UdpProcesser) GetUdpConn(addr string) (singleConn *net.UDPConn, ok bool) {
|
||||
up.mutex.RLock()
|
||||
defer up.mutex.RUnlock()
|
||||
singleConn, ok = up.localUdpConns[addr]
|
||||
return
|
||||
}
|
||||
|
||||
func (up *UdpProcesser) SetUdpConn(addr string, conn *net.UDPConn) {
|
||||
up.mutex.Lock()
|
||||
defer up.mutex.Unlock()
|
||||
up.localUdpConns[addr] = conn
|
||||
}
|
||||
|
||||
func (up *UdpProcesser) RemoveUdpConn(addr string) {
|
||||
up.mutex.Lock()
|
||||
defer up.mutex.Unlock()
|
||||
if c, ok := up.localUdpConns[addr]; ok {
|
||||
c.Close()
|
||||
}
|
||||
delete(up.localUdpConns, addr)
|
||||
}
|
@ -1,227 +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 metric
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/fatedier/frp/src/models/consts"
|
||||
)
|
||||
|
||||
var (
|
||||
DailyDataKeepDays int = 7
|
||||
ServerMetricInfoMap map[string]*ServerMetric
|
||||
smMutex sync.RWMutex
|
||||
)
|
||||
|
||||
type ServerMetric struct {
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
BindAddr string `json:"bind_addr"`
|
||||
ListenPort int64 `json:"listen_port"`
|
||||
CustomDomains []string `json:"custom_domains"`
|
||||
Locations []string `json:"locations"`
|
||||
Status string `json:"status"`
|
||||
UseEncryption bool `json:"use_encryption"`
|
||||
UseGzip bool `json:"use_gzip"`
|
||||
PrivilegeMode bool `json:"privilege_mode"`
|
||||
|
||||
// statistics
|
||||
CurrentConns int64 `json:"current_conns"`
|
||||
Daily []*DailyServerStats `json:"daily"`
|
||||
mutex sync.RWMutex
|
||||
}
|
||||
|
||||
type DailyServerStats struct {
|
||||
Time string `json:"time"`
|
||||
FlowIn int64 `json:"flow_in"`
|
||||
FlowOut int64 `json:"flow_out"`
|
||||
TotalAcceptConns int64 `json:"total_accept_conns"`
|
||||
}
|
||||
|
||||
// for sort
|
||||
type ServerMetricList []*ServerMetric
|
||||
|
||||
func (l ServerMetricList) Len() int { return len(l) }
|
||||
func (l ServerMetricList) Less(i, j int) bool { return l[i].Name < l[j].Name }
|
||||
func (l ServerMetricList) Swap(i, j int) { l[i], l[j] = l[j], l[i] }
|
||||
|
||||
func init() {
|
||||
ServerMetricInfoMap = make(map[string]*ServerMetric)
|
||||
}
|
||||
|
||||
func (s *ServerMetric) clone() *ServerMetric {
|
||||
copy := *s
|
||||
copy.CustomDomains = make([]string, len(s.CustomDomains))
|
||||
var i int
|
||||
for i = range copy.CustomDomains {
|
||||
copy.CustomDomains[i] = s.CustomDomains[i]
|
||||
}
|
||||
|
||||
copy.Daily = make([]*DailyServerStats, len(s.Daily))
|
||||
for i = range copy.Daily {
|
||||
tmpDaily := *s.Daily[i]
|
||||
copy.Daily[i] = &tmpDaily
|
||||
}
|
||||
return ©
|
||||
}
|
||||
|
||||
func GetAllProxyMetrics() []*ServerMetric {
|
||||
result := make(ServerMetricList, 0)
|
||||
smMutex.RLock()
|
||||
for _, metric := range ServerMetricInfoMap {
|
||||
metric.mutex.RLock()
|
||||
tmpMetric := metric.clone()
|
||||
metric.mutex.RUnlock()
|
||||
result = append(result, tmpMetric)
|
||||
}
|
||||
smMutex.RUnlock()
|
||||
|
||||
// sort for result by proxy name
|
||||
sort.Sort(result)
|
||||
return result
|
||||
}
|
||||
|
||||
// if proxyName isn't exist, return nil
|
||||
func GetProxyMetrics(proxyName string) *ServerMetric {
|
||||
smMutex.RLock()
|
||||
defer smMutex.RUnlock()
|
||||
metric, ok := ServerMetricInfoMap[proxyName]
|
||||
if ok {
|
||||
metric.mutex.RLock()
|
||||
tmpMetric := metric.clone()
|
||||
metric.mutex.RUnlock()
|
||||
return tmpMetric
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func SetProxyInfo(proxyName string, proxyType, bindAddr string,
|
||||
useEncryption, useGzip, privilegeMode bool, customDomains []string,
|
||||
locations []string, listenPort int64) {
|
||||
smMutex.Lock()
|
||||
info, ok := ServerMetricInfoMap[proxyName]
|
||||
if !ok {
|
||||
info = &ServerMetric{}
|
||||
info.Daily = make([]*DailyServerStats, 0)
|
||||
}
|
||||
info.Name = proxyName
|
||||
info.Type = proxyType
|
||||
info.UseEncryption = useEncryption
|
||||
info.UseGzip = useGzip
|
||||
info.PrivilegeMode = privilegeMode
|
||||
info.BindAddr = bindAddr
|
||||
info.ListenPort = listenPort
|
||||
info.CustomDomains = customDomains
|
||||
info.Locations = locations
|
||||
ServerMetricInfoMap[proxyName] = info
|
||||
smMutex.Unlock()
|
||||
}
|
||||
|
||||
func SetStatus(proxyName string, status int64) {
|
||||
smMutex.RLock()
|
||||
metric, ok := ServerMetricInfoMap[proxyName]
|
||||
smMutex.RUnlock()
|
||||
if ok {
|
||||
metric.mutex.Lock()
|
||||
metric.Status = consts.StatusStr[status]
|
||||
metric.mutex.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
type DealFuncType func(*DailyServerStats)
|
||||
|
||||
func DealDailyData(dailyData []*DailyServerStats, fn DealFuncType) (newDailyData []*DailyServerStats) {
|
||||
now := time.Now().Format("20060102")
|
||||
dailyLen := len(dailyData)
|
||||
if dailyLen == 0 {
|
||||
daily := &DailyServerStats{}
|
||||
daily.Time = now
|
||||
fn(daily)
|
||||
dailyData = append(dailyData, daily)
|
||||
} else {
|
||||
daily := dailyData[dailyLen-1]
|
||||
if daily.Time == now {
|
||||
fn(daily)
|
||||
} else {
|
||||
newDaily := &DailyServerStats{}
|
||||
newDaily.Time = now
|
||||
fn(newDaily)
|
||||
if dailyLen == DailyDataKeepDays {
|
||||
for i := 0; i < dailyLen-1; i++ {
|
||||
dailyData[i] = dailyData[i+1]
|
||||
}
|
||||
dailyData[dailyLen-1] = newDaily
|
||||
} else {
|
||||
dailyData = append(dailyData, newDaily)
|
||||
}
|
||||
}
|
||||
}
|
||||
return dailyData
|
||||
}
|
||||
|
||||
func OpenConnection(proxyName string) {
|
||||
smMutex.RLock()
|
||||
metric, ok := ServerMetricInfoMap[proxyName]
|
||||
smMutex.RUnlock()
|
||||
if ok {
|
||||
metric.mutex.Lock()
|
||||
metric.CurrentConns++
|
||||
metric.Daily = DealDailyData(metric.Daily, func(stats *DailyServerStats) {
|
||||
stats.TotalAcceptConns++
|
||||
})
|
||||
metric.mutex.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
func CloseConnection(proxyName string) {
|
||||
smMutex.RLock()
|
||||
metric, ok := ServerMetricInfoMap[proxyName]
|
||||
smMutex.RUnlock()
|
||||
if ok {
|
||||
metric.mutex.Lock()
|
||||
metric.CurrentConns--
|
||||
metric.mutex.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
func AddFlowIn(proxyName string, value int64) {
|
||||
smMutex.RLock()
|
||||
metric, ok := ServerMetricInfoMap[proxyName]
|
||||
smMutex.RUnlock()
|
||||
if ok {
|
||||
metric.mutex.Lock()
|
||||
metric.Daily = DealDailyData(metric.Daily, func(stats *DailyServerStats) {
|
||||
stats.FlowIn += value
|
||||
})
|
||||
metric.mutex.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
func AddFlowOut(proxyName string, value int64) {
|
||||
smMutex.RLock()
|
||||
metric, ok := ServerMetricInfoMap[proxyName]
|
||||
smMutex.RUnlock()
|
||||
if ok {
|
||||
metric.mutex.Lock()
|
||||
metric.Daily = DealDailyData(metric.Daily, func(stats *DailyServerStats) {
|
||||
stats.FlowOut += value
|
||||
})
|
||||
metric.mutex.Unlock()
|
||||
}
|
||||
}
|
@ -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"`
|
||||
}
|
@ -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
|
||||
}
|
@ -1,72 +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 (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"net"
|
||||
)
|
||||
|
||||
type UdpPacket struct {
|
||||
Content []byte `json:"-"`
|
||||
Src *net.UDPAddr `json:"-"`
|
||||
Dst *net.UDPAddr `json:"-"`
|
||||
|
||||
EncodeContent string `json:"content"`
|
||||
SrcStr string `json:"src"`
|
||||
DstStr string `json:"dst"`
|
||||
}
|
||||
|
||||
func NewUdpPacket(content []byte, src, dst *net.UDPAddr) *UdpPacket {
|
||||
up := &UdpPacket{
|
||||
Src: src,
|
||||
Dst: dst,
|
||||
EncodeContent: base64.StdEncoding.EncodeToString(content),
|
||||
SrcStr: src.String(),
|
||||
DstStr: dst.String(),
|
||||
}
|
||||
return up
|
||||
}
|
||||
|
||||
// parse one udp packet struct to bytes
|
||||
func (up *UdpPacket) Pack() []byte {
|
||||
b, _ := json.Marshal(up)
|
||||
return b
|
||||
}
|
||||
|
||||
// parse from bytes to UdpPacket struct
|
||||
func (up *UdpPacket) UnPack(packet []byte) error {
|
||||
err := json.Unmarshal(packet, &up)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
up.Content, err = base64.StdEncoding.DecodeString(up.EncodeContent)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
up.Src, err = net.ResolveUDPAddr("udp", up.SrcStr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
up.Dst, err = net.ResolveUDPAddr("udp", up.DstStr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
@ -1,50 +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 (
|
||||
"net"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
var (
|
||||
content string = "udp packet test"
|
||||
src string = "1.1.1.1:1000"
|
||||
dst string = "2.2.2.2:2000"
|
||||
|
||||
udpMsg *UdpPacket
|
||||
)
|
||||
|
||||
func init() {
|
||||
srcAddr, _ := net.ResolveUDPAddr("udp", src)
|
||||
dstAddr, _ := net.ResolveUDPAddr("udp", dst)
|
||||
udpMsg = NewUdpPacket([]byte(content), srcAddr, dstAddr)
|
||||
}
|
||||
|
||||
func TestPack(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
msg := udpMsg.Pack()
|
||||
assert.Equal(string(msg), `{"content":"dWRwIHBhY2tldCB0ZXN0","src":"1.1.1.1:1000","dst":"2.2.2.2:2000"}`)
|
||||
}
|
||||
|
||||
func TestUnpack(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
udpMsg.UnPack([]byte(`{"content":"dWRwIHBhY2tldCB0ZXN0","src":"1.1.1.1:1000","dst":"2.2.2.2:2000"}`))
|
||||
assert.Equal(content, string(udpMsg.Content))
|
||||
assert.Equal(src, udpMsg.Src.String())
|
||||
assert.Equal(dst, udpMsg.Dst.String())
|
||||
}
|
@ -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
|
||||
}
|
@ -1,67 +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 (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/fatedier/frp/src/models/metric"
|
||||
"github.com/fatedier/frp/src/utils/log"
|
||||
)
|
||||
|
||||
type GeneralResponse struct {
|
||||
Code int64 `json:"code"`
|
||||
Msg string `json:"msg"`
|
||||
}
|
||||
|
||||
func apiReload(w http.ResponseWriter, r *http.Request) {
|
||||
var buf []byte
|
||||
res := &GeneralResponse{}
|
||||
defer func() {
|
||||
log.Info("Http response [/api/reload]: %s", string(buf))
|
||||
}()
|
||||
|
||||
log.Info("Http request: [/api/reload]")
|
||||
err := ReloadConf(ConfigFile)
|
||||
if err != nil {
|
||||
res.Code = 2
|
||||
res.Msg = fmt.Sprintf("%v", err)
|
||||
log.Error("frps reload error: %v", err)
|
||||
}
|
||||
|
||||
buf, _ = json.Marshal(res)
|
||||
w.Write(buf)
|
||||
}
|
||||
|
||||
type ProxiesResponse struct {
|
||||
Code int64 `json:"code"`
|
||||
Msg string `json:"msg"`
|
||||
Proxies []*metric.ServerMetric `json:"proxies"`
|
||||
}
|
||||
|
||||
func apiProxies(w http.ResponseWriter, r *http.Request) {
|
||||
var buf []byte
|
||||
res := &ProxiesResponse{}
|
||||
defer func() {
|
||||
log.Info("Http response [/api/proxies]: code [%d]", res.Code)
|
||||
}()
|
||||
|
||||
log.Info("Http request: [/api/proxies]")
|
||||
res.Proxies = metric.GetAllProxyMetrics()
|
||||
buf, _ = json.Marshal(res)
|
||||
w.Write(buf)
|
||||
}
|
@ -1,40 +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 (
|
||||
"html/template"
|
||||
"net/http"
|
||||
|
||||
"github.com/fatedier/frp/src/assets"
|
||||
"github.com/fatedier/frp/src/models/metric"
|
||||
"github.com/fatedier/frp/src/utils/log"
|
||||
)
|
||||
|
||||
func viewDashboard(w http.ResponseWriter, r *http.Request) {
|
||||
metrics := metric.GetAllProxyMetrics()
|
||||
dashboardTpl, err := assets.ReadFile("index.html")
|
||||
if err != nil {
|
||||
http.Error(w, "get dashboard template file error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
t := template.Must(template.New("index.html").Delims("<<<", ">>>").Parse(dashboardTpl))
|
||||
|
||||
err = t.Execute(w, metrics)
|
||||
if err != nil {
|
||||
log.Warn("parse template file [index.html] error: %v", err)
|
||||
http.Error(w, "parse template file error", http.StatusInternalServerError)
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
@ -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)
|
||||
}
|
@ -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))
|
||||
}
|
||||
}
|
@ -1,17 +0,0 @@
|
||||
[common]
|
||||
bind_addr = 0.0.0.0
|
||||
bind_port = 10700
|
||||
vhost_http_port = 10710
|
||||
log_file = ./frps.log
|
||||
log_level = debug
|
||||
|
||||
[echo]
|
||||
type = tcp
|
||||
auth_token = 123
|
||||
bind_addr = 0.0.0.0
|
||||
listen_port = 10711
|
||||
|
||||
[web]
|
||||
type = http
|
||||
auth_token = 123
|
||||
custom_domains = 127.0.0.1
|
@ -1,45 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/fatedier/frp/src/utils/conn"
|
||||
)
|
||||
|
||||
var (
|
||||
PORT int64 = 10701
|
||||
)
|
||||
|
||||
func main() {
|
||||
l, err := conn.Listen("0.0.0.0", PORT)
|
||||
if err != nil {
|
||||
fmt.Printf("echo server listen error: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
for {
|
||||
c, err := l.Accept()
|
||||
if err != nil {
|
||||
fmt.Printf("echo server accept error: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
go echoWorker(c)
|
||||
}
|
||||
}
|
||||
|
||||
func echoWorker(c *conn.Conn) {
|
||||
for {
|
||||
buff, err := c.ReadLine()
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
fmt.Printf("echo server read error: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
c.WriteString(buff)
|
||||
}
|
||||
}
|
@ -1,20 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
var (
|
||||
PORT int64 = 10702
|
||||
HTTP_RES_STR string = "Hello World"
|
||||
)
|
||||
|
||||
func main() {
|
||||
http.HandleFunc("/", request)
|
||||
http.ListenAndServe(fmt.Sprintf("0.0.0.0:%d", PORT), nil)
|
||||
}
|
||||
|
||||
func request(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write([]byte(HTTP_RES_STR))
|
||||
}
|
@ -1,34 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
./bin/echo_server &
|
||||
./bin/http_server &
|
||||
./../bin/frps -c ./conf/auto_test_frps.ini &
|
||||
sleep 1
|
||||
./../bin/frpc -c ./conf/auto_test_frpc.ini &
|
||||
|
||||
# wait until proxies are connected
|
||||
for((i=1; i<15; i++))
|
||||
do
|
||||
sleep 1
|
||||
str=`ss -ant|grep 10700|grep LISTEN`
|
||||
if [ -z "${str}" ]; then
|
||||
echo "wait"
|
||||
continue
|
||||
fi
|
||||
|
||||
str=`ss -ant|grep 10710|grep LISTEN`
|
||||
if [ -z "${str}" ]; then
|
||||
echo "wait"
|
||||
continue
|
||||
fi
|
||||
|
||||
str=`ss -ant|grep 10711|grep LISTEN`
|
||||
if [ -z "${str}" ]; then
|
||||
echo "wait"
|
||||
continue
|
||||
fi
|
||||
|
||||
break
|
||||
done
|
||||
|
||||
sleep 1
|
@ -1,15 +1,5 @@
|
||||
#!/bin/bash
|
||||
|
||||
pid=`ps aux|grep './bin/echo_server'|grep -v grep|awk {'print $2'}`
|
||||
if [ -n "${pid}" ]; then
|
||||
kill ${pid}
|
||||
fi
|
||||
|
||||
pid=`ps aux|grep './bin/http_server'|grep -v grep|awk {'print $2'}`
|
||||
if [ -n "${pid}" ]; then
|
||||
kill ${pid}
|
||||
fi
|
||||
|
||||
pid=`ps aux|grep './../bin/frps -c ./conf/auto_test_frps.ini'|grep -v grep|awk {'print $2'}`
|
||||
if [ -n "${pid}" ]; then
|
||||
kill ${pid}
|
@ -4,17 +4,26 @@ server_port = 10700
|
||||
log_file = ./frpc.log
|
||||
# debug, info, warn, error
|
||||
log_level = debug
|
||||
auth_token = 123
|
||||
privilege_token = 123456
|
||||
|
||||
[echo]
|
||||
type = tcp
|
||||
local_ip = 127.0.0.1
|
||||
local_port = 10701
|
||||
remote_port = 10711
|
||||
use_encryption = true
|
||||
use_gzip = true
|
||||
use_compression = true
|
||||
|
||||
[web]
|
||||
type = http
|
||||
local_ip = 127.0.0.1
|
||||
local_port = 10702
|
||||
use_gzip = true
|
||||
use_encryption = true
|
||||
use_compression = true
|
||||
custom_domains = 127.0.0.1
|
||||
|
||||
[udp]
|
||||
type = udp
|
||||
local_ip = 127.0.0.1
|
||||
local_port = 10703
|
||||
remote_port = 10712
|
7
tests/conf/auto_test_frps.ini
Normal file
7
tests/conf/auto_test_frps.ini
Normal file
@ -0,0 +1,7 @@
|
||||
[common]
|
||||
bind_addr = 0.0.0.0
|
||||
bind_port = 10700
|
||||
vhost_http_port = 10710
|
||||
log_file = ./frps.log
|
||||
log_level = debug
|
||||
privilege_token = 123456
|
61
tests/echo_server.go
Normal file
61
tests/echo_server.go
Normal file
@ -0,0 +1,61 @@
|
||||
package tests
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/fatedier/frp/utils/net"
|
||||
)
|
||||
|
||||
func StartEchoServer() {
|
||||
l, err := net.ListenTcp("127.0.0.1", 10701)
|
||||
if err != nil {
|
||||
fmt.Printf("echo server listen error: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
for {
|
||||
c, err := l.Accept()
|
||||
if err != nil {
|
||||
fmt.Printf("echo server accept error: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
go echoWorker(c)
|
||||
}
|
||||
}
|
||||
|
||||
func StartUdpEchoServer() {
|
||||
l, err := net.ListenUDP("127.0.0.1", 10703)
|
||||
if err != nil {
|
||||
fmt.Printf("udp echo server listen error: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
for {
|
||||
c, err := l.Accept()
|
||||
if err != nil {
|
||||
fmt.Printf("udp echo server accept error: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
go echoWorker(c)
|
||||
}
|
||||
}
|
||||
|
||||
func echoWorker(c net.Conn) {
|
||||
br := bufio.NewReader(c)
|
||||
for {
|
||||
buf, err := br.ReadString('\n')
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
fmt.Printf("echo server read error: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
c.Write([]byte(buf + "\n"))
|
||||
}
|
||||
}
|
@ -1,40 +1,52 @@
|
||||
package test
|
||||
package tests
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/fatedier/frp/src/utils/conn"
|
||||
frpNet "github.com/fatedier/frp/utils/net"
|
||||
)
|
||||
|
||||
var (
|
||||
ECHO_PORT int64 = 10711
|
||||
UDP_ECHO_PORT int64 = 10712
|
||||
HTTP_PORT int64 = 10710
|
||||
ECHO_TEST_STR string = "Hello World\n"
|
||||
HTTP_RES_STR string = "Hello World"
|
||||
)
|
||||
|
||||
func init() {
|
||||
go StartEchoServer()
|
||||
go StartUdpEchoServer()
|
||||
go StartHttpServer()
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
}
|
||||
|
||||
func TestEchoServer(t *testing.T) {
|
||||
c, err := conn.ConnectServer(fmt.Sprintf("0.0.0.0:%d", ECHO_PORT))
|
||||
c, err := frpNet.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 + "\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"))
|
||||
}
|
||||
}
|
||||
|
||||
@ -58,3 +70,28 @@ func TestHttpServer(t *testing.T) {
|
||||
t.Fatalf("http code from http server error [%d]", res.StatusCode)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUdpEchoServer(t *testing.T) {
|
||||
addr, err := net.ResolveUDPAddr("udp", "127.0.0.1:10712")
|
||||
if err != nil {
|
||||
t.Fatalf("do udp request error: %v", err)
|
||||
}
|
||||
conn, err := net.DialUDP("udp", nil, addr)
|
||||
if err != nil {
|
||||
t.Fatalf("dial udp server error: %v", err)
|
||||
}
|
||||
defer conn.Close()
|
||||
_, err = conn.Write([]byte("hello frp\n"))
|
||||
if err != nil {
|
||||
t.Fatalf("write to udp server error: %v", err)
|
||||
}
|
||||
data := make([]byte, 20)
|
||||
n, err := conn.Read(data)
|
||||
if err != nil {
|
||||
t.Fatalf("read from udp server error: %v", err)
|
||||
}
|
||||
|
||||
if string(bytes.TrimSpace(data[:n])) != "hello frp" {
|
||||
t.Fatalf("message got from udp server error, get %s", string(data[:n-1]))
|
||||
}
|
||||
}
|
15
tests/http_server.go
Normal file
15
tests/http_server.go
Normal file
@ -0,0 +1,15 @@
|
||||
package tests
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func StartHttpServer() {
|
||||
http.HandleFunc("/", request)
|
||||
http.ListenAndServe(fmt.Sprintf("0.0.0.0:%d", 10702), nil)
|
||||
}
|
||||
|
||||
func request(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write([]byte(HTTP_RES_STR))
|
||||
}
|
8
tests/run_test.sh
Executable file
8
tests/run_test.sh
Executable file
@ -0,0 +1,8 @@
|
||||
#!/bin/bash
|
||||
|
||||
./../bin/frps -c ./conf/auto_test_frps.ini &
|
||||
sleep 1
|
||||
./../bin/frpc -c ./conf/auto_test_frpc.ini &
|
||||
|
||||
# wait until proxies are connected
|
||||
sleep 2
|
41
utils/crypto/crypto_test.go
Normal file
41
utils/crypto/crypto_test.go
Normal file
@ -0,0 +1,41 @@
|
||||
// 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 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)
|
||||
decReader := NewReader(buffer, []byte(key))
|
||||
|
||||
encWriter.Write([]byte(text))
|
||||
|
||||
c := bytes.NewBuffer(nil)
|
||||
io.Copy(c, decReader)
|
||||
assert.Equal(text, string(c.Bytes()))
|
||||
}
|
75
utils/crypto/decode.go
Normal file
75
utils/crypto/decode.go
Normal file
@ -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 {
|
||||
key = pbkdf2.Key(key, []byte(salt), 64, aes.BlockSize, sha1.New)
|
||||
|
||||
return &Reader{
|
||||
r: r,
|
||||
key: key,
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
89
utils/crypto/encode.go
Normal file
89
utils/crypto/encode.go
Normal file
@ -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 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) {
|
||||
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
|
||||
}
|
@ -12,19 +12,25 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package config
|
||||
package errors
|
||||
|
||||
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 (
|
||||
"errors"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrMsgType = errors.New("message type error")
|
||||
ErrCtlClosed = errors.New("control is closed")
|
||||
)
|
||||
|
||||
func PanicToError(fn func()) (err error) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
err = fmt.Errorf("Panic error: %v", r)
|
||||
}
|
||||
}()
|
||||
|
||||
fn()
|
||||
return
|
||||
}
|
16
utils/errors/errors_test.go
Normal file
16
utils/errors/errors_test.go
Normal file
@ -0,0 +1,16 @@
|
||||
package errors
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestPanicToError(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
err := PanicToError(func() {
|
||||
panic("test error")
|
||||
})
|
||||
assert.Contains(err.Error(), "test error")
|
||||
}
|
@ -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)
|
||||
}
|
||||
@ -54,6 +55,8 @@ func SetLogLevel(logLevel string) {
|
||||
level = 6
|
||||
case "debug":
|
||||
level = 7
|
||||
case "trace":
|
||||
level = 8
|
||||
default:
|
||||
level = 4
|
||||
}
|
||||
@ -61,6 +64,7 @@ func SetLogLevel(logLevel string) {
|
||||
}
|
||||
|
||||
// wrap log
|
||||
|
||||
func Error(format string, v ...interface{}) {
|
||||
Log.Error(format, v...)
|
||||
}
|
||||
@ -76,3 +80,70 @@ func Info(format string, v ...interface{}) {
|
||||
func Debug(format string, v ...interface{}) {
|
||||
Log.Debug(format, v...)
|
||||
}
|
||||
|
||||
func Trace(format string, v ...interface{}) {
|
||||
Log.Trace(format, v...)
|
||||
}
|
||||
|
||||
// Logger
|
||||
type Logger interface {
|
||||
AddLogPrefix(string)
|
||||
GetAllPrefix() []string
|
||||
ClearLogPrefix()
|
||||
Error(string, ...interface{})
|
||||
Warn(string, ...interface{})
|
||||
Info(string, ...interface{})
|
||||
Debug(string, ...interface{})
|
||||
Trace(string, ...interface{})
|
||||
}
|
||||
|
||||
type PrefixLogger struct {
|
||||
prefix string
|
||||
allPrefix []string
|
||||
}
|
||||
|
||||
func NewPrefixLogger(prefix string) *PrefixLogger {
|
||||
logger := &PrefixLogger{
|
||||
allPrefix: make([]string, 0),
|
||||
}
|
||||
logger.AddLogPrefix(prefix)
|
||||
return logger
|
||||
}
|
||||
|
||||
func (pl *PrefixLogger) AddLogPrefix(prefix string) {
|
||||
if len(prefix) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
pl.prefix += "[" + prefix + "] "
|
||||
pl.allPrefix = append(pl.allPrefix, prefix)
|
||||
}
|
||||
|
||||
func (pl *PrefixLogger) GetAllPrefix() []string {
|
||||
return pl.allPrefix
|
||||
}
|
||||
|
||||
func (pl *PrefixLogger) ClearLogPrefix() {
|
||||
pl.prefix = ""
|
||||
pl.allPrefix = make([]string, 0)
|
||||
}
|
||||
|
||||
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...)
|
||||
}
|
||||
|
||||
func (pl *PrefixLogger) Trace(format string, v ...interface{}) {
|
||||
Log.Trace(pl.prefix+format, v...)
|
||||
}
|
60
utils/metric/counter.go
Normal file
60
utils/metric/counter.go
Normal file
@ -0,0 +1,60 @@
|
||||
// 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 metric
|
||||
|
||||
import (
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
type Counter interface {
|
||||
Count() int64
|
||||
Inc(int64)
|
||||
Dec(int64)
|
||||
Snapshot() Counter
|
||||
Clear()
|
||||
}
|
||||
|
||||
func NewCounter() Counter {
|
||||
return &StandardCounter{
|
||||
count: 0,
|
||||
}
|
||||
}
|
||||
|
||||
type StandardCounter struct {
|
||||
count int64
|
||||
}
|
||||
|
||||
func (c *StandardCounter) Count() int64 {
|
||||
return atomic.LoadInt64(&c.count)
|
||||
}
|
||||
|
||||
func (c *StandardCounter) Inc(count int64) {
|
||||
atomic.AddInt64(&c.count, count)
|
||||
}
|
||||
|
||||
func (c *StandardCounter) Dec(count int64) {
|
||||
atomic.AddInt64(&c.count, -count)
|
||||
}
|
||||
|
||||
func (c *StandardCounter) Snapshot() Counter {
|
||||
tmp := &StandardCounter{
|
||||
count: atomic.LoadInt64(&c.count),
|
||||
}
|
||||
return tmp
|
||||
}
|
||||
|
||||
func (c *StandardCounter) Clear() {
|
||||
atomic.StoreInt64(&c.count, 0)
|
||||
}
|
23
utils/metric/counter_test.go
Normal file
23
utils/metric/counter_test.go
Normal file
@ -0,0 +1,23 @@
|
||||
package metric
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestCounter(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
c := NewCounter()
|
||||
c.Inc(10)
|
||||
assert.EqualValues(10, c.Count())
|
||||
|
||||
c.Dec(5)
|
||||
assert.EqualValues(5, c.Count())
|
||||
|
||||
cTmp := c.Snapshot()
|
||||
assert.EqualValues(5, cTmp.Count())
|
||||
|
||||
c.Clear()
|
||||
assert.EqualValues(0, c.Count())
|
||||
}
|
134
utils/metric/date_counter.go
Normal file
134
utils/metric/date_counter.go
Normal file
@ -0,0 +1,134 @@
|
||||
// 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 metric
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type DateCounter interface {
|
||||
TodayCount() int64
|
||||
GetLastDaysCount(lastdays int64) []int64
|
||||
Inc(int64)
|
||||
Dec(int64)
|
||||
Snapshot() DateCounter
|
||||
Clear()
|
||||
}
|
||||
|
||||
func NewDateCounter(reserveDays int64) DateCounter {
|
||||
if reserveDays <= 0 {
|
||||
reserveDays = 1
|
||||
}
|
||||
return newStandardDateCounter(reserveDays)
|
||||
}
|
||||
|
||||
type StandardDateCounter struct {
|
||||
reserveDays int64
|
||||
counts []int64
|
||||
|
||||
lastUpdateDate time.Time
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
func newStandardDateCounter(reserveDays int64) *StandardDateCounter {
|
||||
now := time.Now()
|
||||
now = time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location())
|
||||
s := &StandardDateCounter{
|
||||
reserveDays: reserveDays,
|
||||
counts: make([]int64, reserveDays),
|
||||
lastUpdateDate: now,
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func (c *StandardDateCounter) TodayCount() int64 {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
|
||||
c.rotate(time.Now())
|
||||
return c.counts[0]
|
||||
}
|
||||
|
||||
func (c *StandardDateCounter) GetLastDaysCount(lastdays int64) []int64 {
|
||||
if lastdays > c.reserveDays {
|
||||
lastdays = c.reserveDays
|
||||
}
|
||||
counts := make([]int64, lastdays)
|
||||
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
c.rotate(time.Now())
|
||||
for i := 0; i < int(lastdays); i++ {
|
||||
counts[i] = c.counts[i]
|
||||
}
|
||||
return counts
|
||||
}
|
||||
|
||||
func (c *StandardDateCounter) Inc(count int64) {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
c.rotate(time.Now())
|
||||
c.counts[0] += count
|
||||
}
|
||||
|
||||
func (c *StandardDateCounter) Dec(count int64) {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
c.rotate(time.Now())
|
||||
c.counts[0] -= count
|
||||
}
|
||||
|
||||
func (c *StandardDateCounter) Snapshot() DateCounter {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
tmp := newStandardDateCounter(c.reserveDays)
|
||||
for i := 0; i < int(c.reserveDays); i++ {
|
||||
tmp.counts[i] = c.counts[i]
|
||||
}
|
||||
return tmp
|
||||
}
|
||||
|
||||
func (c *StandardDateCounter) Clear() {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
for i := 0; i < int(c.reserveDays); i++ {
|
||||
c.counts[i] = 0
|
||||
}
|
||||
}
|
||||
|
||||
// rotate
|
||||
// Must hold the lock before calling this function.
|
||||
func (c *StandardDateCounter) rotate(now time.Time) {
|
||||
now = time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location())
|
||||
days := int(now.Sub(c.lastUpdateDate).Hours() / 24)
|
||||
|
||||
defer func() {
|
||||
c.lastUpdateDate = now
|
||||
}()
|
||||
|
||||
if days <= 0 {
|
||||
return
|
||||
} else if days >= int(c.reserveDays) {
|
||||
c.counts = make([]int64, c.reserveDays)
|
||||
return
|
||||
}
|
||||
newCounts := make([]int64, c.reserveDays)
|
||||
|
||||
for i := days; i < int(c.reserveDays); i++ {
|
||||
newCounts[i] = c.counts[i-days]
|
||||
}
|
||||
c.counts = newCounts
|
||||
}
|
27
utils/metric/date_counter_test.go
Normal file
27
utils/metric/date_counter_test.go
Normal file
@ -0,0 +1,27 @@
|
||||
package metric
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestDateCounter(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
dc := NewDateCounter(3)
|
||||
dc.Inc(10)
|
||||
assert.EqualValues(10, dc.TodayCount())
|
||||
|
||||
dc.Dec(5)
|
||||
assert.EqualValues(5, dc.TodayCount())
|
||||
|
||||
counts := dc.GetLastDaysCount(3)
|
||||
assert.EqualValues(3, len(counts))
|
||||
assert.EqualValues(5, counts[0])
|
||||
assert.EqualValues(0, counts[1])
|
||||
assert.EqualValues(0, counts[2])
|
||||
|
||||
dcTmp := dc.Snapshot()
|
||||
assert.EqualValues(5, dcTmp.TodayCount())
|
||||
}
|
@ -12,18 +12,22 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package conn
|
||||
package net
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
|
||||
"github.com/fatedier/frp/utils/log"
|
||||
)
|
||||
|
||||
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)
|
||||
return
|
||||
// Conn is the interface of connections used in frp.
|
||||
type Conn interface {
|
||||
net.Conn
|
||||
log.Logger
|
||||
}
|
||||
|
||||
type Listener interface {
|
||||
Accept() (Conn, error)
|
||||
Close() error
|
||||
log.Logger
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user