mirror of
https://github.com/fatedier/frp.git
synced 2025-07-29 09:18:11 +00:00
Compare commits
280 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
ad3cf9a64a | ||
|
e3fc73dbc5 | ||
|
f884e894f2 | ||
|
d57ed7d3d8 | ||
|
a2c318d24c | ||
|
32f8745d61 | ||
|
66120fe49d | ||
|
fca7f42b37 | ||
|
5b303f5148 | ||
|
2a044c9d6d | ||
|
70e2aee46d | ||
|
6742fa2ea8 | ||
|
511503d34c | ||
|
1eaf17fd05 | ||
|
04f4fd0a81 | ||
|
3a4d769bb3 | ||
|
84341b7fcc | ||
|
80ba931326 | ||
|
7ebcc7503a | ||
|
74cf57feb3 | ||
|
712afed0ab | ||
|
e29a1330ed | ||
|
44971c7918 | ||
|
7bc6c72844 | ||
|
93461e0094 | ||
|
03d55201b2 | ||
|
e6d82f3162 | ||
|
1af6276be9 | ||
|
d1f5ec083a | ||
|
716ec281f6 | ||
|
67bfae5d23 | ||
|
f0dc3ed47b | ||
|
08b0885564 | ||
|
49b503c17b | ||
|
150682ec63 | ||
|
4dc96f41c9 | ||
|
6c13b6d37a | ||
|
1c04de380d | ||
|
738e5dad22 | ||
|
6d81e4c8c6 | ||
|
faf584e1dd | ||
|
ba6afd5789 | ||
|
11260389a1 | ||
|
b8082e6e08 | ||
|
7957572ced | ||
|
c2ff37d0d8 | ||
|
c67f9d5e76 | ||
|
1cc61b60f9 | ||
|
9c38baeb9e | ||
|
84465a7463 | ||
|
3fe50df200 | ||
|
93d86ca635 | ||
|
b600a07ec0 | ||
|
a5f06489cb | ||
|
2883d70ea9 | ||
|
3f17837a2c | ||
|
fd268b5082 | ||
|
69b09eb8a2 | ||
|
a84dd05351 | ||
|
71f7caa1ee | ||
|
5360febd72 | ||
|
1b70f0c4fd | ||
|
5c75efa222 | ||
|
ab4a53965b | ||
|
a0c83bdb78 | ||
|
30aeaf968e | ||
|
58d0d41501 | ||
|
6a95a63fd4 | ||
|
aa185eb9f3 | ||
|
d8683a0079 | ||
|
8b2cde3a30 | ||
|
634e048d0c | ||
|
a4fece3f51 | ||
|
9e683fe446 | ||
|
54bbfe26b0 | ||
|
a1023fdfc2 | ||
|
b02e1007fb | ||
|
f90028cf96 | ||
|
f83a2a73ab | ||
|
307b74cc13 | ||
|
f00a28598f | ||
|
6ee0b25782 | ||
|
88083d21e8 | ||
|
a22440aade | ||
|
b006540141 | ||
|
e655f07674 | ||
|
aafa96db58 | ||
|
1325148cd3 | ||
|
3f9749488a | ||
|
f9a0d891a1 | ||
|
92daa45b68 | ||
|
5f20a22b0d | ||
|
63be94c611 | ||
|
694ee44af6 | ||
|
edb97abf50 | ||
|
0c10279deb | ||
|
1f49510e3e | ||
|
1868b3bafb | ||
|
a23521885c | ||
|
c80dcd050d | ||
|
043ab62587 | ||
|
a8969b1901 | ||
|
e26285eefc | ||
|
299bd7b5cb | ||
|
90d1384bf7 | ||
|
a5434e31b7 | ||
|
044bb692dc | ||
|
34b98dde52 | ||
|
020f786bf5 | ||
|
cdcc1240ec | ||
|
c2c9f68a00 | ||
|
37470c26f0 | ||
|
04a4591caa | ||
|
8bf61d5e39 | ||
|
659f84bab2 | ||
|
9faf4acd62 | ||
|
4c3fb22295 | ||
|
d243f70125 | ||
|
a56f068f8c | ||
|
6a6ccc5302 | ||
|
6f90c3400c | ||
|
eb4f779384 | ||
|
59a34b81e0 | ||
|
b1d1a7a20a | ||
|
6b34ed4644 | ||
|
dde734c953 | ||
|
5532881b09 | ||
|
94ddeebc21 | ||
|
ddbb56ee8f | ||
|
10fc6c67e0 | ||
|
0573ddcd84 | ||
|
5eb5fec761 | ||
|
52fe721202 | ||
|
d7d2b72431 | ||
|
d04d31b39a | ||
|
d9304d8166 | ||
|
a44be1e2ed | ||
|
2bf1d3e922 | ||
|
19f349a65e | ||
|
b0e56945cd | ||
|
f2999e3317 | ||
|
a4c05e6ff9 | ||
|
d93dd82ed9 | ||
|
edf4bc431d | ||
|
47db75e921 | ||
|
c702355669 | ||
|
7cc5d03f35 | ||
|
54beb19435 | ||
|
396e148f80 | ||
|
4c69a4810e | ||
|
40e023f5f4 | ||
|
adcb2c1ea5 | ||
|
8c497793c5 | ||
|
78c6845781 | ||
|
dc5e130d33 | ||
|
fbc504dfa3 | ||
|
77f207d69a | ||
|
b65e037b5e | ||
|
b8a28e945c | ||
|
0476a85a7d | ||
|
5661537f7c | ||
|
19f7950485 | ||
|
c21f8ad291 | ||
|
3d6578b15f | ||
|
899d6837df | ||
|
0e1752b5ce | ||
|
da182ecd81 | ||
|
94c7f57949 | ||
|
c8e5096f48 | ||
|
5079bf01fd | ||
|
603d7df49a | ||
|
2b1c39e03d | ||
|
46ee2f2bc8 | ||
|
3d5c3acee0 | ||
|
41fd4bb673 | ||
|
e1e18ba9d6 | ||
|
ab9eff97a8 | ||
|
6f40b1a70a | ||
|
87c9b8f548 | ||
|
3fcf7efc5a | ||
|
a655f5699b | ||
|
09624b56ca | ||
|
e262ac6abd | ||
|
47c1a3e52c | ||
|
4dadaac905 | ||
|
e1ed6660b0 | ||
|
b71b2cf46d | ||
|
a0903d4121 | ||
|
b403e4142b | ||
|
46716acd8e | ||
|
c7f85bcdd3 | ||
|
ddd2acfe9f | ||
|
e3bf7e2b2b | ||
|
4914472215 | ||
|
5d9300c1e9 | ||
|
b4a577b0d7 | ||
|
32d0ce9ea0 | ||
|
2d30a6e8a7 | ||
|
740691b080 | ||
|
11fe4b1d8b | ||
|
c64931fce9 | ||
|
d4ecc2218d | ||
|
9c0ca8675d | ||
|
5cdb84c666 | ||
|
060277308b | ||
|
31dfd5101f | ||
|
4300169041 | ||
|
3ab9850871 | ||
|
d813b953dd | ||
|
1da81ad7d3 | ||
|
3b06d771ac | ||
|
7f386fc042 | ||
|
df8edefa56 | ||
|
ecb6ad4885 | ||
|
785dcaad44 | ||
|
fd3c97a0e9 | ||
|
8f5f0b0a9a | ||
|
452e02adab | ||
|
d2e1cfa5bc | ||
|
6dd51e0951 | ||
|
e0f2993b70 | ||
|
4067591a4d | ||
|
926d0b74a9 | ||
|
4f49458af0 | ||
|
fd6b94908b | ||
|
dee4cbd48c | ||
|
9a3564f29c | ||
|
ac09ba3982 | ||
|
a9bf25f255 | ||
|
6bc05de58e | ||
|
5265b79957 | ||
|
fefc0a38c3 | ||
|
c387138006 | ||
|
36f8beee3d | ||
|
366a0c898d | ||
|
d747f9207e | ||
|
5400366036 | ||
|
9dae7ad6fe | ||
|
a4e051d494 | ||
|
28251a8104 | ||
|
e99357da4e | ||
|
e580c7b6e6 | ||
|
ba74934a1f | ||
|
1bad5c6561 | ||
|
f968f3eace | ||
|
b14441d5cd | ||
|
0a50c3bd82 | ||
|
ef5702213f | ||
|
c5e4b24f8f | ||
|
1987a399c1 | ||
|
ab6c5c813e | ||
|
51eaec14ab | ||
|
f3876d69bb | ||
|
817f4463f4 | ||
|
654981019d | ||
|
740fb05b21 | ||
|
e8c830e5c8 | ||
|
2640c0b570 | ||
|
04014bb78f | ||
|
150c4beef8 | ||
|
5febee6201 | ||
|
ee8786a6b3 | ||
|
d569a60eff | ||
|
14607b352d | ||
|
bc7ad2bb20 | ||
|
cd59bbdad6 | ||
|
f404a0a5ee | ||
|
da7c473288 | ||
|
ea323084ad | ||
|
c680d87edc | ||
|
d3c4401473 | ||
|
7a9a675d58 | ||
|
040841db48 | ||
|
d39d745e43 | ||
|
c10321ead6 | ||
|
d7797cbd18 | ||
|
0b9d823168 | ||
|
deb750652f | ||
|
14ba38a1b4 | ||
|
f650d3f330 |
5
.dockerignore
Normal file
5
.dockerignore
Normal file
@@ -0,0 +1,5 @@
|
||||
Dockerfile
|
||||
.git
|
||||
*~
|
||||
*#
|
||||
.#*
|
30
.github/ISSUE_TEMPLATE
vendored
Normal file
30
.github/ISSUE_TEMPLATE
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
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.
|
||||
(为了节约时间,提高处理问题的效率,不按照格式填写的 issue 将会直接关闭。)
|
||||
|
||||
Use the commands below to provide key information from your environment:
|
||||
You do NOT have to include this information if this is a FEATURE REQUEST
|
||||
|
||||
**What version of frp are you using (./frpc -v or ./frps -v)?**
|
||||
|
||||
|
||||
**What operating system and processor architecture are you using (`go env`)?**
|
||||
|
||||
|
||||
**Configures you used:**
|
||||
|
||||
|
||||
**Steps to reproduce the issue:**
|
||||
1.
|
||||
2.
|
||||
3.
|
||||
|
||||
**Describe the results you received:**
|
||||
|
||||
|
||||
**Describe the results you expected:**
|
||||
|
||||
|
||||
**Additional information you deem important (e.g. issue happens only occasionally):**
|
||||
|
||||
|
||||
**Can you point out what caused this issue (optional)**
|
1
.gitignore
vendored
1
.gitignore
vendored
@@ -26,6 +26,7 @@ _testmain.go
|
||||
# Self
|
||||
bin/
|
||||
packages/
|
||||
test/bin/
|
||||
|
||||
# Cache
|
||||
*.swp
|
||||
|
@@ -2,11 +2,10 @@ sudo: false
|
||||
language: go
|
||||
|
||||
go:
|
||||
- 1.4.2
|
||||
- 1.5.3
|
||||
- 1.8.x
|
||||
|
||||
install:
|
||||
- make
|
||||
|
||||
script:
|
||||
- make test
|
||||
- make alltest
|
||||
|
17
Dockerfile
Normal file
17
Dockerfile
Normal file
@@ -0,0 +1,17 @@
|
||||
FROM golang:1.8
|
||||
|
||||
COPY . /go/src/github.com/fatedier/frp
|
||||
|
||||
RUN cd /go/src/github.com/fatedier/frp \
|
||||
&& make \
|
||||
&& mv bin/frpc /frpc \
|
||||
&& mv bin/frps /frps \
|
||||
&& mv conf/frpc_min.ini /frpc.ini \
|
||||
&& mv conf/frps_min.ini /frps.ini \
|
||||
&& make clean
|
||||
|
||||
WORKDIR /
|
||||
|
||||
EXPOSE 80 443 6000 7000 7500
|
||||
|
||||
ENTRYPOINT ["/frps"]
|
12
Dockerfile_alpine
Normal file
12
Dockerfile_alpine
Normal file
@@ -0,0 +1,12 @@
|
||||
FROM alpine:3.5
|
||||
|
||||
COPY tmp/frpc /frpc
|
||||
COPY tmp/frps /frps
|
||||
COPY conf/frpc_min.ini /frpc.ini
|
||||
COPY conf/frps_min.ini /frps.ini
|
||||
|
||||
WORKDIR /
|
||||
|
||||
EXPOSE 80 443 6000 7000 7500
|
||||
|
||||
ENTRYPOINT ["/frps"]
|
113
Godeps/Godeps.json
generated
113
Godeps/Godeps.json
generated
@@ -1,23 +1,126 @@
|
||||
{
|
||||
"ImportPath": "frp",
|
||||
"GoVersion": "go1.4",
|
||||
"ImportPath": "github.com/fatedier/frp",
|
||||
"GoVersion": "go1.8",
|
||||
"GodepVersion": "v79",
|
||||
"Packages": [
|
||||
"./..."
|
||||
],
|
||||
"Deps": [
|
||||
{
|
||||
"ImportPath": "github.com/astaxie/beego/logs",
|
||||
"Comment": "v1.5.0-9-gfb7314f",
|
||||
"Rev": "fb7314f8ac86b83ccd34386518d97cf2363e2ae5"
|
||||
"ImportPath": "github.com/davecgh/go-spew/spew",
|
||||
"Comment": "v1.1.0",
|
||||
"Rev": "346938d642f2ec3594ed81d874461961cd0faa76"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/docopt/docopt-go",
|
||||
"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/klauspost/cpuid",
|
||||
"Comment": "v1.0",
|
||||
"Rev": "09cded8978dc9e80714c4d85b0322337b0a1e5e0"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/klauspost/reedsolomon",
|
||||
"Comment": "1.3-1-gdde6ad5",
|
||||
"Rev": "dde6ad55c5e5a6379a4e82dcca32ee407346eb6d"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/pkg/errors",
|
||||
"Comment": "v0.8.0-5-gc605e28",
|
||||
"Rev": "c605e284fe17294bda444b34710735b29d1a9d90"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/pmezard/go-difflib/difflib",
|
||||
"Comment": "v1.0.0",
|
||||
"Rev": "792786c7400a136282c1664665ae0a8db921c6c2"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/rakyll/statik/fs",
|
||||
"Comment": "v0.1.0",
|
||||
"Rev": "274df120e9065bdd08eb1120e0375e3dc1ae8465"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/stretchr/testify/assert",
|
||||
"Comment": "v1.1.4-25-g2402e8e",
|
||||
"Rev": "2402e8e7a02fc811447d11f881aa9746cdc57983"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/vaughan0/go-ini",
|
||||
"Rev": "a98ad7ee00ec53921f08832bc06ecf7fd600e6a1"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/xtaci/kcp-go",
|
||||
"Comment": "v3.17",
|
||||
"Rev": "df437e2b8ec365a336200f9d9da53441cf72ed47"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/xtaci/smux",
|
||||
"Comment": "v1.0.5-8-g2de5471",
|
||||
"Rev": "2de5471dfcbc029f5fe1392b83fe784127c4943e"
|
||||
},
|
||||
{
|
||||
"ImportPath": "golang.org/x/crypto/blowfish",
|
||||
"Rev": "e1a4589e7d3ea14a3352255d04b6f1a418845e5e"
|
||||
},
|
||||
{
|
||||
"ImportPath": "golang.org/x/crypto/cast5",
|
||||
"Rev": "e1a4589e7d3ea14a3352255d04b6f1a418845e5e"
|
||||
},
|
||||
{
|
||||
"ImportPath": "golang.org/x/crypto/pbkdf2",
|
||||
"Rev": "e1a4589e7d3ea14a3352255d04b6f1a418845e5e"
|
||||
},
|
||||
{
|
||||
"ImportPath": "golang.org/x/crypto/salsa20",
|
||||
"Rev": "e1a4589e7d3ea14a3352255d04b6f1a418845e5e"
|
||||
},
|
||||
{
|
||||
"ImportPath": "golang.org/x/crypto/salsa20/salsa",
|
||||
"Rev": "e1a4589e7d3ea14a3352255d04b6f1a418845e5e"
|
||||
},
|
||||
{
|
||||
"ImportPath": "golang.org/x/crypto/tea",
|
||||
"Rev": "e1a4589e7d3ea14a3352255d04b6f1a418845e5e"
|
||||
},
|
||||
{
|
||||
"ImportPath": "golang.org/x/crypto/twofish",
|
||||
"Rev": "e1a4589e7d3ea14a3352255d04b6f1a418845e5e"
|
||||
},
|
||||
{
|
||||
"ImportPath": "golang.org/x/crypto/xtea",
|
||||
"Rev": "e1a4589e7d3ea14a3352255d04b6f1a418845e5e"
|
||||
},
|
||||
{
|
||||
"ImportPath": "golang.org/x/net/bpf",
|
||||
"Rev": "e4fa1c5465ad6111f206fc92186b8c83d64adbe1"
|
||||
},
|
||||
{
|
||||
"ImportPath": "golang.org/x/net/internal/iana",
|
||||
"Rev": "e4fa1c5465ad6111f206fc92186b8c83d64adbe1"
|
||||
},
|
||||
{
|
||||
"ImportPath": "golang.org/x/net/internal/socket",
|
||||
"Rev": "e4fa1c5465ad6111f206fc92186b8c83d64adbe1"
|
||||
},
|
||||
{
|
||||
"ImportPath": "golang.org/x/net/ipv4",
|
||||
"Rev": "e4fa1c5465ad6111f206fc92186b8c83d64adbe1"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
2
Godeps/_workspace/.gitignore
generated
vendored
2
Godeps/_workspace/.gitignore
generated
vendored
@@ -1,2 +0,0 @@
|
||||
/pkg
|
||||
/bin
|
95
Godeps/_workspace/src/github.com/astaxie/beego/logs/console.go
generated
vendored
95
Godeps/_workspace/src/github.com/astaxie/beego/logs/console.go
generated
vendored
@@ -1,95 +0,0 @@
|
||||
// Copyright 2014 beego Author. All Rights Reserved.
|
||||
//
|
||||
// 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 logs
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"log"
|
||||
"os"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
type Brush func(string) string
|
||||
|
||||
func NewBrush(color string) Brush {
|
||||
pre := "\033["
|
||||
reset := "\033[0m"
|
||||
return func(text string) string {
|
||||
return pre + color + "m" + text + reset
|
||||
}
|
||||
}
|
||||
|
||||
var colors = []Brush{
|
||||
NewBrush("1;37"), // Emergency white
|
||||
NewBrush("1;36"), // Alert cyan
|
||||
NewBrush("1;35"), // Critical magenta
|
||||
NewBrush("1;31"), // Error red
|
||||
NewBrush("1;33"), // Warning yellow
|
||||
NewBrush("1;32"), // Notice green
|
||||
NewBrush("1;34"), // Informational blue
|
||||
NewBrush("1;34"), // Debug blue
|
||||
}
|
||||
|
||||
// ConsoleWriter implements LoggerInterface and writes messages to terminal.
|
||||
type ConsoleWriter struct {
|
||||
lg *log.Logger
|
||||
Level int `json:"level"`
|
||||
}
|
||||
|
||||
// create ConsoleWriter returning as LoggerInterface.
|
||||
func NewConsole() LoggerInterface {
|
||||
cw := &ConsoleWriter{
|
||||
lg: log.New(os.Stdout, "", log.Ldate|log.Ltime),
|
||||
Level: LevelDebug,
|
||||
}
|
||||
return cw
|
||||
}
|
||||
|
||||
// init console logger.
|
||||
// jsonconfig like '{"level":LevelTrace}'.
|
||||
func (c *ConsoleWriter) Init(jsonconfig string) error {
|
||||
if len(jsonconfig) == 0 {
|
||||
return nil
|
||||
}
|
||||
return json.Unmarshal([]byte(jsonconfig), c)
|
||||
}
|
||||
|
||||
// write message in console.
|
||||
func (c *ConsoleWriter) WriteMsg(msg string, level int) error {
|
||||
if level > c.Level {
|
||||
return nil
|
||||
}
|
||||
if goos := runtime.GOOS; goos == "windows" {
|
||||
c.lg.Println(msg)
|
||||
return nil
|
||||
}
|
||||
c.lg.Println(colors[level](msg))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// implementing method. empty.
|
||||
func (c *ConsoleWriter) Destroy() {
|
||||
|
||||
}
|
||||
|
||||
// implementing method. empty.
|
||||
func (c *ConsoleWriter) Flush() {
|
||||
|
||||
}
|
||||
|
||||
func init() {
|
||||
Register("console", NewConsole)
|
||||
}
|
76
Godeps/_workspace/src/github.com/astaxie/beego/logs/es/es.go
generated
vendored
76
Godeps/_workspace/src/github.com/astaxie/beego/logs/es/es.go
generated
vendored
@@ -1,76 +0,0 @@
|
||||
package es
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"github.com/astaxie/beego/logs"
|
||||
"github.com/belogik/goes"
|
||||
)
|
||||
|
||||
func NewES() logs.LoggerInterface {
|
||||
cw := &esLogger{
|
||||
Level: logs.LevelDebug,
|
||||
}
|
||||
return cw
|
||||
}
|
||||
|
||||
type esLogger struct {
|
||||
*goes.Connection
|
||||
DSN string `json:"dsn"`
|
||||
Level int `json:"level"`
|
||||
}
|
||||
|
||||
// {"dsn":"http://localhost:9200/","level":1}
|
||||
func (el *esLogger) Init(jsonconfig string) error {
|
||||
err := json.Unmarshal([]byte(jsonconfig), el)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if el.DSN == "" {
|
||||
return errors.New("empty dsn")
|
||||
} else if u, err := url.Parse(el.DSN); err != nil {
|
||||
return err
|
||||
} else if u.Path == "" {
|
||||
return errors.New("missing prefix")
|
||||
} else if host, port, err := net.SplitHostPort(u.Host); err != nil {
|
||||
return err
|
||||
} else {
|
||||
conn := goes.NewConnection(host, port)
|
||||
el.Connection = conn
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (el *esLogger) WriteMsg(msg string, level int) error {
|
||||
if level > el.Level {
|
||||
return nil
|
||||
}
|
||||
t := time.Now()
|
||||
vals := make(map[string]interface{})
|
||||
vals["@timestamp"] = t.Format(time.RFC3339)
|
||||
vals["@msg"] = msg
|
||||
d := goes.Document{
|
||||
Index: fmt.Sprintf("%04d.%02d.%02d", t.Year(), t.Month(), t.Day()),
|
||||
Type: "logs",
|
||||
Fields: vals,
|
||||
}
|
||||
_, err := el.Index(d, nil)
|
||||
return err
|
||||
}
|
||||
|
||||
func (el *esLogger) Destroy() {
|
||||
|
||||
}
|
||||
|
||||
func (el *esLogger) Flush() {
|
||||
|
||||
}
|
||||
|
||||
func init() {
|
||||
logs.Register("es", NewES)
|
||||
}
|
283
Godeps/_workspace/src/github.com/astaxie/beego/logs/file.go
generated
vendored
283
Godeps/_workspace/src/github.com/astaxie/beego/logs/file.go
generated
vendored
@@ -1,283 +0,0 @@
|
||||
// Copyright 2014 beego Author. All Rights Reserved.
|
||||
//
|
||||
// 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 logs
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// FileLogWriter implements LoggerInterface.
|
||||
// It writes messages by lines limit, file size limit, or time frequency.
|
||||
type FileLogWriter struct {
|
||||
*log.Logger
|
||||
mw *MuxWriter
|
||||
// The opened file
|
||||
Filename string `json:"filename"`
|
||||
|
||||
Maxlines int `json:"maxlines"`
|
||||
maxlines_curlines int
|
||||
|
||||
// Rotate at size
|
||||
Maxsize int `json:"maxsize"`
|
||||
maxsize_cursize int
|
||||
|
||||
// Rotate daily
|
||||
Daily bool `json:"daily"`
|
||||
Maxdays int64 `json:"maxdays"`
|
||||
daily_opendate int
|
||||
|
||||
Rotate bool `json:"rotate"`
|
||||
|
||||
startLock sync.Mutex // Only one log can write to the file
|
||||
|
||||
Level int `json:"level"`
|
||||
}
|
||||
|
||||
// an *os.File writer with locker.
|
||||
type MuxWriter struct {
|
||||
sync.Mutex
|
||||
fd *os.File
|
||||
}
|
||||
|
||||
// write to os.File.
|
||||
func (l *MuxWriter) Write(b []byte) (int, error) {
|
||||
l.Lock()
|
||||
defer l.Unlock()
|
||||
return l.fd.Write(b)
|
||||
}
|
||||
|
||||
// set os.File in writer.
|
||||
func (l *MuxWriter) SetFd(fd *os.File) {
|
||||
if l.fd != nil {
|
||||
l.fd.Close()
|
||||
}
|
||||
l.fd = fd
|
||||
}
|
||||
|
||||
// create a FileLogWriter returning as LoggerInterface.
|
||||
func NewFileWriter() LoggerInterface {
|
||||
w := &FileLogWriter{
|
||||
Filename: "",
|
||||
Maxlines: 1000000,
|
||||
Maxsize: 1 << 28, //256 MB
|
||||
Daily: true,
|
||||
Maxdays: 7,
|
||||
Rotate: true,
|
||||
Level: LevelTrace,
|
||||
}
|
||||
// use MuxWriter instead direct use os.File for lock write when rotate
|
||||
w.mw = new(MuxWriter)
|
||||
// set MuxWriter as Logger's io.Writer
|
||||
w.Logger = log.New(w.mw, "", log.Ldate|log.Ltime)
|
||||
return w
|
||||
}
|
||||
|
||||
// Init file logger with json config.
|
||||
// jsonconfig like:
|
||||
// {
|
||||
// "filename":"logs/beego.log",
|
||||
// "maxlines":10000,
|
||||
// "maxsize":1<<30,
|
||||
// "daily":true,
|
||||
// "maxdays":15,
|
||||
// "rotate":true
|
||||
// }
|
||||
func (w *FileLogWriter) Init(jsonconfig string) error {
|
||||
err := json.Unmarshal([]byte(jsonconfig), w)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(w.Filename) == 0 {
|
||||
return errors.New("jsonconfig must have filename")
|
||||
}
|
||||
err = w.startLogger()
|
||||
return err
|
||||
}
|
||||
|
||||
// start file logger. create log file and set to locker-inside file writer.
|
||||
func (w *FileLogWriter) startLogger() error {
|
||||
fd, err := w.createLogFile()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
w.mw.SetFd(fd)
|
||||
return w.initFd()
|
||||
}
|
||||
|
||||
func (w *FileLogWriter) docheck(size int) {
|
||||
w.startLock.Lock()
|
||||
defer w.startLock.Unlock()
|
||||
if w.Rotate && ((w.Maxlines > 0 && w.maxlines_curlines >= w.Maxlines) ||
|
||||
(w.Maxsize > 0 && w.maxsize_cursize >= w.Maxsize) ||
|
||||
(w.Daily && time.Now().Day() != w.daily_opendate)) {
|
||||
if err := w.DoRotate(); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "FileLogWriter(%q): %s\n", w.Filename, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
w.maxlines_curlines++
|
||||
w.maxsize_cursize += size
|
||||
}
|
||||
|
||||
// write logger message into file.
|
||||
func (w *FileLogWriter) WriteMsg(msg string, level int) error {
|
||||
if level > w.Level {
|
||||
return nil
|
||||
}
|
||||
n := 24 + len(msg) // 24 stand for the length "2013/06/23 21:00:22 [T] "
|
||||
w.docheck(n)
|
||||
w.Logger.Println(msg)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *FileLogWriter) createLogFile() (*os.File, error) {
|
||||
// Open the log file
|
||||
fd, err := os.OpenFile(w.Filename, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0660)
|
||||
return fd, err
|
||||
}
|
||||
|
||||
func (w *FileLogWriter) initFd() error {
|
||||
fd := w.mw.fd
|
||||
finfo, err := fd.Stat()
|
||||
if err != nil {
|
||||
return fmt.Errorf("get stat err: %s\n", err)
|
||||
}
|
||||
w.maxsize_cursize = int(finfo.Size())
|
||||
w.daily_opendate = time.Now().Day()
|
||||
w.maxlines_curlines = 0
|
||||
if finfo.Size() > 0 {
|
||||
count, err := w.lines()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
w.maxlines_curlines = count
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *FileLogWriter) lines() (int, error) {
|
||||
fd, err := os.Open(w.Filename)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
defer fd.Close()
|
||||
|
||||
buf := make([]byte, 32768) // 32k
|
||||
count := 0
|
||||
lineSep := []byte{'\n'}
|
||||
|
||||
for {
|
||||
c, err := fd.Read(buf)
|
||||
if err != nil && err != io.EOF {
|
||||
return count, err
|
||||
}
|
||||
|
||||
count += bytes.Count(buf[:c], lineSep)
|
||||
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return count, nil
|
||||
}
|
||||
|
||||
// DoRotate means it need to write file in new file.
|
||||
// new file name like xx.log.2013-01-01.2
|
||||
func (w *FileLogWriter) DoRotate() error {
|
||||
_, err := os.Lstat(w.Filename)
|
||||
if err == nil { // file exists
|
||||
// Find the next available number
|
||||
num := 1
|
||||
fname := ""
|
||||
for ; err == nil && num <= 999; num++ {
|
||||
fname = w.Filename + fmt.Sprintf(".%s.%03d", time.Now().Format("2006-01-02"), num)
|
||||
_, err = os.Lstat(fname)
|
||||
}
|
||||
// return error if the last file checked still existed
|
||||
if err == nil {
|
||||
return fmt.Errorf("Rotate: Cannot find free log number to rename %s\n", w.Filename)
|
||||
}
|
||||
|
||||
// block Logger's io.Writer
|
||||
w.mw.Lock()
|
||||
defer w.mw.Unlock()
|
||||
|
||||
fd := w.mw.fd
|
||||
fd.Close()
|
||||
|
||||
// close fd before rename
|
||||
// Rename the file to its newfound home
|
||||
err = os.Rename(w.Filename, fname)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Rotate: %s\n", err)
|
||||
}
|
||||
|
||||
// re-start logger
|
||||
err = w.startLogger()
|
||||
if err != nil {
|
||||
return fmt.Errorf("Rotate StartLogger: %s\n", err)
|
||||
}
|
||||
|
||||
go w.deleteOldLog()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *FileLogWriter) deleteOldLog() {
|
||||
dir := filepath.Dir(w.Filename)
|
||||
filepath.Walk(dir, func(path string, info os.FileInfo, err error) (returnErr error) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
returnErr = fmt.Errorf("Unable to delete old log '%s', error: %+v", path, r)
|
||||
fmt.Println(returnErr)
|
||||
}
|
||||
}()
|
||||
|
||||
if !info.IsDir() && info.ModTime().Unix() < (time.Now().Unix()-60*60*24*w.Maxdays) {
|
||||
if strings.HasPrefix(filepath.Base(path), filepath.Base(w.Filename)) {
|
||||
os.Remove(path)
|
||||
}
|
||||
}
|
||||
return
|
||||
})
|
||||
}
|
||||
|
||||
// destroy file logger, close file writer.
|
||||
func (w *FileLogWriter) Destroy() {
|
||||
w.mw.fd.Close()
|
||||
}
|
||||
|
||||
// flush file logger.
|
||||
// there are no buffering messages in file logger in memory.
|
||||
// flush file means sync file from disk.
|
||||
func (w *FileLogWriter) Flush() {
|
||||
w.mw.fd.Sync()
|
||||
}
|
||||
|
||||
func init() {
|
||||
Register("file", NewFileWriter)
|
||||
}
|
350
Godeps/_workspace/src/github.com/astaxie/beego/logs/log.go
generated
vendored
350
Godeps/_workspace/src/github.com/astaxie/beego/logs/log.go
generated
vendored
@@ -1,350 +0,0 @@
|
||||
// Copyright 2014 beego Author. All Rights Reserved.
|
||||
//
|
||||
// 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.
|
||||
|
||||
// Usage:
|
||||
//
|
||||
// import "github.com/astaxie/beego/logs"
|
||||
//
|
||||
// log := NewLogger(10000)
|
||||
// log.SetLogger("console", "")
|
||||
//
|
||||
// > the first params stand for how many channel
|
||||
//
|
||||
// Use it like this:
|
||||
//
|
||||
// log.Trace("trace")
|
||||
// log.Info("info")
|
||||
// log.Warn("warning")
|
||||
// log.Debug("debug")
|
||||
// log.Critical("critical")
|
||||
//
|
||||
// more docs http://beego.me/docs/module/logs.md
|
||||
package logs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path"
|
||||
"runtime"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// RFC5424 log message levels.
|
||||
const (
|
||||
LevelEmergency = iota
|
||||
LevelAlert
|
||||
LevelCritical
|
||||
LevelError
|
||||
LevelWarning
|
||||
LevelNotice
|
||||
LevelInformational
|
||||
LevelDebug
|
||||
)
|
||||
|
||||
// Legacy loglevel constants to ensure backwards compatibility.
|
||||
//
|
||||
// Deprecated: will be removed in 1.5.0.
|
||||
const (
|
||||
LevelInfo = LevelInformational
|
||||
LevelTrace = LevelDebug
|
||||
LevelWarn = LevelWarning
|
||||
)
|
||||
|
||||
type loggerType func() LoggerInterface
|
||||
|
||||
// LoggerInterface defines the behavior of a log provider.
|
||||
type LoggerInterface interface {
|
||||
Init(config string) error
|
||||
WriteMsg(msg string, level int) error
|
||||
Destroy()
|
||||
Flush()
|
||||
}
|
||||
|
||||
var adapters = make(map[string]loggerType)
|
||||
|
||||
// Register makes a log provide available by the provided name.
|
||||
// If Register is called twice with the same name or if driver is nil,
|
||||
// it panics.
|
||||
func Register(name string, log loggerType) {
|
||||
if log == nil {
|
||||
panic("logs: Register provide is nil")
|
||||
}
|
||||
if _, dup := adapters[name]; dup {
|
||||
panic("logs: Register called twice for provider " + name)
|
||||
}
|
||||
adapters[name] = log
|
||||
}
|
||||
|
||||
// BeeLogger is default logger in beego application.
|
||||
// it can contain several providers and log message into all providers.
|
||||
type BeeLogger struct {
|
||||
lock sync.Mutex
|
||||
level int
|
||||
enableFuncCallDepth bool
|
||||
loggerFuncCallDepth int
|
||||
asynchronous bool
|
||||
msg chan *logMsg
|
||||
outputs map[string]LoggerInterface
|
||||
}
|
||||
|
||||
type logMsg struct {
|
||||
level int
|
||||
msg string
|
||||
}
|
||||
|
||||
// NewLogger returns a new BeeLogger.
|
||||
// channellen means the number of messages in chan.
|
||||
// if the buffering chan is full, logger adapters write to file or other way.
|
||||
func NewLogger(channellen int64) *BeeLogger {
|
||||
bl := new(BeeLogger)
|
||||
bl.level = LevelDebug
|
||||
bl.loggerFuncCallDepth = 2
|
||||
bl.msg = make(chan *logMsg, channellen)
|
||||
bl.outputs = make(map[string]LoggerInterface)
|
||||
return bl
|
||||
}
|
||||
|
||||
func (bl *BeeLogger) Async() *BeeLogger {
|
||||
bl.asynchronous = true
|
||||
go bl.startLogger()
|
||||
return bl
|
||||
}
|
||||
|
||||
// SetLogger provides a given logger adapter into BeeLogger with config string.
|
||||
// config need to be correct JSON as string: {"interval":360}.
|
||||
func (bl *BeeLogger) SetLogger(adaptername string, config string) error {
|
||||
bl.lock.Lock()
|
||||
defer bl.lock.Unlock()
|
||||
if log, ok := adapters[adaptername]; ok {
|
||||
lg := log()
|
||||
err := lg.Init(config)
|
||||
bl.outputs[adaptername] = lg
|
||||
if err != nil {
|
||||
fmt.Println("logs.BeeLogger.SetLogger: " + err.Error())
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
return fmt.Errorf("logs: unknown adaptername %q (forgotten Register?)", adaptername)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// remove a logger adapter in BeeLogger.
|
||||
func (bl *BeeLogger) DelLogger(adaptername string) error {
|
||||
bl.lock.Lock()
|
||||
defer bl.lock.Unlock()
|
||||
if lg, ok := bl.outputs[adaptername]; ok {
|
||||
lg.Destroy()
|
||||
delete(bl.outputs, adaptername)
|
||||
return nil
|
||||
} else {
|
||||
return fmt.Errorf("logs: unknown adaptername %q (forgotten Register?)", adaptername)
|
||||
}
|
||||
}
|
||||
|
||||
func (bl *BeeLogger) writerMsg(loglevel int, msg string) error {
|
||||
lm := new(logMsg)
|
||||
lm.level = loglevel
|
||||
if bl.enableFuncCallDepth {
|
||||
_, file, line, ok := runtime.Caller(bl.loggerFuncCallDepth)
|
||||
if !ok {
|
||||
file = "???"
|
||||
line = 0
|
||||
}
|
||||
_, filename := path.Split(file)
|
||||
lm.msg = fmt.Sprintf("[%s:%d] %s", filename, line, msg)
|
||||
} else {
|
||||
lm.msg = msg
|
||||
}
|
||||
if bl.asynchronous {
|
||||
bl.msg <- lm
|
||||
} else {
|
||||
for name, l := range bl.outputs {
|
||||
err := l.WriteMsg(lm.msg, lm.level)
|
||||
if err != nil {
|
||||
fmt.Println("unable to WriteMsg to adapter:", name, err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Set log message level.
|
||||
//
|
||||
// If message level (such as LevelDebug) is higher than logger level (such as LevelWarning),
|
||||
// log providers will not even be sent the message.
|
||||
func (bl *BeeLogger) SetLevel(l int) {
|
||||
bl.level = l
|
||||
}
|
||||
|
||||
// set log funcCallDepth
|
||||
func (bl *BeeLogger) SetLogFuncCallDepth(d int) {
|
||||
bl.loggerFuncCallDepth = d
|
||||
}
|
||||
|
||||
// get log funcCallDepth for wrapper
|
||||
func (bl *BeeLogger) GetLogFuncCallDepth() int {
|
||||
return bl.loggerFuncCallDepth
|
||||
}
|
||||
|
||||
// enable log funcCallDepth
|
||||
func (bl *BeeLogger) EnableFuncCallDepth(b bool) {
|
||||
bl.enableFuncCallDepth = b
|
||||
}
|
||||
|
||||
// start logger chan reading.
|
||||
// when chan is not empty, write logs.
|
||||
func (bl *BeeLogger) startLogger() {
|
||||
for {
|
||||
select {
|
||||
case bm := <-bl.msg:
|
||||
for _, l := range bl.outputs {
|
||||
err := l.WriteMsg(bm.msg, bm.level)
|
||||
if err != nil {
|
||||
fmt.Println("ERROR, unable to WriteMsg:", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Log EMERGENCY level message.
|
||||
func (bl *BeeLogger) Emergency(format string, v ...interface{}) {
|
||||
if LevelEmergency > bl.level {
|
||||
return
|
||||
}
|
||||
msg := fmt.Sprintf("[M] "+format, v...)
|
||||
bl.writerMsg(LevelEmergency, msg)
|
||||
}
|
||||
|
||||
// Log ALERT level message.
|
||||
func (bl *BeeLogger) Alert(format string, v ...interface{}) {
|
||||
if LevelAlert > bl.level {
|
||||
return
|
||||
}
|
||||
msg := fmt.Sprintf("[A] "+format, v...)
|
||||
bl.writerMsg(LevelAlert, msg)
|
||||
}
|
||||
|
||||
// Log CRITICAL level message.
|
||||
func (bl *BeeLogger) Critical(format string, v ...interface{}) {
|
||||
if LevelCritical > bl.level {
|
||||
return
|
||||
}
|
||||
msg := fmt.Sprintf("[C] "+format, v...)
|
||||
bl.writerMsg(LevelCritical, msg)
|
||||
}
|
||||
|
||||
// Log ERROR level message.
|
||||
func (bl *BeeLogger) Error(format string, v ...interface{}) {
|
||||
if LevelError > bl.level {
|
||||
return
|
||||
}
|
||||
msg := fmt.Sprintf("[E] "+format, v...)
|
||||
bl.writerMsg(LevelError, msg)
|
||||
}
|
||||
|
||||
// Log WARNING level message.
|
||||
func (bl *BeeLogger) Warning(format string, v ...interface{}) {
|
||||
if LevelWarning > bl.level {
|
||||
return
|
||||
}
|
||||
msg := fmt.Sprintf("[W] "+format, v...)
|
||||
bl.writerMsg(LevelWarning, msg)
|
||||
}
|
||||
|
||||
// Log NOTICE level message.
|
||||
func (bl *BeeLogger) Notice(format string, v ...interface{}) {
|
||||
if LevelNotice > bl.level {
|
||||
return
|
||||
}
|
||||
msg := fmt.Sprintf("[N] "+format, v...)
|
||||
bl.writerMsg(LevelNotice, msg)
|
||||
}
|
||||
|
||||
// Log INFORMATIONAL level message.
|
||||
func (bl *BeeLogger) Informational(format string, v ...interface{}) {
|
||||
if LevelInformational > bl.level {
|
||||
return
|
||||
}
|
||||
msg := fmt.Sprintf("[I] "+format, v...)
|
||||
bl.writerMsg(LevelInformational, msg)
|
||||
}
|
||||
|
||||
// Log DEBUG level message.
|
||||
func (bl *BeeLogger) Debug(format string, v ...interface{}) {
|
||||
if LevelDebug > bl.level {
|
||||
return
|
||||
}
|
||||
msg := fmt.Sprintf("[D] "+format, v...)
|
||||
bl.writerMsg(LevelDebug, msg)
|
||||
}
|
||||
|
||||
// Log WARN level message.
|
||||
// compatibility alias for Warning()
|
||||
func (bl *BeeLogger) Warn(format string, v ...interface{}) {
|
||||
if LevelWarning > bl.level {
|
||||
return
|
||||
}
|
||||
msg := fmt.Sprintf("[W] "+format, v...)
|
||||
bl.writerMsg(LevelWarning, msg)
|
||||
}
|
||||
|
||||
// Log INFO level message.
|
||||
// compatibility alias for Informational()
|
||||
func (bl *BeeLogger) Info(format string, v ...interface{}) {
|
||||
if LevelInformational > bl.level {
|
||||
return
|
||||
}
|
||||
msg := fmt.Sprintf("[I] "+format, v...)
|
||||
bl.writerMsg(LevelInformational, msg)
|
||||
}
|
||||
|
||||
// Log TRACE level message.
|
||||
// compatibility alias for Debug()
|
||||
func (bl *BeeLogger) Trace(format string, v ...interface{}) {
|
||||
if LevelDebug > bl.level {
|
||||
return
|
||||
}
|
||||
msg := fmt.Sprintf("[D] "+format, v...)
|
||||
bl.writerMsg(LevelDebug, msg)
|
||||
}
|
||||
|
||||
// flush all chan data.
|
||||
func (bl *BeeLogger) Flush() {
|
||||
for _, l := range bl.outputs {
|
||||
l.Flush()
|
||||
}
|
||||
}
|
||||
|
||||
// close logger, flush all chan data and destroy all adapters in BeeLogger.
|
||||
func (bl *BeeLogger) Close() {
|
||||
for {
|
||||
if len(bl.msg) > 0 {
|
||||
bm := <-bl.msg
|
||||
for _, l := range bl.outputs {
|
||||
err := l.WriteMsg(bm.msg, bm.level)
|
||||
if err != nil {
|
||||
fmt.Println("ERROR, unable to WriteMsg (while closing logger):", err)
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
break
|
||||
}
|
||||
for _, l := range bl.outputs {
|
||||
l.Flush()
|
||||
l.Destroy()
|
||||
}
|
||||
}
|
55
Makefile
55
Makefile
@@ -1,22 +1,53 @@
|
||||
export PATH := $(GOPATH)/bin:$(PATH)
|
||||
export OLDGOPATH := $(GOPATH)
|
||||
export GOPATH := $(shell pwd):$(GOPATH)
|
||||
export GO15VENDOREXPERIMENT := 1
|
||||
|
||||
all: build
|
||||
all: fmt build
|
||||
|
||||
build: godep fmt frps frpc
|
||||
build: frps frpc
|
||||
|
||||
godep:
|
||||
GOPATH=$(OLDGOPATH) go get github.com/tools/godep
|
||||
# compile assets into binary file
|
||||
file:
|
||||
rm -rf ./assets/static/*
|
||||
cp -rf ./web/frps/dist/* ./assets/static
|
||||
go get -d github.com/rakyll/statik
|
||||
go install github.com/rakyll/statik
|
||||
rm -rf ./assets/statik
|
||||
go generate ./assets/...
|
||||
|
||||
fmt:
|
||||
godep go fmt ./...
|
||||
|
||||
go fmt ./assets/...
|
||||
go fmt ./client/...
|
||||
go fmt ./cmd/...
|
||||
go fmt ./models/...
|
||||
go fmt ./server/...
|
||||
go fmt ./utils/...
|
||||
|
||||
frps:
|
||||
godep go build -o bin/frps ./src/frp/cmd/frps
|
||||
go build -o bin/frps ./cmd/frps
|
||||
@cp -rf ./assets/static ./bin
|
||||
|
||||
frpc:
|
||||
godep go build -o bin/frpc ./src/frp/cmd/frpc
|
||||
go build -o bin/frpc ./cmd/frpc
|
||||
|
||||
test:
|
||||
godep go test -v ./...
|
||||
test: gotest
|
||||
|
||||
gotest:
|
||||
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: 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
|
||||
cd ./tests && ./clean_test.sh && cd -
|
||||
|
||||
save:
|
||||
godep save ./...
|
||||
|
@@ -1,15 +1,32 @@
|
||||
export PATH := $(GOPATH)/bin:$(PATH)
|
||||
export OLDGOPATH := $(GOPATH)
|
||||
export GOPATH := $(shell pwd)/Godeps/_workspace:$(shell pwd):$(GOPATH)
|
||||
export OS_TARGETS=linux windows
|
||||
export ARCH_TARGETS=386 amd64
|
||||
export GO15VENDOREXPERIMENT := 1
|
||||
LDFLAGS := -s -w
|
||||
|
||||
all: build
|
||||
|
||||
build: godep app
|
||||
|
||||
godep:
|
||||
GOPATH=$(OLDGOPATH) go get github.com/mitchellh/gox
|
||||
build: app
|
||||
|
||||
app:
|
||||
gox -os "$(OS_TARGETS)" -arch="$(ARCH_TARGETS)" ./...
|
||||
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 -ldflags "$(LDFLAGS)" -o ./frpc_linux_mips ./cmd/frpc
|
||||
env CGO_ENABLED=0 GOOS=linux GOARCH=mips go build -ldflags "$(LDFLAGS)" -o ./frps_linux_mips ./cmd/frps
|
||||
env CGO_ENABLED=0 GOOS=linux GOARCH=mipsle go build -ldflags "$(LDFLAGS)" -o ./frpc_linux_mipsle ./cmd/frpc
|
||||
env CGO_ENABLED=0 GOOS=linux GOARCH=mipsle go build -ldflags "$(LDFLAGS)" -o ./frps_linux_mipsle ./cmd/frps
|
||||
|
||||
temp:
|
||||
env CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags "$(LDFLAGS)" -o ./frps_linux_amd64 ./cmd/frps
|
||||
|
524
README.md
524
README.md
@@ -1,46 +1,528 @@
|
||||
# frp
|
||||
|
||||
[](https://travis-ci.org/fatedier/frp)
|
||||
[](https://travis-ci.org/fatedier/frp)
|
||||
|
||||
[README](README.md) | [中文文档](README_zh.md)
|
||||
|
||||
## What is frp?
|
||||
|
||||
frp is a fast reverse proxy to help you expose a local server behind a NAT or firewall to the internet.
|
||||
frp is a fast reverse proxy to help you expose a local server behind a NAT or firewall to the internet. Now, it supports tcp, udp, http and https protocol when requests can be forwarded by domains to backward web services.
|
||||
|
||||
## Table of Contents
|
||||
|
||||
<!-- vim-markdown-toc GFM -->
|
||||
* [What can I do with frp?](#what-can-i-do-with-frp)
|
||||
* [Status](#status)
|
||||
* [Architecture](#architecture)
|
||||
* [Example Usage](#example-usage)
|
||||
* [Access your computer in LAN by SSH](#access-your-computer-in-lan-by-ssh)
|
||||
* [Visit your web service in LAN by custom domains](#visit-your-web-service-in-lan-by-custom-domains)
|
||||
* [Forward DNS query request](#forward-dns-query-request)
|
||||
* [Forward unix domain socket](#forward-unix-domain-socket)
|
||||
* [Connect website through frpc's network](#connect-website-through-frpcs-network)
|
||||
* [Features](#features)
|
||||
* [Dashboard](#dashboard)
|
||||
* [Authentication](#authentication)
|
||||
* [Encryption and Compression](#encryption-and-compression)
|
||||
* [Reload configures without frps stopped](#reload-configures-without-frps-stopped)
|
||||
* [Privilege Mode](#privilege-mode)
|
||||
* [Port White List](#port-white-list)
|
||||
* [TCP Stream Multiplexing](#tcp-stream-multiplexing)
|
||||
* [Support KCP Protocol](#support-kcp-protocol)
|
||||
* [Connection Pool](#connection-pool)
|
||||
* [Rewriting the Host Header](#rewriting-the-host-header)
|
||||
* [Password protecting your web service](#password-protecting-your-web-service)
|
||||
* [Custom subdomain names](#custom-subdomain-names)
|
||||
* [URL routing](#url-routing)
|
||||
* [Connect frps by HTTP PROXY](#connect-frps-by-http-proxy)
|
||||
* [Plugin](#plugin)
|
||||
* [Development Plan](#development-plan)
|
||||
* [Contributing](#contributing)
|
||||
* [Donation](#donation)
|
||||
* [AliPay](#alipay)
|
||||
* [Wechat Pay](#wechat-pay)
|
||||
* [Paypal](#paypal)
|
||||
|
||||
<!-- vim-markdown-toc -->
|
||||
|
||||
## What can I do with frp?
|
||||
|
||||
* Expose any http service behind a NAT or firewall to the internet by a server with public IP address(Name-based Virtual Host Support).
|
||||
* Expose any tcp service behind a NAT or firewall to the internet by a server with public IP address.
|
||||
* Inspect all http requests/responses that are transmitted over the tunnel(future).
|
||||
* Expose any http and https service behind a NAT or firewall to the internet by a server with public IP address(Name-based Virtual Host Support).
|
||||
* Expose any tcp or udp service behind a NAT or firewall to the internet by a server with public IP address.
|
||||
|
||||
## Status
|
||||
|
||||
frp is under development and you can try it with latest release version.Master branch for releasing stable version when dev branch for developing.
|
||||
frp is under development and you can try it with latest release version. Master branch for releasing stable version when dev branch for developing.
|
||||
|
||||
**We may change any protocol and can't promise backward compatible before version 1.x.**
|
||||
|
||||
## Quick Start
|
||||
|
||||
Read the [QuickStart](/doc/quick_start_en.md)
|
||||
|
||||
[Tcp port forwarding](/doc/quick_start_en.md#tcp-port-forwarding)
|
||||
|
||||
[Http port forwarding and Custom domain binding](/doc/quick_start_en.md#http-port-forwarding-and-custom-domains-binding)
|
||||
**We may change any protocol and can't promise backward compatible. Please check the release log when upgrading.**
|
||||
|
||||
## Architecture
|
||||
|
||||

|
||||
|
||||
## Example Usage
|
||||
|
||||
Firstly, download the latest programs from [Release](https://github.com/fatedier/frp/releases) page according to your os and arch.
|
||||
|
||||
Put **frps** and **frps.ini** to your server with public IP.
|
||||
|
||||
Put **frpc** and **frpc.ini** to your server in LAN.
|
||||
|
||||
### Access your computer in LAN by SSH
|
||||
|
||||
1. Modify frps.ini:
|
||||
|
||||
```ini
|
||||
# frps.ini
|
||||
[common]
|
||||
bind_port = 7000
|
||||
```
|
||||
|
||||
2. Start frps:
|
||||
|
||||
`./frps -c ./frps.ini`
|
||||
|
||||
3. Modify frpc.ini, `server_addr` is your frps's server IP:
|
||||
|
||||
```ini
|
||||
# frpc.ini
|
||||
[common]
|
||||
server_addr = x.x.x.x
|
||||
server_port = 7000
|
||||
|
||||
[ssh]
|
||||
type = tcp
|
||||
local_ip = 127.0.0.1
|
||||
local_port = 22
|
||||
remote_port = 6000
|
||||
```
|
||||
|
||||
4. Start frpc:
|
||||
|
||||
`./frpc -c ./frpc.ini`
|
||||
|
||||
5. Connect to server in LAN by ssh assuming that username is test:
|
||||
|
||||
`ssh -oPort=6000 test@x.x.x.x`
|
||||
|
||||
### Visit your web service in LAN by custom domains
|
||||
|
||||
Sometimes we want to expose a local web service behind a NAT network to others for testing with your own domain name and unfortunately we can't resolve a domain name to a local ip.
|
||||
|
||||
However, we can expose a http or https service using frp.
|
||||
|
||||
1. Modify frps.ini, configure http port 8080:
|
||||
|
||||
```ini
|
||||
# frps.ini
|
||||
[common]
|
||||
bind_port = 7000
|
||||
vhost_http_port = 8080
|
||||
```
|
||||
|
||||
2. Start frps:
|
||||
|
||||
`./frps -c ./frps.ini`
|
||||
|
||||
3. Modify frpc.ini and set remote frps server's IP as x.x.x.x. The `local_port` is the port of your web service:
|
||||
|
||||
```ini
|
||||
# frpc.ini
|
||||
[common]
|
||||
server_addr = x.x.x.x
|
||||
server_port = 7000
|
||||
|
||||
[web]
|
||||
type = http
|
||||
local_port = 80
|
||||
custom_domains = www.yourdomain.com
|
||||
```
|
||||
|
||||
4. Start frpc:
|
||||
|
||||
`./frpc -c ./frpc.ini`
|
||||
|
||||
5. Resolve A record of `www.yourdomain.com` to IP `x.x.x.x` or CNAME record to your origin domain.
|
||||
|
||||
6. Now visit your local web service using url `http://www.yourdomain.com:8080`.
|
||||
|
||||
### Forward DNS query request
|
||||
|
||||
1. Modify frps.ini:
|
||||
|
||||
```ini
|
||||
# frps.ini
|
||||
[common]
|
||||
bind_port = 7000
|
||||
```
|
||||
|
||||
2. Start frps:
|
||||
|
||||
`./frps -c ./frps.ini`
|
||||
|
||||
3. Modify frpc.ini, set remote frps's server IP as x.x.x.x, forward dns query request to google dns server `8.8.8.8:53`:
|
||||
|
||||
```ini
|
||||
# frpc.ini
|
||||
[common]
|
||||
server_addr = x.x.x.x
|
||||
server_port = 7000
|
||||
|
||||
[dns]
|
||||
type = udp
|
||||
local_ip = 8.8.8.8
|
||||
local_port = 53
|
||||
remote_port = 6000
|
||||
```
|
||||
|
||||
4. Start frpc:
|
||||
|
||||
`./frpc -c ./frpc.ini`
|
||||
|
||||
5. Send dns query request by dig:
|
||||
|
||||
`dig @x.x.x.x -p 6000 www.goolge.com`
|
||||
|
||||
### Forward unix domain socket
|
||||
|
||||
Using tcp port to connect unix domain socket like docker daemon.
|
||||
|
||||
1. Modify frps.ini:
|
||||
|
||||
```ini
|
||||
# frps.ini
|
||||
[common]
|
||||
bind_port = 7000
|
||||
```
|
||||
|
||||
2. Start frps:
|
||||
|
||||
`./frps -c ./frps.ini`
|
||||
|
||||
3. Modify frpc.ini:
|
||||
|
||||
```ini
|
||||
# frpc.ini
|
||||
[common]
|
||||
server_addr = x.x.x.x
|
||||
server_port = 7000
|
||||
|
||||
[unix_domain_socket]
|
||||
type = tcp
|
||||
remote_port = 6000
|
||||
plugin = unix_domain_socket
|
||||
plugin_unix_path = /var/run/docker.sock
|
||||
```
|
||||
|
||||
4. Start frpc:
|
||||
|
||||
`./frpc -c ./frpc.ini`
|
||||
|
||||
5. Get docker version by curl command:
|
||||
|
||||
`curl http://x.x.x.x:6000/version`
|
||||
|
||||
### Connect website through frpc's network
|
||||
|
||||
Configure frps same as above.
|
||||
|
||||
1. Modify frpc.ini:
|
||||
|
||||
```ini
|
||||
# frpc.ini
|
||||
[common]
|
||||
server_addr = x.x.x.x
|
||||
server_port = 7000
|
||||
|
||||
[http_proxy]
|
||||
type = tcp
|
||||
remote_port = 6000
|
||||
plugin = http_proxy
|
||||
```
|
||||
|
||||
4. Start frpc:
|
||||
|
||||
`./frpc -c ./frpc.ini`
|
||||
|
||||
5. Set http proxy `x.x.x.x:6000` in your browser and visit website through frpc's network.
|
||||
|
||||
## Features
|
||||
|
||||
### Dashboard
|
||||
|
||||
Check frp's status and proxies's statistics information by Dashboard.
|
||||
|
||||
Configure a port for dashboard to enable this feature:
|
||||
|
||||
```ini
|
||||
[common]
|
||||
dashboard_port = 7500
|
||||
# dashboard's username and password are both optional,if not set, default is admin.
|
||||
dashboard_user = admin
|
||||
dashboard_pwd = admin
|
||||
```
|
||||
|
||||
Then visit `http://[server_addr]:7500` to see dashboard, default username and password are both `admin`.
|
||||
|
||||

|
||||
|
||||
### Authentication
|
||||
|
||||
Since v0.10.0, you only need to set `privilege_token` in frps.ini and frpc.ini.
|
||||
|
||||
Note that time duration between server of frpc and frps mustn't exceed 15 minutes because timestamp is used for authentication.
|
||||
|
||||
Howerver, this timeout duration can be modified by setting `authentication_timeout` in frps's configure file. It's defalut value is 900, means 15 minutes. If it is equals 0, then frps will not check authentication timeout.
|
||||
|
||||
### Encryption and Compression
|
||||
|
||||
Defalut value is false, you could decide if the proxy will use encryption or compression:
|
||||
|
||||
```ini
|
||||
# frpc.ini
|
||||
[ssh]
|
||||
type = tcp
|
||||
local_port = 22
|
||||
remote_port = 6000
|
||||
use_encryption = true
|
||||
use_compression = true
|
||||
```
|
||||
|
||||
### Reload configures without frps stopped
|
||||
|
||||
This feature is removed since v0.10.0.
|
||||
|
||||
### Privilege Mode
|
||||
|
||||
Privilege mode is the default and only mode support in frp since v0.10.0. All proxy configurations are set in client.
|
||||
|
||||
#### Port White List
|
||||
|
||||
`privilege_allow_ports` in frps.ini is used for preventing abuse of ports:
|
||||
|
||||
```ini
|
||||
# frps.ini
|
||||
[common]
|
||||
privilege_allow_ports = 2000-3000,3001,3003,4000-50000
|
||||
```
|
||||
|
||||
`privilege_allow_ports` consists of a specific port or a range of ports divided by `,`.
|
||||
|
||||
### TCP Stream Multiplexing
|
||||
|
||||
frp support tcp stream multiplexing since v0.10.0 like HTTP2 Multiplexing. All user requests to same frpc can use only one tcp connection.
|
||||
|
||||
You can disable this feature by modify frps.ini and frpc.ini:
|
||||
|
||||
```ini
|
||||
# frps.ini and frpc.ini, must be same
|
||||
[common]
|
||||
tcp_mux = false
|
||||
```
|
||||
|
||||
### Support KCP Protocol
|
||||
|
||||
frp support kcp protocol since v0.12.0.
|
||||
|
||||
KCP is a fast and reliable protocol that can achieve the transmission effect of a reduction of the average latency by 30% to 40% and reduction of the maximum delay by a factor of three, at the cost of 10% to 20% more bandwidth wasted than TCP.
|
||||
|
||||
Using kcp in frp:
|
||||
|
||||
1. Enable kcp protocol in frps:
|
||||
|
||||
```ini
|
||||
# frps.ini
|
||||
[common]
|
||||
bind_port = 7000
|
||||
# kcp needs to bind a udp port, it can be same with 'bind_port'
|
||||
kcp_bind_port = 7000
|
||||
```
|
||||
|
||||
2. Configure the protocol used in frpc to connect frps:
|
||||
|
||||
```ini
|
||||
# frpc.ini
|
||||
[common]
|
||||
server_addr = x.x.x.x
|
||||
# specify the 'kcp_bind_port' in frps
|
||||
server_port = 7000
|
||||
protocol = kcp
|
||||
```
|
||||
|
||||
### Connection Pool
|
||||
|
||||
By default, frps send message to frpc for create a new connection to backward service when getting an user request.If a proxy's connection pool is enabled, there will be a specified number of connections pre-established.
|
||||
|
||||
This feature is fit for a large number of short connections.
|
||||
|
||||
1. Configure the limit of pool count each proxy can use in frps.ini:
|
||||
|
||||
```ini
|
||||
# frps.ini
|
||||
[common]
|
||||
max_pool_count = 5
|
||||
```
|
||||
|
||||
2. Enable and specify the number of connection pool:
|
||||
|
||||
```ini
|
||||
# frpc.ini
|
||||
[common]
|
||||
pool_count = 1
|
||||
```
|
||||
|
||||
### Rewriting the Host Header
|
||||
|
||||
When forwarding to a local port, frp does not modify the tunneled HTTP requests at all, they are copied to your server byte-for-byte as they are received. Some application servers use the Host header for determining which development site to display. For this reason, frp can rewrite your requests with a modified Host header. Use the `host_header_rewrite` switch to rewrite incoming HTTP requests.
|
||||
|
||||
```ini
|
||||
# frpc.ini
|
||||
[web]
|
||||
type = http
|
||||
local_port = 80
|
||||
custom_domains = test.yourdomain.com
|
||||
host_header_rewrite = dev.yourdomain.com
|
||||
```
|
||||
|
||||
If `host_header_rewrite` is specified, the Host header will be rewritten to match the hostname portion of the forwarding address.
|
||||
|
||||
### Password protecting your web service
|
||||
|
||||
Anyone who can guess your tunnel URL can access your local web server unless you protect it with a password.
|
||||
|
||||
This enforces HTTP Basic Auth on all requests with the username and password you specify in frpc's configure file.
|
||||
|
||||
It can only be enabled when proxy type is http.
|
||||
|
||||
```ini
|
||||
# frpc.ini
|
||||
[web]
|
||||
type = http
|
||||
local_port = 80
|
||||
custom_domains = test.yourdomain.com
|
||||
http_user = abc
|
||||
http_pwd = abc
|
||||
```
|
||||
|
||||
Visit `http://test.yourdomain.com` and now you need to input username and password.
|
||||
|
||||
### Custom subdomain names
|
||||
|
||||
It is convenient to use `subdomain` configure for http、https type when many people use one frps server together.
|
||||
|
||||
```ini
|
||||
# frps.ini
|
||||
subdomain_host = frps.com
|
||||
```
|
||||
|
||||
Resolve `*.frps.com` to the frps server's IP.
|
||||
|
||||
```ini
|
||||
# frpc.ini
|
||||
[web]
|
||||
type = http
|
||||
local_port = 80
|
||||
subdomain = test
|
||||
```
|
||||
|
||||
Now you can visit your web service by host `test.frps.com`.
|
||||
|
||||
Note that if `subdomain_host` is not empty, `custom_domains` should not be the subdomain of `subdomain_host`.
|
||||
|
||||
### URL routing
|
||||
|
||||
frp support forward http requests to different backward web services by url routing.
|
||||
|
||||
`locations` specify the prefix of URL used for routing. frps first searches for the most specific prefix location given by literal strings regardless of the listed order.
|
||||
|
||||
```ini
|
||||
# frpc.ini
|
||||
[web01]
|
||||
type = http
|
||||
local_port = 80
|
||||
custom_domains = web.yourdomain.com
|
||||
locations = /
|
||||
|
||||
[web02]
|
||||
type = http
|
||||
local_port = 81
|
||||
custom_domains = web.yourdomain.com
|
||||
locations = /news,/about
|
||||
```
|
||||
Http requests with url prefix `/news` and `/about` will be forwarded to **web02** and others to **web01**.
|
||||
|
||||
### Connect frps by HTTP PROXY
|
||||
|
||||
frpc can connect frps using HTTP PROXY if you set os environment `HTTP_PROXY` or configure `http_proxy` param in frpc.ini file.
|
||||
|
||||
It only works when protocol is tcp.
|
||||
|
||||
```ini
|
||||
# frpc.ini
|
||||
[common]
|
||||
server_addr = x.x.x.x
|
||||
server_port = 7000
|
||||
http_proxy = http://user:pwd@192.168.1.128:8080
|
||||
```
|
||||
|
||||
### Plugin
|
||||
|
||||
frpc only forward request to local tcp or udp port by default.
|
||||
|
||||
Plugin is used for providing rich features. There are built-in plugins such as **unix_domain_socket**, **http_proxy** and you can see [example usage](#example-usage).
|
||||
|
||||
Specify which plugin to use by `plugin` parameter. Configuration parameters of plugin should be started with `plugin_`. `local_ip` and `local_port` is useless for plugin.
|
||||
|
||||
Using plugin **http_proxy**:
|
||||
|
||||
```ini
|
||||
# frpc.ini
|
||||
[http_proxy]
|
||||
type = tcp
|
||||
remote_port = 6000
|
||||
plugin = http_proxy
|
||||
plugin_http_user = abc
|
||||
plugin_http_passwd = abc
|
||||
```
|
||||
|
||||
`plugin_http_user` and `plugin_http_passwd` are configuration parameters used in `http_proxy` plugin.
|
||||
|
||||
|
||||
## Development Plan
|
||||
|
||||
* Log http request information in frps.
|
||||
* Direct reverse proxy, like haproxy.
|
||||
* Load balance to different service in frpc.
|
||||
* Frpc can directly be a webserver for static files.
|
||||
* P2p communicate by make udp hole to penetrate NAT.
|
||||
* kubernetes ingress support.
|
||||
|
||||
|
||||
## Contributing
|
||||
|
||||
Interested in getting involved? We would love to help you!
|
||||
Interested in getting involved? We would like to help you!
|
||||
|
||||
* Take a look at our [issues list](https://github.com/fatedier/frp/issues) and consider submitting a patch
|
||||
* Take a look at our [issues list](https://github.com/fatedier/frp/issues) and consider sending a Pull Request to **dev branch**.
|
||||
* If you want to add a new feature, please create an issue first to describe the new feature, as well as the implementation approach. Once a proposal is accepted, create an implementation of the new features and submit it as a pull request.
|
||||
* Sorry for my poor english and improvement for this document is welcome even some typo fix.
|
||||
* If you have some wanderful ideas, send email to fatedier@gmail.com.
|
||||
|
||||
## Contributors
|
||||
**Note: We prefer you to give your advise in [issues](https://github.com/fatedier/frp/issues), so others with a same question can search it quickly and we don't need to answer them repeatly.**
|
||||
|
||||
* [fatedier](https://github.com/fatedier)
|
||||
* [Hurricanezwf](https://github.com/Hurricanezwf)
|
||||
* [vashstorm](https://github.com/vashstorm)
|
||||
## Donation
|
||||
|
||||
If frp help you a lot, you can support us by:
|
||||
|
||||
frp QQ group: 606194980
|
||||
|
||||
### AliPay
|
||||
|
||||

|
||||
|
||||
### Wechat Pay
|
||||
|
||||

|
||||
|
||||
### Paypal
|
||||
|
||||
Donate money by [paypal](https://www.paypal.me/fatedier) to my account **fatedier@gmail.com**.
|
||||
|
550
README_zh.md
550
README_zh.md
@@ -1,45 +1,547 @@
|
||||
# frp
|
||||
|
||||
[](https://travis-ci.org/fatedier/frp)
|
||||
[](https://travis-ci.org/fatedier/frp)
|
||||
|
||||
[README](README.md) | [中文文档](README_zh.md)
|
||||
|
||||
>frp 是一个高性能的反向代理应用,可以帮助你轻松的进行内网穿透,对外网提供服务,对于 http 服务还支持虚拟主机功能,访问80端口,可以根据域名路由到后端不同的 http 服务。
|
||||
frp 是一个可用于内网穿透的高性能的反向代理应用,支持 tcp, udp, http, https 协议。
|
||||
|
||||
## frp 的作用?
|
||||
## 目录
|
||||
|
||||
* 利用处于内网或防火墙后的机器,对外网环境提供 http 服务。
|
||||
* 对于 http 服务支持基于域名的虚拟主机,支持自定义域名绑定,使多个域名可以共用一个80端口。
|
||||
* 利用处于内网或防火墙后的机器,对外网环境提供 tcp 服务,例如在家里通过 ssh 访问公司局域网内的主机。
|
||||
* 可查看通过代理的所有 http 请求和响应的详细信息。(待开发)
|
||||
<!-- vim-markdown-toc GFM -->
|
||||
* [frp 的作用](#frp-的作用)
|
||||
* [开发状态](#开发状态)
|
||||
* [架构](#架构)
|
||||
* [使用示例](#使用示例)
|
||||
* [通过 ssh 访问公司内网机器](#通过-ssh-访问公司内网机器)
|
||||
* [通过自定义域名访问部署于内网的 web 服务](#通过自定义域名访问部署于内网的-web-服务)
|
||||
* [转发 DNS 查询请求](#转发-dns-查询请求)
|
||||
* [转发 Unix域套接字](#转发-unix域套接字)
|
||||
* [通过 frpc 所在机器访问外网](#通过-frpc-所在机器访问外网)
|
||||
* [功能说明](#功能说明)
|
||||
* [Dashboard](#dashboard)
|
||||
* [身份验证](#身份验证)
|
||||
* [加密与压缩](#加密与压缩)
|
||||
* [服务器端热加载配置文件](#服务器端热加载配置文件)
|
||||
* [特权模式](#特权模式)
|
||||
* [端口白名单](#端口白名单)
|
||||
* [TCP 多路复用](#tcp-多路复用)
|
||||
* [支持 kcp 协议](#支持-kcp-协议)
|
||||
* [连接池](#连接池)
|
||||
* [修改 Host Header](#修改-host-header)
|
||||
* [通过密码保护你的 web 服务](#通过密码保护你的-web-服务)
|
||||
* [自定义二级域名](#自定义二级域名)
|
||||
* [URL 路由](#url-路由)
|
||||
* [通过代理连接 frps](#通过代理连接-frps)
|
||||
* [插件](#插件)
|
||||
* [开发计划](#开发计划)
|
||||
* [为 frp 做贡献](#为-frp-做贡献)
|
||||
* [捐助](#捐助)
|
||||
* [支付宝扫码捐赠](#支付宝扫码捐赠)
|
||||
* [微信支付捐赠](#微信支付捐赠)
|
||||
* [Paypal 捐赠](#paypal-捐赠)
|
||||
|
||||
<!-- vim-markdown-toc -->
|
||||
|
||||
## frp 的作用
|
||||
|
||||
* 利用处于内网或防火墙后的机器,对外网环境提供 http 或 https 服务。
|
||||
* 对于 http, https 服务支持基于域名的虚拟主机,支持自定义域名绑定,使多个域名可以共用一个80端口。
|
||||
* 利用处于内网或防火墙后的机器,对外网环境提供 tcp 和 udp 服务,例如在家里通过 ssh 访问处于公司内网环境内的主机。
|
||||
|
||||
## 开发状态
|
||||
|
||||
frp 目前正在前期开发阶段,master分支用于发布稳定版本,dev分支用于开发,您可以尝试下载最新的 release 版本进行测试。
|
||||
frp 仍然处于前期开发阶段,未经充分测试与验证,不推荐用于生产环境。
|
||||
|
||||
**在 1.0 版本以前,交互协议都可能会被改变,不能保证向后兼容。**
|
||||
master 分支用于发布稳定版本,dev 分支用于开发,您可以尝试下载最新的 release 版本进行测试。
|
||||
|
||||
## 快速开始
|
||||
|
||||
[使用文档](/doc/quick_start_zh.md)
|
||||
|
||||
[tcp 端口转发](/doc/quick_start_zh.md#tcp-端口转发)
|
||||
|
||||
[http 端口转发,自定义域名绑定](/doc/quick_start_zh.md#http-端口转发自定义域名绑定)
|
||||
**目前的交互协议可能随时改变,不保证向后兼容,升级新版本时需要注意公告说明同时升级服务端和客户端。**
|
||||
|
||||
## 架构
|
||||
|
||||

|
||||
|
||||
## 贡献代码
|
||||
## 使用示例
|
||||
|
||||
如果您对这个项目感兴趣,我们非常欢迎您参与其中!
|
||||
根据对应的操作系统及架构,从 [Release](https://github.com/fatedier/frp/releases) 页面下载最新版本的程序。
|
||||
|
||||
* 如果您需要提交问题,可以通过 [issues](https://github.com/fatedier/frp/issues) 来完成。
|
||||
* 如果您有新的功能需求,可以反馈至 fatedier@gmail.com 共同讨论。
|
||||
将 **frps** 及 **frps.ini** 放到具有公网 IP 的机器上。
|
||||
|
||||
## 贡献者
|
||||
将 **frpc** 及 **frpc.ini** 放到处于内网环境的机器上。
|
||||
|
||||
* [fatedier](https://github.com/fatedier)
|
||||
* [Hurricanezwf](https://github.com/Hurricanezwf)
|
||||
* [vashstorm](https://github.com/vashstorm)
|
||||
### 通过 ssh 访问公司内网机器
|
||||
|
||||
1. 修改 frps.ini 文件,这里使用了最简化的配置:
|
||||
|
||||
```ini
|
||||
# frps.ini
|
||||
[common]
|
||||
bind_port = 7000
|
||||
```
|
||||
|
||||
2. 启动 frps:
|
||||
|
||||
`./frps -c ./frps.ini`
|
||||
|
||||
3. 修改 frpc.ini 文件,假设 frps 所在服务器的公网 IP 为 x.x.x.x;
|
||||
|
||||
```ini
|
||||
# frpc.ini
|
||||
[common]
|
||||
server_addr = x.x.x.x
|
||||
server_port = 7000
|
||||
|
||||
[ssh]
|
||||
type = tcp
|
||||
local_ip = 127.0.0.1
|
||||
local_port = 22
|
||||
remote_port = 6000
|
||||
```
|
||||
|
||||
4. 启动 frpc:
|
||||
|
||||
`./frpc -c ./frpc.ini`
|
||||
|
||||
5. 通过 ssh 访问内网机器,假设用户名为 test:
|
||||
|
||||
`ssh -oPort=6000 test@x.x.x.x`
|
||||
|
||||
### 通过自定义域名访问部署于内网的 web 服务
|
||||
|
||||
有时想要让其他人通过域名访问或者测试我们在本地搭建的 web 服务,但是由于本地机器没有公网 IP,无法将域名解析到本地的机器,通过 frp 就可以实现这一功能,以下示例为 http 服务,https 服务配置方法相同, vhost_http_port 替换为 vhost_https_port, type 设置为 https 即可。
|
||||
|
||||
1. 修改 frps.ini 文件,设置 http 访问端口为 8080:
|
||||
|
||||
```ini
|
||||
# frps.ini
|
||||
[common]
|
||||
bind_port = 7000
|
||||
vhost_http_port = 8080
|
||||
```
|
||||
|
||||
2. 启动 frps;
|
||||
|
||||
`./frps -c ./frps.ini`
|
||||
|
||||
3. 修改 frpc.ini 文件,假设 frps 所在的服务器的 IP 为 x.x.x.x,local_port 为本地机器上 web 服务对应的端口, 绑定自定义域名 `www.yourdomain.com`:
|
||||
|
||||
```ini
|
||||
# frpc.ini
|
||||
[common]
|
||||
server_addr = x.x.x.x
|
||||
server_port = 7000
|
||||
|
||||
[web]
|
||||
type = http
|
||||
local_port = 80
|
||||
custom_domains = www.yourdomain.com
|
||||
```
|
||||
|
||||
4. 启动 frpc:
|
||||
|
||||
`./frpc -c ./frpc.ini`
|
||||
|
||||
5. 将 `www.yourdomain.com` 的域名 A 记录解析到 IP `x.x.x.x`,如果服务器已经有对应的域名,也可以将 CNAME 记录解析到服务器原先的域名。
|
||||
|
||||
6. 通过浏览器访问 `http://www.yourdomain.com:8080` 即可访问到处于内网机器上的 web 服务。
|
||||
|
||||
### 转发 DNS 查询请求
|
||||
|
||||
DNS 查询请求通常使用 UDP 协议,frp 支持对内网 UDP 服务的穿透,配置方式和 TCP 基本一致。
|
||||
|
||||
1. 修改 frps.ini 文件:
|
||||
|
||||
```ini
|
||||
# frps.ini
|
||||
[common]
|
||||
bind_port = 7000
|
||||
```
|
||||
|
||||
2. 启动 frps:
|
||||
|
||||
`./frps -c ./frps.ini`
|
||||
|
||||
3. 修改 frpc.ini 文件,设置 frps 所在服务器的 IP 为 x.x.x.x,转发到 Google 的 DNS 查询服务器 `8.8.8.8` 的 udp 53 端口:
|
||||
|
||||
```ini
|
||||
# frpc.ini
|
||||
[common]
|
||||
server_addr = x.x.x.x
|
||||
server_port = 7000
|
||||
|
||||
[dns]
|
||||
type = udp
|
||||
local_ip = 8.8.8.8
|
||||
local_port = 53
|
||||
remote_port = 6000
|
||||
```
|
||||
|
||||
4. 启动 frpc:
|
||||
|
||||
`./frpc -c ./frpc.ini`
|
||||
|
||||
5. 通过 dig 测试 UDP 包转发是否成功,预期会返回 `www.google.com` 域名的解析结果:
|
||||
|
||||
`dig @x.x.x.x -p 6000 www.goolge.com`
|
||||
|
||||
### 转发 Unix域套接字
|
||||
|
||||
通过 tcp 端口访问内网的 unix域套接字(和 docker daemon 通信)。
|
||||
|
||||
1. 修改 frps.ini 文件:
|
||||
|
||||
```ini
|
||||
# frps.ini
|
||||
[common]
|
||||
bind_port = 7000
|
||||
```
|
||||
|
||||
2. 启动 frps:
|
||||
|
||||
`./frps -c ./frps.ini`
|
||||
|
||||
3. 修改 frpc.ini 文件,启用 unix_domain_socket 插件:
|
||||
|
||||
```ini
|
||||
# frpc.ini
|
||||
[common]
|
||||
server_addr = x.x.x.x
|
||||
server_port = 7000
|
||||
|
||||
[unix_domain_socket]
|
||||
type = tcp
|
||||
remote_port = 6000
|
||||
plugin = unix_domain_socket
|
||||
plugin_unix_path = /var/run/docker.sock
|
||||
```
|
||||
|
||||
4. 启动 frpc:
|
||||
|
||||
`./frpc -c ./frpc.ini`
|
||||
|
||||
5. 通过 curl 命令查看 docker 版本信息
|
||||
|
||||
`curl http://x.x.x.x:6000/version`
|
||||
|
||||
### 通过 frpc 所在机器访问外网
|
||||
|
||||
frpc 内置了 http proxy 插件,可以使其他机器通过 frpc 的网络访问互联网。
|
||||
|
||||
frps 的部署步骤同上。
|
||||
|
||||
1. 修改 frpc.ini 文件,启用 http_proxy 插件:
|
||||
|
||||
```ini
|
||||
# frpc.ini
|
||||
[common]
|
||||
server_addr = x.x.x.x
|
||||
server_port = 7000
|
||||
|
||||
[http_proxy]
|
||||
type = tcp
|
||||
remote_port = 6000
|
||||
plugin = http_proxy
|
||||
```
|
||||
|
||||
4. 启动 frpc:
|
||||
|
||||
`./frpc -c ./frpc.ini`
|
||||
|
||||
5. 浏览器设置 http 代理地址为 `x.x.x.x:6000`,通过 frpc 机器的网络访问互联网。
|
||||
|
||||
## 功能说明
|
||||
|
||||
### Dashboard
|
||||
|
||||
通过浏览器查看 frp 的状态以及代理统计信息展示。
|
||||
|
||||
需要在 frps.ini 中指定 dashboard 服务使用的端口,即可开启此功能:
|
||||
|
||||
```ini
|
||||
[common]
|
||||
dashboard_port = 7500
|
||||
# dashboard 用户名密码,默认都为 admin
|
||||
dashboard_user = admin
|
||||
dashboard_pwd = admin
|
||||
```
|
||||
|
||||
打开浏览器通过 `http://[server_addr]:7500` 访问 dashboard 界面,用户名密码默认为 `admin`。
|
||||
|
||||

|
||||
|
||||
### 身份验证
|
||||
|
||||
从 v0.10.0 版本开始,所有 proxy 配置全部放在客户端(也就是之前版本的特权模式),服务端和客户端的 common 配置中的 `privilege_token` 参数一致则身份验证通过。
|
||||
|
||||
需要注意的是 frpc 所在机器和 frps 所在机器的时间相差不能超过 15 分钟,因为时间戳会被用于加密验证中,防止报文被劫持后被其他人利用。
|
||||
|
||||
这个超时时间可以在配置文件中通过 `authentication_timeout` 这个参数来修改,单位为秒,默认值为 900,即 15 分钟。如果修改为 0,则 frps 将不对身份验证报文的时间戳进行超时校验。
|
||||
|
||||
### 加密与压缩
|
||||
|
||||
这两个功能默认是不开启的,需要在 frpc.ini 中通过配置来为指定的代理启用加密与压缩的功能,压缩算法使用 snappy:
|
||||
|
||||
```ini
|
||||
# frpc.ini
|
||||
[ssh]
|
||||
type = tcp
|
||||
local_port = 22
|
||||
remote_port = 6000
|
||||
use_encryption = true
|
||||
use_compression = true
|
||||
```
|
||||
|
||||
如果公司内网防火墙对外网访问进行了流量识别与屏蔽,例如禁止了 ssh 协议等,通过设置 `use_encryption = true`,将 frpc 与 frps 之间的通信内容加密传输,将会有效防止流量被拦截。
|
||||
|
||||
如果传输的报文长度较长,通过设置 `use_compression = true` 对传输内容进行压缩,可以有效减小 frpc 与 frps 之间的网络流量,加快流量转发速度,但是会额外消耗一些 cpu 资源。
|
||||
|
||||
### 服务器端热加载配置文件
|
||||
|
||||
由于从 v0.10.0 版本开始,所有 proxy 都在客户端配置,这个功能暂时移除。
|
||||
|
||||
### 特权模式
|
||||
|
||||
由于从 v0.10.0 版本开始,所有 proxy 都在客户端配置,原先的特权模式是目前唯一支持的模式。
|
||||
|
||||
#### 端口白名单
|
||||
|
||||
为了防止端口被滥用,可以手动指定允许哪些端口被使用,在 frps.ini 中通过 privilege_allow_ports 来指定:
|
||||
|
||||
```ini
|
||||
# frps.ini
|
||||
[common]
|
||||
privilege_allow_ports = 2000-3000,3001,3003,4000-50000
|
||||
```
|
||||
|
||||
privilege_allow_ports 可以配置允许使用的某个指定端口或者是一个范围内的所有端口,以 `,` 分隔,指定的范围以 `-` 分隔。
|
||||
|
||||
### TCP 多路复用
|
||||
|
||||
从 v0.10.0 版本开始,客户端和服务器端之间的连接支持多路复用,不再需要为每一个用户请求创建一个连接,使连接建立的延迟降低,并且避免了大量文件描述符的占用,使 frp 可以承载更高的并发数。
|
||||
|
||||
该功能默认启用,如需关闭,可以在 frps.ini 和 frpc.ini 中配置,该配置项在服务端和客户端必须一致:
|
||||
|
||||
```ini
|
||||
# frps.ini 和 frpc.ini 中
|
||||
[common]
|
||||
tcp_mux = false
|
||||
```
|
||||
|
||||
### 支持 kcp 协议
|
||||
|
||||
从 v0.12.0 版本开始,底层通信协议支持选择 kcp 协议,在弱网环境下传输效率提升明显,但是会有一些额外的流量消耗。
|
||||
|
||||
开启 kcp 协议支持:
|
||||
|
||||
1. 在 frps.ini 中启用 kcp 协议支持,指定一个 udp 端口用于接收客户端请求:
|
||||
|
||||
```ini
|
||||
# frps.ini
|
||||
[common]
|
||||
bind_port = 7000
|
||||
# kcp 绑定的是 udp 端口,可以和 bind_port 一样
|
||||
kcp_bind_port = 7000
|
||||
```
|
||||
|
||||
2. 在 frpc.ini 指定需要使用的协议类型,目前只支持 tcp 和 kcp。其他代理配置不需要变更:
|
||||
|
||||
```ini
|
||||
# frpc.ini
|
||||
[common]
|
||||
server_addr = x.x.x.x
|
||||
# server_port 指定为 frps 的 kcp_bind_port
|
||||
server_port = 7000
|
||||
protocol = kcp
|
||||
```
|
||||
|
||||
3. 像之前一样使用 frp,需要注意开放相关机器上的 udp 的端口的访问权限。
|
||||
|
||||
### 连接池
|
||||
|
||||
默认情况下,当用户请求建立连接后,frps 才会请求 frpc 主动与后端服务建立一个连接。当为指定的代理启用连接池后,frp 会预先和后端服务建立起指定数量的连接,每次接收到用户请求后,会从连接池中取出一个连接和用户连接关联起来,避免了等待与后端服务建立连接以及 frpc 和 frps 之间传递控制信息的时间。
|
||||
|
||||
这一功能比较适合有大量短连接请求时开启。
|
||||
|
||||
1. 首先可以在 frps.ini 中设置每个代理可以创建的连接池上限,避免大量资源占用,客户端设置超过此配置后会被调整到当前值:
|
||||
|
||||
```ini
|
||||
# frps.ini
|
||||
[common]
|
||||
max_pool_count = 5
|
||||
```
|
||||
|
||||
2. 在 frpc.ini 中为客户端启用连接池,指定预创建连接的数量:
|
||||
|
||||
```ini
|
||||
# frpc.ini
|
||||
[common]
|
||||
pool_count = 1
|
||||
```
|
||||
|
||||
### 修改 Host Header
|
||||
|
||||
通常情况下 frp 不会修改转发的任何数据。但有一些后端服务会根据 http 请求 header 中的 host 字段来展现不同的网站,例如 nginx 的虚拟主机服务,启用 host-header 的修改功能可以动态修改 http 请求中的 host 字段。该功能仅限于 http 类型的代理。
|
||||
|
||||
```ini
|
||||
# frpc.ini
|
||||
[web]
|
||||
type = http
|
||||
local_port = 80
|
||||
custom_domains = test.yourdomain.com
|
||||
host_header_rewrite = dev.yourdomain.com
|
||||
```
|
||||
|
||||
原来 http 请求中的 host 字段 `test.yourdomain.com` 转发到后端服务时会被替换为 `dev.yourdomain.com`。
|
||||
|
||||
### 通过密码保护你的 web 服务
|
||||
|
||||
由于所有客户端共用一个 frps 的 http 服务端口,任何知道你的域名和 url 的人都能访问到你部署在内网的 web 服务,但是在某些场景下需要确保只有限定的用户才能访问。
|
||||
|
||||
frp 支持通过 HTTP Basic Auth 来保护你的 web 服务,使用户需要通过用户名和密码才能访问到你的服务。
|
||||
|
||||
该功能目前仅限于 http 类型的代理,需要在 frpc 的代理配置中添加用户名和密码的设置。
|
||||
|
||||
```ini
|
||||
# frpc.ini
|
||||
[web]
|
||||
type = http
|
||||
local_port = 80
|
||||
custom_domains = test.yourdomain.com
|
||||
http_user = abc
|
||||
http_pwd = abc
|
||||
```
|
||||
|
||||
通过浏览器访问 `http://test.yourdomain.com`,需要输入配置的用户名和密码才能访问。
|
||||
|
||||
### 自定义二级域名
|
||||
|
||||
在多人同时使用一个 frps 时,通过自定义二级域名的方式来使用会更加方便。
|
||||
|
||||
通过在 frps 的配置文件中配置 `subdomain_host`,就可以启用该特性。之后在 frpc 的 http、https 类型的代理中可以不配置 `custom_domains`,而是配置一个 `subdomain` 参数。
|
||||
|
||||
只需要将 `*.{subdomain_host}` 解析到 frps 所在服务器。之后用户可以通过 `subdomain` 自行指定自己的 web 服务所需要使用的二级域名,通过 `{subdomain}.{subdomain_host}` 来访问自己的 web 服务。
|
||||
|
||||
```ini
|
||||
# frps.ini
|
||||
[common]
|
||||
subdomain_host = frps.com
|
||||
```
|
||||
|
||||
将泛域名 `*.frps.com` 解析到 frps 所在服务器的 IP 地址。
|
||||
|
||||
```ini
|
||||
# frpc.ini
|
||||
[web]
|
||||
type = http
|
||||
local_port = 80
|
||||
subdomain = test
|
||||
```
|
||||
|
||||
frps 和 fprc 都启动成功后,通过 `test.frps.com` 就可以访问到内网的 web 服务。
|
||||
|
||||
需要注意的是如果 frps 配置了 `subdomain_host`,则 `custom_domains` 中不能是属于 `subdomain_host` 的子域名或者泛域名。
|
||||
|
||||
同一个 http 或 https 类型的代理中 `custom_domains` 和 `subdomain` 可以同时配置。
|
||||
|
||||
### URL 路由
|
||||
|
||||
frp 支持根据请求的 URL 路径路由转发到不同的后端服务。
|
||||
|
||||
通过配置文件中的 `locations` 字段指定一个或多个 proxy 能够匹配的 URL 前缀(目前仅支持最大前缀匹配,之后会考虑正则匹配)。例如指定 `locations = /news`,则所有 URL 以 `/news` 开头的请求都会被转发到这个服务。
|
||||
|
||||
```ini
|
||||
# frpc.ini
|
||||
[web01]
|
||||
type = http
|
||||
local_port = 80
|
||||
custom_domains = web.yourdomain.com
|
||||
locations = /
|
||||
|
||||
[web02]
|
||||
type = http
|
||||
local_port = 81
|
||||
custom_domains = web.yourdomain.com
|
||||
locations = /news,/about
|
||||
```
|
||||
|
||||
按照上述的示例配置后,`web.yourdomain.com` 这个域名下所有以 `/news` 以及 `/about` 作为前缀的 URL 请求都会被转发到 web02,其余的请求会被转发到 web01。
|
||||
|
||||
### 通过代理连接 frps
|
||||
|
||||
在只能通过代理访问外网的环境内,frpc 支持通过 HTTP PROXY 和 frps 进行通信。
|
||||
|
||||
可以通过设置 `HTTP_PROXY` 系统环境变量或者通过在 frpc 的配置文件中设置 `http_proxy` 参数来使用此功能。
|
||||
|
||||
仅在 `protocol = tcp` 时生效。
|
||||
|
||||
```ini
|
||||
# frpc.ini
|
||||
[common]
|
||||
server_addr = x.x.x.x
|
||||
server_port = 7000
|
||||
http_proxy = http://user:pwd@192.168.1.128:8080
|
||||
```
|
||||
|
||||
### 插件
|
||||
|
||||
默认情况下,frpc 只会转发请求到本地 tcp 或 udp 端口。
|
||||
|
||||
插件模式是为了在客户端提供更加丰富的功能,目前内置的插件有 **unix_domain_socket**、**http_proxy**。具体使用方式请查看[使用示例](#使用示例)。
|
||||
|
||||
通过 `plugin` 指定需要使用的插件,插件的配置参数都以 `plugin_` 开头。使用插件后 `local_ip` 和 `local_port 不再需要配置。
|
||||
|
||||
使用 **http_proxy** 插件的示例:
|
||||
|
||||
```ini
|
||||
# frpc.ini
|
||||
[http_proxy]
|
||||
type = tcp
|
||||
remote_port = 6000
|
||||
plugin = http_proxy
|
||||
plugin_http_user = abc
|
||||
plugin_http_passwd = abc
|
||||
```
|
||||
|
||||
`plugin_http_user` 和 `plugin_http_passwd` 即为 `http_proxy` 插件可选的配置参数。
|
||||
|
||||
## 开发计划
|
||||
|
||||
计划在后续版本中加入的功能与优化,排名不分先后,如果有其他功能建议欢迎在 [issues](https://github.com/fatedier/frp/issues) 中反馈。
|
||||
|
||||
* frps 记录 http 请求日志。
|
||||
* frps 支持直接反向代理,类似 haproxy。
|
||||
* frpc 支持负载均衡到后端不同服务。
|
||||
* frpc 支持直接作为 webserver 访问指定静态页面。
|
||||
* 支持 udp 打洞的方式,提供两边内网机器直接通信,流量不经过服务器转发。
|
||||
* 集成对 k8s 等平台的支持。
|
||||
|
||||
## 为 frp 做贡献
|
||||
|
||||
frp 是一个免费且开源的项目,我们欢迎任何人为其开发和进步贡献力量。
|
||||
|
||||
* 在使用过程中出现任何问题,可以通过 [issues](https://github.com/fatedier/frp/issues) 来反馈。
|
||||
* Bug 的修复可以直接提交 Pull Request 到 dev 分支。
|
||||
* 如果是增加新的功能特性,请先创建一个 issue 并做简单描述以及大致的实现方法,提议被采纳后,就可以创建一个实现新特性的 Pull Request。
|
||||
* 欢迎对说明文档做出改善,帮助更多的人使用 frp,特别是英文文档。
|
||||
* 贡献代码请提交 PR 至 dev 分支,master 分支仅用于发布稳定可用版本。
|
||||
* 如果你有任何其他方面的问题,欢迎反馈至 fatedier@gmail.com 共同交流。
|
||||
|
||||
**提醒:和项目相关的问题最好在 [issues](https://github.com/fatedier/frp/issues) 中反馈,这样方便其他有类似问题的人可以快速查找解决方法,并且也避免了我们重复回答一些问题。**
|
||||
|
||||
## 捐助
|
||||
|
||||
如果您觉得 frp 对你有帮助,欢迎给予我们一定的捐助来维持项目的长期发展。
|
||||
|
||||
frp 交流群:606194980 (QQ 群号)
|
||||
|
||||
### 支付宝扫码捐赠
|
||||
|
||||

|
||||
|
||||
### 微信支付捐赠
|
||||
|
||||

|
||||
|
||||
### Paypal 捐赠
|
||||
|
||||
海外用户推荐通过 [Paypal](https://www.paypal.me/fatedier) 向我的账户 **fatedier@gmail.com** 进行捐赠。
|
||||
|
75
assets/assets.go
Normal file
75
assets/assets.go
Normal file
@@ -0,0 +1,75 @@
|
||||
// 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 assets
|
||||
|
||||
//go:generate statik -src=./static
|
||||
//go:generate go fmt statik/statik.go
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"path"
|
||||
|
||||
"github.com/rakyll/statik/fs"
|
||||
|
||||
_ "github.com/fatedier/frp/assets/statik"
|
||||
)
|
||||
|
||||
var (
|
||||
// store static files in memory by statik
|
||||
FileSystem http.FileSystem
|
||||
|
||||
// if prefix is not empty, we get file content from disk
|
||||
prefixPath string
|
||||
)
|
||||
|
||||
// if path is empty, load assets in memory
|
||||
// or set FileSystem using disk files
|
||||
func Load(path string) (err error) {
|
||||
prefixPath = path
|
||||
if prefixPath != "" {
|
||||
FileSystem = http.Dir(prefixPath)
|
||||
return nil
|
||||
} else {
|
||||
FileSystem, err = fs.New()
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func ReadFile(file string) (content string, err error) {
|
||||
if prefixPath == "" {
|
||||
file, err := FileSystem.Open(path.Join("/", file))
|
||||
if err != nil {
|
||||
return content, err
|
||||
}
|
||||
buf, err := ioutil.ReadAll(file)
|
||||
if err != nil {
|
||||
return content, err
|
||||
}
|
||||
content = string(buf)
|
||||
} else {
|
||||
file, err := os.Open(path.Join(prefixPath, file))
|
||||
if err != nil {
|
||||
return content, err
|
||||
}
|
||||
buf, err := ioutil.ReadAll(file)
|
||||
if err != nil {
|
||||
return content, err
|
||||
}
|
||||
content = string(buf)
|
||||
}
|
||||
return content, err
|
||||
}
|
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?5217927b66cc446ebfd3"></script><script type="text/javascript" src="vendor.js?66dfcf2d1c500e900413"></script><script type="text/javascript" src="index.js?bf962cded96400bef9a0"></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:"bf962cded96400bef9a0",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
446
client/control.go
Normal file
446
client/control.go
Normal file
@@ -0,0 +1,446 @@
|
||||
// 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"
|
||||
"github.com/xtaci/smux"
|
||||
)
|
||||
|
||||
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
|
||||
|
||||
// tcp stream multiplexing, if enabled
|
||||
session *smux.Session
|
||||
|
||||
// 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 {
|
||||
for {
|
||||
err := ctl.login()
|
||||
if err != nil {
|
||||
// if login_fail_exit is true, just exit this program
|
||||
// otherwise sleep a while and continues relogin to server
|
||||
if config.ClientCommonCfg.LoginFailExit {
|
||||
return err
|
||||
} else {
|
||||
ctl.Warn("login to server fail: %v", err)
|
||||
time.Sleep(30 * time.Second)
|
||||
}
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
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() {
|
||||
var (
|
||||
workConn net.Conn
|
||||
err error
|
||||
)
|
||||
if config.ClientCommonCfg.TcpMux {
|
||||
stream, err := ctl.session.OpenStream()
|
||||
if err != nil {
|
||||
ctl.Warn("start new work connection error: %v", err)
|
||||
return
|
||||
}
|
||||
workConn = net.WrapConn(stream)
|
||||
|
||||
} else {
|
||||
workConn, err = net.ConnectServerByHttpProxy(config.ClientCommonCfg.HttpProxy, config.ClientCommonCfg.Protocol,
|
||||
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.Debug("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()
|
||||
}
|
||||
if ctl.session != nil {
|
||||
ctl.session.Close()
|
||||
}
|
||||
|
||||
conn, err := net.ConnectServerByHttpProxy(config.ClientCommonCfg.HttpProxy, config.ClientCommonCfg.Protocol,
|
||||
fmt.Sprintf("%s:%d", config.ClientCommonCfg.ServerAddr, config.ClientCommonCfg.ServerPort))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if err != nil {
|
||||
conn.Close()
|
||||
}
|
||||
}()
|
||||
|
||||
if config.ClientCommonCfg.TcpMux {
|
||||
session, errRet := smux.Client(conn, nil)
|
||||
if errRet != nil {
|
||||
return errRet
|
||||
}
|
||||
stream, errRet := session.OpenStream()
|
||||
if errRet != nil {
|
||||
session.Close()
|
||||
return errRet
|
||||
}
|
||||
conn = net.WrapConn(stream)
|
||||
ctl.session = session
|
||||
}
|
||||
|
||||
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)
|
||||
ctl.sendCh <- &msg.CloseProxy{
|
||||
ProxyName: m.ProxyName,
|
||||
}
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
308
client/proxy.go
Normal file
308
client/proxy.go
Normal file
@@ -0,0 +1,308 @@
|
||||
// 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/plugin"
|
||||
"github.com/fatedier/frp/models/proto/udp"
|
||||
"github.com/fatedier/frp/utils/errors"
|
||||
frpIo "github.com/fatedier/frp/utils/io"
|
||||
"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
|
||||
proxyPlugin plugin.Plugin
|
||||
}
|
||||
|
||||
func (pxy *TcpProxy) Run() (err error) {
|
||||
if pxy.cfg.Plugin != "" {
|
||||
pxy.proxyPlugin, err = plugin.Create(pxy.cfg.Plugin, pxy.cfg.PluginParams)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (pxy *TcpProxy) Close() {
|
||||
if pxy.proxyPlugin != nil {
|
||||
pxy.proxyPlugin.Close()
|
||||
}
|
||||
}
|
||||
|
||||
func (pxy *TcpProxy) InWorkConn(conn frpNet.Conn) {
|
||||
HandleTcpWorkConnection(&pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, conn)
|
||||
}
|
||||
|
||||
// HTTP
|
||||
type HttpProxy struct {
|
||||
BaseProxy
|
||||
|
||||
cfg *config.HttpProxyConf
|
||||
proxyPlugin plugin.Plugin
|
||||
}
|
||||
|
||||
func (pxy *HttpProxy) Run() (err error) {
|
||||
if pxy.cfg.Plugin != "" {
|
||||
pxy.proxyPlugin, err = plugin.Create(pxy.cfg.Plugin, pxy.cfg.PluginParams)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (pxy *HttpProxy) Close() {
|
||||
if pxy.proxyPlugin != nil {
|
||||
pxy.proxyPlugin.Close()
|
||||
}
|
||||
}
|
||||
|
||||
func (pxy *HttpProxy) InWorkConn(conn frpNet.Conn) {
|
||||
HandleTcpWorkConnection(&pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, conn)
|
||||
}
|
||||
|
||||
// HTTPS
|
||||
type HttpsProxy struct {
|
||||
BaseProxy
|
||||
|
||||
cfg *config.HttpsProxyConf
|
||||
proxyPlugin plugin.Plugin
|
||||
}
|
||||
|
||||
func (pxy *HttpsProxy) Run() (err error) {
|
||||
if pxy.cfg.Plugin != "" {
|
||||
pxy.proxyPlugin, err = plugin.Create(pxy.cfg.Plugin, pxy.cfg.PluginParams)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (pxy *HttpsProxy) Close() {
|
||||
if pxy.proxyPlugin != nil {
|
||||
pxy.proxyPlugin.Close()
|
||||
}
|
||||
}
|
||||
|
||||
func (pxy *HttpsProxy) InWorkConn(conn frpNet.Conn) {
|
||||
HandleTcpWorkConnection(&pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &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")
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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, proxyPlugin plugin.Plugin,
|
||||
baseInfo *config.BaseProxyConf, workConn frpNet.Conn) {
|
||||
|
||||
var (
|
||||
remote io.ReadWriteCloser
|
||||
err error
|
||||
)
|
||||
remote = workConn
|
||||
if baseInfo.UseEncryption {
|
||||
remote, err = frpIo.WithEncryption(remote, []byte(config.ClientCommonCfg.PrivilegeToken))
|
||||
if err != nil {
|
||||
workConn.Error("create encryption stream error: %v", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
if baseInfo.UseCompression {
|
||||
remote = frpIo.WithCompression(remote)
|
||||
}
|
||||
|
||||
if proxyPlugin != nil {
|
||||
// if plugin is set, let plugin handle connections first
|
||||
workConn.Debug("handle by plugin: %s", proxyPlugin.Name())
|
||||
proxyPlugin.Handle(remote)
|
||||
workConn.Debug("handle by plugin finished")
|
||||
return
|
||||
} else {
|
||||
localConn, err := frpNet.ConnectServer("tcp", 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
|
||||
}
|
||||
|
||||
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())
|
||||
frpIo.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"
|
||||
|
||||
"frp/models/client"
|
||||
"frp/utils/log"
|
||||
"frp/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 (
|
||||
@@ -36,7 +37,8 @@ var usage string = `frpc is the client of frp
|
||||
|
||||
Usage:
|
||||
frpc [-c config_file] [-L log_file] [--log-level=<log_level>] [--server-addr=<server_addr>]
|
||||
frpc -h | --help | --version
|
||||
frpc -h | --help
|
||||
frpc -v | --version
|
||||
|
||||
Options:
|
||||
-c config_file set config file
|
||||
@@ -44,33 +46,42 @@ Options:
|
||||
--log-level=<log_level> set log level: debug, info, warn, error
|
||||
--server-addr=<server_addr> addr which frps is listening for, example: 0.0.0.0:7000
|
||||
-h --help show this screen
|
||||
--version show version
|
||||
-v --version show version
|
||||
`
|
||||
|
||||
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 {
|
||||
@@ -84,22 +95,30 @@ 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
|
||||
}
|
||||
|
||||
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)
|
||||
if args["-v"] != nil {
|
||||
if args["-v"].(bool) {
|
||||
fmt.Println(version.Full())
|
||||
os.Exit(0)
|
||||
}
|
||||
}
|
||||
|
||||
log.Info("Start frpc success")
|
||||
pxyCfgs, err := config.LoadProxyConfFromFile(config.ClientCommonCfg.User, conf, config.ClientCommonCfg.Start)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
wait.Wait()
|
||||
log.Warn("All proxy exit!")
|
||||
log.InitLog(config.ClientCommonCfg.LogWay, config.ClientCommonCfg.LogFile,
|
||||
config.ClientCommonCfg.LogLevel, config.ClientCommonCfg.LogMaxDays)
|
||||
|
||||
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()
|
||||
}
|
@@ -1,32 +1,9 @@
|
||||
# [common] is integral section
|
||||
[common]
|
||||
server_addr = 0.0.0.0
|
||||
server_addr = 127.0.0.1
|
||||
server_port = 7000
|
||||
# console or real logFile path like ./frpc.log
|
||||
log_file = ./frpc.log
|
||||
# debug, info, warn, error
|
||||
log_level = info
|
||||
log_max_days = 3
|
||||
# for authentication
|
||||
auth_token = 123
|
||||
|
||||
# ssh is the proxy name same as server's configuration
|
||||
[ssh]
|
||||
# tcp | http, 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 = true
|
||||
|
||||
# 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
|
||||
[web01]
|
||||
type = http
|
||||
local_ip = 127.0.0.1
|
||||
local_port = 80
|
||||
use_encryption = true
|
||||
|
||||
[web02]
|
||||
type = http
|
||||
local_ip = 127.0.0.1
|
||||
local_port = 8000
|
||||
remote_port = 6000
|
||||
|
112
conf/frpc_full.ini
Normal file
112
conf/frpc_full.ini
Normal file
@@ -0,0 +1,112 @@
|
||||
# [common] is integral section
|
||||
[common]
|
||||
# A literal address or host name for IPv6 must be enclosed
|
||||
# in square brackets, as in "[::1]:80", "[ipv6-host]:http" or "[ipv6-host%zone]:80"
|
||||
server_addr = 0.0.0.0
|
||||
server_port = 7000
|
||||
|
||||
# if you want to connect frps by http proxy, you can set http_proxy here or in global environment variables
|
||||
# it only works when protocol is tcp
|
||||
# http_proxy = http://user:pwd@192.168.1.128:8080
|
||||
|
||||
# console or real logFile path like ./frpc.log
|
||||
log_file = ./frpc.log
|
||||
|
||||
# trace, debug, info, warn, error
|
||||
log_level = info
|
||||
|
||||
log_max_days = 3
|
||||
|
||||
# for authentication
|
||||
privilege_token = 12345678
|
||||
|
||||
# connections will be established in advance, default value is zero
|
||||
pool_count = 5
|
||||
|
||||
# if tcp stream multiplexing is used, default is true, it must be same with frps
|
||||
tcp_mux = true
|
||||
|
||||
# your proxy name will be changed to {user}.{proxy}
|
||||
user = your_name
|
||||
|
||||
# decide if exit program when first login failed, otherwise continuous relogin to frps
|
||||
# default is true
|
||||
login_fail_exit = true
|
||||
|
||||
# communication protocol used to connect to server
|
||||
# now it supports tcp and kcp, default is tcp
|
||||
protocol = tcp
|
||||
|
||||
# proxy names you want to start divided by ','
|
||||
# default is empty, means all proxies
|
||||
# start = ssh,dns
|
||||
|
||||
# heartbeat configure, it's not recommended to modify the default value
|
||||
# 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 | 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
|
||||
# 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
|
||||
remote_port = 6002
|
||||
use_encryption = false
|
||||
use_compression = false
|
||||
|
||||
# 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_encryption = false
|
||||
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 = web01
|
||||
custom_domains = web02.yourdomain.com
|
||||
# locations is only useful for http type
|
||||
locations = /,/pic
|
||||
host_header_rewrite = example.com
|
||||
|
||||
[web02]
|
||||
type = https
|
||||
local_ip = 127.0.0.1
|
||||
local_port = 8000
|
||||
use_encryption = false
|
||||
use_compression = false
|
||||
subdomain = web01
|
||||
custom_domains = web02.yourdomain.com
|
||||
|
||||
[plugin_unix_domain_socket]
|
||||
type = tcp
|
||||
remote_port = 6003
|
||||
# if plugin is defined, local_ip and local_port is useless
|
||||
# plugin will handle connections got from frps
|
||||
plugin = unix_domain_socket
|
||||
# params set with prefix "plugin_" that plugin needed
|
||||
plugin_unix_path = /var/run/docker.sock
|
||||
|
||||
[plugin_http_proxy]
|
||||
type = tcp
|
||||
remote_port = 6004
|
||||
plugin = http_proxy
|
||||
plugin_http_user = abc
|
||||
plugin_http_passwd = abc
|
@@ -1,29 +1,2 @@
|
||||
# [common] is integral section
|
||||
[common]
|
||||
bind_addr = 0.0.0.0
|
||||
bind_port = 7000
|
||||
# optional
|
||||
vhost_http_port = 80
|
||||
# console or real logFile path like ./frps.log
|
||||
log_file = ./frps.log
|
||||
# debug, info, warn, error
|
||||
log_level = info
|
||||
log_max_days = 3
|
||||
|
||||
# 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
|
||||
|
||||
[web01]
|
||||
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]
|
||||
type = http
|
||||
auth_token = 123
|
||||
custom_domains = web02.yourdomain.com
|
||||
|
55
conf/frps_full.ini
Normal file
55
conf/frps_full.ini
Normal file
@@ -0,0 +1,55 @@
|
||||
# [common] is integral section
|
||||
[common]
|
||||
# A literal address or host name for IPv6 must be enclosed
|
||||
# in square brackets, as in "[::1]:80", "[ipv6-host]:http" or "[ipv6-host%zone]:80"
|
||||
bind_addr = 0.0.0.0
|
||||
bind_port = 7000
|
||||
|
||||
# udp port used for kcp protocol, it can be same with 'bind_port'
|
||||
# if not set, kcp is disabled in frps
|
||||
kcp_bind_port = 7000
|
||||
|
||||
# if you want to support virtual host, you must set the http port for listening (optional)
|
||||
vhost_http_port = 80
|
||||
vhost_https_port = 443
|
||||
|
||||
# if you want to configure or reload frps by dashboard, dashboard_port must be set
|
||||
dashboard_port = 7500
|
||||
|
||||
# dashboard user and pwd for basic auth protect, if not set, both default value is admin
|
||||
dashboard_user = admin
|
||||
dashboard_pwd = admin
|
||||
|
||||
# dashboard assets directory(only for debug mode)
|
||||
# assets_dir = ./static
|
||||
# console or real logFile path like ./frps.log
|
||||
log_file = ./frps.log
|
||||
|
||||
# trace, debug, info, warn, error
|
||||
log_level = info
|
||||
|
||||
log_max_days = 3
|
||||
|
||||
# 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 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 = 5
|
||||
|
||||
# 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
|
||||
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
|
||||
|
||||
# if tcp stream multiplexing is used, default is true
|
||||
tcp_mux = true
|
BIN
doc/pic/dashboard.png
Normal file
BIN
doc/pic/dashboard.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 31 KiB |
BIN
doc/pic/donate-alipay.png
Normal file
BIN
doc/pic/donate-alipay.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 36 KiB |
BIN
doc/pic/donate-wechatpay.png
Normal file
BIN
doc/pic/donate-wechatpay.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 27 KiB |
200
models/config/client_common.go
Normal file
200
models/config/client_common.go
Normal file
@@ -0,0 +1,200 @@
|
||||
// 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"
|
||||
"strings"
|
||||
|
||||
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
|
||||
TcpMux bool
|
||||
User string
|
||||
LoginFailExit bool
|
||||
Start map[string]struct{}
|
||||
Protocol 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,
|
||||
TcpMux: true,
|
||||
User: "",
|
||||
LoginFailExit: true,
|
||||
Start: make(map[string]struct{}),
|
||||
Protocol: "tcp",
|
||||
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", "tcp_mux")
|
||||
if ok && tmpStr == "false" {
|
||||
cfg.TcpMux = false
|
||||
} else {
|
||||
cfg.TcpMux = true
|
||||
}
|
||||
|
||||
tmpStr, ok = conf.Get("common", "user")
|
||||
if ok {
|
||||
cfg.User = tmpStr
|
||||
}
|
||||
|
||||
tmpStr, ok = conf.Get("common", "start")
|
||||
if ok {
|
||||
proxyNames := strings.Split(tmpStr, ",")
|
||||
for _, name := range proxyNames {
|
||||
cfg.Start[name] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
tmpStr, ok = conf.Get("common", "login_fail_exit")
|
||||
if ok && tmpStr == "false" {
|
||||
cfg.LoginFailExit = false
|
||||
} else {
|
||||
cfg.LoginFailExit = true
|
||||
}
|
||||
|
||||
tmpStr, ok = conf.Get("common", "protocol")
|
||||
if ok {
|
||||
// Now it only support tcp and kcp.
|
||||
if tmpStr != "kcp" {
|
||||
tmpStr = "tcp"
|
||||
}
|
||||
cfg.Protocol = 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
|
||||
}
|
492
models/config/proxy.go
Normal file
492
models/config/proxy.go
Normal file
@@ -0,0 +1,492 @@
|
||||
// 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"
|
||||
|
||||
"github.com/fatedier/frp/utils/util"
|
||||
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 := util.ContainsPort(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
|
||||
}
|
||||
|
||||
// Local service info
|
||||
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
|
||||
}
|
||||
|
||||
type PluginConf struct {
|
||||
Plugin string `json:"-"`
|
||||
PluginParams map[string]string `json:"-"`
|
||||
}
|
||||
|
||||
func (cfg *PluginConf) LoadFromFile(name string, section ini.Section) (err error) {
|
||||
cfg.Plugin = section["plugin"]
|
||||
cfg.PluginParams = make(map[string]string)
|
||||
if cfg.Plugin != "" {
|
||||
// get params begin with "plugin_"
|
||||
for k, v := range section {
|
||||
if strings.HasPrefix(k, "plugin_") {
|
||||
cfg.PluginParams[k] = v
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return fmt.Errorf("Parse conf error: proxy [%s] no plugin info found", name)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// TCP
|
||||
type TcpProxyConf struct {
|
||||
BaseProxyConf
|
||||
BindInfoConf
|
||||
|
||||
LocalSvrConf
|
||||
PluginConf
|
||||
}
|
||||
|
||||
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.PluginConf.LoadFromFile(name, section); err != nil {
|
||||
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
|
||||
PluginConf
|
||||
|
||||
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
|
||||
PluginConf
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// if len(startProxy) is 0, start all
|
||||
// otherwise just start proxies in startProxy map
|
||||
func LoadProxyConfFromFile(prefix string, conf ini.File, startProxy map[string]struct{}) (proxyConfs map[string]ProxyConf, err error) {
|
||||
if prefix != "" {
|
||||
prefix += "."
|
||||
}
|
||||
|
||||
startAll := true
|
||||
if len(startProxy) > 0 {
|
||||
startAll = false
|
||||
}
|
||||
proxyConfs = make(map[string]ProxyConf)
|
||||
for name, section := range conf {
|
||||
_, shouldStart := startProxy[name]
|
||||
if name != "common" && (startAll || shouldStart) {
|
||||
cfg, err := NewProxyConfFromFile(name, section)
|
||||
if err != nil {
|
||||
return proxyConfs, err
|
||||
}
|
||||
proxyConfs[prefix+name] = cfg
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
255
models/config/server_common.go
Normal file
255
models/config/server_common.go
Normal file
@@ -0,0 +1,255 @@
|
||||
// Copyright 2016 fatedier, fatedier@gmail.com
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/fatedier/frp/utils/util"
|
||||
ini "github.com/vaughan0/go-ini"
|
||||
)
|
||||
|
||||
var ServerCommonCfg *ServerCommonConf
|
||||
|
||||
// common config
|
||||
type ServerCommonConf struct {
|
||||
ConfigFile string
|
||||
BindAddr string
|
||||
BindPort int64
|
||||
KcpBindPort 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
|
||||
TcpMux bool
|
||||
|
||||
// if PrivilegeAllowPorts is not nil, tcp proxies which remote port exist in this map can be connected
|
||||
PrivilegeAllowPorts [][2]int64
|
||||
MaxPoolCount int64
|
||||
HeartBeatTimeout int64
|
||||
UserConnTimeout int64
|
||||
}
|
||||
|
||||
func GetDefaultServerCommonConf() *ServerCommonConf {
|
||||
return &ServerCommonConf{
|
||||
ConfigFile: "./frps.ini",
|
||||
BindAddr: "0.0.0.0",
|
||||
BindPort: 7000,
|
||||
KcpBindPort: 0,
|
||||
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: "",
|
||||
TcpMux: true,
|
||||
MaxPoolCount: 5,
|
||||
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", "kcp_bind_port")
|
||||
if ok {
|
||||
v, err = strconv.ParseInt(tmpStr, 10, 64)
|
||||
if err == nil && v > 0 {
|
||||
cfg.KcpBindPort = 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 {
|
||||
cfg.PrivilegeToken, _ = conf.Get("common", "privilege_token")
|
||||
|
||||
allowPortsStr, ok := conf.Get("common", "privilege_allow_ports")
|
||||
// TODO: check if conflicts exist in port ranges
|
||||
if ok {
|
||||
cfg.PrivilegeAllowPorts, err = util.GetPortRanges(allowPortsStr)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("Parse conf error: privilege_allow_ports is incorrect, %v", err)
|
||||
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", "tcp_mux")
|
||||
if ok && tmpStr == "false" {
|
||||
cfg.TcpMux = false
|
||||
} else {
|
||||
cfg.TcpMux = true
|
||||
}
|
||||
|
||||
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,21 +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"
|
||||
|
||||
// msg type
|
||||
const (
|
||||
NewCtlConn = iota
|
||||
NewWorkConn
|
||||
NoticeUserConn
|
||||
NewCtlConnRes
|
||||
HeartbeatReq
|
||||
HeartbeatRes
|
||||
var (
|
||||
ErrMsgType = errors.New("message type error")
|
||||
)
|
135
models/msg/msg.go
Normal file
135
models/msg/msg.go
Normal file
@@ -0,0 +1,135 @@
|
||||
// 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'
|
||||
TypeCloseProxy = 'c'
|
||||
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[TypeCloseProxy] = reflect.TypeOf(CloseProxy{})
|
||||
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 CloseProxy struct {
|
||||
ProxyName string `json:"proxy_name"`
|
||||
}
|
||||
|
||||
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
|
||||
}
|
283
models/plugin/http_proxy.go
Normal file
283
models/plugin/http_proxy.go
Normal file
@@ -0,0 +1,283 @@
|
||||
// Copyright 2017 frp team
|
||||
//
|
||||
// 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 plugin
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/fatedier/frp/utils/errors"
|
||||
frpIo "github.com/fatedier/frp/utils/io"
|
||||
frpNet "github.com/fatedier/frp/utils/net"
|
||||
)
|
||||
|
||||
const PluginHttpProxy = "http_proxy"
|
||||
|
||||
func init() {
|
||||
Register(PluginHttpProxy, NewHttpProxyPlugin)
|
||||
}
|
||||
|
||||
type Listener struct {
|
||||
conns chan net.Conn
|
||||
closed bool
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
func NewProxyListener() *Listener {
|
||||
return &Listener{
|
||||
conns: make(chan net.Conn, 64),
|
||||
}
|
||||
}
|
||||
|
||||
func (l *Listener) Accept() (net.Conn, error) {
|
||||
conn, ok := <-l.conns
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("listener closed")
|
||||
}
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
func (l *Listener) PutConn(conn net.Conn) error {
|
||||
err := errors.PanicToError(func() {
|
||||
l.conns <- conn
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
func (l *Listener) Close() error {
|
||||
l.mu.Lock()
|
||||
defer l.mu.Unlock()
|
||||
if !l.closed {
|
||||
close(l.conns)
|
||||
l.closed = true
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *Listener) Addr() net.Addr {
|
||||
return (*net.TCPAddr)(nil)
|
||||
}
|
||||
|
||||
type HttpProxy struct {
|
||||
l *Listener
|
||||
s *http.Server
|
||||
AuthUser string
|
||||
AuthPasswd string
|
||||
}
|
||||
|
||||
func NewHttpProxyPlugin(params map[string]string) (Plugin, error) {
|
||||
user := params["plugin_http_user"]
|
||||
passwd := params["plugin_http_passwd"]
|
||||
listener := NewProxyListener()
|
||||
|
||||
hp := &HttpProxy{
|
||||
l: listener,
|
||||
AuthUser: user,
|
||||
AuthPasswd: passwd,
|
||||
}
|
||||
|
||||
hp.s = &http.Server{
|
||||
Handler: hp,
|
||||
}
|
||||
|
||||
go hp.s.Serve(listener)
|
||||
return hp, nil
|
||||
}
|
||||
|
||||
func (hp *HttpProxy) Name() string {
|
||||
return PluginHttpProxy
|
||||
}
|
||||
|
||||
func (hp *HttpProxy) Handle(conn io.ReadWriteCloser) {
|
||||
var wrapConn frpNet.Conn
|
||||
if realConn, ok := conn.(frpNet.Conn); ok {
|
||||
wrapConn = realConn
|
||||
} else {
|
||||
wrapConn = frpNet.WrapReadWriteCloserToConn(conn)
|
||||
}
|
||||
|
||||
sc, rd := frpNet.NewShareConn(wrapConn)
|
||||
request, err := http.ReadRequest(bufio.NewReader(rd))
|
||||
if err != nil {
|
||||
wrapConn.Close()
|
||||
return
|
||||
}
|
||||
|
||||
if request.Method == http.MethodConnect {
|
||||
hp.handleConnectReq(request, frpIo.WrapReadWriteCloser(rd, wrapConn, nil))
|
||||
return
|
||||
}
|
||||
|
||||
hp.l.PutConn(sc)
|
||||
return
|
||||
}
|
||||
|
||||
func (hp *HttpProxy) Close() error {
|
||||
hp.s.Close()
|
||||
hp.l.Close()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (hp *HttpProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||
if ok := hp.Auth(req); !ok {
|
||||
rw.Header().Set("Proxy-Authenticate", "Basic")
|
||||
rw.WriteHeader(http.StatusProxyAuthRequired)
|
||||
return
|
||||
}
|
||||
|
||||
if req.Method == http.MethodConnect {
|
||||
// deprecated
|
||||
// Connect request is handled in Handle function.
|
||||
hp.ConnectHandler(rw, req)
|
||||
} else {
|
||||
hp.HttpHandler(rw, req)
|
||||
}
|
||||
}
|
||||
|
||||
func (hp *HttpProxy) HttpHandler(rw http.ResponseWriter, req *http.Request) {
|
||||
removeProxyHeaders(req)
|
||||
|
||||
resp, err := http.DefaultTransport.RoundTrip(req)
|
||||
if err != nil {
|
||||
http.Error(rw, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
copyHeaders(rw.Header(), resp.Header)
|
||||
rw.WriteHeader(resp.StatusCode)
|
||||
|
||||
_, err = io.Copy(rw, resp.Body)
|
||||
if err != nil && err != io.EOF {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// deprecated
|
||||
// Hijack needs to SetReadDeadline on the Conn of the request, but if we use stream compression here,
|
||||
// we may always get i/o timeout error.
|
||||
func (hp *HttpProxy) ConnectHandler(rw http.ResponseWriter, req *http.Request) {
|
||||
hj, ok := rw.(http.Hijacker)
|
||||
if !ok {
|
||||
rw.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
client, _, err := hj.Hijack()
|
||||
if err != nil {
|
||||
rw.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
remote, err := net.Dial("tcp", req.URL.Host)
|
||||
if err != nil {
|
||||
http.Error(rw, "Failed", http.StatusBadRequest)
|
||||
client.Close()
|
||||
return
|
||||
}
|
||||
client.Write([]byte("HTTP/1.1 200 OK\r\n\r\n"))
|
||||
|
||||
go frpIo.Join(remote, client)
|
||||
}
|
||||
|
||||
func (hp *HttpProxy) Auth(req *http.Request) bool {
|
||||
if hp.AuthUser == "" && hp.AuthPasswd == "" {
|
||||
return true
|
||||
}
|
||||
|
||||
s := strings.SplitN(req.Header.Get("Proxy-Authorization"), " ", 2)
|
||||
if len(s) != 2 {
|
||||
return false
|
||||
}
|
||||
|
||||
b, err := base64.StdEncoding.DecodeString(s[1])
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
pair := strings.SplitN(string(b), ":", 2)
|
||||
if len(pair) != 2 {
|
||||
return false
|
||||
}
|
||||
|
||||
if pair[0] != hp.AuthUser || pair[1] != hp.AuthPasswd {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (hp *HttpProxy) handleConnectReq(req *http.Request, rwc io.ReadWriteCloser) {
|
||||
defer rwc.Close()
|
||||
if ok := hp.Auth(req); !ok {
|
||||
res := getBadResponse()
|
||||
res.Write(rwc)
|
||||
return
|
||||
}
|
||||
|
||||
remote, err := net.Dial("tcp", req.URL.Host)
|
||||
if err != nil {
|
||||
res := &http.Response{
|
||||
StatusCode: 400,
|
||||
Proto: "HTTP/1.1",
|
||||
ProtoMajor: 1,
|
||||
ProtoMinor: 1,
|
||||
}
|
||||
res.Write(rwc)
|
||||
return
|
||||
}
|
||||
rwc.Write([]byte("HTTP/1.1 200 OK\r\n\r\n"))
|
||||
|
||||
frpIo.Join(remote, rwc)
|
||||
}
|
||||
|
||||
func copyHeaders(dst, src http.Header) {
|
||||
for key, values := range src {
|
||||
for _, value := range values {
|
||||
dst.Add(key, value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func removeProxyHeaders(req *http.Request) {
|
||||
req.RequestURI = ""
|
||||
req.Header.Del("Proxy-Connection")
|
||||
req.Header.Del("Connection")
|
||||
req.Header.Del("Proxy-Authenticate")
|
||||
req.Header.Del("Proxy-Authorization")
|
||||
req.Header.Del("TE")
|
||||
req.Header.Del("Trailers")
|
||||
req.Header.Del("Transfer-Encoding")
|
||||
req.Header.Del("Upgrade")
|
||||
}
|
||||
|
||||
func getBadResponse() *http.Response {
|
||||
header := make(map[string][]string)
|
||||
header["Proxy-Authenticate"] = []string{"Basic"}
|
||||
res := &http.Response{
|
||||
Status: "407 Not authorized",
|
||||
StatusCode: 407,
|
||||
Proto: "HTTP/1.1",
|
||||
ProtoMajor: 1,
|
||||
ProtoMinor: 1,
|
||||
Header: header,
|
||||
}
|
||||
return res
|
||||
}
|
45
models/plugin/plugin.go
Normal file
45
models/plugin/plugin.go
Normal file
@@ -0,0 +1,45 @@
|
||||
// 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 plugin
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
// Creators is used for create plugins to handle connections.
|
||||
var creators = make(map[string]CreatorFn)
|
||||
|
||||
// params has prefix "plugin_"
|
||||
type CreatorFn func(params map[string]string) (Plugin, error)
|
||||
|
||||
func Register(name string, fn CreatorFn) {
|
||||
creators[name] = fn
|
||||
}
|
||||
|
||||
func Create(name string, params map[string]string) (p Plugin, err error) {
|
||||
if fn, ok := creators[name]; ok {
|
||||
p, err = fn(params)
|
||||
} else {
|
||||
err = fmt.Errorf("plugin [%s] is not registered", name)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
type Plugin interface {
|
||||
Name() string
|
||||
Handle(conn io.ReadWriteCloser)
|
||||
Close() error
|
||||
}
|
69
models/plugin/unix_domain_socket.go
Normal file
69
models/plugin/unix_domain_socket.go
Normal file
@@ -0,0 +1,69 @@
|
||||
// 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 plugin
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
|
||||
frpIo "github.com/fatedier/frp/utils/io"
|
||||
)
|
||||
|
||||
const PluginUnixDomainSocket = "unix_domain_socket"
|
||||
|
||||
func init() {
|
||||
Register(PluginUnixDomainSocket, NewUnixDomainSocketPlugin)
|
||||
}
|
||||
|
||||
type UnixDomainSocketPlugin struct {
|
||||
UnixAddr *net.UnixAddr
|
||||
}
|
||||
|
||||
func NewUnixDomainSocketPlugin(params map[string]string) (p Plugin, err error) {
|
||||
unixPath, ok := params["plugin_unix_path"]
|
||||
if !ok {
|
||||
err = fmt.Errorf("plugin_unix_path not found")
|
||||
return
|
||||
}
|
||||
|
||||
unixAddr, errRet := net.ResolveUnixAddr("unix", unixPath)
|
||||
if errRet != nil {
|
||||
err = errRet
|
||||
return
|
||||
}
|
||||
|
||||
p = &UnixDomainSocketPlugin{
|
||||
UnixAddr: unixAddr,
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (uds *UnixDomainSocketPlugin) Handle(conn io.ReadWriteCloser) {
|
||||
localConn, err := net.DialUnix("unix", nil, uds.UnixAddr)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
frpIo.Join(localConn, conn)
|
||||
}
|
||||
|
||||
func (uds *UnixDomainSocketPlugin) Name() string {
|
||||
return PluginUnixDomainSocket
|
||||
}
|
||||
|
||||
func (uds *UnixDomainSocketPlugin) Close() error {
|
||||
return nil
|
||||
}
|
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)
|
||||
}
|
@@ -14,18 +14,32 @@ make -f ./Makefile.cross-compiles
|
||||
rm -rf ./packages
|
||||
mkdir ./packages
|
||||
|
||||
os_all='linux windows'
|
||||
arch_all='386 amd64'
|
||||
os_all='linux windows darwin'
|
||||
arch_all='386 amd64 arm mips64 mips64le mips mipsle'
|
||||
|
||||
for os in $os_all; do
|
||||
for arch in $arch_all; do
|
||||
frp_dir_name="frp_${frp_version}_${os}_${arch}"
|
||||
frp_path="./packages/frp_${frp_version}_${os}_${arch}"
|
||||
mkdir ${frp_path}
|
||||
|
||||
if [ "x${os}" = x"windows" ]; then
|
||||
if [ ! -f "./frpc_${os}_${arch}.exe" ]; then
|
||||
continue
|
||||
fi
|
||||
if [ ! -f "./frps_${os}_${arch}.exe" ]; then
|
||||
continue
|
||||
fi
|
||||
mkdir ${frp_path}
|
||||
mv ./frpc_${os}_${arch}.exe ${frp_path}/frpc.exe
|
||||
mv ./frps_${os}_${arch}.exe ${frp_path}/frps.exe
|
||||
else
|
||||
if [ ! -f "./frpc_${os}_${arch}" ]; then
|
||||
continue
|
||||
fi
|
||||
if [ ! -f "./frps_${os}_${arch}" ]; then
|
||||
continue
|
||||
fi
|
||||
mkdir ${frp_path}
|
||||
mv ./frpc_${os}_${arch} ${frp_path}/frpc
|
||||
mv ./frps_${os}_${arch} ${frp_path}/frps
|
||||
fi
|
383
server/control.go
Normal file
383
server/control.go
Normal file
@@ -0,0 +1,383 @@
|
||||
// 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 map[string]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(map[string]Proxy),
|
||||
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()
|
||||
}
|
||||
|
||||
ctl.mu.Lock()
|
||||
defer ctl.mu.Unlock()
|
||||
for _, pxy := range ctl.proxies {
|
||||
pxy.Close()
|
||||
ctl.svr.DelProxy(pxy.GetName())
|
||||
StatsCloseProxy(pxy.GetName(), 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.CloseProxy:
|
||||
ctl.CloseProxy(m)
|
||||
ctl.conn.Info("close proxy [%s] success", m.ProxyName)
|
||||
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.mu.Lock()
|
||||
ctl.proxies[pxy.GetName()] = pxy
|
||||
ctl.mu.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ctl *Control) CloseProxy(closeMsg *msg.CloseProxy) (err error) {
|
||||
ctl.mu.Lock()
|
||||
defer ctl.mu.Unlock()
|
||||
|
||||
pxy, ok := ctl.proxies[closeMsg.ProxyName]
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
pxy.Close()
|
||||
ctl.svr.DelProxy(pxy.GetName())
|
||||
StatsCloseProxy(pxy.GetName(), pxy.GetConf().GetBaseInfo().ProxyType)
|
||||
return
|
||||
}
|
161
server/dashboard.go
Normal file
161
server/dashboard.go
Normal file
@@ -0,0 +1,161 @@
|
||||
// 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 (
|
||||
"compress/gzip"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/fatedier/frp/assets"
|
||||
"github.com/fatedier/frp/models/config"
|
||||
|
||||
"github.com/julienschmidt/httprouter"
|
||||
)
|
||||
|
||||
var (
|
||||
httpServerReadTimeout = 10 * time.Second
|
||||
httpServerWriteTimeout = 10 * time.Second
|
||||
)
|
||||
|
||||
func RunDashboardServer(addr string, port int64) (err error) {
|
||||
// url router
|
||||
router := httprouter.New()
|
||||
|
||||
// api, see dashboard_api.go
|
||||
router.GET("/api/serverinfo", httprouterBasicAuth(apiServerInfo))
|
||||
router.GET("/api/proxy/tcp", httprouterBasicAuth(apiProxyTcp))
|
||||
router.GET("/api/proxy/udp", httprouterBasicAuth(apiProxyUdp))
|
||||
router.GET("/api/proxy/http", httprouterBasicAuth(apiProxyHttp))
|
||||
router.GET("/api/proxy/https", httprouterBasicAuth(apiProxyHttps))
|
||||
router.GET("/api/proxy/traffic/:name", httprouterBasicAuth(apiProxyTraffic))
|
||||
|
||||
// view
|
||||
router.Handler("GET", "/favicon.ico", http.FileServer(assets.FileSystem))
|
||||
router.Handler("GET", "/static/*filepath", MakeGzipHandler(basicAuthWraper(http.StripPrefix("/static/", http.FileServer(assets.FileSystem)))))
|
||||
router.HandlerFunc("GET", "/", basicAuth(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: router,
|
||||
ReadTimeout: httpServerReadTimeout,
|
||||
WriteTimeout: httpServerWriteTimeout,
|
||||
}
|
||||
if address == "" {
|
||||
address = ":http"
|
||||
}
|
||||
ln, err := net.Listen("tcp", address)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
go server.Serve(ln)
|
||||
return
|
||||
}
|
||||
|
||||
func use(h http.HandlerFunc, middleware ...func(http.HandlerFunc) http.HandlerFunc) http.HandlerFunc {
|
||||
for _, m := range middleware {
|
||||
h = m(h)
|
||||
}
|
||||
return h
|
||||
}
|
||||
|
||||
type AuthWraper struct {
|
||||
h http.Handler
|
||||
user string
|
||||
passwd string
|
||||
}
|
||||
|
||||
func (aw *AuthWraper) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
user, passwd, hasAuth := r.BasicAuth()
|
||||
if (aw.user == "" && aw.passwd == "") || (hasAuth && user == aw.user && passwd == aw.passwd) {
|
||||
aw.h.ServeHTTP(w, r)
|
||||
} else {
|
||||
w.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`)
|
||||
http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
|
||||
}
|
||||
}
|
||||
|
||||
func basicAuthWraper(h http.Handler) http.Handler {
|
||||
return &AuthWraper{
|
||||
h: h,
|
||||
user: config.ServerCommonCfg.DashboardUser,
|
||||
passwd: config.ServerCommonCfg.DashboardPwd,
|
||||
}
|
||||
}
|
||||
|
||||
func basicAuth(h http.HandlerFunc) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
user, passwd, hasAuth := r.BasicAuth()
|
||||
if (config.ServerCommonCfg.DashboardUser == "" && config.ServerCommonCfg.DashboardPwd == "") ||
|
||||
(hasAuth && user == config.ServerCommonCfg.DashboardUser && passwd == config.ServerCommonCfg.DashboardPwd) {
|
||||
h.ServeHTTP(w, r)
|
||||
} else {
|
||||
w.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`)
|
||||
http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func httprouterBasicAuth(h httprouter.Handle) httprouter.Handle {
|
||||
return func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
|
||||
user, passwd, hasAuth := r.BasicAuth()
|
||||
if (config.ServerCommonCfg.DashboardUser == "" && config.ServerCommonCfg.DashboardPwd == "") ||
|
||||
(hasAuth && user == config.ServerCommonCfg.DashboardUser && passwd == config.ServerCommonCfg.DashboardPwd) {
|
||||
h(w, r, ps)
|
||||
} else {
|
||||
w.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`)
|
||||
http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type GzipWraper struct {
|
||||
h http.Handler
|
||||
}
|
||||
|
||||
func (gw *GzipWraper) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
if !strings.Contains(r.Header.Get("Accept-Encoding"), "gzip") {
|
||||
gw.h.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
w.Header().Set("Content-Encoding", "gzip")
|
||||
gz := gzip.NewWriter(w)
|
||||
defer gz.Close()
|
||||
gzr := gzipResponseWriter{Writer: gz, ResponseWriter: w}
|
||||
gw.h.ServeHTTP(gzr, r)
|
||||
}
|
||||
|
||||
func MakeGzipHandler(h http.Handler) http.Handler {
|
||||
return &GzipWraper{
|
||||
h: h,
|
||||
}
|
||||
}
|
||||
|
||||
type gzipResponseWriter struct {
|
||||
io.Writer
|
||||
http.ResponseWriter
|
||||
}
|
||||
|
||||
func (w gzipResponseWriter) Write(b []byte) (int, error) {
|
||||
return w.Writer.Write(b)
|
||||
}
|
225
server/dashboard_api.go
Normal file
225
server/dashboard_api.go
Normal file
@@ -0,0 +1,225 @@
|
||||
// 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/fatedier/frp/utils/version"
|
||||
|
||||
"github.com/julienschmidt/httprouter"
|
||||
)
|
||||
|
||||
type GeneralResponse struct {
|
||||
Code int64 `json:"code"`
|
||||
Msg string `json:"msg"`
|
||||
}
|
||||
|
||||
// api/serverinfo
|
||||
type ServerInfoResp struct {
|
||||
GeneralResponse
|
||||
|
||||
Version string `json:"version"`
|
||||
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{
|
||||
Version: version.Full(),
|
||||
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"`
|
||||
LastStartTime string `json:"last_start_time"`
|
||||
LastCloseTime string `json:"last_close_time"`
|
||||
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.Name = ps.Name
|
||||
proxyInfo.TodayTrafficIn = ps.TodayTrafficIn
|
||||
proxyInfo.TodayTrafficOut = ps.TodayTrafficOut
|
||||
proxyInfo.CurConns = ps.CurConns
|
||||
proxyInfo.LastStartTime = ps.LastStartTime
|
||||
proxyInfo.LastCloseTime = ps.LastCloseTime
|
||||
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
|
||||
}
|
285
server/metric.go
Normal file
285
server/metric.go
Normal file
@@ -0,0 +1,285 @@
|
||||
// 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"
|
||||
"time"
|
||||
|
||||
"github.com/fatedier/frp/models/config"
|
||||
"github.com/fatedier/frp/utils/log"
|
||||
"github.com/fatedier/frp/utils/metric"
|
||||
)
|
||||
|
||||
const (
|
||||
ReserveDays = 7
|
||||
)
|
||||
|
||||
var globalStats *ServerStatistics
|
||||
|
||||
type ServerStatistics struct {
|
||||
TotalTrafficIn metric.DateCounter
|
||||
TotalTrafficOut metric.DateCounter
|
||||
CurConns metric.Counter
|
||||
|
||||
// counter for clients
|
||||
ClientCounts metric.Counter
|
||||
|
||||
// counter for proxy types
|
||||
ProxyTypeCounts map[string]metric.Counter
|
||||
|
||||
// statistics for different proxies
|
||||
// key is proxy name
|
||||
ProxyStatistics map[string]*ProxyStatistics
|
||||
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
type ProxyStatistics struct {
|
||||
Name string
|
||||
ProxyType string
|
||||
TrafficIn metric.DateCounter
|
||||
TrafficOut metric.DateCounter
|
||||
CurConns metric.Counter
|
||||
LastStartTime time.Time
|
||||
LastCloseTime time.Time
|
||||
}
|
||||
|
||||
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),
|
||||
}
|
||||
|
||||
go func() {
|
||||
for {
|
||||
time.Sleep(12 * time.Hour)
|
||||
log.Debug("start to clear useless proxy statistics data...")
|
||||
StatsClearUselessInfo()
|
||||
log.Debug("finish to clear useless proxy statistics data")
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func StatsClearUselessInfo() {
|
||||
// To check if there are proxies that closed than 7 days and drop them.
|
||||
globalStats.mu.Lock()
|
||||
defer globalStats.mu.Unlock()
|
||||
for name, data := range globalStats.ProxyStatistics {
|
||||
if !data.LastCloseTime.IsZero() && time.Since(data.LastCloseTime) > time.Duration(7*24)*time.Hour {
|
||||
delete(globalStats.ProxyStatistics, name)
|
||||
log.Trace("clear proxy [%s]'s statistics data, lastCloseTime: [%s]", name, data.LastCloseTime.String())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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.ProxyType == proxyType) {
|
||||
proxyStats = &ProxyStatistics{
|
||||
Name: name,
|
||||
ProxyType: proxyType,
|
||||
CurConns: metric.NewCounter(),
|
||||
TrafficIn: metric.NewDateCounter(ReserveDays),
|
||||
TrafficOut: metric.NewDateCounter(ReserveDays),
|
||||
}
|
||||
globalStats.ProxyStatistics[name] = proxyStats
|
||||
}
|
||||
proxyStats.LastStartTime = time.Now()
|
||||
}
|
||||
}
|
||||
|
||||
func StatsCloseProxy(proxyName string, proxyType string) {
|
||||
if config.ServerCommonCfg.DashboardPort != 0 {
|
||||
globalStats.mu.Lock()
|
||||
defer globalStats.mu.Unlock()
|
||||
if counter, ok := globalStats.ProxyTypeCounts[proxyType]; ok {
|
||||
counter.Dec(1)
|
||||
}
|
||||
if proxyStats, ok := globalStats.ProxyStatistics[proxyName]; ok {
|
||||
proxyStats.LastCloseTime = time.Now()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
LastStartTime string
|
||||
LastCloseTime string
|
||||
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(),
|
||||
}
|
||||
if !proxyStats.LastStartTime.IsZero() {
|
||||
ps.LastStartTime = proxyStats.LastStartTime.Format("01-02 15:04:05")
|
||||
}
|
||||
if !proxyStats.LastCloseTime.IsZero() {
|
||||
ps.LastCloseTime = proxyStats.LastCloseTime.Format("01-02 15:04:05")
|
||||
}
|
||||
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
|
||||
}
|
482
server/proxy.go
Normal file
482
server/proxy.go
Normal file
@@ -0,0 +1,482 @@
|
||||
// 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/udp"
|
||||
"github.com/fatedier/frp/utils/errors"
|
||||
frpIo "github.com/fatedier/frp/utils/io"
|
||||
"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()
|
||||
if pxy.workConn != nil {
|
||||
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 = frpIo.WithEncryption(local, []byte(config.ServerCommonCfg.PrivilegeToken))
|
||||
if err != nil {
|
||||
pxy.Error("create encryption stream error: %v", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
if cfg.UseCompression {
|
||||
local = frpIo.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 := frpIo.Join(local, userConn)
|
||||
StatsCloseConnection(pxy.GetName())
|
||||
StatsAddTrafficIn(pxy.GetName(), inCount)
|
||||
StatsAddTrafficOut(pxy.GetName(), outCount)
|
||||
pxy.Debug("join connections closed")
|
||||
}
|
272
server/service.go
Normal file
272
server/service.go
Normal file
@@ -0,0 +1,272 @@
|
||||
// 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"
|
||||
frpNet "github.com/fatedier/frp/utils/net"
|
||||
"github.com/fatedier/frp/utils/util"
|
||||
"github.com/fatedier/frp/utils/version"
|
||||
"github.com/fatedier/frp/utils/vhost"
|
||||
|
||||
"github.com/xtaci/smux"
|
||||
)
|
||||
|
||||
const (
|
||||
connReadTimeout time.Duration = 10 * time.Second
|
||||
)
|
||||
|
||||
var ServerService *Service
|
||||
|
||||
// Server service.
|
||||
type Service struct {
|
||||
// Accept connections from client.
|
||||
listener frpNet.Listener
|
||||
|
||||
// Accept connections using kcp.
|
||||
kcpListener frpNet.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 = frpNet.ListenTcp(config.ServerCommonCfg.BindAddr, config.ServerCommonCfg.BindPort)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("Create server listener error, %v", err)
|
||||
return
|
||||
}
|
||||
log.Info("frps tcp listen on %s:%d", config.ServerCommonCfg.BindAddr, config.ServerCommonCfg.BindPort)
|
||||
|
||||
// Listen for accepting connections from client using kcp protocol.
|
||||
if config.ServerCommonCfg.KcpBindPort > 0 {
|
||||
svr.kcpListener, err = frpNet.ListenKcp(config.ServerCommonCfg.BindAddr, config.ServerCommonCfg.KcpBindPort)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("Listen on kcp address udp [%s:%d] error: %v", config.ServerCommonCfg.BindAddr, config.ServerCommonCfg.KcpBindPort, err)
|
||||
return
|
||||
}
|
||||
log.Info("frps kcp listen on udp %s:%d", config.ServerCommonCfg.BindAddr, config.ServerCommonCfg.BindPort)
|
||||
}
|
||||
|
||||
// Create http vhost muxer.
|
||||
if config.ServerCommonCfg.VhostHttpPort > 0 {
|
||||
var l frpNet.Listener
|
||||
l, err = frpNet.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
|
||||
}
|
||||
log.Info("http service listen on %s:%d", config.ServerCommonCfg.BindAddr, config.ServerCommonCfg.VhostHttpPort)
|
||||
}
|
||||
|
||||
// Create https vhost muxer.
|
||||
if config.ServerCommonCfg.VhostHttpsPort > 0 {
|
||||
var l frpNet.Listener
|
||||
l, err = frpNet.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
|
||||
}
|
||||
log.Info("https service listen on %s:%d", config.ServerCommonCfg.BindAddr, config.ServerCommonCfg.VhostHttpsPort)
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
log.Info("Dashboard listen on %s:%d", config.ServerCommonCfg.BindAddr, config.ServerCommonCfg.DashboardPort)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (svr *Service) Run() {
|
||||
if config.ServerCommonCfg.KcpBindPort > 0 {
|
||||
go svr.HandleListener(svr.kcpListener)
|
||||
}
|
||||
svr.HandleListener(svr.listener)
|
||||
|
||||
}
|
||||
|
||||
func (svr *Service) HandleListener(l frpNet.Listener) {
|
||||
// Listen for incoming connections from client.
|
||||
for {
|
||||
c, err := l.Accept()
|
||||
if err != nil {
|
||||
log.Warn("Listener for incoming connections from client closed")
|
||||
return
|
||||
}
|
||||
|
||||
// Start a new goroutine for dealing connections.
|
||||
go func(frpConn frpNet.Conn) {
|
||||
dealFn := func(conn frpNet.Conn) {
|
||||
var rawMsg msg.Message
|
||||
conn.SetReadDeadline(time.Now().Add(connReadTimeout))
|
||||
if rawMsg, err = msg.ReadMsg(conn); err != nil {
|
||||
log.Trace("Failed to read message: %v", err)
|
||||
conn.Close()
|
||||
return
|
||||
}
|
||||
conn.SetReadDeadline(time.Time{})
|
||||
|
||||
switch m := rawMsg.(type) {
|
||||
case *msg.Login:
|
||||
err = svr.RegisterControl(conn, m)
|
||||
// If login failed, send error message there.
|
||||
// Otherwise send success message in control's work goroutine.
|
||||
if err != nil {
|
||||
conn.Warn("%v", err)
|
||||
msg.WriteMsg(conn, &msg.LoginResp{
|
||||
Version: version.Full(),
|
||||
Error: err.Error(),
|
||||
})
|
||||
conn.Close()
|
||||
}
|
||||
case *msg.NewWorkConn:
|
||||
svr.RegisterWorkConn(conn, m)
|
||||
default:
|
||||
log.Warn("Error message type for the new connection [%s]", conn.RemoteAddr().String())
|
||||
conn.Close()
|
||||
}
|
||||
}
|
||||
|
||||
if config.ServerCommonCfg.TcpMux {
|
||||
session, err := smux.Server(frpConn, nil)
|
||||
if err != nil {
|
||||
log.Warn("Failed to create mux connection: %v", err)
|
||||
frpConn.Close()
|
||||
return
|
||||
}
|
||||
|
||||
for {
|
||||
stream, err := session.AcceptStream()
|
||||
if err != nil {
|
||||
log.Warn("Accept new mux stream error: %v", err)
|
||||
session.Close()
|
||||
return
|
||||
}
|
||||
wrapConn := frpNet.WrapConn(stream)
|
||||
go dealFn(wrapConn)
|
||||
}
|
||||
} else {
|
||||
dealFn(frpConn)
|
||||
}
|
||||
}(c)
|
||||
}
|
||||
}
|
||||
|
||||
func (svr *Service) RegisterControl(ctlConn frpNet.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 frpNet.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)
|
||||
}
|
@@ -1,191 +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"
|
||||
|
||||
"frp/models/client"
|
||||
"frp/models/consts"
|
||||
"frp/models/msg"
|
||||
"frp/utils/conn"
|
||||
"frp/utils/log"
|
||||
"frp/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
|
||||
c.Close()
|
||||
log.Error("ProxyName [%s], heartbeatRes from frps timeout", cli.Name)
|
||||
})
|
||||
defer timer.Stop()
|
||||
|
||||
for {
|
||||
buf, err := c.ReadLine()
|
||||
if err == io.EOF || c == nil || c.IsClosed() {
|
||||
c.Close()
|
||||
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)
|
||||
break
|
||||
}
|
||||
|
||||
if delayTime < 60 {
|
||||
delayTime = delayTime * 2
|
||||
}
|
||||
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)
|
||||
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.Write(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) {
|
||||
c, err = conn.ConnectServer(client.ServerAddr, client.ServerPort)
|
||||
if err != nil {
|
||||
log.Error("ProxyName [%s], connect to server [%s:%d] error, %v", cli.Name, client.ServerAddr, client.ServerPort, err)
|
||||
return
|
||||
}
|
||||
|
||||
nowTime := time.Now().Unix()
|
||||
authKey := pcrypto.GetAuthKey(cli.Name + cli.AuthToken + fmt.Sprintf("%d", nowTime))
|
||||
req := &msg.ControlReq{
|
||||
Type: consts.NewCtlConn,
|
||||
ProxyName: cli.Name,
|
||||
AuthKey: authKey,
|
||||
UseEncryption: cli.UseEncryption,
|
||||
Timestamp: nowTime,
|
||||
}
|
||||
buf, _ := json.Marshal(req)
|
||||
err = c.Write(string(buf) + "\n")
|
||||
if err != nil {
|
||||
log.Error("ProxyName [%s], write to server error, %v", cli.Name, err)
|
||||
return
|
||||
}
|
||||
|
||||
res, err := c.ReadLine()
|
||||
if err != nil {
|
||||
log.Error("ProxyName [%s], read from server error, %v", cli.Name, err)
|
||||
return
|
||||
}
|
||||
log.Debug("ProxyName [%s], read [%s]", cli.Name, res)
|
||||
|
||||
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.Debug("ProxyName [%s], connect to server [%s:%d] success!", cli.Name, 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.Debug("Heartbeat goroutine exit")
|
||||
}
|
@@ -1,250 +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"
|
||||
"time"
|
||||
|
||||
"frp/models/consts"
|
||||
"frp/models/msg"
|
||||
"frp/models/server"
|
||||
"frp/utils/conn"
|
||||
"frp/utils/log"
|
||||
"frp/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
|
||||
}
|
||||
|
||||
// do login when type is NewCtlConn or NewWorkConn
|
||||
ret, info := doLogin(cliReq, c)
|
||||
s, ok := server.ProxyServers[cliReq.ProxyName]
|
||||
if !ok {
|
||||
log.Warn("ProxyName [%s] is not exist", cliReq.ProxyName)
|
||||
return
|
||||
}
|
||||
// if login type is NewWorkConn, nothing will be send to frpc
|
||||
if cliReq.Type != consts.NewWorkConn {
|
||||
cliRes := &msg.ControlRes{
|
||||
Type: consts.NewCtlConnRes,
|
||||
Code: ret,
|
||||
Msg: info,
|
||||
}
|
||||
byteBuf, _ := json.Marshal(cliRes)
|
||||
err = c.Write(string(byteBuf) + "\n")
|
||||
if err != nil {
|
||||
log.Warn("ProxyName [%s], write to client error, proxy exit", s.Name)
|
||||
time.Sleep(1 * time.Second)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
closeFlag = false
|
||||
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()
|
||||
c.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)
|
||||
return err
|
||||
} else if c == nil || c.IsClosed() {
|
||||
log.Warn("ProxyName [%s], client connection is closed", s.Name)
|
||||
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.Write(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
|
||||
func doLogin(req *msg.ControlReq, c *conn.Conn) (ret int64, info string) {
|
||||
ret = 1
|
||||
// check if proxy name exist
|
||||
s, ok := server.ProxyServers[req.ProxyName]
|
||||
if !ok {
|
||||
info = fmt.Sprintf("ProxyName [%s] is not exist", req.ProxyName)
|
||||
log.Warn(info)
|
||||
return
|
||||
}
|
||||
|
||||
// check authKey
|
||||
nowTime := time.Now().Unix()
|
||||
authKey := pcrypto.GetAuthKey(req.ProxyName + s.AuthToken + fmt.Sprintf("%d", req.Timestamp))
|
||||
// authKey avaiable in 15 minutes
|
||||
if nowTime-req.Timestamp > 15*60 {
|
||||
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)
|
||||
return
|
||||
}
|
||||
|
||||
// control conn
|
||||
if req.Type == consts.NewCtlConn {
|
||||
if s.Status == consts.Working {
|
||||
info = fmt.Sprintf("ProxyName [%s], already in use", req.ProxyName)
|
||||
log.Warn(info)
|
||||
return
|
||||
}
|
||||
|
||||
// set infomations from frpc
|
||||
s.UseEncryption = req.UseEncryption
|
||||
|
||||
// start proxy and listen for user connections, no block
|
||||
err := s.Start()
|
||||
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)
|
||||
} 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.RecvNewWorkConn(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,116 +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 (
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
docopt "github.com/docopt/docopt-go"
|
||||
|
||||
"frp/models/server"
|
||||
"frp/utils/conn"
|
||||
"frp/utils/log"
|
||||
"frp/utils/version"
|
||||
"frp/utils/vhost"
|
||||
)
|
||||
|
||||
var (
|
||||
configFile string = "./frps.ini"
|
||||
)
|
||||
|
||||
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 | --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
|
||||
--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 {
|
||||
configFile = args["-c"].(string)
|
||||
}
|
||||
err = server.LoadConf(configFile)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(-1)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
log.InitLog(server.LogWay, server.LogFile, server.LogLevel, server.LogMaxDays)
|
||||
|
||||
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.VhostMuxer, err = vhost.NewHttpMuxer(vhostListener, 30*time.Second)
|
||||
if err != nil {
|
||||
log.Error("Create vhost httpMuxer error, %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
log.Info("Start frps success")
|
||||
ProcessControlConn(l)
|
||||
}
|
@@ -1,99 +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"
|
||||
"time"
|
||||
|
||||
"frp/models/consts"
|
||||
"frp/models/msg"
|
||||
"frp/utils/conn"
|
||||
"frp/utils/log"
|
||||
"frp/utils/pcrypto"
|
||||
)
|
||||
|
||||
type ProxyClient struct {
|
||||
Name string
|
||||
AuthToken string
|
||||
LocalIp string
|
||||
LocalPort int64
|
||||
Type string
|
||||
UseEncryption bool
|
||||
}
|
||||
|
||||
func (p *ProxyClient) GetLocalConn() (c *conn.Conn, err error) {
|
||||
c, err = conn.ConnectServer(p.LocalIp, p.LocalPort)
|
||||
if err != nil {
|
||||
log.Error("ProxyName [%s], connect to local port error, %v", p.Name, err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (p *ProxyClient) GetRemoteConn(addr string, port int64) (c *conn.Conn, err error) {
|
||||
defer func() {
|
||||
if err != nil {
|
||||
c.Close()
|
||||
}
|
||||
}()
|
||||
|
||||
c, err = conn.ConnectServer(addr, port)
|
||||
if err != nil {
|
||||
log.Error("ProxyName [%s], connect to server [%s:%d] error, %v", p.Name, addr, port, err)
|
||||
return
|
||||
}
|
||||
|
||||
nowTime := time.Now().Unix()
|
||||
authKey := pcrypto.GetAuthKey(p.Name + p.AuthToken + fmt.Sprintf("%d", nowTime))
|
||||
req := &msg.ControlReq{
|
||||
Type: consts.NewWorkConn,
|
||||
ProxyName: p.Name,
|
||||
AuthKey: authKey,
|
||||
Timestamp: nowTime,
|
||||
}
|
||||
|
||||
buf, _ := json.Marshal(req)
|
||||
err = c.Write(string(buf) + "\n")
|
||||
if err != nil {
|
||||
log.Error("ProxyName [%s], write to server error, %v", p.Name, err)
|
||||
return
|
||||
}
|
||||
|
||||
err = nil
|
||||
return
|
||||
}
|
||||
|
||||
func (p *ProxyClient) StartTunnel(serverAddr string, serverPort int64) (err error) {
|
||||
localConn, err := p.GetLocalConn()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
remoteConn, err := p.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())
|
||||
if p.UseEncryption {
|
||||
go conn.JoinMore(localConn, remoteConn, p.AuthToken)
|
||||
} else {
|
||||
go conn.Join(localConn, remoteConn)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
@@ -1,140 +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"
|
||||
"strconv"
|
||||
|
||||
ini "github.com/vaughan0/go-ini"
|
||||
)
|
||||
|
||||
// common config
|
||||
var (
|
||||
ServerAddr string = "0.0.0.0"
|
||||
ServerPort int64 = 7000
|
||||
LogFile string = "console"
|
||||
LogWay string = "console"
|
||||
LogLevel string = "info"
|
||||
LogMaxDays int64 = 3
|
||||
HeartBeatInterval int64 = 20
|
||||
HeartBeatTimeout int64 = 90
|
||||
)
|
||||
|
||||
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", "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)
|
||||
}
|
||||
|
||||
var authToken string
|
||||
tmpStr, ok = conf.Get("common", "auth_token")
|
||||
if ok {
|
||||
authToken = tmpStr
|
||||
} else {
|
||||
return fmt.Errorf("auth_token not found")
|
||||
}
|
||||
|
||||
// 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
|
||||
portStr, ok := section["local_port"]
|
||||
if ok {
|
||||
proxyClient.LocalPort, err = strconv.ParseInt(portStr, 10, 64)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Parse ini file error: proxy [%s] local_port error", proxyClient.Name)
|
||||
}
|
||||
} else {
|
||||
return fmt.Errorf("Parse ini file error: proxy [%s] local_port not found", proxyClient.Name)
|
||||
}
|
||||
|
||||
// type
|
||||
proxyClient.Type = "tcp"
|
||||
typeStr, ok := section["type"]
|
||||
if ok {
|
||||
if typeStr != "tcp" && typeStr != "http" {
|
||||
return fmt.Errorf("Parse ini file error: proxy [%s] type error", proxyClient.Name)
|
||||
}
|
||||
proxyClient.Type = typeStr
|
||||
}
|
||||
|
||||
// use_encryption
|
||||
proxyClient.UseEncryption = false
|
||||
useEncryptionStr, ok := section["use_encryption"]
|
||||
if ok && useEncryptionStr == "true" {
|
||||
proxyClient.UseEncryption = true
|
||||
}
|
||||
|
||||
ProxyClients[proxyClient.Name] = proxyClient
|
||||
}
|
||||
}
|
||||
|
||||
if len(ProxyClients) == 0 {
|
||||
return fmt.Errorf("Parse ini file 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 server
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
ini "github.com/vaughan0/go-ini"
|
||||
|
||||
"frp/utils/vhost"
|
||||
)
|
||||
|
||||
// common config
|
||||
var (
|
||||
BindAddr string = "0.0.0.0"
|
||||
BindPort int64 = 7000
|
||||
VhostHttpPort int64 = 0 // if VhostHttpPort equals 0, do not listen a public port for http
|
||||
LogFile string = "console"
|
||||
LogWay string = "console" // console or file
|
||||
LogLevel string = "info"
|
||||
LogMaxDays int64 = 3
|
||||
HeartBeatTimeout int64 = 90
|
||||
UserConnTimeout int64 = 10
|
||||
|
||||
VhostMuxer *vhost.HttpMuxer
|
||||
)
|
||||
|
||||
var ProxyServers map[string]*ProxyServer = make(map[string]*ProxyServer)
|
||||
|
||||
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", "bind_addr")
|
||||
if ok {
|
||||
BindAddr = tmpStr
|
||||
}
|
||||
|
||||
tmpStr, ok = conf.Get("common", "bind_port")
|
||||
if ok {
|
||||
BindPort, _ = strconv.ParseInt(tmpStr, 10, 64)
|
||||
}
|
||||
|
||||
tmpStr, ok = conf.Get("common", "vhost_http_port")
|
||||
if ok {
|
||||
VhostHttpPort, _ = strconv.ParseInt(tmpStr, 10, 64)
|
||||
} else {
|
||||
VhostHttpPort = 0
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
// servers
|
||||
for name, section := range conf {
|
||||
if name != "common" {
|
||||
proxyServer := &ProxyServer{}
|
||||
proxyServer.CustomDomains = make([]string, 0)
|
||||
proxyServer.Name = name
|
||||
|
||||
proxyServer.Type, ok = section["type"]
|
||||
if ok {
|
||||
if proxyServer.Type != "tcp" && proxyServer.Type != "http" {
|
||||
return fmt.Errorf("Parse ini file error: proxy [%s] type error", proxyServer.Name)
|
||||
}
|
||||
} else {
|
||||
proxyServer.Type = "tcp"
|
||||
}
|
||||
|
||||
proxyServer.AuthToken, ok = section["auth_token"]
|
||||
if !ok {
|
||||
return fmt.Errorf("Parse ini file error: proxy [%s] no auth_token found", proxyServer.Name)
|
||||
}
|
||||
|
||||
// for tcp
|
||||
if proxyServer.Type == "tcp" {
|
||||
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 fmt.Errorf("Parse ini file error: proxy [%s] listen_port error", proxyServer.Name)
|
||||
}
|
||||
} else {
|
||||
return fmt.Errorf("Parse ini file error: proxy [%s] listen_port not found", proxyServer.Name)
|
||||
}
|
||||
} else if proxyServer.Type == "http" {
|
||||
// for http
|
||||
domainStr, ok := section["custom_domains"]
|
||||
if ok {
|
||||
var suffix string
|
||||
if VhostHttpPort != 80 {
|
||||
suffix = fmt.Sprintf(":%d", VhostHttpPort)
|
||||
}
|
||||
proxyServer.CustomDomains = strings.Split(domainStr, ",")
|
||||
for i, domain := range proxyServer.CustomDomains {
|
||||
proxyServer.CustomDomains[i] = strings.ToLower(strings.TrimSpace(domain)) + suffix
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
proxyServer.Init()
|
||||
ProxyServers[proxyServer.Name] = proxyServer
|
||||
}
|
||||
}
|
||||
|
||||
if len(ProxyServers) == 0 {
|
||||
return fmt.Errorf("Parse ini file error: no proxy config found")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
@@ -1,200 +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 (
|
||||
"container/list"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"frp/models/consts"
|
||||
"frp/utils/conn"
|
||||
"frp/utils/log"
|
||||
)
|
||||
|
||||
type Listener interface {
|
||||
Accept() (*conn.Conn, error)
|
||||
Close() error
|
||||
}
|
||||
|
||||
type ProxyServer struct {
|
||||
Name string
|
||||
AuthToken string
|
||||
Type string
|
||||
BindAddr string
|
||||
ListenPort int64
|
||||
UseEncryption bool
|
||||
CustomDomains []string
|
||||
|
||||
Status int64
|
||||
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
|
||||
userConnList *list.List // store user conns
|
||||
mutex sync.Mutex
|
||||
}
|
||||
|
||||
func (p *ProxyServer) Init() {
|
||||
p.Status = consts.Idle
|
||||
p.workConnChan = make(chan *conn.Conn)
|
||||
p.ctlMsgChan = make(chan int64)
|
||||
p.userConnList = list.New()
|
||||
p.listeners = make([]Listener, 0)
|
||||
}
|
||||
|
||||
func (p *ProxyServer) Lock() {
|
||||
p.mutex.Lock()
|
||||
}
|
||||
|
||||
func (p *ProxyServer) Unlock() {
|
||||
p.mutex.Unlock()
|
||||
}
|
||||
|
||||
// start listening for user conns
|
||||
func (p *ProxyServer) Start() (err error) {
|
||||
p.Init()
|
||||
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 {
|
||||
l, err := VhostMuxer.Listen(domain)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
p.listeners = append(p.listeners, l)
|
||||
}
|
||||
}
|
||||
|
||||
p.Status = consts.Working
|
||||
|
||||
// start a goroutine for 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())
|
||||
|
||||
// insert into list
|
||||
p.Lock()
|
||||
if p.Status != consts.Working {
|
||||
log.Debug("ProxyName [%s] is not working, new user conn close", p.Name)
|
||||
c.Close()
|
||||
p.Unlock()
|
||||
return
|
||||
}
|
||||
p.userConnList.PushBack(c)
|
||||
p.Unlock()
|
||||
|
||||
// put msg to control conn
|
||||
p.ctlMsgChan <- 1
|
||||
|
||||
// set timeout
|
||||
time.AfterFunc(time.Duration(UserConnTimeout)*time.Second, func() {
|
||||
p.Lock()
|
||||
element := p.userConnList.Front()
|
||||
p.Unlock()
|
||||
if element == nil {
|
||||
return
|
||||
}
|
||||
|
||||
userConn := element.Value.(*conn.Conn)
|
||||
if userConn == c {
|
||||
log.Warn("ProxyName [%s], user conn [%s] timeout", p.Name, c.GetRemoteAddr())
|
||||
userConn.Close()
|
||||
}
|
||||
})
|
||||
}
|
||||
}(listener)
|
||||
}
|
||||
|
||||
// start another goroutine for join two conns from frpc and user
|
||||
go func() {
|
||||
for {
|
||||
workConn, ok := <-p.workConnChan
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
p.Lock()
|
||||
element := p.userConnList.Front()
|
||||
|
||||
var userConn *conn.Conn
|
||||
if element != nil {
|
||||
userConn = element.Value.(*conn.Conn)
|
||||
p.userConnList.Remove(element)
|
||||
} else {
|
||||
workConn.Close()
|
||||
p.Unlock()
|
||||
continue
|
||||
}
|
||||
p.Unlock()
|
||||
|
||||
// msg will transfer 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())
|
||||
|
||||
if p.UseEncryption {
|
||||
go conn.JoinMore(userConn, workConn, p.AuthToken)
|
||||
} else {
|
||||
go conn.Join(userConn, workConn)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *ProxyServer) Close() {
|
||||
p.Lock()
|
||||
if p.Status != consts.Closed {
|
||||
p.Status = consts.Closed
|
||||
if len(p.listeners) != 0 {
|
||||
for _, l := range p.listeners {
|
||||
if l != nil {
|
||||
l.Close()
|
||||
}
|
||||
}
|
||||
}
|
||||
close(p.ctlMsgChan)
|
||||
close(p.workConnChan)
|
||||
p.userConnList = list.New()
|
||||
}
|
||||
p.Unlock()
|
||||
}
|
||||
|
||||
func (p *ProxyServer) WaitUserConn() (closeFlag bool) {
|
||||
closeFlag = false
|
||||
|
||||
_, ok := <-p.ctlMsgChan
|
||||
if !ok {
|
||||
closeFlag = true
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (p *ProxyServer) RecvNewWorkConn(c *conn.Conn) {
|
||||
p.workConnChan <- c
|
||||
}
|
@@ -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,269 +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"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"frp/utils/log"
|
||||
"frp/utils/pcrypto"
|
||||
)
|
||||
|
||||
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("tcp4", fmt.Sprintf("%s:%d", bindAddr, bindPort))
|
||||
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 := &Conn{
|
||||
TcpConn: conn,
|
||||
closeFlag: false,
|
||||
}
|
||||
c.Reader = bufio.NewReader(c.TcpConn)
|
||||
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
|
||||
closeFlag bool
|
||||
}
|
||||
|
||||
func NewConn(conn net.Conn) (c *Conn) {
|
||||
c = &Conn{}
|
||||
c.TcpConn = conn
|
||||
c.Reader = bufio.NewReader(c.TcpConn)
|
||||
c.closeFlag = false
|
||||
return c
|
||||
}
|
||||
|
||||
func ConnectServer(host string, port int64) (c *Conn, err error) {
|
||||
c = &Conn{}
|
||||
servertAddr, err := net.ResolveTCPAddr("tcp4", fmt.Sprintf("%s:%d", host, port))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
conn, err := net.DialTCP("tcp", nil, servertAddr)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
c.TcpConn = conn
|
||||
c.Reader = bufio.NewReader(c.TcpConn)
|
||||
c.closeFlag = false
|
||||
return c, nil
|
||||
}
|
||||
|
||||
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) ReadLine() (buff string, err error) {
|
||||
buff, err = c.Reader.ReadString('\n')
|
||||
if err == io.EOF {
|
||||
c.closeFlag = true
|
||||
}
|
||||
return buff, err
|
||||
}
|
||||
|
||||
func (c *Conn) Write(content string) (err error) {
|
||||
_, err = c.TcpConn.Write([]byte(content))
|
||||
return err
|
||||
|
||||
}
|
||||
|
||||
func (c *Conn) SetDeadline(t time.Time) error {
|
||||
err := c.TcpConn.SetDeadline(t)
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *Conn) Close() {
|
||||
if c.TcpConn != nil && c.closeFlag == false {
|
||||
c.closeFlag = true
|
||||
c.TcpConn.Close()
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Conn) IsClosed() bool {
|
||||
return c.closeFlag
|
||||
}
|
||||
|
||||
// will block until connection close
|
||||
func Join(c1 *Conn, c2 *Conn) {
|
||||
var wait sync.WaitGroup
|
||||
pipe := func(to *Conn, from *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
|
||||
}
|
||||
|
||||
// messages from c1 to c2 will be encrypted
|
||||
// and from c2 to c1 will be decrypted
|
||||
func JoinMore(c1 *Conn, c2 *Conn, cryptKey string) {
|
||||
var wait sync.WaitGroup
|
||||
encryptPipe := func(from *Conn, to *Conn, key string) {
|
||||
defer from.Close()
|
||||
defer to.Close()
|
||||
defer wait.Done()
|
||||
|
||||
// we don't care about errors here
|
||||
PipeEncrypt(from.TcpConn, to.TcpConn, key)
|
||||
}
|
||||
|
||||
decryptPipe := func(to *Conn, from *Conn, key string) {
|
||||
defer from.Close()
|
||||
defer to.Close()
|
||||
defer wait.Done()
|
||||
|
||||
// we don't care about errors here
|
||||
PipeDecrypt(to.TcpConn, from.TcpConn, key)
|
||||
}
|
||||
|
||||
wait.Add(2)
|
||||
go encryptPipe(c1, c2, cryptKey)
|
||||
go decryptPipe(c2, c1, cryptKey)
|
||||
wait.Wait()
|
||||
log.Debug("One tunnel stopped")
|
||||
return
|
||||
}
|
||||
|
||||
// decrypt msg from reader, then write into writer
|
||||
func PipeDecrypt(r net.Conn, w net.Conn, key string) error {
|
||||
laes := new(pcrypto.Pcrypto)
|
||||
if err := laes.Init([]byte(key)); err != nil {
|
||||
log.Error("Pcrypto Init error: %v", err)
|
||||
return fmt.Errorf("Pcrypto Init error: %v", err)
|
||||
}
|
||||
|
||||
nreader := bufio.NewReader(r)
|
||||
for {
|
||||
buf, err := nreader.ReadBytes('\n')
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
res, err := laes.Decrypt(buf)
|
||||
if err != nil {
|
||||
log.Error("Decrypt [%s] error, %v", string(buf), err)
|
||||
return fmt.Errorf("Decrypt [%s] error: %v", string(buf), err)
|
||||
}
|
||||
|
||||
_, err = w.Write(res)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// recvive msg from reader, then encrypt msg into write
|
||||
func PipeEncrypt(r net.Conn, w net.Conn, key string) error {
|
||||
laes := new(pcrypto.Pcrypto)
|
||||
if err := laes.Init([]byte(key)); err != nil {
|
||||
log.Error("Pcrypto Init error: %v", err)
|
||||
return fmt.Errorf("Pcrypto Init error: %v", err)
|
||||
}
|
||||
|
||||
nreader := bufio.NewReader(r)
|
||||
buf := make([]byte, 10*1024)
|
||||
|
||||
for {
|
||||
n, err := nreader.Read(buf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
res, err := laes.Encrypt(buf[:n])
|
||||
if err != nil {
|
||||
log.Error("Encrypt error: %v", err)
|
||||
return fmt.Errorf("Encrypt error: %v", err)
|
||||
}
|
||||
|
||||
res = append(res, '\n')
|
||||
_, err = w.Write(res)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
@@ -1,115 +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"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
)
|
||||
|
||||
type Pcrypto struct {
|
||||
pkey []byte
|
||||
paes cipher.Block
|
||||
}
|
||||
|
||||
func (pc *Pcrypto) Init(key []byte) error {
|
||||
var err error
|
||||
pc.pkey = pKCS7Padding(key, aes.BlockSize)
|
||||
pc.paes, err = aes.NewCipher(pc.pkey)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (pc *Pcrypto) Encrypt(src []byte) ([]byte, error) {
|
||||
// gzip
|
||||
var zbuf bytes.Buffer
|
||||
zwr, err := gzip.NewWriterLevel(&zbuf, -1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer zwr.Close()
|
||||
zwr.Write(src)
|
||||
zwr.Flush()
|
||||
|
||||
// aes
|
||||
src = pKCS7Padding(zbuf.Bytes(), aes.BlockSize)
|
||||
blockMode := cipher.NewCBCEncrypter(pc.paes, pc.pkey)
|
||||
crypted := make([]byte, len(src))
|
||||
blockMode.CryptBlocks(crypted, src)
|
||||
|
||||
// base64
|
||||
return []byte(base64.StdEncoding.EncodeToString(crypted)), nil
|
||||
}
|
||||
|
||||
func (pc *Pcrypto) Decrypt(str []byte) ([]byte, error) {
|
||||
// base64
|
||||
data, err := base64.StdEncoding.DecodeString(string(str))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// aes
|
||||
decryptText, err := hex.DecodeString(fmt.Sprintf("%x", data))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(decryptText)%aes.BlockSize != 0 {
|
||||
return nil, errors.New("crypto/cipher: ciphertext is not a multiple of the block size")
|
||||
}
|
||||
|
||||
blockMode := cipher.NewCBCDecrypter(pc.paes, pc.pkey)
|
||||
|
||||
blockMode.CryptBlocks(decryptText, decryptText)
|
||||
decryptText = pKCS7UnPadding(decryptText)
|
||||
|
||||
// gunzip
|
||||
zbuf := bytes.NewBuffer(decryptText)
|
||||
zrd, err := gzip.NewReader(zbuf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer zrd.Close()
|
||||
data, _ = ioutil.ReadAll(zrd)
|
||||
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func pKCS7Padding(ciphertext []byte, blockSize int) []byte {
|
||||
padding := blockSize - len(ciphertext)%blockSize
|
||||
padtext := bytes.Repeat([]byte{byte(padding)}, padding)
|
||||
return append(ciphertext, padtext...)
|
||||
}
|
||||
|
||||
func pKCS7UnPadding(origData []byte) []byte {
|
||||
length := len(origData)
|
||||
unpadding := int(origData[length-1])
|
||||
return origData[:(length - unpadding)]
|
||||
}
|
||||
|
||||
func GetAuthKey(str string) (authKey string) {
|
||||
md5Ctx := md5.New()
|
||||
md5Ctx.Write([]byte(str))
|
||||
md5Str := md5Ctx.Sum(nil)
|
||||
return hex.EncodeToString(md5Str)
|
||||
}
|
@@ -1,193 +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 vhost
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"frp/utils/conn"
|
||||
)
|
||||
|
||||
type muxFunc func(*conn.Conn) (net.Conn, string, error)
|
||||
|
||||
type VhostMuxer struct {
|
||||
listener *conn.Listener
|
||||
timeout time.Duration
|
||||
vhostFunc muxFunc
|
||||
registryMap map[string]*Listener
|
||||
mutex sync.RWMutex
|
||||
}
|
||||
|
||||
func NewVhostMuxer(listener *conn.Listener, vhostFunc muxFunc, timeout time.Duration) (mux *VhostMuxer, err error) {
|
||||
mux = &VhostMuxer{
|
||||
listener: listener,
|
||||
timeout: timeout,
|
||||
vhostFunc: vhostFunc,
|
||||
registryMap: make(map[string]*Listener),
|
||||
}
|
||||
go mux.run()
|
||||
return mux, nil
|
||||
}
|
||||
|
||||
func (v *VhostMuxer) Listen(name string) (l *Listener, err error) {
|
||||
v.mutex.Lock()
|
||||
defer v.mutex.Unlock()
|
||||
if _, exist := v.registryMap[name]; exist {
|
||||
return nil, fmt.Errorf("name %s is already bound", name)
|
||||
}
|
||||
|
||||
l = &Listener{
|
||||
name: name,
|
||||
mux: v,
|
||||
accept: make(chan *conn.Conn),
|
||||
}
|
||||
v.registryMap[name] = l
|
||||
return l, nil
|
||||
}
|
||||
|
||||
func (v *VhostMuxer) getListener(name string) (l *Listener, exist bool) {
|
||||
v.mutex.RLock()
|
||||
defer v.mutex.RUnlock()
|
||||
l, exist = v.registryMap[name]
|
||||
return l, exist
|
||||
}
|
||||
|
||||
func (v *VhostMuxer) unRegister(name string) {
|
||||
v.mutex.Lock()
|
||||
defer v.mutex.Unlock()
|
||||
delete(v.registryMap, name)
|
||||
}
|
||||
|
||||
func (v *VhostMuxer) run() {
|
||||
for {
|
||||
conn, err := v.listener.Accept()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
go v.handle(conn)
|
||||
}
|
||||
}
|
||||
|
||||
func (v *VhostMuxer) handle(c *conn.Conn) {
|
||||
if err := c.SetDeadline(time.Now().Add(v.timeout)); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
sConn, name, err := v.vhostFunc(c)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
name = strings.ToLower(name)
|
||||
|
||||
l, ok := v.getListener(name)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
if err = sConn.SetDeadline(time.Time{}); err != nil {
|
||||
return
|
||||
}
|
||||
c.TcpConn = sConn
|
||||
|
||||
l.accept <- c
|
||||
}
|
||||
|
||||
type HttpMuxer struct {
|
||||
*VhostMuxer
|
||||
}
|
||||
|
||||
func GetHttpHostname(c *conn.Conn) (_ net.Conn, routerName string, err error) {
|
||||
sc, rd := newShareConn(c.TcpConn)
|
||||
|
||||
request, err := http.ReadRequest(bufio.NewReader(rd))
|
||||
if err != nil {
|
||||
return sc, "", err
|
||||
}
|
||||
routerName = request.Host
|
||||
request.Body.Close()
|
||||
|
||||
return sc, routerName, nil
|
||||
}
|
||||
|
||||
func NewHttpMuxer(listener *conn.Listener, timeout time.Duration) (*HttpMuxer, error) {
|
||||
mux, err := NewVhostMuxer(listener, GetHttpHostname, timeout)
|
||||
return &HttpMuxer{mux}, err
|
||||
}
|
||||
|
||||
type Listener struct {
|
||||
name string
|
||||
mux *VhostMuxer // for closing VhostMuxer
|
||||
accept chan *conn.Conn
|
||||
}
|
||||
|
||||
func (l *Listener) Accept() (*conn.Conn, error) {
|
||||
conn, ok := <-l.accept
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("Listener closed")
|
||||
}
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
func (l *Listener) Close() error {
|
||||
l.mux.unRegister(l.name)
|
||||
close(l.accept)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *Listener) Name() string {
|
||||
return l.name
|
||||
}
|
||||
|
||||
type sharedConn struct {
|
||||
net.Conn
|
||||
sync.Mutex
|
||||
buff *bytes.Buffer
|
||||
}
|
||||
|
||||
func newShareConn(conn net.Conn) (*sharedConn, io.Reader) {
|
||||
sc := &sharedConn{
|
||||
Conn: conn,
|
||||
buff: bytes.NewBuffer(make([]byte, 0, 1024)),
|
||||
}
|
||||
return sc, io.TeeReader(conn, sc.buff)
|
||||
}
|
||||
|
||||
func (sc *sharedConn) Read(p []byte) (n int, err error) {
|
||||
sc.Lock()
|
||||
if sc.buff == nil {
|
||||
sc.Unlock()
|
||||
return sc.Conn.Read(p)
|
||||
}
|
||||
n, err = sc.buff.Read(p)
|
||||
|
||||
if err == io.EOF {
|
||||
sc.buff = nil
|
||||
var n2 int
|
||||
n2, err = sc.Conn.Read(p[n:])
|
||||
|
||||
n += n2
|
||||
}
|
||||
sc.Unlock()
|
||||
return
|
||||
}
|
14
tests/clean_test.sh
Executable file
14
tests/clean_test.sh
Executable file
@@ -0,0 +1,14 @@
|
||||
#!/bin/bash
|
||||
|
||||
pid=`ps aux|grep './../bin/frps -c ./conf/auto_test_frps.ini'|grep -v grep|awk {'print $2'}`
|
||||
if [ -n "${pid}" ]; then
|
||||
kill ${pid}
|
||||
fi
|
||||
|
||||
pid=`ps aux|grep './../bin/frpc -c ./conf/auto_test_frpc.ini'|grep -v grep|awk {'print $2'}`
|
||||
if [ -n "${pid}" ]; then
|
||||
kill ${pid}
|
||||
fi
|
||||
|
||||
rm -f ./frps.log
|
||||
rm -f ./frpc.log
|
35
tests/conf/auto_test_frpc.ini
Normal file
35
tests/conf/auto_test_frpc.ini
Normal file
@@ -0,0 +1,35 @@
|
||||
[common]
|
||||
server_addr = 0.0.0.0
|
||||
server_port = 10700
|
||||
log_file = ./frpc.log
|
||||
# debug, info, warn, error
|
||||
log_level = debug
|
||||
privilege_token = 123456
|
||||
|
||||
[echo]
|
||||
type = tcp
|
||||
local_ip = 127.0.0.1
|
||||
local_port = 10701
|
||||
remote_port = 10711
|
||||
use_encryption = true
|
||||
use_compression = true
|
||||
|
||||
[web]
|
||||
type = http
|
||||
local_ip = 127.0.0.1
|
||||
local_port = 10702
|
||||
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
|
||||
|
||||
[unix_domain]
|
||||
type = tcp
|
||||
remote_port = 10704
|
||||
plugin = unix_domain_socket
|
||||
plugin_unix_path = /tmp/frp_echo_server.sock
|
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
|
85
tests/echo_server.go
Normal file
85
tests/echo_server.go
Normal file
@@ -0,0 +1,85 @@
|
||||
package tests
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"os"
|
||||
"syscall"
|
||||
|
||||
frpNet "github.com/fatedier/frp/utils/net"
|
||||
)
|
||||
|
||||
func StartEchoServer() {
|
||||
l, err := frpNet.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 := frpNet.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 StartUnixDomainServer() {
|
||||
unixPath := "/tmp/frp_echo_server.sock"
|
||||
os.Remove(unixPath)
|
||||
syscall.Umask(0)
|
||||
l, err := net.Listen("unix", unixPath)
|
||||
if err != nil {
|
||||
fmt.Printf("unix domain server listen error: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
for {
|
||||
c, err := l.Accept()
|
||||
if err != nil {
|
||||
fmt.Printf("unix domain 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"))
|
||||
}
|
||||
}
|
119
tests/func_test.go
Normal file
119
tests/func_test.go
Normal file
@@ -0,0 +1,119 @@
|
||||
package tests
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
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()
|
||||
go StartUnixDomainServer()
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
}
|
||||
|
||||
func TestEchoServer(t *testing.T) {
|
||||
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.Write([]byte(ECHO_TEST_STR + "\n"))
|
||||
|
||||
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 != buf {
|
||||
t.Fatalf("content error, send [%s], get [%s]", strings.Trim(ECHO_TEST_STR, "\n"), strings.Trim(buf, "\n"))
|
||||
}
|
||||
}
|
||||
|
||||
func TestHttpServer(t *testing.T) {
|
||||
client := &http.Client{}
|
||||
req, _ := http.NewRequest("GET", fmt.Sprintf("http://127.0.0.1:%d", HTTP_PORT), nil)
|
||||
res, err := client.Do(req)
|
||||
if err != nil {
|
||||
t.Fatalf("do http request error: %v", err)
|
||||
}
|
||||
if res.StatusCode == 200 {
|
||||
body, err := ioutil.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
t.Fatalf("read from http server error: %v", err)
|
||||
}
|
||||
bodystr := string(body)
|
||||
if bodystr != HTTP_RES_STR {
|
||||
t.Fatalf("content from http server error [%s], correct string is [%s]", bodystr, HTTP_RES_STR)
|
||||
}
|
||||
} else {
|
||||
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]))
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnixDomainServer(t *testing.T) {
|
||||
c, err := frpNet.ConnectTcpServer(fmt.Sprintf("127.0.0.1:%d", 10704))
|
||||
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.Write([]byte(ECHO_TEST_STR + "\n"))
|
||||
|
||||
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 != buf {
|
||||
t.Fatalf("content error, send [%s], get [%s]", strings.Trim(ECHO_TEST_STR, "\n"), strings.Trim(buf, "\n"))
|
||||
}
|
||||
}
|
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,24 +12,25 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package msg
|
||||
package errors
|
||||
|
||||
type GeneralRes struct {
|
||||
Code int64 `json:"code"`
|
||||
Msg string `json:"msg"`
|
||||
}
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// messages between control connection of frpc and frps
|
||||
type ControlReq struct {
|
||||
Type int64 `json:"type"`
|
||||
ProxyName string `json:"proxy_name,omitempty"`
|
||||
AuthKey string `json:"auth_key, omitempty"`
|
||||
UseEncryption bool `json:"use_encryption, omitempty"`
|
||||
Timestamp int64 `json:"timestamp, omitempty"`
|
||||
}
|
||||
var (
|
||||
ErrMsgType = errors.New("message type error")
|
||||
ErrCtlClosed = errors.New("control is closed")
|
||||
)
|
||||
|
||||
type ControlRes struct {
|
||||
Type int64 `json:"type"`
|
||||
Code int64 `json:"code"`
|
||||
Msg string `json:"msg"`
|
||||
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")
|
||||
}
|
124
utils/io/io.go
Normal file
124
utils/io/io.go
Normal file
@@ -0,0 +1,124 @@
|
||||
// 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 io
|
||||
|
||||
import (
|
||||
"io"
|
||||
"sync"
|
||||
|
||||
"github.com/fatedier/frp/utils/crypto"
|
||||
"github.com/fatedier/frp/utils/pool"
|
||||
)
|
||||
|
||||
// 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()
|
||||
|
||||
buf := pool.GetBuf(16 * 1024)
|
||||
defer pool.PutBuf(buf)
|
||||
*count, _ = io.CopyBuffer(to, from, buf)
|
||||
}
|
||||
|
||||
wait.Add(2)
|
||||
go pipe(c1, c2, &inCount)
|
||||
go pipe(c2, c1, &outCount)
|
||||
wait.Wait()
|
||||
return
|
||||
}
|
||||
|
||||
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, func() error {
|
||||
return rwc.Close()
|
||||
}), nil
|
||||
}
|
||||
|
||||
func WithCompression(rwc io.ReadWriteCloser) io.ReadWriteCloser {
|
||||
sr := pool.GetSnappyReader(rwc)
|
||||
sw := pool.GetSnappyWriter(rwc)
|
||||
return WrapReadWriteCloser(sr, sw, func() error {
|
||||
err := rwc.Close()
|
||||
pool.PutSnappyReader(sr)
|
||||
pool.PutSnappyWriter(sw)
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
type ReadWriteCloser struct {
|
||||
r io.Reader
|
||||
w io.Writer
|
||||
closeFn func() error
|
||||
|
||||
closed bool
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
// closeFn will be called only once
|
||||
func WrapReadWriteCloser(r io.Reader, w io.Writer, closeFn func() error) io.ReadWriteCloser {
|
||||
return &ReadWriteCloser{
|
||||
r: r,
|
||||
w: w,
|
||||
closeFn: closeFn,
|
||||
closed: false,
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
rwc.mu.Lock()
|
||||
if rwc.closed {
|
||||
rwc.mu.Unlock()
|
||||
return
|
||||
}
|
||||
rwc.closed = true
|
||||
rwc.mu.Unlock()
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
if rwc.closeFn != nil {
|
||||
err = rwc.closeFn()
|
||||
if err != nil {
|
||||
errRet = err
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
145
utils/io/io_test.go
Normal file
145
utils/io/io_test.go
Normal file
@@ -0,0 +1,145 @@
|
||||
// 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 io
|
||||
|
||||
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, nil)
|
||||
conn2 := WrapReadWriteCloser(pr2, pw, nil)
|
||||
conn3 := WrapReadWriteCloser(pr3, pw4, nil)
|
||||
conn4 := WrapReadWriteCloser(pr4, pw3, nil)
|
||||
|
||||
go func() {
|
||||
Join(conn2, conn3)
|
||||
}()
|
||||
|
||||
buf1 := make([]byte, 1024)
|
||||
buf2 := make([]byte, 1024)
|
||||
|
||||
conn1.Write([]byte(text1))
|
||||
conn4.Write([]byte(text2))
|
||||
|
||||
n, err = conn4.Read(buf1)
|
||||
assert.NoError(err)
|
||||
assert.Equal(text1, string(buf1[:n]))
|
||||
|
||||
n, err = conn1.Read(buf2)
|
||||
assert.NoError(err)
|
||||
assert.Equal(text2, string(buf2[:n]))
|
||||
|
||||
conn1.Close()
|
||||
conn2.Close()
|
||||
conn3.Close()
|
||||
conn4.Close()
|
||||
}
|
||||
|
||||
func TestWithCompression(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
// Forward compression bytes.
|
||||
pr, pw := io.Pipe()
|
||||
pr2, pw2 := io.Pipe()
|
||||
|
||||
conn1 := WrapReadWriteCloser(pr, pw2, nil)
|
||||
conn2 := WrapReadWriteCloser(pr2, pw, nil)
|
||||
|
||||
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, nil)
|
||||
conn2 := WrapReadWriteCloser(pr2, pw, nil)
|
||||
conn3 := WrapReadWriteCloser(pr3, pw4, nil)
|
||||
conn4 := WrapReadWriteCloser(pr4, pw3, nil)
|
||||
conn5 := WrapReadWriteCloser(pr5, pw6, nil)
|
||||
conn6 := WrapReadWriteCloser(pr6, pw5, nil)
|
||||
|
||||
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)
|
||||
}
|
@@ -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(200)
|
||||
Log.EnableFuncCallDepth(true)
|
||||
Log.SetLogFuncCallDepth(Log.GetLogFuncCallDepth() + 1)
|
||||
}
|
||||
@@ -42,7 +43,7 @@ func SetLogFile(logWay string, logFile string, maxdays int64) {
|
||||
}
|
||||
}
|
||||
|
||||
// value: error, warning, info, debug
|
||||
// value: error, warning, info, debug, trace
|
||||
func SetLogLevel(logLevel string) {
|
||||
level := 4 // warning
|
||||
switch logLevel {
|
||||
@@ -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())
|
||||
}
|
158
utils/net/conn.go
Normal file
158
utils/net/conn.go
Normal file
@@ -0,0 +1,158 @@
|
||||
// Copyright 2016 fatedier, fatedier@gmail.com
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package net
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/fatedier/frp/utils/log"
|
||||
|
||||
kcp "github.com/xtaci/kcp-go"
|
||||
)
|
||||
|
||||
// Conn is the interface of connections used in frp.
|
||||
type Conn interface {
|
||||
net.Conn
|
||||
log.Logger
|
||||
}
|
||||
|
||||
type WrapLogConn struct {
|
||||
net.Conn
|
||||
log.Logger
|
||||
}
|
||||
|
||||
func WrapConn(c net.Conn) Conn {
|
||||
return &WrapLogConn{
|
||||
Conn: c,
|
||||
Logger: log.NewPrefixLogger(""),
|
||||
}
|
||||
}
|
||||
|
||||
type WrapReadWriteCloserConn struct {
|
||||
io.ReadWriteCloser
|
||||
log.Logger
|
||||
}
|
||||
|
||||
func WrapReadWriteCloserToConn(rwc io.ReadWriteCloser) Conn {
|
||||
return &WrapReadWriteCloserConn{
|
||||
ReadWriteCloser: rwc,
|
||||
Logger: log.NewPrefixLogger(""),
|
||||
}
|
||||
}
|
||||
|
||||
func (conn *WrapReadWriteCloserConn) LocalAddr() net.Addr {
|
||||
return (*net.TCPAddr)(nil)
|
||||
}
|
||||
|
||||
func (conn *WrapReadWriteCloserConn) RemoteAddr() net.Addr {
|
||||
return (*net.TCPAddr)(nil)
|
||||
}
|
||||
|
||||
func (conn *WrapReadWriteCloserConn) SetDeadline(t time.Time) error {
|
||||
return &net.OpError{Op: "set", Net: "wrap", Source: nil, Addr: nil, Err: errors.New("deadline not supported")}
|
||||
}
|
||||
|
||||
func (conn *WrapReadWriteCloserConn) SetReadDeadline(t time.Time) error {
|
||||
return &net.OpError{Op: "set", Net: "wrap", Source: nil, Addr: nil, Err: errors.New("deadline not supported")}
|
||||
}
|
||||
|
||||
func (conn *WrapReadWriteCloserConn) SetWriteDeadline(t time.Time) error {
|
||||
return &net.OpError{Op: "set", Net: "wrap", Source: nil, Addr: nil, Err: errors.New("deadline not supported")}
|
||||
}
|
||||
|
||||
func ConnectServer(protocol string, addr string) (c Conn, err error) {
|
||||
switch protocol {
|
||||
case "tcp":
|
||||
return ConnectTcpServer(addr)
|
||||
case "kcp":
|
||||
kcpConn, errRet := kcp.DialWithOptions(addr, nil, 10, 3)
|
||||
if errRet != nil {
|
||||
err = errRet
|
||||
return
|
||||
}
|
||||
kcpConn.SetStreamMode(true)
|
||||
kcpConn.SetWriteDelay(true)
|
||||
kcpConn.SetNoDelay(1, 20, 2, 1)
|
||||
kcpConn.SetWindowSize(128, 512)
|
||||
kcpConn.SetMtu(1350)
|
||||
kcpConn.SetACKNoDelay(false)
|
||||
kcpConn.SetReadBuffer(4194304)
|
||||
kcpConn.SetWriteBuffer(4194304)
|
||||
c = WrapConn(kcpConn)
|
||||
return
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupport protocol: %s", protocol)
|
||||
}
|
||||
}
|
||||
|
||||
func ConnectServerByHttpProxy(httpProxy string, protocol string, addr string) (c Conn, err error) {
|
||||
switch protocol {
|
||||
case "tcp":
|
||||
return ConnectTcpServerByHttpProxy(httpProxy, addr)
|
||||
case "kcp":
|
||||
// http proxy is not supported for kcp
|
||||
return ConnectServer(protocol, addr)
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupport protocol: %s", protocol)
|
||||
}
|
||||
}
|
||||
|
||||
type SharedConn struct {
|
||||
Conn
|
||||
sync.Mutex
|
||||
buf *bytes.Buffer
|
||||
}
|
||||
|
||||
// the bytes you read in io.Reader, will be reserved in SharedConn
|
||||
func NewShareConn(conn Conn) (*SharedConn, io.Reader) {
|
||||
sc := &SharedConn{
|
||||
Conn: conn,
|
||||
buf: bytes.NewBuffer(make([]byte, 0, 1024)),
|
||||
}
|
||||
return sc, io.TeeReader(conn, sc.buf)
|
||||
}
|
||||
|
||||
func (sc *SharedConn) Read(p []byte) (n int, err error) {
|
||||
sc.Lock()
|
||||
if sc.buf == nil {
|
||||
sc.Unlock()
|
||||
return sc.Conn.Read(p)
|
||||
}
|
||||
sc.Unlock()
|
||||
n, err = sc.buf.Read(p)
|
||||
|
||||
if err == io.EOF {
|
||||
sc.Lock()
|
||||
sc.buf = nil
|
||||
sc.Unlock()
|
||||
var n2 int
|
||||
n2, err = sc.Conn.Read(p[n:])
|
||||
|
||||
n += n2
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (sc *SharedConn) WriteBuff(buffer []byte) (err error) {
|
||||
sc.buf.Reset()
|
||||
_, err = sc.buf.Write(buffer)
|
||||
return err
|
||||
}
|
87
utils/net/kcp.go
Normal file
87
utils/net/kcp.go
Normal file
@@ -0,0 +1,87 @@
|
||||
// Copyright 2017 fatedier, fatedier@gmail.com
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package net
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
|
||||
"github.com/fatedier/frp/utils/log"
|
||||
|
||||
kcp "github.com/xtaci/kcp-go"
|
||||
)
|
||||
|
||||
type KcpListener struct {
|
||||
net.Addr
|
||||
listener net.Listener
|
||||
accept chan Conn
|
||||
closeFlag bool
|
||||
log.Logger
|
||||
}
|
||||
|
||||
func ListenKcp(bindAddr string, bindPort int64) (l *KcpListener, err error) {
|
||||
listener, err := kcp.ListenWithOptions(fmt.Sprintf("%s:%d", bindAddr, bindPort), nil, 10, 3)
|
||||
if err != nil {
|
||||
return l, err
|
||||
}
|
||||
listener.SetReadBuffer(4194304)
|
||||
listener.SetWriteBuffer(4194304)
|
||||
|
||||
l = &KcpListener{
|
||||
Addr: listener.Addr(),
|
||||
listener: listener,
|
||||
accept: make(chan Conn),
|
||||
closeFlag: false,
|
||||
Logger: log.NewPrefixLogger(""),
|
||||
}
|
||||
|
||||
go func() {
|
||||
for {
|
||||
conn, err := listener.AcceptKCP()
|
||||
if err != nil {
|
||||
if l.closeFlag {
|
||||
close(l.accept)
|
||||
return
|
||||
}
|
||||
continue
|
||||
}
|
||||
conn.SetStreamMode(true)
|
||||
conn.SetWriteDelay(true)
|
||||
conn.SetNoDelay(1, 20, 2, 1)
|
||||
conn.SetMtu(1350)
|
||||
conn.SetWindowSize(1024, 1024)
|
||||
conn.SetACKNoDelay(false)
|
||||
|
||||
l.accept <- WrapConn(conn)
|
||||
}
|
||||
}()
|
||||
return l, err
|
||||
}
|
||||
|
||||
func (l *KcpListener) Accept() (Conn, error) {
|
||||
conn, ok := <-l.accept
|
||||
if !ok {
|
||||
return conn, fmt.Errorf("channel for kcp listener closed")
|
||||
}
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
func (l *KcpListener) Close() error {
|
||||
if !l.closeFlag {
|
||||
l.closeFlag = true
|
||||
l.listener.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
46
utils/net/listener.go
Normal file
46
utils/net/listener.go
Normal file
@@ -0,0 +1,46 @@
|
||||
// Copyright 2017 fatedier, fatedier@gmail.com
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package net
|
||||
|
||||
import (
|
||||
"net"
|
||||
|
||||
"github.com/fatedier/frp/utils/log"
|
||||
)
|
||||
|
||||
type Listener interface {
|
||||
Accept() (Conn, error)
|
||||
Close() error
|
||||
log.Logger
|
||||
}
|
||||
|
||||
type LogListener struct {
|
||||
l net.Listener
|
||||
net.Listener
|
||||
log.Logger
|
||||
}
|
||||
|
||||
func WrapLogListener(l net.Listener) Listener {
|
||||
return &LogListener{
|
||||
l: l,
|
||||
Listener: l,
|
||||
Logger: log.NewPrefixLogger(""),
|
||||
}
|
||||
}
|
||||
|
||||
func (logL *LogListener) Accept() (Conn, error) {
|
||||
c, err := logL.l.Accept()
|
||||
return WrapConn(c), err
|
||||
}
|
166
utils/net/tcp.go
Normal file
166
utils/net/tcp.go
Normal file
@@ -0,0 +1,166 @@
|
||||
// Copyright 2016 fatedier, fatedier@gmail.com
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package net
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"github.com/fatedier/frp/utils/log"
|
||||
)
|
||||
|
||||
type TcpListener struct {
|
||||
net.Addr
|
||||
listener net.Listener
|
||||
accept chan Conn
|
||||
closeFlag bool
|
||||
log.Logger
|
||||
}
|
||||
|
||||
func ListenTcp(bindAddr string, bindPort int64) (l *TcpListener, err error) {
|
||||
tcpAddr, err := net.ResolveTCPAddr("tcp", fmt.Sprintf("%s:%d", bindAddr, bindPort))
|
||||
if err != nil {
|
||||
return l, err
|
||||
}
|
||||
listener, err := net.ListenTCP("tcp", tcpAddr)
|
||||
if err != nil {
|
||||
return l, err
|
||||
}
|
||||
|
||||
l = &TcpListener{
|
||||
Addr: listener.Addr(),
|
||||
listener: listener,
|
||||
accept: make(chan Conn),
|
||||
closeFlag: false,
|
||||
Logger: log.NewPrefixLogger(""),
|
||||
}
|
||||
|
||||
go func() {
|
||||
for {
|
||||
conn, err := listener.AcceptTCP()
|
||||
if err != nil {
|
||||
if l.closeFlag {
|
||||
close(l.accept)
|
||||
return
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
c := NewTcpConn(conn)
|
||||
l.accept <- c
|
||||
}
|
||||
}()
|
||||
return l, err
|
||||
}
|
||||
|
||||
// Wait util get one new connection or listener is closed
|
||||
// if listener is closed, err returned.
|
||||
func (l *TcpListener) Accept() (Conn, error) {
|
||||
conn, ok := <-l.accept
|
||||
if !ok {
|
||||
return conn, fmt.Errorf("channel for tcp listener closed")
|
||||
}
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
func (l *TcpListener) Close() error {
|
||||
if !l.closeFlag {
|
||||
l.closeFlag = true
|
||||
l.listener.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Wrap for TCPConn.
|
||||
type TcpConn struct {
|
||||
net.Conn
|
||||
log.Logger
|
||||
}
|
||||
|
||||
func NewTcpConn(conn *net.TCPConn) (c *TcpConn) {
|
||||
c = &TcpConn{
|
||||
Conn: conn,
|
||||
Logger: log.NewPrefixLogger(""),
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func ConnectTcpServer(addr string) (c Conn, err error) {
|
||||
servertAddr, err := net.ResolveTCPAddr("tcp", addr)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
conn, err := net.DialTCP("tcp", nil, servertAddr)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
c = NewTcpConn(conn)
|
||||
return
|
||||
}
|
||||
|
||||
// ConnectTcpServerByHttpProxy try to connect remote server by http proxy.
|
||||
// If httpProxy is empty, it will connect server directly.
|
||||
func ConnectTcpServerByHttpProxy(httpProxy string, serverAddr string) (c Conn, err error) {
|
||||
if httpProxy == "" {
|
||||
return ConnectTcpServer(serverAddr)
|
||||
}
|
||||
|
||||
var proxyUrl *url.URL
|
||||
if proxyUrl, err = url.Parse(httpProxy); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var proxyAuth string
|
||||
if proxyUrl.User != nil {
|
||||
username := proxyUrl.User.Username()
|
||||
passwd, _ := proxyUrl.User.Password()
|
||||
proxyAuth = "Basic " + base64.StdEncoding.EncodeToString([]byte(username+":"+passwd))
|
||||
}
|
||||
|
||||
if proxyUrl.Scheme != "http" {
|
||||
err = fmt.Errorf("Proxy URL scheme must be http, not [%s]", proxyUrl.Scheme)
|
||||
return
|
||||
}
|
||||
|
||||
if c, err = ConnectTcpServer(proxyUrl.Host); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("CONNECT", "http://"+serverAddr, nil)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if proxyAuth != "" {
|
||||
req.Header.Set("Proxy-Authorization", proxyAuth)
|
||||
}
|
||||
req.Header.Set("User-Agent", "Mozilla/5.0")
|
||||
req.Write(c)
|
||||
|
||||
resp, err := http.ReadResponse(bufio.NewReader(c), req)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
resp.Body.Close()
|
||||
if resp.StatusCode != 200 {
|
||||
err = fmt.Errorf("ConnectTcpServer using proxy error, StatusCode [%d]", resp.StatusCode)
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
259
utils/net/udp.go
Normal file
259
utils/net/udp.go
Normal file
@@ -0,0 +1,259 @@
|
||||
// Copyright 2017 fatedier, fatedier@gmail.com
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package net
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/fatedier/frp/utils/log"
|
||||
"github.com/fatedier/frp/utils/pool"
|
||||
)
|
||||
|
||||
type UdpPacket struct {
|
||||
Buf []byte
|
||||
LocalAddr net.Addr
|
||||
RemoteAddr net.Addr
|
||||
}
|
||||
|
||||
type FakeUdpConn struct {
|
||||
log.Logger
|
||||
l *UdpListener
|
||||
|
||||
localAddr net.Addr
|
||||
remoteAddr net.Addr
|
||||
packets chan []byte
|
||||
closeFlag bool
|
||||
|
||||
lastActive time.Time
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
func NewFakeUdpConn(l *UdpListener, laddr, raddr net.Addr) *FakeUdpConn {
|
||||
fc := &FakeUdpConn{
|
||||
Logger: log.NewPrefixLogger(""),
|
||||
l: l,
|
||||
localAddr: laddr,
|
||||
remoteAddr: raddr,
|
||||
packets: make(chan []byte, 20),
|
||||
}
|
||||
|
||||
go func() {
|
||||
for {
|
||||
time.Sleep(5 * time.Second)
|
||||
fc.mu.RLock()
|
||||
if time.Now().Sub(fc.lastActive) > 10*time.Second {
|
||||
fc.mu.RUnlock()
|
||||
fc.Close()
|
||||
break
|
||||
}
|
||||
fc.mu.RUnlock()
|
||||
}
|
||||
}()
|
||||
return fc
|
||||
}
|
||||
|
||||
func (c *FakeUdpConn) putPacket(content []byte) {
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
}
|
||||
}()
|
||||
|
||||
select {
|
||||
case c.packets <- content:
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
func (c *FakeUdpConn) Read(b []byte) (n int, err error) {
|
||||
content, ok := <-c.packets
|
||||
if !ok {
|
||||
return 0, io.EOF
|
||||
}
|
||||
c.mu.Lock()
|
||||
c.lastActive = time.Now()
|
||||
c.mu.Unlock()
|
||||
|
||||
if len(b) < len(content) {
|
||||
n = len(b)
|
||||
} else {
|
||||
n = len(content)
|
||||
}
|
||||
copy(b, content)
|
||||
return n, nil
|
||||
}
|
||||
|
||||
func (c *FakeUdpConn) Write(b []byte) (n int, err error) {
|
||||
c.mu.RLock()
|
||||
if c.closeFlag {
|
||||
c.mu.RUnlock()
|
||||
return 0, io.ErrClosedPipe
|
||||
}
|
||||
c.mu.RUnlock()
|
||||
|
||||
packet := &UdpPacket{
|
||||
Buf: b,
|
||||
LocalAddr: c.localAddr,
|
||||
RemoteAddr: c.remoteAddr,
|
||||
}
|
||||
c.l.writeUdpPacket(packet)
|
||||
|
||||
c.mu.Lock()
|
||||
c.lastActive = time.Now()
|
||||
c.mu.Unlock()
|
||||
return len(b), nil
|
||||
}
|
||||
|
||||
func (c *FakeUdpConn) Close() error {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
if !c.closeFlag {
|
||||
c.closeFlag = true
|
||||
close(c.packets)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *FakeUdpConn) IsClosed() bool {
|
||||
c.mu.RLock()
|
||||
defer c.mu.RUnlock()
|
||||
return c.closeFlag
|
||||
}
|
||||
|
||||
func (c *FakeUdpConn) LocalAddr() net.Addr {
|
||||
return c.localAddr
|
||||
}
|
||||
|
||||
func (c *FakeUdpConn) RemoteAddr() net.Addr {
|
||||
return c.remoteAddr
|
||||
}
|
||||
|
||||
func (c *FakeUdpConn) SetDeadline(t time.Time) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *FakeUdpConn) SetReadDeadline(t time.Time) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *FakeUdpConn) SetWriteDeadline(t time.Time) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type UdpListener struct {
|
||||
net.Addr
|
||||
accept chan Conn
|
||||
writeCh chan *UdpPacket
|
||||
readConn net.Conn
|
||||
closeFlag bool
|
||||
|
||||
fakeConns map[string]*FakeUdpConn
|
||||
|
||||
log.Logger
|
||||
}
|
||||
|
||||
func ListenUDP(bindAddr string, bindPort int64) (l *UdpListener, err error) {
|
||||
udpAddr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", bindAddr, bindPort))
|
||||
if err != nil {
|
||||
return l, err
|
||||
}
|
||||
readConn, err := net.ListenUDP("udp", udpAddr)
|
||||
|
||||
l = &UdpListener{
|
||||
Addr: udpAddr,
|
||||
accept: make(chan Conn),
|
||||
writeCh: make(chan *UdpPacket, 1000),
|
||||
fakeConns: make(map[string]*FakeUdpConn),
|
||||
Logger: log.NewPrefixLogger(""),
|
||||
}
|
||||
|
||||
// for reading
|
||||
go func() {
|
||||
for {
|
||||
buf := pool.GetBuf(1450)
|
||||
n, remoteAddr, err := readConn.ReadFromUDP(buf)
|
||||
if err != nil {
|
||||
close(l.accept)
|
||||
close(l.writeCh)
|
||||
return
|
||||
}
|
||||
|
||||
fakeConn, exist := l.fakeConns[remoteAddr.String()]
|
||||
if !exist || fakeConn.IsClosed() {
|
||||
fakeConn = NewFakeUdpConn(l, l.Addr, remoteAddr)
|
||||
l.fakeConns[remoteAddr.String()] = fakeConn
|
||||
}
|
||||
fakeConn.putPacket(buf[:n])
|
||||
|
||||
l.accept <- fakeConn
|
||||
}
|
||||
}()
|
||||
|
||||
// for writing
|
||||
go func() {
|
||||
for {
|
||||
packet, ok := <-l.writeCh
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
if addr, ok := packet.RemoteAddr.(*net.UDPAddr); ok {
|
||||
readConn.WriteToUDP(packet.Buf, addr)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (l *UdpListener) writeUdpPacket(packet *UdpPacket) (err error) {
|
||||
defer func() {
|
||||
if errRet := recover(); errRet != nil {
|
||||
err = fmt.Errorf("udp write closed listener")
|
||||
l.Info("udp write closed listener")
|
||||
}
|
||||
}()
|
||||
l.writeCh <- packet
|
||||
return
|
||||
}
|
||||
|
||||
func (l *UdpListener) WriteMsg(buf []byte, remoteAddr *net.UDPAddr) (err error) {
|
||||
// only set remote addr here
|
||||
packet := &UdpPacket{
|
||||
Buf: buf,
|
||||
RemoteAddr: remoteAddr,
|
||||
}
|
||||
err = l.writeUdpPacket(packet)
|
||||
return
|
||||
}
|
||||
|
||||
func (l *UdpListener) Accept() (Conn, error) {
|
||||
conn, ok := <-l.accept
|
||||
if !ok {
|
||||
return conn, fmt.Errorf("channel for udp listener closed")
|
||||
}
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
func (l *UdpListener) Close() error {
|
||||
if !l.closeFlag {
|
||||
l.closeFlag = true
|
||||
l.readConn.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
63
utils/pool/buf.go
Normal file
63
utils/pool/buf.go
Normal file
@@ -0,0 +1,63 @@
|
||||
// Copyright 2016 fatedier, fatedier@gmail.com
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package pool
|
||||
|
||||
import "sync"
|
||||
|
||||
var (
|
||||
bufPool16k sync.Pool
|
||||
bufPool5k sync.Pool
|
||||
bufPool2k sync.Pool
|
||||
bufPool1k sync.Pool
|
||||
bufPool sync.Pool
|
||||
)
|
||||
|
||||
func GetBuf(size int) []byte {
|
||||
var x interface{}
|
||||
if size >= 16*1024 {
|
||||
x = bufPool16k.Get()
|
||||
} else if size >= 5*1024 {
|
||||
x = bufPool5k.Get()
|
||||
} else if size >= 2*1024 {
|
||||
x = bufPool2k.Get()
|
||||
} else if size >= 1*1024 {
|
||||
x = bufPool1k.Get()
|
||||
} else {
|
||||
x = bufPool.Get()
|
||||
}
|
||||
if x == nil {
|
||||
return make([]byte, size)
|
||||
}
|
||||
buf := x.([]byte)
|
||||
if cap(buf) < size {
|
||||
return make([]byte, size)
|
||||
}
|
||||
return buf[:size]
|
||||
}
|
||||
|
||||
func PutBuf(buf []byte) {
|
||||
size := cap(buf)
|
||||
if size >= 16*1024 {
|
||||
bufPool16k.Put(buf)
|
||||
} else if size >= 5*1024 {
|
||||
bufPool5k.Put(buf)
|
||||
} else if size >= 2*1024 {
|
||||
bufPool2k.Put(buf)
|
||||
} else if size >= 1*1024 {
|
||||
bufPool1k.Put(buf)
|
||||
} else {
|
||||
bufPool.Put(buf)
|
||||
}
|
||||
}
|
@@ -12,36 +12,40 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package pcrypto
|
||||
package pool
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestEncrypt(t *testing.T) {
|
||||
pp := new(Pcrypto)
|
||||
pp.Init([]byte("Hana"))
|
||||
res, err := pp.Encrypt([]byte("Just One Test!"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
func TestPutBuf(t *testing.T) {
|
||||
buf := make([]byte, 512)
|
||||
PutBuf(buf)
|
||||
|
||||
fmt.Printf("[%x]\n", res)
|
||||
buf = make([]byte, 1025)
|
||||
PutBuf(buf)
|
||||
|
||||
buf = make([]byte, 2*1025)
|
||||
PutBuf(buf)
|
||||
|
||||
buf = make([]byte, 5*1025)
|
||||
PutBuf(buf)
|
||||
}
|
||||
|
||||
func TestDecrypt(t *testing.T) {
|
||||
pp := new(Pcrypto)
|
||||
pp.Init([]byte("Hana"))
|
||||
res, err := pp.Encrypt([]byte("Just One Test!"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
func TestGetBuf(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
res, err = pp.Decrypt(res)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
buf := GetBuf(200)
|
||||
assert.Len(buf, 200)
|
||||
|
||||
fmt.Printf("[%s]\n", string(res))
|
||||
buf = GetBuf(1025)
|
||||
assert.Len(buf, 1025)
|
||||
|
||||
buf = GetBuf(2 * 1024)
|
||||
assert.Len(buf, 2*1024)
|
||||
|
||||
buf = GetBuf(5 * 2000)
|
||||
assert.Len(buf, 5*2000)
|
||||
}
|
57
utils/pool/snappy.go
Normal file
57
utils/pool/snappy.go
Normal file
@@ -0,0 +1,57 @@
|
||||
// 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 pool
|
||||
|
||||
import (
|
||||
"io"
|
||||
"sync"
|
||||
|
||||
"github.com/golang/snappy"
|
||||
)
|
||||
|
||||
var (
|
||||
snappyReaderPool sync.Pool
|
||||
snappyWriterPool sync.Pool
|
||||
)
|
||||
|
||||
func GetSnappyReader(r io.Reader) *snappy.Reader {
|
||||
var x interface{}
|
||||
x = snappyReaderPool.Get()
|
||||
if x == nil {
|
||||
return snappy.NewReader(r)
|
||||
}
|
||||
sr := x.(*snappy.Reader)
|
||||
sr.Reset(r)
|
||||
return sr
|
||||
}
|
||||
|
||||
func PutSnappyReader(sr *snappy.Reader) {
|
||||
snappyReaderPool.Put(sr)
|
||||
}
|
||||
|
||||
func GetSnappyWriter(w io.Writer) *snappy.Writer {
|
||||
var x interface{}
|
||||
x = snappyWriterPool.Get()
|
||||
if x == nil {
|
||||
return snappy.NewWriter(w)
|
||||
}
|
||||
sw := x.(*snappy.Writer)
|
||||
sw.Reset(w)
|
||||
return sw
|
||||
}
|
||||
|
||||
func PutSnappyWriter(sw *snappy.Writer) {
|
||||
snappyWriterPool.Put(sw)
|
||||
}
|
62
utils/shutdown/shutdown.go
Normal file
62
utils/shutdown/shutdown.go
Normal file
@@ -0,0 +1,62 @@
|
||||
// Copyright 2017 fatedier, fatedier@gmail.com
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package shutdown
|
||||
|
||||
import (
|
||||
"sync"
|
||||
)
|
||||
|
||||
type Shutdown struct {
|
||||
doing bool
|
||||
ending bool
|
||||
start chan struct{}
|
||||
down chan struct{}
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
func New() *Shutdown {
|
||||
return &Shutdown{
|
||||
doing: false,
|
||||
ending: false,
|
||||
start: make(chan struct{}),
|
||||
down: make(chan struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Shutdown) Start() {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
if !s.doing {
|
||||
s.doing = true
|
||||
close(s.start)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Shutdown) WaitStart() {
|
||||
<-s.start
|
||||
}
|
||||
|
||||
func (s *Shutdown) Done() {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
if !s.ending {
|
||||
s.ending = true
|
||||
close(s.down)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Shutdown) WaitDown() {
|
||||
<-s.down
|
||||
}
|
21
utils/shutdown/shutdown_test.go
Normal file
21
utils/shutdown/shutdown_test.go
Normal file
@@ -0,0 +1,21 @@
|
||||
package shutdown
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestShutdown(t *testing.T) {
|
||||
s := New()
|
||||
go func() {
|
||||
time.Sleep(time.Millisecond)
|
||||
s.Start()
|
||||
}()
|
||||
s.WaitStart()
|
||||
|
||||
go func() {
|
||||
time.Sleep(time.Millisecond)
|
||||
s.Done()
|
||||
}()
|
||||
s.WaitDown()
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user