Compare commits

...

158 Commits

Author SHA1 Message Date
fatedier
66a69f873f Merge pull request #890 from fatedier/dev
bump version to v0.21.0
2018-08-12 12:10:16 +08:00
fatedier
fb13774457 version to v0.21.0 2018-08-12 12:04:12 +08:00
fatedier
f14ed87b29 Merge pull request #886 from fatedier/websocket
Connect protocol support websocket
2018-08-10 16:36:02 +08:00
fatedier
07623027bc websocket: fix 2018-08-10 16:31:49 +08:00
fatedier
941ac25648 fix ci 2018-08-10 12:02:38 +08:00
fatedier
f645082d72 vendor: add package golang.org/x/net/websocket 2018-08-10 11:49:33 +08:00
fatedier
7793f55545 websocket: update muxer for websocket 2018-08-10 11:45:48 +08:00
fatedier
ca88b07ecf optimize 2018-08-08 11:18:38 +08:00
fatedier
6e305db4be Merge pull request #882 from 235832289/master
Fix the problem of long connection for more than 30 seconds and disconnection of the server
2018-08-08 11:03:08 +08:00
itcode
9bb08396c7 Fix the problem of long connection for more than 30 seconds and disconnection of the server 2018-08-08 10:52:08 +08:00
fatedier
64136a3b3e Merge pull request #875 from jettyu/jettyu-websocket
websocket protocol
2018-08-06 15:20:09 +08:00
FishFish
b8037475ed websocket protocol 2018-08-05 12:55:31 +08:00
fatedier
b6c219aa97 Merge pull request #814 from neohe/dev
update xtcp log info
2018-06-08 22:51:12 +08:00
Neo He
bbc36be052 update xtcp log info 2018-06-08 21:27:58 +08:00
fatedier
f5778349d5 config: fix server_name not using user as prefix, fix #804 2018-06-08 14:44:19 +08:00
fatedier
71603c6d0b Merge pull request #807 from kac-/allowPorts
cmd: frps: allow_ports option
2018-06-08 10:52:05 +08:00
kac-
e64fcce417 cmd: frps: allow_ports option 2018-06-06 18:25:55 +02:00
fatedier
629f2856b1 Merge pull request #801 from fatedier/dev
bump version v0.20.0
2018-06-01 00:15:22 +08:00
fatedier
aeb9f2b64d update golib 2018-05-29 02:08:41 +08:00
fatedier
85dd41c17b Merge pull request #795 from fatedier/lb
support lb
2018-05-26 22:42:03 +08:00
fatedier
102408d37f doc: load balancing 2018-05-26 02:28:13 +08:00
fatedier
495b577819 update group ci 2018-05-23 14:39:12 +08:00
fatedier
f56b49ad3b new feature: load balancing for tcp proxy 2018-05-22 23:59:35 +08:00
fatedier
cb1bf71bef Merge pull request #787 from lonwern/arm64
build linux_arm64 package
2018-05-21 21:43:16 +08:00
fatedier
b9f062bef2 support lb 2018-05-21 21:22:10 +08:00
fatedier
490019fb51 update vendor, fix #788 2018-05-21 21:09:18 +08:00
fatedier
2e497274ba add ci for setting headers 2018-05-20 23:55:22 +08:00
fatedier
cf4136fe99 Merge pull request #786 from fatedier/header
support setting headers in http request
2018-05-20 23:44:19 +08:00
fatedier
b1e9cff622 doc: about headers 2018-05-20 23:41:15 +08:00
fatedier
db2d1fce76 http: support setting headers 2018-05-20 23:22:07 +08:00
lonwern
8579de9d3f build linux_arm64 package 2018-05-20 23:05:29 +08:00
fatedier
0c35273759 Merge pull request #783 from fatedier/stcp
frps dashboard add stcp
2018-05-20 19:09:43 +08:00
fatedier
6eb8146334 frps dashboard add stcp 2018-05-20 19:06:05 +08:00
fatedier
da78e3f52e Merge pull request #781 from fatedier/dev
bump version to 0.19.1
2018-05-19 23:10:17 +08:00
fatedier
e1918f6396 frps add '-t' to set token 2018-05-18 10:58:08 +08:00
fatedier
ad1e32fd2d fix panic error when vhost_http_port is not set but there is a http
proxy, fix #776
2018-05-18 00:21:11 +08:00
fatedier
3726f99b04 fix ci 2018-05-17 00:44:53 +08:00
fatedier
c8a7405992 version to 0.19.1 2018-05-17 00:17:22 +08:00
fatedier
040d198e36 Merge pull request #773 from fatedier/nat
return error quickly if nathole make error
2018-05-17 00:10:53 +08:00
fatedier
1a6cbbb2d2 return error quickly if nathole make error 2018-05-17 00:07:56 +08:00
fatedier
ea79e03bd0 Merge pull request #772 from fatedier/web
update dashboard ui
2018-05-16 23:56:12 +08:00
fatedier
3e349455a0 commands for xtcp, stcp add 'bind_port', fix #767 2018-05-16 23:45:44 +08:00
fatedier
c7a457a045 update web assets 2018-05-16 03:34:43 +08:00
fatedier
0b0d5c982e update web 2018-05-16 02:51:02 +08:00
fatedier
c4f873c07a update web package 2018-05-14 16:17:13 +08:00
fatedier
01b1df2b91 update cmd help info 2018-05-14 15:01:16 +08:00
fatedier
f1bea49314 Merge pull request #766 from fatedier/dev
bump version to 0.19.0
2018-05-14 11:08:46 +08:00
fatedier
2ffae3489b dashboard: more params 2018-05-11 17:25:01 +08:00
fatedier
96b94d9164 dashbaord_api: more info 2018-05-11 17:14:16 +08:00
fatedier
76b04f52d1 udpate doc 2018-05-11 16:54:36 +08:00
fatedier
97db0d187a Merge pull request #760 from fatedier/vendor
move some packages to golib (https://github.com/fatedier/golib)
2018-05-11 16:47:03 +08:00
fatedier
d9aadab4cb vendor: add golib/msg 2018-05-11 16:42:08 +08:00
fatedier
a0fe2fc2c2 vendor: models/msg 2018-05-11 16:37:44 +08:00
fatedier
1464836f05 logs panic debug strace info 2018-05-11 12:05:37 +08:00
fatedier
b2a2037032 change accept connection error loglevel to debug 2018-05-11 10:35:16 +08:00
fatedier
071cbf4b15 vendor: update 2018-05-09 01:05:14 +08:00
fatedier
20fcb58437 vendor: add package golib/net 2018-05-09 00:23:42 +08:00
fatedier
a27e3dda88 vendor: update shutdown 2018-05-08 23:51:13 +08:00
fatedier
1dd7317c06 vendor: add package io 2018-05-08 23:42:04 +08:00
fatedier
58a54bd0fb Merge pull request #755 from king6cong/master
typo fix
2018-05-08 17:12:02 +08:00
king6cong
caec4982cc typo fix 2018-05-08 17:02:49 +08:00
fatedier
dd8f788ca4 use dep instead of glide 2018-05-08 02:40:11 +08:00
fatedier
8a6d6c534a vendor: udpate 2018-05-08 02:13:30 +08:00
fatedier
39089cf262 Merge pull request #750 from fatedier/doc
doc: update
2018-05-07 16:48:52 +08:00
fatedier
55800dc29f doc: update 2018-05-07 16:46:04 +08:00
fatedier
04560c1896 web: translate web interface completely to English, fix #680 2018-05-06 23:39:33 +08:00
fatedier
178efd67f1 Merge pull request #746 from fatedier/mux
http port and https port can be same with frps bind_port
2018-05-06 22:54:30 +08:00
fatedier
6ef5fb6391 Merge pull request #744 from wujingquan/patch-1
Update frps_full.ini
2018-05-06 22:53:59 +08:00
fatedier
1ae43e4d41 plugin: update http_proxy 2018-05-06 22:47:26 +08:00
fatedier
5db605ca02 frps: vhost_http_port and vhost_https_port can be same with frps bind
port
2018-05-06 20:25:52 +08:00
Touch
e087301425 Update frps_full.ini 2018-05-06 04:08:36 +08:00
fatedier
f45283dbdb disable yamux default log 2018-05-05 00:09:39 +08:00
fatedier
b0959b3caa bump version to v0.19.0 2018-05-04 23:14:38 +08:00
fatedier
c5c89a2519 doc: update 2018-05-04 23:12:23 +08:00
fatedier
bebd1db22a Merge pull request #740 from fatedier/socks5
frpc: support connectiong frps by socks5 proxy
2018-05-04 19:15:11 +08:00
fatedier
cd37d22f3b vendor: udpate golang.org/x/net 2018-05-04 18:37:43 +08:00
fatedier
30af32728a frpc: support connectiong frps by socks5 proxy 2018-05-04 18:36:38 +08:00
fatedier
60ecd1d58c cmd: change http_user and http_pwd default value to empty 2018-05-03 10:20:09 +08:00
fatedier
a60be8f562 update Makefile 2018-05-03 00:32:05 +08:00
fatedier
c008b14d0f Merge pull request #735 from fatedier/dev
bump version to v0.18.0
2018-05-02 22:37:09 +08:00
fatedier
853892f3cd change version to v0.18.0 2018-05-02 22:09:15 +08:00
fatedier
e43f9f5850 Merge pull request #734 from fatedier/mux
use yamux instead of smux
2018-05-02 21:57:18 +08:00
fatedier
d5f30ccd6b Merge pull request #726 from shuaihanhungry/develop
do not ignore config parsing error
2018-05-02 21:51:02 +08:00
hanshuai
b87df569e7 do not ignore config parsing error 2018-05-02 20:40:33 +08:00
fatedier
976cf3e9f8 use yamux instead of smux 2018-04-25 02:42:00 +08:00
fatedier
371c401f5b Merge pull request #720 from fatedier/dev
bump version to v0.17.0
2018-04-24 01:58:57 +08:00
fatedier
69919e8ef9 Merge pull request #719 from fatedier/doc
update
2018-04-24 01:55:19 +08:00
fatedier
9abbe33790 typo 2018-04-24 01:51:52 +08:00
fatedier
4a5c00286e doc: update 2018-04-24 01:28:25 +08:00
fatedier
dfb892c8f6 Merge pull request #718 from fatedier/cmd
more cmds
2018-04-23 03:07:14 +08:00
fatedier
461c4c18fd update doc 2018-04-23 03:04:33 +08:00
fatedier
00b9ba95ae frpc: support specify default dns server, close #700 2018-04-23 02:59:40 +08:00
fatedier
c47aad348d fix 2018-04-23 02:40:25 +08:00
fatedier
4cb4da3afc add package github.com/spf13/cobra 2018-04-23 02:35:50 +08:00
fatedier
c1f57da00d update packages 2018-04-23 02:31:00 +08:00
fatedier
fe187eb8ec remove package github.com/docopt/docopt-go 2018-04-23 02:15:01 +08:00
fatedier
0f6f674a64 cmd: support more cli command 2018-04-23 02:00:25 +08:00
fatedier
814afbe1f6 Merge pull request #688 from miwee/dashboard_api_client_status
dashboard_api for getting a client status by name
2018-04-10 17:57:26 +08:00
miwee
3fde9176c9 dashboard_api for getting a client status by name 2018-04-04 12:07:20 +05:30
fatedier
af7cca1a93 Merge pull request #685 from toby1991/dev
fix https://github.com/fatedier/frp/issues/684
2018-04-04 11:32:48 +08:00
toby1991
7dd28a14aa fix https://github.com/fatedier/frp/issues/684
#684 Cannot build from Dockerfile
2018-04-04 11:06:47 +08:00
fatedier
1325c59a4c Merge pull request #672 from fatedier/dev
bump version to v0.16.1
2018-03-21 18:09:39 +08:00
fatedier
82dc1e924f vhost: typo fix 2018-03-21 18:06:43 +08:00
fatedier
3166bdf3f0 bump version to v0.16.1 2018-03-21 18:00:31 +08:00
fatedier
8af70c8822 update go version to go1.10 2018-03-21 11:52:11 +08:00
fatedier
87763e8251 Merge pull request #670 from fatedier/new
some fix
2018-03-21 11:45:48 +08:00
fatedier
e9241aeb94 udp proxy: fix #652 2018-03-19 20:22:15 +08:00
fatedier
2eaf134042 Merge pull request #646 from travisghansen/dev
build freebsd packages
2018-02-27 23:14:15 +08:00
Travis Glenn Hansen
1739e012d6 build freebsd packages 2018-02-26 21:00:55 -07:00
fatedier
9e8980429f typo 2018-02-07 11:39:30 +08:00
fatedier
1d0865ca49 statsConn: avoid repetition of close function 2018-02-01 11:15:35 +08:00
fatedier
5c9909aeef typo 2018-01-30 22:07:16 +08:00
fatedier
456ce09061 Merge pull request #630 from fatedier/dev
bump version to v0.16.0
2018-01-30 00:04:02 +08:00
fatedier
ffc13b704a update version 2018-01-30 00:00:05 +08:00
fatedier
5d239127bb Merge pull request #629 from fatedier/new
new feature
2018-01-29 23:58:55 +08:00
fatedier
9b990adf96 frpc: add proxy status 'wait start' 2018-01-29 23:51:46 +08:00
fatedier
44e8108910 ci: add test case for range ports mapping 2018-01-29 23:13:10 +08:00
fatedier
1c35e9a0c6 doc: update 2018-01-29 23:05:17 +08:00
fatedier
8e719ff0ff frps: new params max_ports_per_client 2018-01-26 14:56:55 +08:00
fatedier
637ddbce1f frpc: udpate proxies check and start logic 2018-01-26 00:23:48 +08:00
fatedier
ce8fde793c new feature: range section for mapping range ports 2018-01-25 23:05:07 +08:00
fatedier
eede31c064 doc: about static_file plugin 2018-01-24 23:27:03 +08:00
fatedier
41c41789b6 plugin: socks5 support user password auth, close #484 2018-01-24 23:06:38 +08:00
fatedier
68dfc89bce plugin: new plugin static_file for getting files by http protocol 2018-01-24 17:49:13 +08:00
fatedier
8690075c0c Merge pull request #616 from fatedier/dev
bump version to v0.15.1
2018-01-23 17:33:48 +08:00
fatedier
33d8816ced version: to v0.15.1 2018-01-23 17:28:00 +08:00
fatedier
90cd25ac21 Merge pull request #615 from fatedier/ws
fix websocket and plugin http_proxy
2018-01-23 17:25:32 +08:00
fatedier
ff28668cf2 ci: add plugin http_proxy test case 2018-01-23 17:11:59 +08:00
fatedier
a6f2736b80 fix plugin http_proxy error 2018-01-23 16:31:59 +08:00
fatedier
902f6f84a5 ci: add test for websocket 2018-01-23 14:49:04 +08:00
fatedier
cf9193a429 newhttp: support websocket 2018-01-23 01:29:52 +08:00
fatedier
3f64d73ea9 ci: add subdomain test case 2018-01-22 14:16:46 +08:00
fatedier
a77c7e8625 server: change MIN_PORT from 1024 to 1 2018-01-22 11:48:31 +08:00
fatedier
14733dd109 Merge pull request #608 from fatedier/dev
bump version to 0.15.0
2018-01-18 16:49:55 +08:00
fatedier
74b75e8c57 Merge pull request #607 from fatedier/doc
doc: update
2018-01-18 16:46:33 +08:00
fatedier
63e6e0dc92 doc: update 2018-01-18 16:43:03 +08:00
fatedier
4d4a738aa9 web: update assets 2018-01-18 15:26:30 +08:00
fatedier
1ed130e704 version: to v0.15.0 2018-01-18 15:08:16 +08:00
fatedier
2e773d550b Merge pull request #606 from fatedier/test
tests: more ci case
2018-01-18 15:04:03 +08:00
fatedier
e155ff056e tests: more ci case 2018-01-18 14:53:44 +08:00
fatedier
37210d9983 Merge branch 'dev' of github.com:fatedier/frp into dev 2018-01-18 00:46:21 +08:00
fatedier
338d5bae37 fix panic when using socks5 plugin with encryption and compression, fix #446 2018-01-18 00:45:11 +08:00
fatedier
3e62198612 Merge pull request #604 from fatedier/http
fix new http no traffic stats, fix #590
2018-01-17 23:28:48 +08:00
fatedier
4f7dfcdb31 fix new http no traffic stats, fix #590 2018-01-17 23:17:15 +08:00
fatedier
5b08201e5d Merge pull request #603 from fatedier/test
add test cases and new feature assgin a random port if remote_port is 0
2018-01-17 22:45:02 +08:00
fatedier
b2c846664d new feature: assign a random port if remote_port is 0 in type tcp and
udp
2018-01-17 22:18:34 +08:00
fatedier
3f6799c06a add remoteAddr in NewProxyResp message 2018-01-17 15:01:26 +08:00
fatedier
9a5f0c23c4 fix ci 2018-01-17 01:18:40 +08:00
fatedier
afde0c515c packages: add package github.com/rodaine/table 2018-01-17 01:15:34 +08:00
fatedier
584e098e8e frpc: add status command 2018-01-17 01:09:33 +08:00
fatedier
37395b3ef5 Merge pull request #596 from NemoAlex/patch-2
Use sans-serif font in web
2018-01-10 10:36:28 +08:00
NemoAlex
43fb3f3ff7 Use sans-serif font in web 2018-01-08 13:56:51 +08:00
fatedier
82b127494c Merge pull request #576 from gtt116/master
Close connection if frpc can't connection to local server
2017-12-26 14:51:02 +08:00
gtt116
4d79648657 Close connection if frpc can't connection to local server
Now, when frpc can't connect to local server it leaves the connection alone, the patch fix it.

Fixed #575
2017-12-26 14:39:07 +08:00
fatedier
3bb404dfb5 more test case 2017-12-18 19:35:09 +08:00
fatedier
ff4bdec3f7 add test case 2017-12-16 23:59:46 +08:00
fatedier
69f8b08ac0 update role error log info 2017-12-16 21:56:13 +08:00
fatedier
d873df5ca8 let role default value to 'server' 2017-12-15 11:40:08 +08:00
1416 changed files with 39401 additions and 307256 deletions

View File

@@ -2,8 +2,7 @@ sudo: false
language: go
go:
- 1.8.x
- 1.x
- 1.10.x
install:
- make

View File

@@ -1,4 +1,4 @@
FROM golang:1.8
FROM golang:1.10
COPY . /go/src/github.com/fatedier/frp

180
Gopkg.lock generated Normal file
View File

@@ -0,0 +1,180 @@
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
[[projects]]
name = "github.com/armon/go-socks5"
packages = ["."]
revision = "e75332964ef517daa070d7c38a9466a0d687e0a5"
[[projects]]
name = "github.com/davecgh/go-spew"
packages = ["spew"]
revision = "346938d642f2ec3594ed81d874461961cd0faa76"
version = "v1.1.0"
[[projects]]
name = "github.com/fatedier/beego"
packages = ["logs"]
revision = "6c6a4f5bd5eb5a39f7e289b8f345b55f75e7e3e8"
[[projects]]
branch = "master"
name = "github.com/fatedier/golib"
packages = [
"control/shutdown",
"crypto",
"errors",
"io",
"msg/json",
"net",
"net/mux",
"pool"
]
revision = "280fa74053dee5311c46094f4bdefbf76d3fcbe2"
[[projects]]
branch = "frp"
name = "github.com/fatedier/kcp-go"
packages = ["."]
revision = "cd167d2f15f451b0f33780ce862fca97adc0331e"
[[projects]]
name = "github.com/golang/snappy"
packages = ["."]
revision = "553a641470496b2327abcac10b36396bd98e45c9"
[[projects]]
name = "github.com/gorilla/context"
packages = ["."]
revision = "08b5f424b9271eedf6f9f0ce86cb9396ed337a42"
version = "v1.1.1"
[[projects]]
name = "github.com/gorilla/mux"
packages = ["."]
revision = "e3702bed27f0d39777b0b37b664b6280e8ef8fbf"
version = "v1.6.2"
[[projects]]
name = "github.com/gorilla/websocket"
packages = ["."]
revision = "ea4d1f681babbce9545c9c5f3d5194a789c89f5b"
version = "v1.2.0"
[[projects]]
name = "github.com/hashicorp/yamux"
packages = ["."]
revision = "2658be15c5f05e76244154714161f17e3e77de2e"
[[projects]]
name = "github.com/inconshreveable/mousetrap"
packages = ["."]
revision = "76626ae9c91c4f2a10f34cad8ce83ea42c93bb75"
version = "v1.0"
[[projects]]
name = "github.com/pkg/errors"
packages = ["."]
revision = "645ef00459ed84a119197bfb8d8205042c6df63d"
version = "v0.8.0"
[[projects]]
name = "github.com/pmezard/go-difflib"
packages = ["difflib"]
revision = "792786c7400a136282c1664665ae0a8db921c6c2"
version = "v1.0.0"
[[projects]]
name = "github.com/rakyll/statik"
packages = ["fs"]
revision = "fd36b3595eb2ec8da4b8153b107f7ea08504899d"
version = "v0.1.1"
[[projects]]
name = "github.com/rodaine/table"
packages = ["."]
revision = "212a2ad1c462ed4d5b5511ea2b480a573281dbbd"
version = "v1.0.0"
[[projects]]
name = "github.com/spf13/cobra"
packages = ["."]
revision = "ef82de70bb3f60c65fb8eebacbb2d122ef517385"
version = "v0.0.3"
[[projects]]
name = "github.com/spf13/pflag"
packages = ["."]
revision = "583c0c0531f06d5278b7d917446061adc344b5cd"
version = "v1.0.1"
[[projects]]
name = "github.com/stretchr/testify"
packages = ["assert"]
revision = "12b6f73e6084dad08a7c6e575284b177ecafbc71"
version = "v1.2.1"
[[projects]]
branch = "master"
name = "github.com/templexxx/cpufeat"
packages = ["."]
revision = "3794dfbfb04749f896b521032f69383f24c3687e"
[[projects]]
name = "github.com/templexxx/reedsolomon"
packages = ["."]
revision = "5e06b81a1c7628d9c8d4fb7c3c4e401e37db39b4"
version = "0.1.1"
[[projects]]
name = "github.com/templexxx/xor"
packages = ["."]
revision = "0af8e873c554da75f37f2049cdffda804533d44c"
version = "0.1.2"
[[projects]]
name = "github.com/tjfoc/gmsm"
packages = ["sm4"]
revision = "98aa888b79d8de04afe0fccf45ed10594efc858b"
version = "v1.1"
[[projects]]
name = "github.com/vaughan0/go-ini"
packages = ["."]
revision = "a98ad7ee00ec53921f08832bc06ecf7fd600e6a1"
[[projects]]
name = "golang.org/x/crypto"
packages = [
"blowfish",
"cast5",
"pbkdf2",
"salsa20",
"salsa20/salsa",
"tea",
"twofish",
"xtea"
]
revision = "4ec37c66abab2c7e02ae775328b2ff001c3f025a"
[[projects]]
branch = "master"
name = "golang.org/x/net"
packages = [
"bpf",
"context",
"internal/iana",
"internal/socket",
"internal/socks",
"ipv4",
"proxy",
"websocket"
]
revision = "dfa909b99c79129e1100513e5cd36307665e5723"
[solve-meta]
analyzer-name = "dep"
analyzer-version = 1
inputs-digest = "58a51c884de4111bfa3d2f82a7cfe1cb91e9e8786fafdfdc8d306bed24f23e44"
solver-name = "gps-cdcl"
solver-version = 1

78
Gopkg.toml Normal file
View File

@@ -0,0 +1,78 @@
# Gopkg.toml example
#
# Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md
# for detailed Gopkg.toml documentation.
#
# required = ["github.com/user/thing/cmd/thing"]
# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"]
#
# [[constraint]]
# name = "github.com/user/project"
# version = "1.0.0"
#
# [[constraint]]
# name = "github.com/user/project2"
# branch = "dev"
# source = "github.com/myfork/project2"
#
# [[override]]
# name = "github.com/x/y"
# version = "2.4.0"
#
# [prune]
# non-go = false
# go-tests = true
# unused-packages = true
[[constraint]]
name = "github.com/armon/go-socks5"
revision = "e75332964ef517daa070d7c38a9466a0d687e0a5"
[[constraint]]
name = "github.com/fatedier/beego"
revision = "6c6a4f5bd5eb5a39f7e289b8f345b55f75e7e3e8"
[[constraint]]
name = "github.com/fatedier/golib"
branch = "master"
[[constraint]]
branch = "frp"
name = "github.com/fatedier/kcp-go"
[[constraint]]
name = "github.com/gorilla/websocket"
version = "1.2.0"
[[constraint]]
name = "github.com/hashicorp/yamux"
revision = "2658be15c5f05e76244154714161f17e3e77de2e"
[[constraint]]
name = "github.com/gorilla/mux"
version = "1.6.2"
[[constraint]]
name = "github.com/rakyll/statik"
version = "0.1.0"
[[constraint]]
name = "github.com/rodaine/table"
version = "1.0.0"
[[constraint]]
name = "github.com/spf13/cobra"
version = "0.0.3"
[[constraint]]
name = "github.com/vaughan0/go-ini"
revision = "a98ad7ee00ec53921f08832bc06ecf7fd600e6a1"
[[override]]
name = "github.com/templexxx/reedsolomon"
version = "0.1.1"
[prune]
go-tests = true
unused-packages = true

View File

@@ -1,5 +1,4 @@
export PATH := $(GOPATH)/bin:$(PATH)
export GO15VENDOREXPERIMENT := 1
all: fmt build
@@ -15,12 +14,7 @@ file:
go generate ./assets/...
fmt:
go fmt ./assets/...
go fmt ./client/...
go fmt ./cmd/...
go fmt ./models/...
go fmt ./server/...
go fmt ./utils/...
go fmt ./...
frps:
go build -o bin/frps ./cmd/frps
@@ -39,15 +33,17 @@ gotest:
go test -v ./server/...
go test -v ./utils/...
alltest: gotest
ci:
cd ./tests && ./run_test.sh && cd -
go test -v ./tests/...
cd ./tests && ./clean_test.sh && cd -
cic:
cd ./tests && ./clean_test.sh && cd -
alltest: gotest ci
clean:
rm -f ./bin/frpc
rm -f ./bin/frps
cd ./tests && ./clean_test.sh && cd -
save:
godep save ./...

View File

@@ -1,5 +1,4 @@
export PATH := $(GOPATH)/bin:$(PATH)
export GO15VENDOREXPERIMENT := 1
LDFLAGS := -s -w
all: build
@@ -9,12 +8,18 @@ build: app
app:
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=freebsd GOARCH=386 go build -ldflags "$(LDFLAGS)" -o ./frpc_freebsd_386 ./cmd/frpc
env CGO_ENABLED=0 GOOS=freebsd GOARCH=386 go build -ldflags "$(LDFLAGS)" -o ./frps_freebsd_386 ./cmd/frps
env CGO_ENABLED=0 GOOS=freebsd GOARCH=amd64 go build -ldflags "$(LDFLAGS)" -o ./frpc_freebsd_amd64 ./cmd/frpc
env CGO_ENABLED=0 GOOS=freebsd GOARCH=amd64 go build -ldflags "$(LDFLAGS)" -o ./frps_freebsd_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=linux GOARCH=arm64 go build -ldflags "$(LDFLAGS)" -o ./frpc_linux_arm64 ./cmd/frpc
env CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -ldflags "$(LDFLAGS)" -o ./frps_linux_arm64 ./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
@@ -23,10 +28,10 @@ app:
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
env CGO_ENABLED=0 GOOS=linux GOARCH=mips GOMIPS=softfloat go build -ldflags "$(LDFLAGS)" -o ./frpc_linux_mips ./cmd/frpc
env CGO_ENABLED=0 GOOS=linux GOARCH=mips GOMIPS=softfloat go build -ldflags "$(LDFLAGS)" -o ./frps_linux_mips ./cmd/frps
env CGO_ENABLED=0 GOOS=linux GOARCH=mipsle GOMIPS=softfloat go build -ldflags "$(LDFLAGS)" -o ./frpc_linux_mipsle ./cmd/frpc
env CGO_ENABLED=0 GOOS=linux GOARCH=mipsle GOMIPS=softfloat 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

149
README.md
View File

@@ -20,26 +20,30 @@ frp is a fast reverse proxy to help you expose a local server behind a NAT or fi
* [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)
* [Expose a simple http file server](#expose-a-simple-http-file-server)
* [Expose your service in security](#expose-your-service-in-security)
* [P2P Mode](#p2p-mode)
* [Connect website through frpc's network](#connect-website-through-frpcs-network)
* [Features](#features)
* [Configuration File](#configuration-file)
* [Dashboard](#dashboard)
* [Authentication](#authentication)
* [Encryption and Compression](#encryption-and-compression)
* [Hot-Reload frpc configuration](#hot-reload-frpc-configuration)
* [Privilege Mode](#privilege-mode)
* [Port White List](#port-white-list)
* [Get proxy status from client](#get-proxy-status-from-client)
* [Port White List](#port-white-list)
* [Port Reuse](#port-reuse)
* [TCP Stream Multiplexing](#tcp-stream-multiplexing)
* [Support KCP Protocol](#support-kcp-protocol)
* [Connection Pool](#connection-pool)
* [Load balancing](#load-balancing)
* [Rewriting the Host Header](#rewriting-the-host-header)
* [Set Headers In HTTP Request](#set-headers-in-http-request)
* [Get Real IP](#get-real-ip)
* [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)
* [Range ports mapping](#range-ports-mapping)
* [Plugin](#plugin)
* [Development Plan](#development-plan)
* [Contributing](#contributing)
@@ -213,6 +217,32 @@ Configure frps same as above.
`curl http://x.x.x.x:6000/version`
### Expose a simple http file server
A simple way to visit files in the LAN.
Configure frps same as above.
1. Start frpc with configurations:
```ini
# frpc.ini
[common]
server_addr = x.x.x.x
server_port = 7000
[test_static_file]
type = tcp
remote_port = 6000
plugin = static_file
plugin_local_path = /tmp/file
plugin_strip_prefix = static
plugin_http_user = abc
plugin_http_passwd = abc
```
2. Visit `http://x.x.x.x:6000/static/` by your browser, set correct user and password, so you can see files in `/tmp/file`.
### Expose your service in security
For some services, if expose them to the public network directly will be a security risk.
@@ -305,26 +335,6 @@ Now it can't penetrate all types of NAT devices. You can try **stcp** if **xtcp*
`ssh -oPort=6000 test@127.0.0.1`
### Connect website through frpc's network
Configure frps same as above.
1. Start frpc with configurations:
```ini
# frpc.ini
[common]
server_addr = x.x.x.x
server_port = 7000
[http_proxy]
type = tcp
remote_port = 6000
plugin = http_proxy # or socks5
```
2. Set http proxy or socks5 proxy `x.x.x.x:6000` in your browser and visit website through frpc's network.
## Features
### Configuration File
@@ -355,7 +365,7 @@ Then visit `http://[server_addr]:7500` to see dashboard, default username and pa
### Authentication
Since v0.10.0, you only need to set `privilege_token` in frps.ini and frpc.ini.
Since v0.10.0, you only need to set `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.
@@ -386,25 +396,31 @@ admin_addr = 127.0.0.1
admin_port = 7400
```
Then run command `frpc -c ./frpc.ini --reload` and wait for about 10 seconds to let frpc create or update or delete proxies.
Then run command `frpc reload -c ./frpc.ini` and wait for about 10 seconds to let frpc create or update or delete proxies.
**Note that parameters in [common] section won't be modified except 'start' now.**
### Privilege Mode
### Get proxy status from client
Privilege mode is the default and only mode support in frp since v0.10.0. All proxy configurations are set in client.
Use `frpc status -c ./frpc.ini` to get status of all proxies. You need to set admin port in frpc's configure file.
#### Port White List
### Port White List
`privilege_allow_ports` in frps.ini is used for preventing abuse of ports:
`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
allow_ports = 2000-3000,3001,3003,4000-50000
```
`privilege_allow_ports` consists of a specific port or a range of ports divided by `,`.
`allow_ports` consists of a specific port or a range of ports divided by `,`.
### Port Reuse
Now `vhost_http_port` and `vhost_https_port` in frps can use same port with `bind_port`. frps will detect connection's protocol and handle it correspondingly.
We would like to try to allow multiple proxies bind a same remote port with different protocols in the future.
### TCP Stream Multiplexing
@@ -469,9 +485,35 @@ This feature is fit for a large number of short connections.
pool_count = 1
```
### Load balancing
Load balancing is supported by `group`.
This feature is available only for type `tcp` now.
```ini
# frpc.ini
[test1]
type = tcp
local_port = 8080
remote_port = 80
group = web
group_key = 123
[test2]
type = tcp
local_port = 8081
remote_port = 80
group = web
group_key = 123
```
`group_key` is used for authentication.
Proxies in same group will accept connections from port 80 randomly.
### 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.
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
@@ -482,7 +524,24 @@ 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.
If `host_header_rewrite` is specified, the host header will be rewritten to match the hostname portion of the forwarding address.
### Set Headers In HTTP Request
You can set headers for proxy which type is `http`.
```ini
# frpc.ini
[web]
type = http
local_port = 80
custom_domains = test.yourdomain.com
host_header_rewrite = dev.yourdomain.com
header_X-From-Where = frp
```
Note that params which have prefix `header_` will be added to http request headers.
In this example, it will set header `X-From-Where: frp` to http request.
### Get Real IP
@@ -507,7 +566,7 @@ type = http
local_port = 80
custom_domains = test.yourdomain.com
http_user = abc
http_pwd = abc
http_passwd = abc
```
Visit `http://test.yourdomain.com` and now you need to input username and password.
@@ -571,11 +630,26 @@ server_port = 7000
http_proxy = http://user:pwd@192.168.1.128:8080
```
### Range ports mapping
Proxy name has prefix `range:` will support mapping range ports.
```ini
# frpc.ini
[range:test_tcp]
type = tcp
local_ip = 127.0.0.1
local_port = 6000-6006,6007
remote_port = 6000-6006,6007
```
frpc will generate 8 proxies like `test_tcp_0, test_tcp_1 ... test_tcp_7`.
### 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**, **socks5** and you can see [example usage](#example-usage).
Plugin is used for providing rich features. There are built-in plugins such as `unix_domain_socket`, `http_proxy`, `socks5`, `static_file` 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.
@@ -593,17 +667,12 @@ 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 making udp hole to penetrate NAT.
* kubernetes ingress support.
## Contributing
Interested in getting involved? We would like to help you!

View File

@@ -18,26 +18,30 @@ frp 是一个可用于内网穿透的高性能的反向代理应用,支持 tcp
* [通过自定义域名访问部署于内网的 web 服务](#通过自定义域名访问部署于内网的-web-服务)
* [转发 DNS 查询请求](#转发-dns-查询请求)
* [转发 Unix域套接字](#转发-unix域套接字)
* [对外提供简单的文件访问服务](#对外提供简单的文件访问服务)
* [安全地暴露内网服务](#安全地暴露内网服务)
* [点对点内网穿透](#点对点内网穿透)
* [通过 frpc 所在机器访问外网](#通过-frpc-所在机器访问外网)
* [功能说明](#功能说明)
* [配置文件](#配置文件)
* [Dashboard](#dashboard)
* [身份验证](#身份验证)
* [加密与压缩](#加密与压缩)
* [客户端热加载配置文件](#客户端热加载配置文件)
* [特权模式](#特权模式)
* [端口白名单](#端口白名单)
* [客户端查看代理状态](#客户端查看代理状态)
* [端口白名单](#端口白名单)
* [端口复用](#端口复用)
* [TCP 多路复用](#tcp-多路复用)
* [底层通信可选 kcp 协议](#底层通信可选-kcp-协议)
* [连接池](#连接池)
* [负载均衡](#负载均衡)
* [修改 Host Header](#修改-host-header)
* [设置 HTTP 请求的 header](#设置-http-请求的-header)
* [获取用户真实 IP](#获取用户真实-ip)
* [通过密码保护你的 web 服务](#通过密码保护你的-web-服务)
* [自定义二级域名](#自定义二级域名)
* [URL 路由](#url-路由)
* [通过代理连接 frps](#通过代理连接-frps)
* [范围端口映射](#范围端口映射)
* [插件](#插件)
* [开发计划](#开发计划)
* [为 frp 做贡献](#为-frp-做贡献)
@@ -191,11 +195,11 @@ DNS 查询请求通常使用 UDP 协议frp 支持对内网 UDP 服务的穿
### 转发 Unix域套接字
通过 tcp 端口访问内网的 unix域套接字(和 docker daemon 通信)。
通过 tcp 端口访问内网的 unix域套接字(例如和 docker daemon 通信)。
frps 的部署步骤同上。
1. 启动 frpc启用 unix_domain_socket 插件,配置如下:
1. 启动 frpc启用 `unix_domain_socket` 插件,配置如下:
```ini
# frpc.ini
@@ -214,6 +218,34 @@ frps 的部署步骤同上。
`curl http://x.x.x.x:6000/version`
### 对外提供简单的文件访问服务
通过 `static_file` 插件可以对外提供一个简单的基于 HTTP 的文件访问服务。
frps 的部署步骤同上。
1. 启动 frpc启用 `static_file` 插件,配置如下:
```ini
# frpc.ini
[common]
server_addr = x.x.x.x
server_port = 7000
[test_static_file]
type = tcp
remote_port = 6000
plugin = static_file
# 要对外暴露的文件目录
plugin_local_path = /tmp/file
# 访问 url 中会被去除的前缀,保留的内容即为要访问的文件路径
plugin_strip_prefix = static
plugin_http_user = abc
plugin_http_passwd = abc
```
2. 通过浏览器访问 `http://x.x.x.x:6000/static/` 来查看位于 `/tmp/file` 目录下的文件,会要求输入已设置好的用户名和密码。
### 安全地暴露内网服务
对于某些服务来说如果直接暴露于公网上将会存在安全隐患。
@@ -318,28 +350,6 @@ frp 提供了一种新的代理类型 **xtcp** 用于应对在希望传输大量
`ssh -oPort=6000 test@127.0.0.1`
### 通过 frpc 所在机器访问外网
frpc 内置了 http proxy 和 socks5 插件,可以使其他机器通过 frpc 的网络访问互联网。
frps 的部署步骤同上。
1. 启动 frpc启用 http_proxy 或 socks5 插件(plugin 换为 socks5 即可) 配置如下:
```ini
# frpc.ini
[common]
server_addr = x.x.x.x
server_port = 7000
[http_proxy]
type = tcp
remote_port = 6000
plugin = http_proxy
```
2. 浏览器设置 http 或 socks5 代理地址为 `x.x.x.x:6000`,通过 frpc 机器的网络访问互联网。
## 功能说明
### 配置文件
@@ -370,7 +380,7 @@ dashboard_pwd = admin
### 身份验证
从 v0.10.0 版本开始,所有 proxy 配置全部放在客户端(也就是之前版本的特权模式),服务端和客户端的 common 配置中的 `privilege_token` 参数一致则身份验证通过。
从 v0.10.0 版本开始,所有 proxy 配置全部放在客户端(也就是之前版本的特权模式),服务端和客户端的 common 配置中的 `token` 参数一致则身份验证通过。
需要注意的是 frpc 所在机器和 frps 所在机器的时间相差不能超过 15 分钟,因为时间戳会被用于加密验证中,防止报文被劫持后被其他人利用。
@@ -396,7 +406,7 @@ use_compression = true
### 客户端热加载配置文件
当修改了 frpc 中的代理配置,可以通过 `frpc --reload` 命令来动态加载配置文件,通常会在 10 秒内完成代理的更新。
当修改了 frpc 中的代理配置,可以通过 `frpc reload` 命令来动态加载配置文件,通常会在 10 秒内完成代理的更新。
启用此功能需要在 frpc 中启用 admin 端口,用于提供 API 服务。配置如下:
@@ -409,27 +419,35 @@ admin_port = 7400
之后执行重启命令:
`frpc -c ./frpc.ini --reload`
`frpc reload -c ./frpc.ini`
等待一段时间后客户端会根据新的配置文件创建、更新、删除代理。
**需要注意的是,[common] 中的参数除了 start 外目前无法被修改。**
### 特权模式
### 客户端查看代理状态
由于从 v0.10.0 版本开始,所有 proxy 都在客户端配置,原先的特权模式是目前唯一支持的模式
frpc 支持通过 `frpc status -c ./frpc.ini` 命令查看代理的状态信息,此功能需要在 frpc 中配置 admin 端口
#### 端口白名单
### 端口白名单
为了防止端口被滥用,可以手动指定允许哪些端口被使用,在 frps.ini 中通过 privilege_allow_ports 来指定:
为了防止端口被滥用,可以手动指定允许哪些端口被使用,在 frps.ini 中通过 `allow_ports` 来指定:
```ini
# frps.ini
[common]
privilege_allow_ports = 2000-3000,3001,3003,4000-50000
allow_ports = 2000-3000,3001,3003,4000-50000
```
privilege_allow_ports 可以配置允许使用的某个指定端口或者是一个范围内的所有端口,以 `,` 分隔,指定的范围以 `-` 分隔。
`allow_ports` 可以配置允许使用的某个指定端口或者是一个范围内的所有端口,以 `,` 分隔,指定的范围以 `-` 分隔。
### 端口复用
目前 frps 中的 `vhost_http_port` 和 `vhost_https_port` 支持配置成和 `bind_port` 为同一个端口frps 会对连接的协议进行分析,之后进行不同的处理。
例如在某些限制较严格的网络环境中,可以将 `bind_port` 和 `vhost_https_port` 都设置为 443。
后续会尝试允许多个 proxy 绑定同一个远端端口的不同协议。
### TCP 多路复用
@@ -494,6 +512,32 @@ tcp_mux = false
pool_count = 1
```
### 负载均衡
可以将多个相同类型的 proxy 加入到同一个 group 中,从而实现负载均衡的功能。
目前只支持 tcp 类型的 proxy。
```ini
# fprc.ini
[test1]
type = tcp
local_port = 8080
remote_port = 80
group = web
group_key = 123
[test2]
type = tcp
local_port = 8081
remote_port = 80
group = web
group_key = 123
```
用户连接 frps 服务器的 80 端口frps 会将接收到的用户连接随机分发给其中一个存活的 proxy。这样可以在一台 frpc 机器挂掉后仍然有其他节点能够提供服务。
要求 `group_key` 相同,做权限验证,且 `remote_port` 相同。
### 修改 Host Header
通常情况下 frp 不会修改转发的任何数据。但有一些后端服务会根据 http 请求 header 中的 host 字段来展现不同的网站,例如 nginx 的虚拟主机服务,启用 host-header 的修改功能可以动态修改 http 请求中的 host 字段。该功能仅限于 http 类型的代理。
@@ -509,6 +553,22 @@ host_header_rewrite = dev.yourdomain.com
原来 http 请求中的 host 字段 `test.yourdomain.com` 转发到后端服务时会被替换为 `dev.yourdomain.com`。
### 设置 HTTP 请求的 header
对于 `type = http` 的代理,可以设置在转发中动态添加的 header 参数。
```ini
# frpc.ini
[web]
type = http
local_port = 80
custom_domains = test.yourdomain.com
host_header_rewrite = dev.yourdomain.com
header_X-From-Where = frp
```
对于参数配置中所有以 `header_` 开头的参数(支持同时配置多个),都会被添加到 http 请求的 header 中,根据如上的配置,会在请求的 header 中加上 `X-From-Where: frp`。
### 获取用户真实 IP
目前只有 **http** 类型的代理支持这一功能,可以通过用户请求的 header 中的 `X-Forwarded-For` 和 `X-Real-IP` 来获取用户真实 IP。
@@ -604,11 +664,30 @@ server_port = 7000
http_proxy = http://user:pwd@192.168.1.128:8080
```
### 范围端口映射
在 frpc 的配置文件中可以指定映射多个端口,目前只支持 tcp 和 udp 的类型。
这一功能通过 `range:` 段落标记来实现,客户端会解析这个标记中的配置,将其拆分成多个 proxy每一个 proxy 以数字为后缀命名。
例如要映射本地 6000-6005, 6007 这6个端口主要配置如下
```ini
# frpc.ini
[range:test_tcp]
type = tcp
local_ip = 127.0.0.1
local_port = 6000-6006,6007
remote_port = 6000-6006,6007
```
实际连接成功后会创建 8 个 proxy命名为 `test_tcp_0, test_tcp_1 ... test_tcp_7`。
### 插件
默认情况下frpc 只会转发请求到本地 tcp 或 udp 端口。
插件模式是为了在客户端提供更加丰富的功能,目前内置的插件有 **unix_domain_socket**、**http_proxy**、**socks5**。具体使用方式请查看[使用示例](#使用示例)。
插件模式是为了在客户端提供更加丰富的功能,目前内置的插件有 `unix_domain_socket`、`http_proxy`、`socks5`、`static_file`。具体使用方式请查看[使用示例](#使用示例)。
通过 `plugin` 指定需要使用的插件,插件的配置参数都以 `plugin_` 开头。使用插件后 `local_ip` 和 `local_port` 不再需要配置。
@@ -632,9 +711,6 @@ plugin_http_passwd = abc
* frps 记录 http 请求日志。
* frps 支持直接反向代理,类似 haproxy。
* frpc 支持负载均衡到后端不同服务。
* frpc 支持直接作为 webserver 访问指定静态页面。
* 支持 udp 打洞的方式,提供两边内网机器直接通信,流量不经过服务器转发。
* 集成对 k8s 等平台的支持。
## 为 frp 做贡献

Binary file not shown.

View File

@@ -1 +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>
<!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?bc42bc4eff72df8da372"></script><script type="text/javascript" src="vendor.js?ee403fce53c8757fc931"></script></body> </html>

File diff suppressed because one or more lines are too long

View File

@@ -1 +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}}([]);
!function(e){function n(r){if(t[r])return t[r].exports;var o=t[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}var r=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(r&&r(t,c,u);s.length;)s.shift()();if(u)for(l=0;l<u.length;l++)f=n(n.s=u[l]);return f};var t={},o={1:0};n.e=function(e){function r(){i.onerror=i.onload=null,clearTimeout(a);var n=o[e];0!==n&&(n&&n[1](new Error("Loading chunk "+e+" failed.")),o[e]=void 0)}var t=o[e];if(0===t)return new Promise(function(e){e()});if(t)return t[2];var c=new Promise(function(n,r){t=o[e]=[n,r]});t[2]=c;var u=document.getElementsByTagName("head")[0],i=document.createElement("script");i.type="text/javascript",i.charset="utf-8",i.async=!0,i.timeout=12e4,n.nc&&i.setAttribute("nonce",n.nc),i.src=n.p+""+e+".js?"+{0:"ee403fce53c8757fc931"}[e];var a=setTimeout(r,12e4);return i.onerror=i.onload=r,u.appendChild(i),c},n.m=e,n.c=t,n.i=function(e){return e},n.d=function(e,r,t){n.o(e,r)||Object.defineProperty(e,r,{configurable:!1,enumerable:!0,get:t})},n.n=function(e){var r=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(r,"a",r),r},n.o=function(e,n){return Object.prototype.hasOwnProperty.call(e,n)},n.p="",n.oe=function(e){throw console.error(e),e}}([]);

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -20,10 +20,10 @@ import (
"net/http"
"time"
"github.com/fatedier/frp/models/config"
"github.com/fatedier/frp/g"
frpNet "github.com/fatedier/frp/utils/net"
"github.com/julienschmidt/httprouter"
"github.com/gorilla/mux"
)
var (
@@ -31,14 +31,16 @@ var (
httpServerWriteTimeout = 10 * time.Second
)
func (svr *Service) RunAdminServer(addr string, port int64) (err error) {
func (svr *Service) RunAdminServer(addr string, port int) (err error) {
// url router
router := httprouter.New()
router := mux.NewRouter()
user, passwd := config.ClientCommonCfg.AdminUser, config.ClientCommonCfg.AdminPwd
user, passwd := g.GlbClientCfg.AdminUser, g.GlbClientCfg.AdminPwd
router.Use(frpNet.NewHttpAuthMiddleware(user, passwd).Middleware)
// api, see dashboard_api.go
router.GET("/api/reload", frpNet.HttprouterBasicAuth(svr.apiReload, user, passwd))
router.HandleFunc("/api/reload", svr.apiReload).Methods("GET")
router.HandleFunc("/api/status", svr.apiStatus).Methods("GET")
address := fmt.Sprintf("%s:%d", addr, port)
server := &http.Server{

View File

@@ -16,11 +16,15 @@ package client
import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"sort"
"strings"
"github.com/julienschmidt/httprouter"
ini "github.com/vaughan0/go-ini"
"github.com/fatedier/frp/g"
"github.com/fatedier/frp/models/config"
"github.com/fatedier/frp/utils/log"
)
@@ -35,7 +39,7 @@ type ReloadResp struct {
GeneralResponse
}
func (svr *Service) apiReload(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
func (svr *Service) apiReload(w http.ResponseWriter, r *http.Request) {
var (
buf []byte
res ReloadResp
@@ -48,15 +52,16 @@ func (svr *Service) apiReload(w http.ResponseWriter, r *http.Request, _ httprout
log.Info("Http request: [/api/reload]")
conf, err := ini.LoadFile(config.ClientCommonCfg.ConfigFile)
b, err := ioutil.ReadFile(g.GlbClientCfg.CfgFile)
if err != nil {
res.Code = 1
res.Msg = err.Error()
log.Error("reload frpc config file error: %v", err)
return
}
content := string(b)
newCommonCfg, err := config.LoadClientCommonConf(conf)
newCommonCfg, err := config.UnmarshalClientConfFromIni(nil, content)
if err != nil {
res.Code = 2
res.Msg = err.Error()
@@ -64,7 +69,15 @@ func (svr *Service) apiReload(w http.ResponseWriter, r *http.Request, _ httprout
return
}
pxyCfgs, visitorCfgs, err := config.LoadProxyConfFromFile(config.ClientCommonCfg.User, conf, newCommonCfg.Start)
conf, err := ini.LoadFile(g.GlbClientCfg.CfgFile)
if err != nil {
res.Code = 1
res.Msg = err.Error()
log.Error("reload frpc config file error: %v", err)
return
}
pxyCfgs, visitorCfgs, err := config.LoadProxyConfFromIni(g.GlbClientCfg.User, conf, newCommonCfg.Start)
if err != nil {
res.Code = 3
res.Msg = err.Error()
@@ -72,7 +85,137 @@ func (svr *Service) apiReload(w http.ResponseWriter, r *http.Request, _ httprout
return
}
svr.ctl.reloadConf(pxyCfgs, visitorCfgs)
err = svr.ctl.reloadConf(pxyCfgs, visitorCfgs)
if err != nil {
res.Code = 4
res.Msg = err.Error()
log.Error("reload frpc proxy config error: %v", err)
return
}
log.Info("success reload conf")
return
}
type StatusResp struct {
Tcp []ProxyStatusResp `json:"tcp"`
Udp []ProxyStatusResp `json:"udp"`
Http []ProxyStatusResp `json:"http"`
Https []ProxyStatusResp `json:"https"`
Stcp []ProxyStatusResp `json:"stcp"`
Xtcp []ProxyStatusResp `json:"xtcp"`
}
type ProxyStatusResp struct {
Name string `json:"name"`
Type string `json:"type"`
Status string `json:"status"`
Err string `json:"err"`
LocalAddr string `json:"local_addr"`
Plugin string `json:"plugin"`
RemoteAddr string `json:"remote_addr"`
}
type ByProxyStatusResp []ProxyStatusResp
func (a ByProxyStatusResp) Len() int { return len(a) }
func (a ByProxyStatusResp) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func (a ByProxyStatusResp) Less(i, j int) bool { return strings.Compare(a[i].Name, a[j].Name) < 0 }
func NewProxyStatusResp(status *ProxyStatus) ProxyStatusResp {
psr := ProxyStatusResp{
Name: status.Name,
Type: status.Type,
Status: status.Status,
Err: status.Err,
}
switch cfg := status.Cfg.(type) {
case *config.TcpProxyConf:
if cfg.LocalPort != 0 {
psr.LocalAddr = fmt.Sprintf("%s:%d", cfg.LocalIp, cfg.LocalPort)
}
psr.Plugin = cfg.Plugin
if status.Err != "" {
psr.RemoteAddr = fmt.Sprintf("%s:%d", g.GlbClientCfg.ServerAddr, cfg.RemotePort)
} else {
psr.RemoteAddr = g.GlbClientCfg.ServerAddr + status.RemoteAddr
}
case *config.UdpProxyConf:
if cfg.LocalPort != 0 {
psr.LocalAddr = fmt.Sprintf("%s:%d", cfg.LocalIp, cfg.LocalPort)
}
if status.Err != "" {
psr.RemoteAddr = fmt.Sprintf("%s:%d", g.GlbClientCfg.ServerAddr, cfg.RemotePort)
} else {
psr.RemoteAddr = g.GlbClientCfg.ServerAddr + status.RemoteAddr
}
case *config.HttpProxyConf:
if cfg.LocalPort != 0 {
psr.LocalAddr = fmt.Sprintf("%s:%d", cfg.LocalIp, cfg.LocalPort)
}
psr.Plugin = cfg.Plugin
psr.RemoteAddr = status.RemoteAddr
case *config.HttpsProxyConf:
if cfg.LocalPort != 0 {
psr.LocalAddr = fmt.Sprintf("%s:%d", cfg.LocalIp, cfg.LocalPort)
}
psr.Plugin = cfg.Plugin
psr.RemoteAddr = status.RemoteAddr
case *config.StcpProxyConf:
if cfg.LocalPort != 0 {
psr.LocalAddr = fmt.Sprintf("%s:%d", cfg.LocalIp, cfg.LocalPort)
}
psr.Plugin = cfg.Plugin
case *config.XtcpProxyConf:
if cfg.LocalPort != 0 {
psr.LocalAddr = fmt.Sprintf("%s:%d", cfg.LocalIp, cfg.LocalPort)
}
psr.Plugin = cfg.Plugin
}
return psr
}
// api/status
func (svr *Service) apiStatus(w http.ResponseWriter, r *http.Request) {
var (
buf []byte
res StatusResp
)
res.Tcp = make([]ProxyStatusResp, 0)
res.Udp = make([]ProxyStatusResp, 0)
res.Http = make([]ProxyStatusResp, 0)
res.Https = make([]ProxyStatusResp, 0)
res.Stcp = make([]ProxyStatusResp, 0)
res.Xtcp = make([]ProxyStatusResp, 0)
defer func() {
log.Info("Http response [/api/status]")
buf, _ = json.Marshal(&res)
w.Write(buf)
}()
log.Info("Http request: [/api/status]")
ps := svr.ctl.pm.GetAllProxyStatus()
for _, status := range ps {
switch status.Type {
case "tcp":
res.Tcp = append(res.Tcp, NewProxyStatusResp(status))
case "udp":
res.Udp = append(res.Udp, NewProxyStatusResp(status))
case "http":
res.Http = append(res.Http, NewProxyStatusResp(status))
case "https":
res.Https = append(res.Https, NewProxyStatusResp(status))
case "stcp":
res.Stcp = append(res.Stcp, NewProxyStatusResp(status))
case "xtcp":
res.Xtcp = append(res.Xtcp, NewProxyStatusResp(status))
}
}
sort.Sort(ByProxyStatusResp(res.Tcp))
sort.Sort(ByProxyStatusResp(res.Udp))
sort.Sort(ByProxyStatusResp(res.Http))
sort.Sort(ByProxyStatusResp(res.Https))
sort.Sort(ByProxyStatusResp(res.Stcp))
sort.Sort(ByProxyStatusResp(res.Xtcp))
return
}

View File

@@ -17,19 +17,23 @@ package client
import (
"fmt"
"io"
"io/ioutil"
"runtime"
"runtime/debug"
"sync"
"time"
"github.com/fatedier/frp/g"
"github.com/fatedier/frp/models/config"
"github.com/fatedier/frp/models/msg"
"github.com/fatedier/frp/utils/crypto"
"github.com/fatedier/frp/utils/errors"
"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/xtaci/smux"
"github.com/fatedier/golib/control/shutdown"
"github.com/fatedier/golib/crypto"
fmux "github.com/hashicorp/yamux"
)
const (
@@ -40,26 +44,16 @@ type Control struct {
// frpc service
svr *Service
// login message to server
// login message to server, only used
loginMsg *msg.Login
// proxy configures
pxyCfgs map[string]config.ProxyConf
// proxies
proxies map[string]Proxy
// visitor configures
visitorCfgs map[string]config.ProxyConf
// visitors
visitors map[string]Visitor
pm *ProxyManager
// control connection
conn frpNet.Conn
// tcp stream multiplexing, if enabled
session *smux.Session
session *fmux.Session
// put a message in this channel to send it over control connection to server
sendCh chan (msg.Message)
@@ -79,6 +73,10 @@ type Control struct {
// last time got the Pong message
lastPong time.Time
readerShutdown *shutdown.Shutdown
writerShutdown *shutdown.Shutdown
msgHandlerShutdown *shutdown.Shutdown
mu sync.RWMutex
log.Logger
@@ -88,32 +86,26 @@ func NewControl(svr *Service, pxyCfgs map[string]config.ProxyConf, visitorCfgs m
loginMsg := &msg.Login{
Arch: runtime.GOARCH,
Os: runtime.GOOS,
PoolCount: config.ClientCommonCfg.PoolCount,
User: config.ClientCommonCfg.User,
PoolCount: g.GlbClientCfg.PoolCount,
User: g.GlbClientCfg.User,
Version: version.Full(),
}
return &Control{
svr: svr,
loginMsg: loginMsg,
pxyCfgs: pxyCfgs,
visitorCfgs: visitorCfgs,
proxies: make(map[string]Proxy),
visitors: make(map[string]Visitor),
sendCh: make(chan msg.Message, 10),
readCh: make(chan msg.Message, 10),
closedCh: make(chan int),
Logger: log.NewPrefixLogger(""),
ctl := &Control{
svr: svr,
loginMsg: loginMsg,
sendCh: make(chan msg.Message, 100),
readCh: make(chan msg.Message, 100),
closedCh: make(chan int),
readerShutdown: shutdown.New(),
writerShutdown: shutdown.New(),
msgHandlerShutdown: shutdown.New(),
Logger: log.NewPrefixLogger(""),
}
ctl.pm = NewProxyManager(ctl, ctl.sendCh, "")
ctl.pm.Reload(pxyCfgs, visitorCfgs, false)
return ctl
}
// 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() (err error) {
for {
err = ctl.login()
@@ -122,50 +114,32 @@ func (ctl *Control) Run() (err error) {
// if login_fail_exit is true, just exit this program
// otherwise sleep a while and continues relogin to server
if config.ClientCommonCfg.LoginFailExit {
if g.GlbClientCfg.LoginFailExit {
return
} else {
time.Sleep(30 * time.Second)
time.Sleep(10 * time.Second)
}
} else {
break
}
}
go ctl.controler()
go ctl.manager()
go ctl.writer()
go ctl.reader()
go ctl.worker()
// start all local visitors
for _, cfg := range ctl.visitorCfgs {
visitor := NewVisitor(ctl, cfg)
err = visitor.Run()
if err != nil {
visitor.Warn("start error: %v", err)
continue
}
ctl.visitors[cfg.GetName()] = visitor
visitor.Info("start visitor success")
}
// send NewProxy message for all configured proxies
for _, cfg := range ctl.pxyCfgs {
var newProxyMsg msg.NewProxy
cfg.UnMarshalToMsg(&newProxyMsg)
ctl.sendCh <- &newProxyMsg
}
// start all local visitors and send NewProxy message for all configured proxies
ctl.pm.Reset(ctl.sendCh, ctl.runId)
ctl.pm.CheckAndStartProxy([]string{ProxyStatusNew})
return nil
}
func (ctl *Control) NewWorkConn() {
func (ctl *Control) HandleReqWorkConn(inMsg *msg.ReqWorkConn) {
workConn, err := ctl.connectServer()
if err != nil {
return
}
m := &msg.NewWorkConn{
RunId: ctl.getRunId(),
RunId: ctl.runId,
}
if err = msg.WriteMsg(workConn, m); err != nil {
ctl.Warn("work connection write to server error: %v", err)
@@ -182,33 +156,26 @@ func (ctl *Control) NewWorkConn() {
workConn.AddLogPrefix(startMsg.ProxyName)
// dispatch this work connection to related proxy
pxy, ok := ctl.getProxy(startMsg.ProxyName)
if ok {
workConn.Debug("start a new work connection, localAddr: %s remoteAddr: %s", workConn.LocalAddr().String(), workConn.RemoteAddr().String())
go pxy.InWorkConn(workConn)
ctl.pm.HandleWorkConn(startMsg.ProxyName, workConn)
}
func (ctl *Control) HandleNewProxyResp(inMsg *msg.NewProxyResp) {
// Server will return NewProxyResp message to each NewProxy message.
// Start a new proxy handler if no error got
err := ctl.pm.StartProxy(inMsg.ProxyName, inMsg.RemoteAddr, inMsg.Error)
if err != nil {
ctl.Warn("[%s] start error: %v", inMsg.ProxyName, err)
} else {
workConn.Close()
ctl.Info("[%s] start proxy success", inMsg.ProxyName)
}
}
func (ctl *Control) Close() error {
ctl.mu.Lock()
defer ctl.mu.Unlock()
ctl.exit = true
err := errors.PanicToError(func() {
for name, _ := range ctl.proxies {
ctl.sendCh <- &msg.CloseProxy{
ProxyName: name,
}
}
})
ctl.mu.Unlock()
return err
}
func (ctl *Control) init() {
ctl.sendCh = make(chan msg.Message, 10)
ctl.readCh = make(chan msg.Message, 10)
ctl.closedCh = make(chan int)
ctl.pm.CloseProxies()
return nil
}
// login send a login message to server and wait for a loginResp message.
@@ -220,8 +187,8 @@ func (ctl *Control) login() (err error) {
ctl.session.Close()
}
conn, err := frpNet.ConnectServerByHttpProxy(config.ClientCommonCfg.HttpProxy, config.ClientCommonCfg.Protocol,
fmt.Sprintf("%s:%d", config.ClientCommonCfg.ServerAddr, config.ClientCommonCfg.ServerPort))
conn, err := frpNet.ConnectServerByProxy(g.GlbClientCfg.HttpProxy, g.GlbClientCfg.Protocol,
fmt.Sprintf("%s:%d", g.GlbClientCfg.ServerAddr, g.GlbClientCfg.ServerPort))
if err != nil {
return err
}
@@ -232,8 +199,10 @@ func (ctl *Control) login() (err error) {
}
}()
if config.ClientCommonCfg.TcpMux {
session, errRet := smux.Client(conn, nil)
if g.GlbClientCfg.TcpMux {
fmuxCfg := fmux.DefaultConfig()
fmuxCfg.LogOutput = ioutil.Discard
session, errRet := fmux.Client(conn, fmuxCfg)
if errRet != nil {
return errRet
}
@@ -247,9 +216,9 @@ func (ctl *Control) login() (err error) {
}
now := time.Now().Unix()
ctl.loginMsg.PrivilegeKey = util.GetAuthKey(config.ClientCommonCfg.PrivilegeToken, now)
ctl.loginMsg.PrivilegeKey = util.GetAuthKey(g.GlbClientCfg.Token, now)
ctl.loginMsg.Timestamp = now
ctl.loginMsg.RunId = ctl.getRunId()
ctl.loginMsg.RunId = ctl.runId
if err = msg.WriteMsg(conn, ctl.loginMsg); err != nil {
return err
@@ -270,21 +239,16 @@ func (ctl *Control) login() (err error) {
ctl.conn = conn
// update runId got from server
ctl.setRunId(loginRespMsg.RunId)
config.ClientCommonCfg.ServerUdpPort = loginRespMsg.ServerUdpPort
ctl.runId = loginRespMsg.RunId
g.GlbClientCfg.ServerUdpPort = loginRespMsg.ServerUdpPort
ctl.ClearLogPrefix()
ctl.AddLogPrefix(loginRespMsg.RunId)
ctl.Info("login to server success, get run id [%s], server udp port [%d]", loginRespMsg.RunId, loginRespMsg.ServerUdpPort)
// login success, so we let closedCh available again
ctl.closedCh = make(chan int)
ctl.lastPong = time.Now()
return nil
}
func (ctl *Control) connectServer() (conn frpNet.Conn, err error) {
if config.ClientCommonCfg.TcpMux {
if g.GlbClientCfg.TcpMux {
stream, errRet := ctl.session.OpenStream()
if errRet != nil {
err = errRet
@@ -292,10 +256,9 @@ func (ctl *Control) connectServer() (conn frpNet.Conn, err error) {
return
}
conn = frpNet.WrapConn(stream)
} else {
conn, err = frpNet.ConnectServerByHttpProxy(config.ClientCommonCfg.HttpProxy, config.ClientCommonCfg.Protocol,
fmt.Sprintf("%s:%d", config.ClientCommonCfg.ServerAddr, config.ClientCommonCfg.ServerPort))
conn, err = frpNet.ConnectServerByProxy(g.GlbClientCfg.HttpProxy, g.GlbClientCfg.Protocol,
fmt.Sprintf("%s:%d", g.GlbClientCfg.ServerAddr, g.GlbClientCfg.ServerPort))
if err != nil {
ctl.Warn("start new connection to server error: %v", err)
return
@@ -304,15 +267,18 @@ func (ctl *Control) connectServer() (conn frpNet.Conn, err error) {
return
}
// reader read all messages from frps and send to readCh
func (ctl *Control) reader() {
defer func() {
if err := recover(); err != nil {
ctl.Error("panic error: %v", err)
ctl.Error(string(debug.Stack()))
}
}()
defer ctl.readerShutdown.Done()
defer close(ctl.closedCh)
encReader := crypto.NewReader(ctl.conn, []byte(config.ClientCommonCfg.PrivilegeToken))
encReader := crypto.NewReader(ctl.conn, []byte(g.GlbClientCfg.Token))
for {
if m, err := msg.ReadMsg(encReader); err != nil {
if err == io.EOF {
@@ -328,8 +294,10 @@ func (ctl *Control) reader() {
}
}
// writer writes messages got from sendCh to frps
func (ctl *Control) writer() {
encWriter, err := crypto.NewWriter(ctl.conn, []byte(config.ClientCommonCfg.PrivilegeToken))
defer ctl.writerShutdown.Done()
encWriter, err := crypto.NewWriter(ctl.conn, []byte(g.GlbClientCfg.Token))
if err != nil {
ctl.conn.Error("crypto new writer error: %v", err)
ctl.conn.Close()
@@ -348,19 +316,23 @@ func (ctl *Control) writer() {
}
}
// manager handles all channel events and do corresponding process
func (ctl *Control) manager() {
// msgHandler handles all channel events and do corresponding operations.
func (ctl *Control) msgHandler() {
defer func() {
if err := recover(); err != nil {
ctl.Error("panic error: %v", err)
ctl.Error(string(debug.Stack()))
}
}()
defer ctl.msgHandlerShutdown.Done()
hbSend := time.NewTicker(time.Duration(config.ClientCommonCfg.HeartBeatInterval) * time.Second)
hbSend := time.NewTicker(time.Duration(g.GlbClientCfg.HeartBeatInterval) * time.Second)
defer hbSend.Stop()
hbCheck := time.NewTicker(time.Second)
defer hbCheck.Stop()
ctl.lastPong = time.Now()
for {
select {
case <-hbSend.C:
@@ -368,7 +340,7 @@ func (ctl *Control) manager() {
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 {
if time.Since(ctl.lastPong) > time.Duration(g.GlbClientCfg.HeartBeatTimeout)*time.Second {
ctl.Warn("heartbeat timeout")
// let reader() stop
ctl.conn.Close()
@@ -381,35 +353,9 @@ func (ctl *Control) manager() {
switch m := rawMsg.(type) {
case *msg.ReqWorkConn:
go ctl.NewWorkConn()
go ctl.HandleReqWorkConn(m)
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.getProxyConf(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.getProxy(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.addProxy(m.ProxyName, pxy)
ctl.Info("[%s] start proxy success", m.ProxyName)
ctl.HandleNewProxyResp(m)
case *msg.Pong:
ctl.lastPong = time.Now()
ctl.Debug("receive heartbeat from server")
@@ -419,52 +365,35 @@ func (ctl *Control) manager() {
}
// controler keep watching closedCh, start a new connection if previous control connection is closed.
// If controler is notified by closedCh, reader and writer and manager will exit, then recall these functions.
func (ctl *Control) controler() {
// If controler is notified by closedCh, reader and writer and handler will exit, then recall these functions.
func (ctl *Control) worker() {
go ctl.msgHandler()
go ctl.reader()
go ctl.writer()
var err error
maxDelayTime := 30 * time.Second
maxDelayTime := 20 * time.Second
delayTime := time.Second
checkInterval := 10 * time.Second
checkInterval := 60 * time.Second
checkProxyTicker := time.NewTicker(checkInterval)
for {
select {
case <-checkProxyTicker.C:
// Every 10 seconds, check which proxy registered failed and reregister it to server.
ctl.mu.RLock()
for _, cfg := range ctl.pxyCfgs {
if _, exist := ctl.proxies[cfg.GetName()]; !exist {
ctl.Info("try to register proxy [%s]", cfg.GetName())
var newProxyMsg msg.NewProxy
cfg.UnMarshalToMsg(&newProxyMsg)
ctl.sendCh <- &newProxyMsg
}
}
for _, cfg := range ctl.visitorCfgs {
if _, exist := ctl.visitors[cfg.GetName()]; !exist {
ctl.Info("try to start visitor [%s]", cfg.GetName())
visitor := NewVisitor(ctl, cfg)
err = visitor.Run()
if err != nil {
visitor.Warn("start error: %v", err)
continue
}
ctl.visitors[cfg.GetName()] = visitor
visitor.Info("start visitor success")
}
}
ctl.mu.RUnlock()
// check which proxy registered failed and reregister it to server
ctl.pm.CheckAndStartProxy([]string{ProxyStatusStartErr, ProxyStatusClosed})
case _, ok := <-ctl.closedCh:
// we won't get any variable from this channel
if !ok {
// close related channels
// close related channels and wait until other goroutines done
close(ctl.readCh)
close(ctl.sendCh)
ctl.readerShutdown.WaitDone()
ctl.msgHandlerShutdown.WaitDone()
for _, pxy := range ctl.proxies {
pxy.Close()
}
close(ctl.sendCh)
ctl.writerShutdown.WaitDone()
ctl.pm.CloseProxies()
// if ctl.exit is true, just exit
ctl.mu.RLock()
exit := ctl.exit
@@ -473,9 +402,7 @@ func (ctl *Control) controler() {
return
}
time.Sleep(time.Second)
// loop util reconnect to server success
// loop util reconnecting to server success
for {
ctl.Info("try to reconnect to server...")
err = ctl.login()
@@ -488,27 +415,27 @@ func (ctl *Control) controler() {
}
continue
}
// reconnect success, init the delayTime
// reconnect success, init delayTime
delayTime = time.Second
break
}
// init related channels and variables
ctl.init()
ctl.sendCh = make(chan msg.Message, 100)
ctl.readCh = make(chan msg.Message, 100)
ctl.closedCh = make(chan int)
ctl.readerShutdown = shutdown.New()
ctl.writerShutdown = shutdown.New()
ctl.msgHandlerShutdown = shutdown.New()
ctl.pm.Reset(ctl.sendCh, ctl.runId)
// previous work goroutines should be closed and start them here
go ctl.manager()
go ctl.msgHandler()
go ctl.writer()
go ctl.reader()
// send NewProxy message for all configured proxies
ctl.mu.RLock()
for _, cfg := range ctl.pxyCfgs {
var newProxyMsg msg.NewProxy
cfg.UnMarshalToMsg(&newProxyMsg)
ctl.sendCh <- &newProxyMsg
}
ctl.mu.RUnlock()
// start all configured proxies
ctl.pm.CheckAndStartProxy([]string{ProxyStatusNew, ProxyStatusClosed})
checkProxyTicker.Stop()
checkProxyTicker = time.NewTicker(checkInterval)
@@ -517,106 +444,7 @@ func (ctl *Control) controler() {
}
}
func (ctl *Control) setRunId(runId string) {
ctl.mu.Lock()
defer ctl.mu.Unlock()
ctl.runId = runId
}
func (ctl *Control) getRunId() string {
ctl.mu.RLock()
defer ctl.mu.RUnlock()
return ctl.runId
}
func (ctl *Control) getProxy(name string) (pxy Proxy, ok bool) {
ctl.mu.RLock()
defer ctl.mu.RUnlock()
pxy, ok = ctl.proxies[name]
return
}
func (ctl *Control) addProxy(name string, pxy Proxy) {
ctl.mu.Lock()
defer ctl.mu.Unlock()
ctl.proxies[name] = pxy
}
func (ctl *Control) getProxyConf(name string) (conf config.ProxyConf, ok bool) {
ctl.mu.RLock()
defer ctl.mu.RUnlock()
conf, ok = ctl.pxyCfgs[name]
return
}
func (ctl *Control) reloadConf(pxyCfgs map[string]config.ProxyConf, visitorCfgs map[string]config.ProxyConf) {
ctl.mu.Lock()
defer ctl.mu.Unlock()
removedPxyNames := make([]string, 0)
for name, oldCfg := range ctl.pxyCfgs {
del := false
cfg, ok := pxyCfgs[name]
if !ok {
del = true
} else {
if !oldCfg.Compare(cfg) {
del = true
}
}
if del {
removedPxyNames = append(removedPxyNames, name)
delete(ctl.pxyCfgs, name)
if pxy, ok := ctl.proxies[name]; ok {
pxy.Close()
}
delete(ctl.proxies, name)
ctl.sendCh <- &msg.CloseProxy{
ProxyName: name,
}
}
}
ctl.Info("proxy removed: %v", removedPxyNames)
addedPxyNames := make([]string, 0)
for name, cfg := range pxyCfgs {
if _, ok := ctl.pxyCfgs[name]; !ok {
ctl.pxyCfgs[name] = cfg
addedPxyNames = append(addedPxyNames, name)
}
}
ctl.Info("proxy added: %v", addedPxyNames)
removedVisitorName := make([]string, 0)
for name, oldVisitorCfg := range ctl.visitorCfgs {
del := false
cfg, ok := visitorCfgs[name]
if !ok {
del = true
} else {
if !oldVisitorCfg.Compare(cfg) {
del = true
}
}
if del {
removedVisitorName = append(removedVisitorName, name)
delete(ctl.visitorCfgs, name)
if visitor, ok := ctl.visitors[name]; ok {
visitor.Close()
}
delete(ctl.visitors, name)
}
}
ctl.Info("visitor removed: %v", removedVisitorName)
addedVisitorName := make([]string, 0)
for name, visitorCfg := range visitorCfgs {
if _, ok := ctl.visitorCfgs[name]; !ok {
ctl.visitorCfgs[name] = visitorCfg
addedVisitorName = append(addedVisitorName, name)
}
}
ctl.Info("visitor added: %v", addedVisitorName)
func (ctl *Control) reloadConf(pxyCfgs map[string]config.ProxyConf, visitorCfgs map[string]config.ProxyConf) error {
err := ctl.pm.Reload(pxyCfgs, visitorCfgs, true)
return err
}

View File

@@ -22,15 +22,17 @@ import (
"sync"
"time"
"github.com/fatedier/frp/g"
"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"
"github.com/fatedier/frp/utils/pool"
"github.com/fatedier/golib/errors"
frpIo "github.com/fatedier/golib/io"
"github.com/fatedier/golib/pool"
)
// Proxy defines how to deal with work connections for different proxy type.
@@ -39,14 +41,14 @@ type Proxy interface {
// InWorkConn accept work connections registered to server.
InWorkConn(conn frpNet.Conn)
Close()
log.Logger
}
func NewProxy(ctl *Control, pxyConf config.ProxyConf) (pxy Proxy) {
func NewProxy(pxyConf config.ProxyConf) (pxy Proxy) {
baseProxy := BaseProxy{
ctl: ctl,
Logger: log.NewPrefixLogger(pxyConf.GetName()),
Logger: log.NewPrefixLogger(pxyConf.GetBaseInfo().ProxyName),
}
switch cfg := pxyConf.(type) {
case *config.TcpProxyConf:
@@ -84,7 +86,6 @@ func NewProxy(ctl *Control, pxyConf config.ProxyConf) (pxy Proxy) {
}
type BaseProxy struct {
ctl *Control
closed bool
mu sync.RWMutex
log.Logger
@@ -116,7 +117,7 @@ func (pxy *TcpProxy) Close() {
func (pxy *TcpProxy) InWorkConn(conn frpNet.Conn) {
HandleTcpWorkConnection(&pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, conn,
[]byte(config.ClientCommonCfg.PrivilegeToken))
[]byte(g.GlbClientCfg.Token))
}
// HTTP
@@ -145,7 +146,7 @@ func (pxy *HttpProxy) Close() {
func (pxy *HttpProxy) InWorkConn(conn frpNet.Conn) {
HandleTcpWorkConnection(&pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, conn,
[]byte(config.ClientCommonCfg.PrivilegeToken))
[]byte(g.GlbClientCfg.Token))
}
// HTTPS
@@ -174,7 +175,7 @@ func (pxy *HttpsProxy) Close() {
func (pxy *HttpsProxy) InWorkConn(conn frpNet.Conn) {
HandleTcpWorkConnection(&pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, conn,
[]byte(config.ClientCommonCfg.PrivilegeToken))
[]byte(g.GlbClientCfg.Token))
}
// STCP
@@ -203,7 +204,7 @@ func (pxy *StcpProxy) Close() {
func (pxy *StcpProxy) InWorkConn(conn frpNet.Conn) {
HandleTcpWorkConnection(&pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, conn,
[]byte(config.ClientCommonCfg.PrivilegeToken))
[]byte(g.GlbClientCfg.Token))
}
// XTCP
@@ -244,7 +245,7 @@ func (pxy *XtcpProxy) InWorkConn(conn frpNet.Conn) {
Sid: natHoleSidMsg.Sid,
}
raddr, _ := net.ResolveUDPAddr("udp",
fmt.Sprintf("%s:%d", config.ClientCommonCfg.ServerAddr, config.ClientCommonCfg.ServerUdpPort))
fmt.Sprintf("%s:%d", g.GlbClientCfg.ServerAddr, g.GlbClientCfg.ServerUdpPort))
clientConn, err := net.DialUDP("udp", nil, raddr)
defer clientConn.Close()
@@ -271,6 +272,12 @@ func (pxy *XtcpProxy) InWorkConn(conn frpNet.Conn) {
}
clientConn.SetReadDeadline(time.Time{})
clientConn.Close()
if natHoleRespMsg.Error != "" {
pxy.Error("natHoleRespMsg get error info: %s", natHoleRespMsg.Error)
return
}
pxy.Trace("get natHoleRespMsg, sid [%s], client address [%s]", natHoleRespMsg.Sid, natHoleRespMsg.ClientAddr)
// Send sid to visitor udp address.
@@ -413,9 +420,11 @@ func HandleTcpWorkConnection(localInfo *config.LocalSvrConf, proxyPlugin plugin.
err error
)
remote = workConn
if baseInfo.UseEncryption {
remote, err = frpIo.WithEncryption(remote, encKey)
if err != nil {
workConn.Close()
workConn.Error("create encryption stream error: %v", err)
return
}
@@ -427,12 +436,13 @@ func HandleTcpWorkConnection(localInfo *config.LocalSvrConf, proxyPlugin plugin.
if proxyPlugin != nil {
// if plugin is set, let plugin handle connections first
workConn.Debug("handle by plugin: %s", proxyPlugin.Name())
proxyPlugin.Handle(remote)
proxyPlugin.Handle(remote, workConn)
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.Close()
workConn.Error("connect to local service [%s:%d] error: %v", localInfo.LocalIp, localInfo.LocalPort, err)
return
}

365
client/proxy_manager.go Normal file
View File

@@ -0,0 +1,365 @@
package client
import (
"fmt"
"sync"
"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/golib/errors"
)
const (
ProxyStatusNew = "new"
ProxyStatusStartErr = "start error"
ProxyStatusWaitStart = "wait start"
ProxyStatusRunning = "running"
ProxyStatusClosed = "closed"
)
type ProxyManager struct {
ctl *Control
proxies map[string]*ProxyWrapper
visitorCfgs map[string]config.ProxyConf
visitors map[string]Visitor
sendCh chan (msg.Message)
closed bool
mu sync.RWMutex
log.Logger
}
type ProxyWrapper struct {
Name string
Type string
Status string
Err string
Cfg config.ProxyConf
RemoteAddr string
pxy Proxy
mu sync.RWMutex
}
type ProxyStatus struct {
Name string `json:"name"`
Type string `json:"type"`
Status string `json:"status"`
Err string `json:"err"`
Cfg config.ProxyConf `json:"cfg"`
// Got from server.
RemoteAddr string `json:"remote_addr"`
}
func NewProxyWrapper(cfg config.ProxyConf) *ProxyWrapper {
return &ProxyWrapper{
Name: cfg.GetBaseInfo().ProxyName,
Type: cfg.GetBaseInfo().ProxyType,
Status: ProxyStatusNew,
Cfg: cfg,
pxy: nil,
}
}
func (pw *ProxyWrapper) GetStatusStr() string {
pw.mu.RLock()
defer pw.mu.RUnlock()
return pw.Status
}
func (pw *ProxyWrapper) GetStatus() *ProxyStatus {
pw.mu.RLock()
defer pw.mu.RUnlock()
ps := &ProxyStatus{
Name: pw.Name,
Type: pw.Type,
Status: pw.Status,
Err: pw.Err,
Cfg: pw.Cfg,
RemoteAddr: pw.RemoteAddr,
}
return ps
}
func (pw *ProxyWrapper) WaitStart() {
pw.mu.Lock()
defer pw.mu.Unlock()
pw.Status = ProxyStatusWaitStart
}
func (pw *ProxyWrapper) Start(remoteAddr string, serverRespErr string) error {
if pw.pxy != nil {
pw.pxy.Close()
pw.pxy = nil
}
if serverRespErr != "" {
pw.mu.Lock()
pw.Status = ProxyStatusStartErr
pw.RemoteAddr = remoteAddr
pw.Err = serverRespErr
pw.mu.Unlock()
return fmt.Errorf(serverRespErr)
}
pxy := NewProxy(pw.Cfg)
pw.mu.Lock()
defer pw.mu.Unlock()
pw.RemoteAddr = remoteAddr
if err := pxy.Run(); err != nil {
pw.Status = ProxyStatusStartErr
pw.Err = err.Error()
return err
}
pw.Status = ProxyStatusRunning
pw.Err = ""
pw.pxy = pxy
return nil
}
func (pw *ProxyWrapper) InWorkConn(workConn frpNet.Conn) {
pw.mu.RLock()
pxy := pw.pxy
pw.mu.RUnlock()
if pxy != nil {
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 (pw *ProxyWrapper) Close() {
pw.mu.Lock()
defer pw.mu.Unlock()
if pw.pxy != nil {
pw.pxy.Close()
pw.pxy = nil
}
pw.Status = ProxyStatusClosed
}
func NewProxyManager(ctl *Control, msgSendCh chan (msg.Message), logPrefix string) *ProxyManager {
return &ProxyManager{
ctl: ctl,
proxies: make(map[string]*ProxyWrapper),
visitorCfgs: make(map[string]config.ProxyConf),
visitors: make(map[string]Visitor),
sendCh: msgSendCh,
closed: false,
Logger: log.NewPrefixLogger(logPrefix),
}
}
func (pm *ProxyManager) Reset(msgSendCh chan (msg.Message), logPrefix string) {
pm.mu.Lock()
defer pm.mu.Unlock()
pm.closed = false
pm.sendCh = msgSendCh
pm.ClearLogPrefix()
pm.AddLogPrefix(logPrefix)
}
// Must hold the lock before calling this function.
func (pm *ProxyManager) sendMsg(m msg.Message) error {
err := errors.PanicToError(func() {
pm.sendCh <- m
})
if err != nil {
pm.closed = true
}
return err
}
func (pm *ProxyManager) StartProxy(name string, remoteAddr string, serverRespErr string) error {
pm.mu.Lock()
defer pm.mu.Unlock()
if pm.closed {
return fmt.Errorf("ProxyManager is closed now")
}
pxy, ok := pm.proxies[name]
if !ok {
return fmt.Errorf("no proxy found")
}
if err := pxy.Start(remoteAddr, serverRespErr); err != nil {
errRet := err
err = pm.sendMsg(&msg.CloseProxy{
ProxyName: name,
})
if err != nil {
errRet = fmt.Errorf("send CloseProxy message error")
}
return errRet
}
return nil
}
func (pm *ProxyManager) CloseProxies() {
pm.mu.RLock()
defer pm.mu.RUnlock()
for _, pxy := range pm.proxies {
pxy.Close()
}
}
// pxyStatus: check and start proxies in which status
func (pm *ProxyManager) CheckAndStartProxy(pxyStatus []string) {
pm.mu.RLock()
defer pm.mu.RUnlock()
if pm.closed {
pm.Warn("CheckAndStartProxy error: ProxyManager is closed now")
return
}
for _, pxy := range pm.proxies {
status := pxy.GetStatusStr()
for _, s := range pxyStatus {
if status == s {
var newProxyMsg msg.NewProxy
pxy.Cfg.MarshalToMsg(&newProxyMsg)
err := pm.sendMsg(&newProxyMsg)
if err != nil {
pm.Warn("[%s] proxy send NewProxy message error")
return
}
pxy.WaitStart()
break
}
}
}
for _, cfg := range pm.visitorCfgs {
name := cfg.GetBaseInfo().ProxyName
if _, exist := pm.visitors[name]; !exist {
pm.Info("try to start visitor [%s]", name)
visitor := NewVisitor(pm.ctl, cfg)
err := visitor.Run()
if err != nil {
visitor.Warn("start error: %v", err)
continue
}
pm.visitors[name] = visitor
visitor.Info("start visitor success")
}
}
}
func (pm *ProxyManager) Reload(pxyCfgs map[string]config.ProxyConf, visitorCfgs map[string]config.ProxyConf, startNow bool) error {
pm.mu.Lock()
defer func() {
pm.mu.Unlock()
if startNow {
go pm.CheckAndStartProxy([]string{ProxyStatusNew})
}
}()
if pm.closed {
err := fmt.Errorf("Reload error: ProxyManager is closed now")
pm.Warn(err.Error())
return err
}
delPxyNames := make([]string, 0)
for name, pxy := range pm.proxies {
del := false
cfg, ok := pxyCfgs[name]
if !ok {
del = true
} else {
if !pxy.Cfg.Compare(cfg) {
del = true
}
}
if del {
delPxyNames = append(delPxyNames, name)
delete(pm.proxies, name)
pxy.Close()
err := pm.sendMsg(&msg.CloseProxy{
ProxyName: name,
})
if err != nil {
err = fmt.Errorf("Reload error: ProxyManager is closed now")
pm.Warn(err.Error())
return err
}
}
}
pm.Info("proxy removed: %v", delPxyNames)
addPxyNames := make([]string, 0)
for name, cfg := range pxyCfgs {
if _, ok := pm.proxies[name]; !ok {
pxy := NewProxyWrapper(cfg)
pm.proxies[name] = pxy
addPxyNames = append(addPxyNames, name)
}
}
pm.Info("proxy added: %v", addPxyNames)
delVisitorName := make([]string, 0)
for name, oldVisitorCfg := range pm.visitorCfgs {
del := false
cfg, ok := visitorCfgs[name]
if !ok {
del = true
} else {
if !oldVisitorCfg.Compare(cfg) {
del = true
}
}
if del {
delVisitorName = append(delVisitorName, name)
delete(pm.visitorCfgs, name)
if visitor, ok := pm.visitors[name]; ok {
visitor.Close()
}
delete(pm.visitors, name)
}
}
pm.Info("visitor removed: %v", delVisitorName)
addVisitorName := make([]string, 0)
for name, visitorCfg := range visitorCfgs {
if _, ok := pm.visitorCfgs[name]; !ok {
pm.visitorCfgs[name] = visitorCfg
addVisitorName = append(addVisitorName, name)
}
}
pm.Info("visitor added: %v", addVisitorName)
return nil
}
func (pm *ProxyManager) HandleWorkConn(name string, workConn frpNet.Conn) {
pm.mu.RLock()
pw, ok := pm.proxies[name]
pm.mu.RUnlock()
if ok {
pw.InWorkConn(workConn)
} else {
workConn.Close()
}
}
func (pm *ProxyManager) GetAllProxyStatus() []*ProxyStatus {
ps := make([]*ProxyStatus, 0)
pm.mu.RLock()
defer pm.mu.RUnlock()
for _, pxy := range pm.proxies {
ps = append(ps, pxy.GetStatus())
}
return ps
}

View File

@@ -15,6 +15,7 @@
package client
import (
"github.com/fatedier/frp/g"
"github.com/fatedier/frp/models/config"
"github.com/fatedier/frp/utils/log"
)
@@ -41,18 +42,18 @@ func (svr *Service) Run() error {
return err
}
if config.ClientCommonCfg.AdminPort != 0 {
err = svr.RunAdminServer(config.ClientCommonCfg.AdminAddr, config.ClientCommonCfg.AdminPort)
if g.GlbClientCfg.AdminPort != 0 {
err = svr.RunAdminServer(g.GlbClientCfg.AdminAddr, g.GlbClientCfg.AdminPort)
if err != nil {
log.Warn("run admin server error: %v", err)
}
log.Info("admin server listen on %s:%d", config.ClientCommonCfg.AdminAddr, config.ClientCommonCfg.AdminPort)
log.Info("admin server listen on %s:%d", g.GlbClientCfg.AdminAddr, g.GlbClientCfg.AdminPort)
}
<-svr.closedCh
return nil
}
func (svr *Service) Close() error {
return svr.ctl.Close()
func (svr *Service) Close() {
svr.ctl.Close()
}

View File

@@ -26,13 +26,15 @@ import (
"golang.org/x/net/ipv4"
"github.com/fatedier/frp/g"
"github.com/fatedier/frp/models/config"
"github.com/fatedier/frp/models/msg"
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/pool"
"github.com/fatedier/frp/utils/util"
frpIo "github.com/fatedier/golib/io"
"github.com/fatedier/golib/pool"
)
// Visitor is used for forward traffics from local port tot remote service.
@@ -45,7 +47,7 @@ type Visitor interface {
func NewVisitor(ctl *Control, pxyConf config.ProxyConf) (visitor Visitor) {
baseVisitor := BaseVisitor{
ctl: ctl,
Logger: log.NewPrefixLogger(pxyConf.GetName()),
Logger: log.NewPrefixLogger(pxyConf.GetBaseInfo().ProxyName),
}
switch cfg := pxyConf.(type) {
case *config.StcpProxyConf:
@@ -77,7 +79,7 @@ type StcpVisitor struct {
}
func (sv *StcpVisitor) Run() (err error) {
sv.l, err = frpNet.ListenTcp(sv.cfg.BindAddr, int64(sv.cfg.BindPort))
sv.l, err = frpNet.ListenTcp(sv.cfg.BindAddr, sv.cfg.BindPort)
if err != nil {
return
}
@@ -164,7 +166,7 @@ type XtcpVisitor struct {
}
func (sv *XtcpVisitor) Run() (err error) {
sv.l, err = frpNet.ListenTcp(sv.cfg.BindAddr, int64(sv.cfg.BindPort))
sv.l, err = frpNet.ListenTcp(sv.cfg.BindAddr, sv.cfg.BindPort)
if err != nil {
return
}
@@ -181,7 +183,7 @@ func (sv *XtcpVisitor) worker() {
for {
conn, err := sv.l.Accept()
if err != nil {
sv.Warn("stcp local listener closed")
sv.Warn("xtcp local listener closed")
return
}
@@ -193,13 +195,13 @@ func (sv *XtcpVisitor) handleConn(userConn frpNet.Conn) {
defer userConn.Close()
sv.Debug("get a new xtcp user connection")
if config.ClientCommonCfg.ServerUdpPort == 0 {
if g.GlbClientCfg.ServerUdpPort == 0 {
sv.Error("xtcp is not supported by server")
return
}
raddr, err := net.ResolveUDPAddr("udp",
fmt.Sprintf("%s:%d", config.ClientCommonCfg.ServerAddr, config.ClientCommonCfg.ServerUdpPort))
fmt.Sprintf("%s:%d", g.GlbClientCfg.ServerAddr, g.GlbClientCfg.ServerUdpPort))
visitorConn, err := net.DialUDP("udp", nil, raddr)
defer visitorConn.Close()
@@ -233,6 +235,11 @@ func (sv *XtcpVisitor) handleConn(userConn frpNet.Conn) {
visitorConn.SetReadDeadline(time.Time{})
pool.PutBuf(buf)
if natHoleRespMsg.Error != "" {
sv.Error("natHoleRespMsg get error info: %s", natHoleRespMsg.Error)
return
}
sv.Trace("get natHoleRespMsg, sid [%s], client address [%s]", natHoleRespMsg.Sid, natHoleRespMsg.ClientAddr)
// Close visitorConn, so we can use it's local address.
@@ -255,7 +262,7 @@ func (sv *XtcpVisitor) handleConn(userConn frpNet.Conn) {
sv.Error("get natHoleResp client address error: %s", natHoleRespMsg.ClientAddr)
return
}
sv.sendDetectMsg(array[0], int64(port), laddr, []byte(natHoleRespMsg.Sid))
sv.sendDetectMsg(array[0], int(port), laddr, []byte(natHoleRespMsg.Sid))
sv.Trace("send all detect msg done")
// Listen for visitorConn's address and wait for client connection.
@@ -302,7 +309,7 @@ func (sv *XtcpVisitor) handleConn(userConn frpNet.Conn) {
sv.Debug("join connections closed")
}
func (sv *XtcpVisitor) sendDetectMsg(addr string, port int64, laddr *net.UDPAddr, content []byte) (err error) {
func (sv *XtcpVisitor) sendDetectMsg(addr string, port int, laddr *net.UDPAddr, content []byte) (err error) {
daddr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", addr, port))
if err != nil {
return err

View File

@@ -15,175 +15,13 @@
package main
import (
"encoding/base64"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"os"
"os/signal"
"strconv"
"strings"
"syscall"
"time"
"github.com/fatedier/frp/cmd/frpc/sub"
docopt "github.com/docopt/docopt-go"
ini "github.com/vaughan0/go-ini"
"github.com/fatedier/frp/client"
"github.com/fatedier/frp/models/config"
"github.com/fatedier/frp/utils/log"
"github.com/fatedier/frp/utils/version"
"github.com/fatedier/golib/crypto"
)
var (
configFile string = "./frpc.ini"
)
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 [-c config_file] --reload
frpc -h | --help
frpc -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
--server-addr=<server_addr> addr which frps is listening for, example: 0.0.0.0:7000
--reload reload configure file without program exit
-h --help show this screen
-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)
crypto.DefaultSalt = "frp"
if args["-c"] != nil {
confFile = args["-c"].(string)
}
conf, err := ini.LoadFile(confFile)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
config.ClientCommonCfg, err = config.LoadClientCommonConf(conf)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
config.ClientCommonCfg.ConfigFile = confFile
// check if reload command
if args["--reload"] != nil {
if args["--reload"].(bool) {
req, err := http.NewRequest("GET", "http://"+
config.ClientCommonCfg.AdminAddr+":"+fmt.Sprintf("%d", config.ClientCommonCfg.AdminPort)+"/api/reload", nil)
if err != nil {
fmt.Printf("frps reload error: %v\n", err)
os.Exit(1)
}
authStr := "Basic " + base64.StdEncoding.EncodeToString([]byte(config.ClientCommonCfg.AdminUser+":"+
config.ClientCommonCfg.AdminPwd))
req.Header.Add("Authorization", authStr)
resp, err := http.DefaultClient.Do(req)
if err != nil {
fmt.Printf("frpc reload error: %v\n", err)
os.Exit(1)
} else {
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
fmt.Printf("frpc reload error: %v\n", err)
os.Exit(1)
}
res := &client.GeneralResponse{}
err = json.Unmarshal(body, &res)
if err != nil {
fmt.Printf("http response error: %s\n", strings.TrimSpace(string(body)))
os.Exit(1)
} else if res.Code != 0 {
fmt.Printf("reload error: %s\n", res.Msg)
os.Exit(1)
}
fmt.Printf("reload success\n")
os.Exit(0)
}
}
}
if args["-L"] != nil {
if args["-L"].(string) == "console" {
config.ClientCommonCfg.LogWay = "console"
} else {
config.ClientCommonCfg.LogWay = "file"
config.ClientCommonCfg.LogFile = args["-L"].(string)
}
}
if args["--log-level"] != nil {
config.ClientCommonCfg.LogLevel = args["--log-level"].(string)
}
if args["--server-addr"] != nil {
addr := strings.Split(args["--server-addr"].(string), ":")
if len(addr) != 2 {
fmt.Println("--server-addr format error: example 0.0.0.0:7000")
os.Exit(1)
}
serverPort, err := strconv.ParseInt(addr[1], 10, 64)
if err != nil {
fmt.Println("--server-addr format error, example 0.0.0.0:7000")
os.Exit(1)
}
config.ClientCommonCfg.ServerAddr = addr[0]
config.ClientCommonCfg.ServerPort = serverPort
}
if args["-v"] != nil {
if args["-v"].(bool) {
fmt.Println(version.Full())
os.Exit(0)
}
}
pxyCfgs, visitorCfgs, err := config.LoadProxyConfFromFile(config.ClientCommonCfg.User, conf, config.ClientCommonCfg.Start)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
log.InitLog(config.ClientCommonCfg.LogWay, config.ClientCommonCfg.LogFile,
config.ClientCommonCfg.LogLevel, config.ClientCommonCfg.LogMaxDays)
svr := client.NewService(pxyCfgs, visitorCfgs)
// Capture the exit signal if we use kcp.
if config.ClientCommonCfg.Protocol == "kcp" {
go HandleSignal(svr)
}
err = svr.Run()
if err != nil {
fmt.Println(err)
os.Exit(1)
}
}
func HandleSignal(svr *client.Service) {
ch := make(chan os.Signal)
signal.Notify(ch, syscall.SIGINT, syscall.SIGTERM)
<-ch
svr.Close()
time.Sleep(250 * time.Millisecond)
os.Exit(0)
sub.Execute()
}

96
cmd/frpc/sub/http.go Normal file
View File

@@ -0,0 +1,96 @@
// Copyright 2018 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 sub
import (
"fmt"
"os"
"strings"
"github.com/spf13/cobra"
"github.com/fatedier/frp/models/config"
"github.com/fatedier/frp/models/consts"
)
func init() {
httpCmd.PersistentFlags().StringVarP(&serverAddr, "server_addr", "s", "127.0.0.1:7000", "frp server's address")
httpCmd.PersistentFlags().StringVarP(&user, "user", "u", "", "user")
httpCmd.PersistentFlags().StringVarP(&protocol, "protocol", "p", "tcp", "tcp or kcp")
httpCmd.PersistentFlags().StringVarP(&token, "token", "t", "", "auth token")
httpCmd.PersistentFlags().StringVarP(&logLevel, "log_level", "", "info", "log level")
httpCmd.PersistentFlags().StringVarP(&logFile, "log_file", "", "console", "console or file path")
httpCmd.PersistentFlags().IntVarP(&logMaxDays, "log_max_days", "", 3, "log file reversed days")
httpCmd.PersistentFlags().StringVarP(&proxyName, "proxy_name", "n", "", "proxy name")
httpCmd.PersistentFlags().StringVarP(&localIp, "local_ip", "i", "127.0.0.1", "local ip")
httpCmd.PersistentFlags().IntVarP(&localPort, "local_port", "l", 0, "local port")
httpCmd.PersistentFlags().StringVarP(&customDomains, "custom_domain", "d", "", "custom domain")
httpCmd.PersistentFlags().StringVarP(&subDomain, "sd", "", "", "sub domain")
httpCmd.PersistentFlags().StringVarP(&locations, "locations", "", "", "locations")
httpCmd.PersistentFlags().StringVarP(&httpUser, "http_user", "", "", "http auth user")
httpCmd.PersistentFlags().StringVarP(&httpPwd, "http_pwd", "", "", "http auth password")
httpCmd.PersistentFlags().StringVarP(&hostHeaderRewrite, "host_header_rewrite", "", "", "host header rewrite")
httpCmd.PersistentFlags().BoolVarP(&useEncryption, "ue", "", false, "use encryption")
httpCmd.PersistentFlags().BoolVarP(&useCompression, "uc", "", false, "use compression")
rootCmd.AddCommand(httpCmd)
}
var httpCmd = &cobra.Command{
Use: "http",
Short: "Run frpc with a single http proxy",
RunE: func(cmd *cobra.Command, args []string) error {
err := parseClientCommonCfg(CfgFileTypeCmd, "")
if err != nil {
fmt.Println(err)
os.Exit(1)
}
cfg := &config.HttpProxyConf{}
var prefix string
if user != "" {
prefix = user + "."
}
cfg.ProxyName = prefix + proxyName
cfg.ProxyType = consts.HttpProxy
cfg.LocalIp = localIp
cfg.LocalPort = localPort
cfg.CustomDomains = strings.Split(customDomains, ",")
cfg.SubDomain = subDomain
cfg.Locations = strings.Split(locations, ",")
cfg.HttpUser = httpUser
cfg.HttpPwd = httpPwd
cfg.HostHeaderRewrite = hostHeaderRewrite
cfg.UseEncryption = useEncryption
cfg.UseCompression = useCompression
err = cfg.CheckForCli()
if err != nil {
fmt.Println(err)
os.Exit(1)
}
proxyConfs := map[string]config.ProxyConf{
cfg.ProxyName: cfg,
}
err = startService(proxyConfs, nil)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
return nil
},
}

88
cmd/frpc/sub/https.go Normal file
View File

@@ -0,0 +1,88 @@
// Copyright 2018 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 sub
import (
"fmt"
"os"
"strings"
"github.com/spf13/cobra"
"github.com/fatedier/frp/models/config"
"github.com/fatedier/frp/models/consts"
)
func init() {
httpsCmd.PersistentFlags().StringVarP(&serverAddr, "server_addr", "s", "127.0.0.1:7000", "frp server's address")
httpsCmd.PersistentFlags().StringVarP(&user, "user", "u", "", "user")
httpsCmd.PersistentFlags().StringVarP(&protocol, "protocol", "p", "tcp", "tcp or kcp")
httpsCmd.PersistentFlags().StringVarP(&token, "token", "t", "", "auth token")
httpsCmd.PersistentFlags().StringVarP(&logLevel, "log_level", "", "info", "log level")
httpsCmd.PersistentFlags().StringVarP(&logFile, "log_file", "", "console", "console or file path")
httpsCmd.PersistentFlags().IntVarP(&logMaxDays, "log_max_days", "", 3, "log file reversed days")
httpsCmd.PersistentFlags().StringVarP(&proxyName, "proxy_name", "n", "", "proxy name")
httpsCmd.PersistentFlags().StringVarP(&localIp, "local_ip", "i", "127.0.0.1", "local ip")
httpsCmd.PersistentFlags().IntVarP(&localPort, "local_port", "l", 0, "local port")
httpsCmd.PersistentFlags().StringVarP(&customDomains, "custom_domain", "d", "", "custom domain")
httpsCmd.PersistentFlags().StringVarP(&subDomain, "sd", "", "", "sub domain")
httpsCmd.PersistentFlags().BoolVarP(&useEncryption, "ue", "", false, "use encryption")
httpsCmd.PersistentFlags().BoolVarP(&useCompression, "uc", "", false, "use compression")
rootCmd.AddCommand(httpsCmd)
}
var httpsCmd = &cobra.Command{
Use: "https",
Short: "Run frpc with a single https proxy",
RunE: func(cmd *cobra.Command, args []string) error {
err := parseClientCommonCfg(CfgFileTypeCmd, "")
if err != nil {
fmt.Println(err)
os.Exit(1)
}
cfg := &config.HttpsProxyConf{}
var prefix string
if user != "" {
prefix = user + "."
}
cfg.ProxyName = prefix + proxyName
cfg.ProxyType = consts.HttpsProxy
cfg.LocalIp = localIp
cfg.LocalPort = localPort
cfg.CustomDomains = strings.Split(customDomains, ",")
cfg.SubDomain = subDomain
cfg.UseEncryption = useEncryption
cfg.UseCompression = useCompression
err = cfg.CheckForCli()
if err != nil {
fmt.Println(err)
os.Exit(1)
}
proxyConfs := map[string]config.ProxyConf{
cfg.ProxyName: cfg,
}
err = startService(proxyConfs, nil)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
return nil
},
}

92
cmd/frpc/sub/reload.go Normal file
View File

@@ -0,0 +1,92 @@
// Copyright 2018 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 sub
import (
"encoding/base64"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"os"
"strings"
"github.com/spf13/cobra"
"github.com/fatedier/frp/client"
"github.com/fatedier/frp/g"
)
func init() {
rootCmd.AddCommand(reloadCmd)
}
var reloadCmd = &cobra.Command{
Use: "reload",
Short: "Hot-Reload frpc configuration",
RunE: func(cmd *cobra.Command, args []string) error {
err := parseClientCommonCfg(CfgFileTypeIni, cfgFile)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
err = reload()
if err != nil {
fmt.Printf("frpc reload error: %v\n", err)
os.Exit(1)
}
fmt.Printf("reload success\n")
return nil
},
}
func reload() error {
if g.GlbClientCfg.AdminPort == 0 {
return fmt.Errorf("admin_port shoud be set if you want to use reload feature")
}
req, err := http.NewRequest("GET", "http://"+
g.GlbClientCfg.AdminAddr+":"+fmt.Sprintf("%d", g.GlbClientCfg.AdminPort)+"/api/reload", nil)
if err != nil {
return err
}
authStr := "Basic " + base64.StdEncoding.EncodeToString([]byte(g.GlbClientCfg.AdminUser+":"+
g.GlbClientCfg.AdminPwd))
req.Header.Add("Authorization", authStr)
resp, err := http.DefaultClient.Do(req)
if err != nil {
return err
} else {
if resp.StatusCode != 200 {
return fmt.Errorf("admin api status code [%d]", resp.StatusCode)
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return err
}
res := &client.GeneralResponse{}
err = json.Unmarshal(body, &res)
if err != nil {
return fmt.Errorf("unmarshal http response error: %s", strings.TrimSpace(string(body)))
} else if res.Code != 0 {
return fmt.Errorf(res.Msg)
}
}
return nil
}

216
cmd/frpc/sub/root.go Normal file
View File

@@ -0,0 +1,216 @@
// Copyright 2018 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 sub
import (
"context"
"fmt"
"io/ioutil"
"net"
"os"
"os/signal"
"strconv"
"strings"
"syscall"
"time"
"github.com/spf13/cobra"
ini "github.com/vaughan0/go-ini"
"github.com/fatedier/frp/client"
"github.com/fatedier/frp/g"
"github.com/fatedier/frp/models/config"
"github.com/fatedier/frp/utils/log"
"github.com/fatedier/frp/utils/version"
)
const (
CfgFileTypeIni = iota
CfgFileTypeCmd
)
var (
cfgFile string
showVersion bool
serverAddr string
user string
protocol string
token string
logLevel string
logFile string
logMaxDays int
proxyName string
localIp string
localPort int
remotePort int
useEncryption bool
useCompression bool
customDomains string
subDomain string
httpUser string
httpPwd string
locations string
hostHeaderRewrite string
role string
sk string
serverName string
bindAddr string
bindPort int
)
func init() {
rootCmd.PersistentFlags().StringVarP(&cfgFile, "", "c", "./frpc.ini", "config file of frpc")
rootCmd.PersistentFlags().BoolVarP(&showVersion, "version", "v", false, "version of frpc")
}
var rootCmd = &cobra.Command{
Use: "frpc",
Short: "frpc is the client of frp (https://github.com/fatedier/frp)",
RunE: func(cmd *cobra.Command, args []string) error {
if showVersion {
fmt.Println(version.Full())
return nil
}
// Do not show command usage here.
err := runClient(cfgFile)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
return nil
},
}
func Execute() {
if err := rootCmd.Execute(); err != nil {
os.Exit(1)
}
}
func handleSignal(svr *client.Service) {
ch := make(chan os.Signal)
signal.Notify(ch, syscall.SIGINT, syscall.SIGTERM)
<-ch
svr.Close()
time.Sleep(250 * time.Millisecond)
os.Exit(0)
}
func parseClientCommonCfg(fileType int, filePath string) (err error) {
if fileType == CfgFileTypeIni {
err = parseClientCommonCfgFromIni(filePath)
} else if fileType == CfgFileTypeCmd {
err = parseClientCommonCfgFromCmd()
}
if err != nil {
return
}
g.GlbClientCfg.CfgFile = cfgFile
err = g.GlbClientCfg.ClientCommonConf.Check()
if err != nil {
return
}
return
}
func parseClientCommonCfgFromIni(filePath string) (err error) {
b, err := ioutil.ReadFile(filePath)
if err != nil {
return err
}
content := string(b)
cfg, err := config.UnmarshalClientConfFromIni(&g.GlbClientCfg.ClientCommonConf, content)
if err != nil {
return err
}
g.GlbClientCfg.ClientCommonConf = *cfg
return
}
func parseClientCommonCfgFromCmd() (err error) {
strs := strings.Split(serverAddr, ":")
if len(strs) < 2 {
err = fmt.Errorf("invalid server_addr")
return
}
if strs[0] != "" {
g.GlbClientCfg.ServerAddr = strs[0]
}
g.GlbClientCfg.ServerPort, err = strconv.Atoi(strs[1])
if err != nil {
err = fmt.Errorf("invalid server_addr")
return
}
g.GlbClientCfg.User = user
g.GlbClientCfg.Protocol = protocol
g.GlbClientCfg.Token = token
g.GlbClientCfg.LogLevel = logLevel
g.GlbClientCfg.LogFile = logFile
g.GlbClientCfg.LogMaxDays = int64(logMaxDays)
return nil
}
func runClient(cfgFilePath string) (err error) {
err = parseClientCommonCfg(CfgFileTypeIni, cfgFilePath)
if err != nil {
return
}
conf, err := ini.LoadFile(cfgFilePath)
if err != nil {
return err
}
pxyCfgs, visitorCfgs, err := config.LoadProxyConfFromIni(g.GlbClientCfg.User, conf, g.GlbClientCfg.Start)
if err != nil {
return err
}
err = startService(pxyCfgs, visitorCfgs)
return
}
func startService(pxyCfgs map[string]config.ProxyConf, visitorCfgs map[string]config.ProxyConf) (err error) {
log.InitLog(g.GlbClientCfg.LogWay, g.GlbClientCfg.LogFile, g.GlbClientCfg.LogLevel, g.GlbClientCfg.LogMaxDays)
if g.GlbClientCfg.DnsServer != "" {
s := g.GlbClientCfg.DnsServer
if !strings.Contains(s, ":") {
s += ":53"
}
// Change default dns server for frpc
net.DefaultResolver = &net.Resolver{
PreferGo: true,
Dial: func(ctx context.Context, network, address string) (net.Conn, error) {
return net.Dial("udp", s)
},
}
}
svr := client.NewService(pxyCfgs, visitorCfgs)
// Capture the exit signal if we use kcp.
if g.GlbClientCfg.Protocol == "kcp" {
go handleSignal(svr)
}
err = svr.Run()
return
}

146
cmd/frpc/sub/status.go Normal file
View File

@@ -0,0 +1,146 @@
// Copyright 2018 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 sub
import (
"encoding/base64"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"os"
"strings"
"github.com/rodaine/table"
"github.com/spf13/cobra"
"github.com/fatedier/frp/client"
"github.com/fatedier/frp/g"
)
func init() {
rootCmd.AddCommand(statusCmd)
}
var statusCmd = &cobra.Command{
Use: "status",
Short: "Overview of all proxies status",
RunE: func(cmd *cobra.Command, args []string) error {
err := parseClientCommonCfg(CfgFileTypeIni, cfgFile)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
err = status()
if err != nil {
fmt.Printf("frpc get status error: %v\n", err)
os.Exit(1)
}
return nil
},
}
func status() error {
if g.GlbClientCfg.AdminPort == 0 {
return fmt.Errorf("admin_port shoud be set if you want to get proxy status")
}
req, err := http.NewRequest("GET", "http://"+
g.GlbClientCfg.AdminAddr+":"+fmt.Sprintf("%d", g.GlbClientCfg.AdminPort)+"/api/status", nil)
if err != nil {
return err
}
authStr := "Basic " + base64.StdEncoding.EncodeToString([]byte(g.GlbClientCfg.AdminUser+":"+
g.GlbClientCfg.AdminPwd))
req.Header.Add("Authorization", authStr)
resp, err := http.DefaultClient.Do(req)
if err != nil {
return err
} else {
if resp.StatusCode != 200 {
return fmt.Errorf("admin api status code [%d]", resp.StatusCode)
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return err
}
res := &client.StatusResp{}
err = json.Unmarshal(body, &res)
if err != nil {
return fmt.Errorf("unmarshal http response error: %s", strings.TrimSpace(string(body)))
}
fmt.Println("Proxy Status...")
if len(res.Tcp) > 0 {
fmt.Printf("TCP")
tbl := table.New("Name", "Status", "LocalAddr", "Plugin", "RemoteAddr", "Error")
for _, ps := range res.Tcp {
tbl.AddRow(ps.Name, ps.Status, ps.LocalAddr, ps.Plugin, ps.RemoteAddr, ps.Err)
}
tbl.Print()
fmt.Println("")
}
if len(res.Udp) > 0 {
fmt.Printf("UDP")
tbl := table.New("Name", "Status", "LocalAddr", "Plugin", "RemoteAddr", "Error")
for _, ps := range res.Udp {
tbl.AddRow(ps.Name, ps.Status, ps.LocalAddr, ps.Plugin, ps.RemoteAddr, ps.Err)
}
tbl.Print()
fmt.Println("")
}
if len(res.Http) > 0 {
fmt.Printf("HTTP")
tbl := table.New("Name", "Status", "LocalAddr", "Plugin", "RemoteAddr", "Error")
for _, ps := range res.Http {
tbl.AddRow(ps.Name, ps.Status, ps.LocalAddr, ps.Plugin, ps.RemoteAddr, ps.Err)
}
tbl.Print()
fmt.Println("")
}
if len(res.Https) > 0 {
fmt.Printf("HTTPS")
tbl := table.New("Name", "Status", "LocalAddr", "Plugin", "RemoteAddr", "Error")
for _, ps := range res.Https {
tbl.AddRow(ps.Name, ps.Status, ps.LocalAddr, ps.Plugin, ps.RemoteAddr, ps.Err)
}
tbl.Print()
fmt.Println("")
}
if len(res.Stcp) > 0 {
fmt.Printf("STCP")
tbl := table.New("Name", "Status", "LocalAddr", "Plugin", "RemoteAddr", "Error")
for _, ps := range res.Stcp {
tbl.AddRow(ps.Name, ps.Status, ps.LocalAddr, ps.Plugin, ps.RemoteAddr, ps.Err)
}
tbl.Print()
fmt.Println("")
}
if len(res.Xtcp) > 0 {
fmt.Printf("XTCP")
tbl := table.New("Name", "Status", "LocalAddr", "Plugin", "RemoteAddr", "Error")
for _, ps := range res.Xtcp {
tbl.AddRow(ps.Name, ps.Status, ps.LocalAddr, ps.Plugin, ps.RemoteAddr, ps.Err)
}
tbl.Print()
fmt.Println("")
}
}
return nil
}

104
cmd/frpc/sub/stcp.go Normal file
View File

@@ -0,0 +1,104 @@
// Copyright 2018 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 sub
import (
"fmt"
"os"
"github.com/spf13/cobra"
"github.com/fatedier/frp/models/config"
"github.com/fatedier/frp/models/consts"
)
func init() {
stcpCmd.PersistentFlags().StringVarP(&serverAddr, "server_addr", "s", "127.0.0.1:7000", "frp server's address")
stcpCmd.PersistentFlags().StringVarP(&user, "user", "u", "", "user")
stcpCmd.PersistentFlags().StringVarP(&protocol, "protocol", "p", "tcp", "tcp or kcp")
stcpCmd.PersistentFlags().StringVarP(&token, "token", "t", "", "auth token")
stcpCmd.PersistentFlags().StringVarP(&logLevel, "log_level", "", "info", "log level")
stcpCmd.PersistentFlags().StringVarP(&logFile, "log_file", "", "console", "console or file path")
stcpCmd.PersistentFlags().IntVarP(&logMaxDays, "log_max_days", "", 3, "log file reversed days")
stcpCmd.PersistentFlags().StringVarP(&proxyName, "proxy_name", "n", "", "proxy name")
stcpCmd.PersistentFlags().StringVarP(&role, "role", "", "server", "role")
stcpCmd.PersistentFlags().StringVarP(&sk, "sk", "", "", "secret key")
stcpCmd.PersistentFlags().StringVarP(&serverName, "server_name", "", "", "server name")
stcpCmd.PersistentFlags().StringVarP(&localIp, "local_ip", "i", "127.0.0.1", "local ip")
stcpCmd.PersistentFlags().IntVarP(&localPort, "local_port", "l", 0, "local port")
stcpCmd.PersistentFlags().StringVarP(&bindAddr, "bind_addr", "", "", "bind addr")
stcpCmd.PersistentFlags().IntVarP(&bindPort, "bind_port", "", 0, "bind port")
stcpCmd.PersistentFlags().BoolVarP(&useEncryption, "ue", "", false, "use encryption")
stcpCmd.PersistentFlags().BoolVarP(&useCompression, "uc", "", false, "use compression")
rootCmd.AddCommand(stcpCmd)
}
var stcpCmd = &cobra.Command{
Use: "stcp",
Short: "Run frpc with a single stcp proxy",
RunE: func(cmd *cobra.Command, args []string) error {
err := parseClientCommonCfg(CfgFileTypeCmd, "")
if err != nil {
fmt.Println(err)
os.Exit(1)
}
cfg := &config.StcpProxyConf{}
var prefix string
if user != "" {
prefix = user + "."
}
cfg.ProxyName = prefix + proxyName
cfg.ProxyType = consts.StcpProxy
cfg.Role = role
cfg.Sk = sk
cfg.ServerName = serverName
cfg.LocalIp = localIp
cfg.LocalPort = localPort
cfg.BindAddr = bindAddr
cfg.BindPort = bindPort
cfg.UseEncryption = useEncryption
cfg.UseCompression = useCompression
err = cfg.CheckForCli()
if err != nil {
fmt.Println(err)
os.Exit(1)
}
if cfg.Role == "server" {
proxyConfs := map[string]config.ProxyConf{
cfg.ProxyName: cfg,
}
err = startService(proxyConfs, nil)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
} else {
visitorConfs := map[string]config.ProxyConf{
cfg.ProxyName: cfg,
}
err = startService(nil, visitorConfs)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
}
return nil
},
}

85
cmd/frpc/sub/tcp.go Normal file
View File

@@ -0,0 +1,85 @@
// Copyright 2018 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 sub
import (
"fmt"
"os"
"github.com/spf13/cobra"
"github.com/fatedier/frp/models/config"
"github.com/fatedier/frp/models/consts"
)
func init() {
tcpCmd.PersistentFlags().StringVarP(&serverAddr, "server_addr", "s", "127.0.0.1:7000", "frp server's address")
tcpCmd.PersistentFlags().StringVarP(&user, "user", "u", "", "user")
tcpCmd.PersistentFlags().StringVarP(&protocol, "protocol", "p", "tcp", "tcp or kcp")
tcpCmd.PersistentFlags().StringVarP(&token, "token", "t", "", "auth token")
tcpCmd.PersistentFlags().StringVarP(&logLevel, "log_level", "", "info", "log level")
tcpCmd.PersistentFlags().StringVarP(&logFile, "log_file", "", "console", "console or file path")
tcpCmd.PersistentFlags().IntVarP(&logMaxDays, "log_max_days", "", 3, "log file reversed days")
tcpCmd.PersistentFlags().StringVarP(&proxyName, "proxy_name", "n", "", "proxy name")
tcpCmd.PersistentFlags().StringVarP(&localIp, "local_ip", "i", "127.0.0.1", "local ip")
tcpCmd.PersistentFlags().IntVarP(&localPort, "local_port", "l", 0, "local port")
tcpCmd.PersistentFlags().IntVarP(&remotePort, "remote_port", "r", 0, "remote port")
tcpCmd.PersistentFlags().BoolVarP(&useEncryption, "ue", "", false, "use encryption")
tcpCmd.PersistentFlags().BoolVarP(&useCompression, "uc", "", false, "use compression")
rootCmd.AddCommand(tcpCmd)
}
var tcpCmd = &cobra.Command{
Use: "tcp",
Short: "Run frpc with a single tcp proxy",
RunE: func(cmd *cobra.Command, args []string) error {
err := parseClientCommonCfg(CfgFileTypeCmd, "")
if err != nil {
fmt.Println(err)
os.Exit(1)
}
cfg := &config.TcpProxyConf{}
var prefix string
if user != "" {
prefix = user + "."
}
cfg.ProxyName = prefix + proxyName
cfg.ProxyType = consts.TcpProxy
cfg.LocalIp = localIp
cfg.LocalPort = localPort
cfg.RemotePort = remotePort
cfg.UseEncryption = useEncryption
cfg.UseCompression = useCompression
err = cfg.CheckForCli()
if err != nil {
fmt.Println(err)
os.Exit(1)
}
proxyConfs := map[string]config.ProxyConf{
cfg.ProxyName: cfg,
}
err = startService(proxyConfs, nil)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
return nil
},
}

85
cmd/frpc/sub/udp.go Normal file
View File

@@ -0,0 +1,85 @@
// Copyright 2018 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 sub
import (
"fmt"
"os"
"github.com/spf13/cobra"
"github.com/fatedier/frp/models/config"
"github.com/fatedier/frp/models/consts"
)
func init() {
udpCmd.PersistentFlags().StringVarP(&serverAddr, "server_addr", "s", "127.0.0.1:7000", "frp server's address")
udpCmd.PersistentFlags().StringVarP(&user, "user", "u", "", "user")
udpCmd.PersistentFlags().StringVarP(&protocol, "protocol", "p", "tcp", "tcp or kcp")
udpCmd.PersistentFlags().StringVarP(&token, "token", "t", "", "auth token")
udpCmd.PersistentFlags().StringVarP(&logLevel, "log_level", "", "info", "log level")
udpCmd.PersistentFlags().StringVarP(&logFile, "log_file", "", "console", "console or file path")
udpCmd.PersistentFlags().IntVarP(&logMaxDays, "log_max_days", "", 3, "log file reversed days")
udpCmd.PersistentFlags().StringVarP(&proxyName, "proxy_name", "n", "", "proxy name")
udpCmd.PersistentFlags().StringVarP(&localIp, "local_ip", "i", "127.0.0.1", "local ip")
udpCmd.PersistentFlags().IntVarP(&localPort, "local_port", "l", 0, "local port")
udpCmd.PersistentFlags().IntVarP(&remotePort, "remote_port", "r", 0, "remote port")
udpCmd.PersistentFlags().BoolVarP(&useEncryption, "ue", "", false, "use encryption")
udpCmd.PersistentFlags().BoolVarP(&useCompression, "uc", "", false, "use compression")
rootCmd.AddCommand(udpCmd)
}
var udpCmd = &cobra.Command{
Use: "udp",
Short: "Run frpc with a single udp proxy",
RunE: func(cmd *cobra.Command, args []string) error {
err := parseClientCommonCfg(CfgFileTypeCmd, "")
if err != nil {
fmt.Println(err)
os.Exit(1)
}
cfg := &config.UdpProxyConf{}
var prefix string
if user != "" {
prefix = user + "."
}
cfg.ProxyName = prefix + proxyName
cfg.ProxyType = consts.UdpProxy
cfg.LocalIp = localIp
cfg.LocalPort = localPort
cfg.RemotePort = remotePort
cfg.UseEncryption = useEncryption
cfg.UseCompression = useCompression
err = cfg.CheckForCli()
if err != nil {
fmt.Println(err)
os.Exit(1)
}
proxyConfs := map[string]config.ProxyConf{
cfg.ProxyName: cfg,
}
err = startService(proxyConfs, nil)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
return nil
},
}

104
cmd/frpc/sub/xtcp.go Normal file
View File

@@ -0,0 +1,104 @@
// Copyright 2018 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 sub
import (
"fmt"
"os"
"github.com/spf13/cobra"
"github.com/fatedier/frp/models/config"
"github.com/fatedier/frp/models/consts"
)
func init() {
xtcpCmd.PersistentFlags().StringVarP(&serverAddr, "server_addr", "s", "127.0.0.1:7000", "frp server's address")
xtcpCmd.PersistentFlags().StringVarP(&user, "user", "u", "", "user")
xtcpCmd.PersistentFlags().StringVarP(&protocol, "protocol", "p", "tcp", "tcp or kcp")
xtcpCmd.PersistentFlags().StringVarP(&token, "token", "t", "", "auth token")
xtcpCmd.PersistentFlags().StringVarP(&logLevel, "log_level", "", "info", "log level")
xtcpCmd.PersistentFlags().StringVarP(&logFile, "log_file", "", "console", "console or file path")
xtcpCmd.PersistentFlags().IntVarP(&logMaxDays, "log_max_days", "", 3, "log file reversed days")
xtcpCmd.PersistentFlags().StringVarP(&proxyName, "proxy_name", "n", "", "proxy name")
xtcpCmd.PersistentFlags().StringVarP(&role, "role", "", "server", "role")
xtcpCmd.PersistentFlags().StringVarP(&sk, "sk", "", "", "secret key")
xtcpCmd.PersistentFlags().StringVarP(&serverName, "server_name", "", "", "server name")
xtcpCmd.PersistentFlags().StringVarP(&localIp, "local_ip", "i", "127.0.0.1", "local ip")
xtcpCmd.PersistentFlags().IntVarP(&localPort, "local_port", "l", 0, "local port")
xtcpCmd.PersistentFlags().StringVarP(&bindAddr, "bind_addr", "", "", "bind addr")
xtcpCmd.PersistentFlags().IntVarP(&bindPort, "bind_port", "", 0, "bind port")
xtcpCmd.PersistentFlags().BoolVarP(&useEncryption, "ue", "", false, "use encryption")
xtcpCmd.PersistentFlags().BoolVarP(&useCompression, "uc", "", false, "use compression")
rootCmd.AddCommand(xtcpCmd)
}
var xtcpCmd = &cobra.Command{
Use: "xtcp",
Short: "Run frpc with a single xtcp proxy",
RunE: func(cmd *cobra.Command, args []string) error {
err := parseClientCommonCfg(CfgFileTypeCmd, "")
if err != nil {
fmt.Println(err)
os.Exit(1)
}
cfg := &config.XtcpProxyConf{}
var prefix string
if user != "" {
prefix = user + "."
}
cfg.ProxyName = prefix + proxyName
cfg.ProxyType = consts.XtcpProxy
cfg.Role = role
cfg.Sk = sk
cfg.ServerName = serverName
cfg.LocalIp = localIp
cfg.LocalPort = localPort
cfg.BindAddr = bindAddr
cfg.BindPort = bindPort
cfg.UseEncryption = useEncryption
cfg.UseCompression = useCompression
err = cfg.CheckForCli()
if err != nil {
fmt.Println(err)
os.Exit(1)
}
if cfg.Role == "server" {
proxyConfs := map[string]config.ProxyConf{
cfg.ProxyName: cfg,
}
err = startService(proxyConfs, nil)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
} else {
visitorConfs := map[string]config.ProxyConf{
cfg.ProxyName: cfg,
}
err = startService(nil, visitorConfs)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
}
return nil
},
}

View File

@@ -1,4 +1,4 @@
// Copyright 2016 fatedier, fatedier@gmail.com
// Copyright 2018 fatedier, fatedier@gmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -15,104 +15,11 @@
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"
"github.com/fatedier/golib/crypto"
)
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)
crypto.DefaultSalt = "frp"
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()
Execute()
}

211
cmd/frps/root.go Normal file
View File

@@ -0,0 +1,211 @@
// Copyright 2018 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"
"io/ioutil"
"os"
"github.com/spf13/cobra"
"github.com/fatedier/frp/g"
"github.com/fatedier/frp/models/config"
"github.com/fatedier/frp/server"
"github.com/fatedier/frp/utils/log"
"github.com/fatedier/frp/utils/util"
"github.com/fatedier/frp/utils/version"
)
const (
CfgFileTypeIni = iota
CfgFileTypeCmd
)
var (
cfgFile string
showVersion bool
bindAddr string
bindPort int
bindUdpPort int
kcpBindPort int
proxyBindAddr string
vhostHttpPort int
vhostHttpsPort int
vhostHttpTimeout int64
dashboardAddr string
dashboardPort int
dashboardUser string
dashboardPwd string
assetsDir string
logFile string
logWay string
logLevel string
logMaxDays int64
token string
authTimeout int64
subDomainHost string
tcpMux bool
allowPorts string
maxPoolCount int64
maxPortsPerClient int64
)
func init() {
rootCmd.PersistentFlags().StringVarP(&cfgFile, "", "c", "", "config file of frps")
rootCmd.PersistentFlags().BoolVarP(&showVersion, "version", "v", false, "version of frpc")
rootCmd.PersistentFlags().StringVarP(&bindAddr, "bind_addr", "", "0.0.0.0", "bind address")
rootCmd.PersistentFlags().IntVarP(&bindPort, "bind_port", "p", 7000, "bind port")
rootCmd.PersistentFlags().IntVarP(&bindUdpPort, "bind_udp_port", "", 0, "bind udp port")
rootCmd.PersistentFlags().IntVarP(&kcpBindPort, "kcp_bind_port", "", 0, "kcp bind udp port")
rootCmd.PersistentFlags().StringVarP(&proxyBindAddr, "proxy_bind_addr", "", "0.0.0.0", "proxy bind address")
rootCmd.PersistentFlags().IntVarP(&vhostHttpPort, "vhost_http_port", "", 0, "vhost http port")
rootCmd.PersistentFlags().IntVarP(&vhostHttpsPort, "vhost_https_port", "", 0, "vhost https port")
rootCmd.PersistentFlags().Int64VarP(&vhostHttpTimeout, "vhost_http_timeout", "", 60, "vhost http response header timeout")
rootCmd.PersistentFlags().StringVarP(&dashboardAddr, "dashboard_addr", "", "0.0.0.0", "dasboard address")
rootCmd.PersistentFlags().IntVarP(&dashboardPort, "dashboard_port", "", 0, "dashboard port")
rootCmd.PersistentFlags().StringVarP(&dashboardUser, "dashboard_user", "", "admin", "dashboard user")
rootCmd.PersistentFlags().StringVarP(&dashboardPwd, "dashboard_pwd", "", "admin", "dashboard password")
rootCmd.PersistentFlags().StringVarP(&logFile, "log_file", "", "console", "log file")
rootCmd.PersistentFlags().StringVarP(&logWay, "log_way", "", "console", "log way")
rootCmd.PersistentFlags().StringVarP(&logLevel, "log_level", "", "info", "log level")
rootCmd.PersistentFlags().Int64VarP(&logMaxDays, "log_max_days", "", 3, "log_max_days")
rootCmd.PersistentFlags().StringVarP(&token, "token", "t", "", "auth token")
rootCmd.PersistentFlags().Int64VarP(&authTimeout, "auth_timeout", "", 900, "auth timeout")
rootCmd.PersistentFlags().StringVarP(&subDomainHost, "subdomain_host", "", "", "subdomain host")
rootCmd.PersistentFlags().StringVarP(&allowPorts, "allow_ports", "", "", "allow ports")
rootCmd.PersistentFlags().Int64VarP(&maxPortsPerClient, "max_ports_per_client", "", 0, "max ports per client")
}
var rootCmd = &cobra.Command{
Use: "frps",
Short: "frps is the server of frp (https://github.com/fatedier/frp)",
RunE: func(cmd *cobra.Command, args []string) error {
if showVersion {
fmt.Println(version.Full())
return nil
}
var err error
if cfgFile != "" {
err = parseServerCommonCfg(CfgFileTypeIni, cfgFile)
} else {
err = parseServerCommonCfg(CfgFileTypeCmd, "")
}
if err != nil {
return err
}
err = runServer()
if err != nil {
fmt.Println(err)
os.Exit(1)
}
return nil
},
}
func Execute() {
if err := rootCmd.Execute(); err != nil {
os.Exit(1)
}
}
func parseServerCommonCfg(fileType int, filePath string) (err error) {
if fileType == CfgFileTypeIni {
err = parseServerCommonCfgFromIni(filePath)
} else if fileType == CfgFileTypeCmd {
err = parseServerCommonCfgFromCmd()
}
if err != nil {
return
}
g.GlbServerCfg.CfgFile = filePath
err = g.GlbServerCfg.ServerCommonConf.Check()
if err != nil {
return
}
config.InitServerCfg(&g.GlbServerCfg.ServerCommonConf)
return
}
func parseServerCommonCfgFromIni(filePath string) (err error) {
b, err := ioutil.ReadFile(filePath)
if err != nil {
return err
}
content := string(b)
cfg, err := config.UnmarshalServerConfFromIni(&g.GlbServerCfg.ServerCommonConf, content)
if err != nil {
return err
}
g.GlbServerCfg.ServerCommonConf = *cfg
return
}
func parseServerCommonCfgFromCmd() (err error) {
g.GlbServerCfg.BindAddr = bindAddr
g.GlbServerCfg.BindPort = bindPort
g.GlbServerCfg.BindUdpPort = bindUdpPort
g.GlbServerCfg.KcpBindPort = kcpBindPort
g.GlbServerCfg.ProxyBindAddr = proxyBindAddr
g.GlbServerCfg.VhostHttpPort = vhostHttpPort
g.GlbServerCfg.VhostHttpsPort = vhostHttpsPort
g.GlbServerCfg.VhostHttpTimeout = vhostHttpTimeout
g.GlbServerCfg.DashboardAddr = dashboardAddr
g.GlbServerCfg.DashboardPort = dashboardPort
g.GlbServerCfg.DashboardUser = dashboardUser
g.GlbServerCfg.DashboardPwd = dashboardPwd
g.GlbServerCfg.LogFile = logFile
g.GlbServerCfg.LogWay = logWay
g.GlbServerCfg.LogLevel = logLevel
g.GlbServerCfg.LogMaxDays = logMaxDays
g.GlbServerCfg.Token = token
g.GlbServerCfg.AuthTimeout = authTimeout
g.GlbServerCfg.SubDomainHost = subDomainHost
if len(allowPorts) > 0 {
// e.g. 1000-2000,2001,2002,3000-4000
ports, errRet := util.ParseRangeNumbers(allowPorts)
if errRet != nil {
err = fmt.Errorf("Parse conf error: allow_ports: %v", errRet)
return
}
for _, port := range ports {
g.GlbServerCfg.AllowPorts[int(port)] = struct{}{}
}
}
g.GlbServerCfg.MaxPortsPerClient = maxPortsPerClient
return
}
func runServer() (err error) {
log.InitLog(g.GlbServerCfg.LogWay, g.GlbServerCfg.LogFile, g.GlbServerCfg.LogLevel,
g.GlbServerCfg.LogMaxDays)
svr, err := server.NewService()
if err != nil {
return err
}
log.Info("Start frps success")
server.ServerService = svr
svr.Run()
return
}

View File

@@ -5,9 +5,10 @@
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
# if you want to connect frps by http proxy or socks5 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
# http_proxy = http://user:passwd@192.168.1.128:8080
# http_proxy = socks5://user:passwd@192.168.1.128:1080
# console or real logFile path like ./frpc.log
log_file = ./frpc.log
@@ -18,13 +19,13 @@ log_level = info
log_max_days = 3
# for authentication
privilege_token = 12345678
token = 12345678
# set admin address for control frpc's action by http api such as reload
admin_addr = 127.0.0.1
admin_port = 7400
admin_user = admin
admin_pwd = admin
admin_passwd = admin
# connections will be established in advance, default value is zero
pool_count = 5
@@ -40,9 +41,12 @@ user = your_name
login_fail_exit = true
# communication protocol used to connect to server
# now it supports tcp and kcp, default is tcp
# now it supports tcp and kcp and websocket, default is tcp
protocol = tcp
# specify a dns server, so frpc will use this instead of default one
# dns_server = 8.8.8.8
# proxy names you want to start divided by ','
# default is empty, means all proxies
# start = ssh,dns
@@ -52,10 +56,10 @@ protocol = tcp
# 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' is the unique proxy name
# 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
# tcp | udp | http | https | stcp | xtcp, default is tcp
type = tcp
local_ip = 127.0.0.1
local_port = 22
@@ -65,6 +69,27 @@ use_encryption = false
use_compression = false
# remote port listen by frps
remote_port = 6001
# frps will load balancing connections for proxies in same group
group = test_group
# group should have same group key
group_key = 123456
[ssh_random]
type = tcp
local_ip = 127.0.0.1
local_port = 22
# if remote_port is 0, frps will assign a random port for you
remote_port = 0
# if you want to expose multiple ports, add 'range:' prefix to the section name
# frpc will generate multiple proxies such as 'tcp_port_6010', 'tcp_port_6011' and so on.
[range:tcp_port]
type = tcp
local_ip = 127.0.0.1
local_port = 6010-6020,6022,6024-6028
remote_port = 6010-6020,6022,6024-6028
use_encryption = false
use_compression = false
[dns]
type = udp
@@ -74,6 +99,14 @@ remote_port = 6002
use_encryption = false
use_compression = false
[range:udp_port]
type = udp
local_ip = 127.0.0.1
local_port = 6010-6020
remote_port = 6010-6020
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
@@ -88,16 +121,18 @@ 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 is only available for http type
locations = /,/pic
host_header_rewrite = example.com
# params with prefix "header_" will be used to update http request headers
header_X-From-Where = frp
[web02]
type = https
local_ip = 127.0.0.1
local_port = 8000
use_encryption = false
use_compression = false
use_compression = false
subdomain = web01
custom_domains = web02.yourdomain.com
@@ -107,7 +142,7 @@ 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
# params with prefix "plugin_" that plugin needed
plugin_unix_path = /var/run/docker.sock
[plugin_http_proxy]
@@ -117,6 +152,22 @@ plugin = http_proxy
plugin_http_user = abc
plugin_http_passwd = abc
[plugin_socks5]
type = tcp
remote_port = 6005
plugin = socks5
plugin_user = abc
plugin_passwd = abc
[plugin_static_file]
type = tcp
remote_port = 6006
plugin = static_file
plugin_local_path = /var/www/blog
plugin_strip_prefix = static
plugin_http_user = abc
plugin_http_passwd = abc
[secret_tcp]
# If the type is secret tcp, remote_port is useless
# Who want to connect local port should deploy another frpc with stcp proxy and role is visitor

View File

@@ -16,16 +16,20 @@ kcp_bind_port = 7000
# proxy_bind_addr = 127.0.0.1
# if you want to support virtual host, you must set the http port for listening (optional)
# Note: http port and https port can be same with bind_port
vhost_http_port = 80
vhost_https_port = 443
# response header timeout(seconds) for vhost http server, default is 60s
# vhost_http_timeout = 60
# set dashboard_addr and dashboard_port to view dashboard of frps
# dashboard_addr's default value is same with bind_addr
# dashboard is available only if dashboard_port is set
dashboard_addr = 0.0.0.0
dashboard_port = 7500
# dashboard user and pwd for basic auth protect, if not set, both default value is admin
# dashboard user and passwd for basic auth protect, if not set, both default value is admin
dashboard_user = admin
dashboard_pwd = admin
@@ -39,19 +43,22 @@ log_level = info
log_max_days = 3
# privilege mode is the only supported mode since v0.10.0
privilege_token = 12345678
# auth token
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
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
# max ports can be used for each client, default value is 0 means no limit
max_ports_per_client = 0
# 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

View File

@@ -1,135 +0,0 @@
# Quick Start
frp is easier to use compared with other similar projects.
We will use two simple demo to demonstrate how to use frp.
1. How to create a connection to **server A**'s **ssh port** by **server B** with **public IP address** x.x.x.x(replace to the real IP address of your server).
2. How to visit web service in **server A**'s **8000 port** and **8001 port** by **web01.yourdomain.com** and **web02.yourdomain.com** through **server B** with public ID address.
### Download SourceCode
`go get github.com/fatedier/frp` is recommended, then the code will be copied to the directory `$GOPATH/src/github.com/fatedier/frp`.
Or you can use `git clone https://github.com/fatedier/frp.git $GOPATH/src/github.com/fatedier/frp`.
If you want to try it quickly, download the compiled program and configuration files from [https://github.com/fatedier/frp/releases](https://github.com/fatedier/frp/releases).
### Compile
Enter the root directory and execute `make`, then wait until finished.
**bin** include all executable programs when **conf** include corresponding configuration files.
### Pre-requirement
* Go environment. Version of go >= 1.4.
* Godep (if not exist, `go get` will be executed to download godep when compiling)
### Deploy
1. Move `./bin/frps` and `./conf/frps.ini` to any directory of **server B**.
2. Move `./bin/frpc` and `./conf/frpc.ini` to any directory of **server A**.
3. Modify all configuration files, details in next paragraph.
4. Execute `nohup ./frps &` or `nohup ./frps -c ./frps.ini &` in **server B**.
5. Execute `nohup ./frpc &` or `nohup ./frpc -c ./frpc.ini &` in **server A**.
6. Use `ssh -oPort=6000 {user}@x.x.x.x` to test if frp is work(replace {user} to real username in **server A**), or visit custom domains by browser.
## Tcp port forwarding
### Configuration files
#### frps.ini
```ini
[common]
bind_addr = 0.0.0.0
# for accept connections from frpc
bind_port = 7000
log_file = ./frps.log
log_level = info
# ssh is the custom name of proxy and there can be many proxies with unique name in one configure file
[ssh]
auth_token = 123
bind_addr = 0.0.0.0
# finally we connect to server A by this port
listen_port = 6000
```
#### frpc.ini
```ini
[common]
# server address of frps
server_addr = x.x.x.x
server_port = 7000
log_file = ./frpc.log
log_level = info
# for authentication
auth_token = 123
# ssh is proxy name same with configure in frps.ini
[ssh]
# local port which need to be transferred
local_port = 22
# if use_encryption equals true, messages between frpc and frps will be encrypted, default is false
use_encryption = true
```
## Http port forwarding and Custom domains binding
If you only want to forward port one by one, you just need refer to [Tcp port forwarding](/doc/quick_start_en.md#Tcp-port-forwarding).If you want to visit different web pages deployed in different web servers by **server B**'s **80 port**, you should specify the type as **http**.
You also need to resolve your **A record** of your custom domain to [server_addr], or resolve your **CNAME record** to [server_addr] if [server_addr] is a domain.
After that, you can visit your web pages in local server by custom domains.
### Configuration files
#### frps.ini
```ini
[common]
bind_addr = 0.0.0.0
bind_port = 7000
# if you want to support vhost, specify one port for http services
vhost_http_port = 80
log_file = ./frps.log
log_level = info
[web01]
type = http
auth_token = 123
# # if proxy type equals http, custom_domains must be set separated by commas
custom_domains = web01.yourdomain.com
[web02]
type = http
auth_token = 123
custom_domains = web02.yourdomain.com
```
#### frpc.ini
```ini
[common]
server_addr = x.x.x.x
server_port = 7000
log_file = ./frpc.log
log_level = info
auth_token = 123
# custom domains are set in frps.ini
[web01]
type = http
local_ip = 127.0.0.1
local_port = 8000
# encryption is optional, default is false
use_encryption = true
[web02]
type = http
local_ip = 127.0.0.1
local_port = 8001
```

View File

@@ -1,137 +0,0 @@
# frp 使用文档
相比于其他项目而言 frp 更易于部署和使用,这里我们用两个简单的示例来演示 frp 的使用过程。
1. 如何通过一台拥有公网IP地址的**服务器B**,访问处于公司内部网络环境中的**服务器A**的**ssh**端口,**服务器B**的IP地址为 x.x.x.x测试时替换为真实的IP地址
2. 如何利用一台拥有公网IP地址的**服务器B**,使通过 **web01.yourdomain.com** 可以访问内网环境中**服务器A**上**8000端口**的web服务**web02.yourdomain.com** 可以访问**服务器A**上**8001端口**的web服务。
### 下载源码
推荐直接使用 `go get github.com/fatedier/frp` 下载源代码安装,执行命令后代码将会拷贝到 `$GOPATH/src/github.com/fatedier/frp` 目录下。
或者可以使用 `git clone https://github.com/fatedier/frp.git $GOPATH/src/github.com/fatedier/frp` 拷贝到相应目录下。
如果您想快速进行测试,也可以根据您服务器的操作系统及架构直接下载编译好的程序及示例配置文件,[https://github.com/fatedier/frp/releases](https://github.com/fatedier/frp/releases)。
### 编译
进入下载后的源码根目录,执行 `make` 命令,等待编译完成。
编译完成后, **bin** 目录下是编译好的可执行文件,**conf** 目录下是示例配置文件。
### 依赖
* go 1.4 以上版本
* godep (如果检查不存在,编译时会通过 `go get` 命令安装)
### 部署
1. 将 ./bin/frps 和 ./conf/frps.ini 拷贝至**服务器B**任意目录。
2. 将 ./bin/frpc 和 ./conf/frpc.ini 拷贝至**服务器A**任意目录。
3. 根据要实现的功能修改两边的配置文件,详细内容见后续章节说明。
4. 在服务器B执行 `nohup ./frps &` 或者 `nohup ./frps -c ./frps.ini &`
5. 在服务器A执行 `nohup ./frpc &` 或者 `nohup ./frpc -c ./frpc.ini &`
6. 通过 `ssh -oPort=6000 {user}@x.x.x.x` 测试是否能够成功连接**服务器A**{user}替换为**服务器A**上存在的真实用户),或通过浏览器访问自定义域名验证 http 服务是否转发成功。
## tcp 端口转发
转发 tcp 端口需要按照需求修改 frps 和 frpc 的配置文件。
### 配置文件
#### frps.ini
```ini
[common]
bind_addr = 0.0.0.0
# 用于接收 frpc 连接的端口
bind_port = 7000
log_file = ./frps.log
log_level = info
# ssh 为代理的自定义名称可以有多个不能重复和frpc中名称对应
[ssh]
auth_token = 123
bind_addr = 0.0.0.0
# 最后将通过此端口访问后端服务
listen_port = 6000
```
#### frpc.ini
```ini
[common]
# frps 所在服务器绑定的IP地址
server_addr = x.x.x.x
server_port = 7000
log_file = ./frpc.log
log_level = info
# 用于身份验证
auth_token = 123
# ssh 需要和 frps.ini 中配置一致
[ssh]
# 需要转发的本地端口
local_port = 22
# 启用加密frpc与frps之间通信加密默认为 false
use_encryption = true
```
## http 端口转发,自定义域名绑定
如果只需要一对一的转发,例如**服务器B**的**80端口**转发**服务器A**的**8000端口**,则只需要配置 [tcp 端口转发](/doc/quick_start_zh.md#tcp-端口转发) 即可,如果需要使**服务器B**的**80端口**可以转发至**多个**web服务端口则需要指定代理的类型为 http并且在 frps 的配置文件中配置用于提供 http 转发服务的端口。
按照如下的内容修改配置文件后,需要将自定义域名的 **A 记录**解析到 [server_addr],如果 [server_addr] 是域名也可以将自定义域名的 **CNAME 记录**解析到 [server_addr]。
之后就可以通过自定义域名访问到本地的多个 web 服务。
### 配置文件
#### frps.ini
```ini
[common]
bind_addr = 0.0.0.0
bind_port = 7000
# 如果需要支持http类型的代理则需要指定一个端口
vhost_http_port = 80
log_file = ./frps.log
log_level = info
[web01]
# type 默认为 tcp这里需要特别指定为 http
type = http
auth_token = 123
# 自定义域名绑定,如果需要同时绑定多个以英文逗号分隔
custom_domains = web01.yourdomain.com
[web02]
type = http
auth_token = 123
custom_domains = web02.yourdomain.com
```
#### frpc.ini
```ini
[common]
server_addr = x.x.x.x
server_port = 7000
log_file = ./frpc.log
log_level = info
auth_token = 123
# 自定义域名在 frps.ini 中配置,方便做统一管理
[web01]
type = http
local_ip = 127.0.0.1
local_port = 8000
# 可选是否加密
use_encryption = true
[web02]
type = http
local_ip = 127.0.0.1
local_port = 8001
```

32
g/g.go Normal file
View File

@@ -0,0 +1,32 @@
package g
import (
"github.com/fatedier/frp/models/config"
)
var (
GlbClientCfg *ClientCfg
GlbServerCfg *ServerCfg
)
func init() {
GlbClientCfg = &ClientCfg{
ClientCommonConf: *config.GetDefaultClientConf(),
}
GlbServerCfg = &ServerCfg{
ServerCommonConf: *config.GetDefaultServerConf(),
}
}
type ClientCfg struct {
config.ClientCommonConf
CfgFile string
ServerUdpPort int // this is configured by login response from frps
}
type ServerCfg struct {
config.ServerCommonConf
CfgFile string
}

75
glide.lock generated
View File

@@ -1,75 +0,0 @@
hash: 03ff8b71f63e9038c0182a4ef2a55aa9349782f4813c331e2d1f02f3dd15b4f8
updated: 2017-11-01T16:16:18.577622991+08:00
imports:
- name: github.com/armon/go-socks5
version: e75332964ef517daa070d7c38a9466a0d687e0a5
- name: github.com/davecgh/go-spew
version: 346938d642f2ec3594ed81d874461961cd0faa76
subpackages:
- spew
- name: github.com/docopt/docopt-go
version: 784ddc588536785e7299f7272f39101f7faccc3f
- name: github.com/fatedier/beego
version: 6c6a4f5bd5eb5a39f7e289b8f345b55f75e7e3e8
subpackages:
- logs
- name: github.com/fatedier/kcp-go
version: cd167d2f15f451b0f33780ce862fca97adc0331e
- name: github.com/golang/snappy
version: 5979233c5d6225d4a8e438cdd0b411888449ddab
- name: github.com/julienschmidt/httprouter
version: 8a45e95fc75cb77048068a62daed98cc22fdac7c
- name: github.com/klauspost/cpuid
version: 09cded8978dc9e80714c4d85b0322337b0a1e5e0
- name: github.com/klauspost/reedsolomon
version: dde6ad55c5e5a6379a4e82dcca32ee407346eb6d
- name: github.com/pkg/errors
version: c605e284fe17294bda444b34710735b29d1a9d90
- name: github.com/pmezard/go-difflib
version: 792786c7400a136282c1664665ae0a8db921c6c2
subpackages:
- difflib
- name: github.com/rakyll/statik
version: 274df120e9065bdd08eb1120e0375e3dc1ae8465
subpackages:
- fs
- name: github.com/stretchr/testify
version: 2402e8e7a02fc811447d11f881aa9746cdc57983
subpackages:
- assert
- name: github.com/templexxx/cpufeat
version: 3794dfbfb04749f896b521032f69383f24c3687e
- name: github.com/templexxx/reedsolomon
version: 7092926d7d05c415fabb892b1464a03f8228ab80
- name: github.com/templexxx/xor
version: 0af8e873c554da75f37f2049cdffda804533d44c
- name: github.com/tjfoc/gmsm
version: 21d76dee237dbbc8dfe1510000b9bf2733635aa1
subpackages:
- sm4
- name: github.com/vaughan0/go-ini
version: a98ad7ee00ec53921f08832bc06ecf7fd600e6a1
- name: github.com/xtaci/kcp-go
version: df437e2b8ec365a336200f9d9da53441cf72ed47
- name: github.com/xtaci/smux
version: 2de5471dfcbc029f5fe1392b83fe784127c4943e
- name: golang.org/x/crypto
version: e1a4589e7d3ea14a3352255d04b6f1a418845e5e
subpackages:
- blowfish
- cast5
- pbkdf2
- salsa20
- salsa20/salsa
- tea
- twofish
- xtea
- name: golang.org/x/net
version: e4fa1c5465ad6111f206fc92186b8c83d64adbe1
subpackages:
- bpf
- context
- internal/iana
- internal/socket
- ipv4
testImports: []

View File

@@ -1,73 +0,0 @@
package: github.com/fatedier/frp
import:
- package: github.com/armon/go-socks5
version: e75332964ef517daa070d7c38a9466a0d687e0a5
- package: github.com/davecgh/go-spew
version: v1.1.0
subpackages:
- spew
- package: github.com/docopt/docopt-go
version: 0.6.2
- package: github.com/fatedier/beego
version: 6c6a4f5bd5eb5a39f7e289b8f345b55f75e7e3e8
subpackages:
- logs
- package: github.com/fatedier/kcp-go
version: cd167d2f15f451b0f33780ce862fca97adc0331e
- package: github.com/golang/snappy
version: 5979233c5d6225d4a8e438cdd0b411888449ddab
- package: github.com/julienschmidt/httprouter
version: 8a45e95fc75cb77048068a62daed98cc22fdac7c
- package: github.com/klauspost/cpuid
version: v1.0
- package: github.com/klauspost/reedsolomon
version: dde6ad55c5e5a6379a4e82dcca32ee407346eb6d
- package: github.com/pkg/errors
version: c605e284fe17294bda444b34710735b29d1a9d90
- package: github.com/pmezard/go-difflib
version: v1.0.0
subpackages:
- difflib
- package: github.com/rakyll/statik
version: v0.1.0
subpackages:
- fs
- package: github.com/stretchr/testify
version: 2402e8e7a02fc811447d11f881aa9746cdc57983
subpackages:
- assert
- package: github.com/templexxx/cpufeat
version: 3794dfbfb04749f896b521032f69383f24c3687e
- package: github.com/templexxx/reedsolomon
version: 7092926d7d05c415fabb892b1464a03f8228ab80
- package: github.com/templexxx/xor
version: 0.1.2
- package: github.com/tjfoc/gmsm
version: 21d76dee237dbbc8dfe1510000b9bf2733635aa1
subpackages:
- sm4
- package: github.com/vaughan0/go-ini
version: a98ad7ee00ec53921f08832bc06ecf7fd600e6a1
- package: github.com/xtaci/kcp-go
version: v3.17
- package: github.com/xtaci/smux
version: 2de5471dfcbc029f5fe1392b83fe784127c4943e
- package: golang.org/x/crypto
version: e1a4589e7d3ea14a3352255d04b6f1a418845e5e
subpackages:
- blowfish
- cast5
- pbkdf2
- salsa20
- salsa20/salsa
- tea
- twofish
- xtea
- package: golang.org/x/net
version: e4fa1c5465ad6111f206fc92186b8c83d64adbe1
subpackages:
- bpf
- context
- internal/iana
- internal/socket
- ipv4

View File

@@ -23,46 +23,41 @@ import (
ini "github.com/vaughan0/go-ini"
)
var ClientCommonCfg *ClientCommonConf
// client common config
type ClientCommonConf struct {
ConfigFile string
ServerAddr string
ServerPort int64
ServerUdpPort int64 // this is specified by login response message from frps
HttpProxy string
LogFile string
LogWay string
LogLevel string
LogMaxDays int64
PrivilegeToken string
AdminAddr string
AdminPort int64
AdminUser string
AdminPwd string
PoolCount int
TcpMux bool
User string
LoginFailExit bool
Start map[string]struct{}
Protocol string
HeartBeatInterval int64
HeartBeatTimeout int64
ServerAddr string `json:"server_addr"`
ServerPort int `json:"server_port"`
HttpProxy string `json:"http_proxy"`
LogFile string `json:"log_file"`
LogWay string `json:"log_way"`
LogLevel string `json:"log_level"`
LogMaxDays int64 `json:"log_max_days"`
Token string `json:"token"`
AdminAddr string `json:"admin_addr"`
AdminPort int `json:"admin_port"`
AdminUser string `json:"admin_user"`
AdminPwd string `json:"admin_pwd"`
PoolCount int `json:"pool_count"`
TcpMux bool `json:"tcp_mux"`
User string `json:"user"`
DnsServer string `json:"dns_server"`
LoginFailExit bool `json:"login_fail_exit"`
Start map[string]struct{} `json:"start"`
Protocol string `json:"protocol"`
HeartBeatInterval int64 `json:"heartbeat_interval"`
HeartBeatTimeout int64 `json:"heartbeat_timeout"`
}
func GetDeaultClientCommonConf() *ClientCommonConf {
func GetDefaultClientConf() *ClientCommonConf {
return &ClientCommonConf{
ConfigFile: "./frpc.ini",
ServerAddr: "0.0.0.0",
ServerPort: 7000,
ServerUdpPort: 0,
HttpProxy: "",
HttpProxy: os.Getenv("http_proxy"),
LogFile: "console",
LogWay: "console",
LogLevel: "info",
LogMaxDays: 3,
PrivilegeToken: "",
Token: "",
AdminAddr: "127.0.0.1",
AdminPort: 0,
AdminUser: "",
@@ -70,6 +65,7 @@ func GetDeaultClientCommonConf() *ClientCommonConf {
PoolCount: 1,
TcpMux: true,
User: "",
DnsServer: "",
LoginFailExit: true,
Start: make(map[string]struct{}),
Protocol: "tcp",
@@ -78,34 +74,41 @@ func GetDeaultClientCommonConf() *ClientCommonConf {
}
}
func LoadClientCommonConf(conf ini.File) (cfg *ClientCommonConf, err error) {
func UnmarshalClientConfFromIni(defaultCfg *ClientCommonConf, content string) (cfg *ClientCommonConf, err error) {
cfg = defaultCfg
if cfg == nil {
cfg = GetDefaultClientConf()
}
conf, err := ini.Load(strings.NewReader(content))
if err != nil {
err = fmt.Errorf("parse ini conf file error: %v", err)
return nil, err
}
var (
tmpStr string
ok bool
v int64
)
cfg = GetDeaultClientCommonConf()
tmpStr, ok = conf.Get("common", "server_addr")
if ok {
if tmpStr, ok = conf.Get("common", "server_addr"); ok {
cfg.ServerAddr = tmpStr
}
tmpStr, ok = conf.Get("common", "server_port")
if ok {
cfg.ServerPort, _ = strconv.ParseInt(tmpStr, 10, 64)
if tmpStr, ok = conf.Get("common", "server_port"); ok {
v, err = strconv.ParseInt(tmpStr, 10, 64)
if err != nil {
err = fmt.Errorf("Parse conf error: invalid server_port")
return
}
cfg.ServerPort = int(v)
}
tmpStr, ok = conf.Get("common", "http_proxy")
if ok {
if tmpStr, ok = conf.Get("common", "http_proxy"); 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 {
if tmpStr, ok = conf.Get("common", "log_file"); ok {
cfg.LogFile = tmpStr
if cfg.LogFile == "console" {
cfg.LogWay = "console"
@@ -114,120 +117,111 @@ func LoadClientCommonConf(conf ini.File) (cfg *ClientCommonConf, err error) {
}
}
tmpStr, ok = conf.Get("common", "log_level")
if ok {
if tmpStr, ok = conf.Get("common", "log_level"); ok {
cfg.LogLevel = tmpStr
}
tmpStr, ok = conf.Get("common", "log_max_days")
if ok {
if tmpStr, ok = conf.Get("common", "log_max_days"); ok {
if v, err = strconv.ParseInt(tmpStr, 10, 64); err == nil {
cfg.LogMaxDays = v
}
}
tmpStr, ok = conf.Get("common", "privilege_token")
if ok {
cfg.PrivilegeToken = tmpStr
if tmpStr, ok = conf.Get("common", "token"); ok {
cfg.Token = tmpStr
}
tmpStr, ok = conf.Get("common", "admin_addr")
if ok {
if tmpStr, ok = conf.Get("common", "admin_addr"); ok {
cfg.AdminAddr = tmpStr
}
tmpStr, ok = conf.Get("common", "admin_port")
if ok {
if tmpStr, ok = conf.Get("common", "admin_port"); ok {
if v, err = strconv.ParseInt(tmpStr, 10, 64); err == nil {
cfg.AdminPort = v
cfg.AdminPort = int(v)
} else {
err = fmt.Errorf("Parse conf error: invalid admin_port")
return
}
}
tmpStr, ok = conf.Get("common", "admin_user")
if ok {
if tmpStr, ok = conf.Get("common", "admin_user"); ok {
cfg.AdminUser = tmpStr
}
tmpStr, ok = conf.Get("common", "admin_pwd")
if ok {
if tmpStr, ok = conf.Get("common", "admin_pwd"); ok {
cfg.AdminPwd = tmpStr
}
tmpStr, ok = conf.Get("common", "pool_count")
if ok {
v, err = strconv.ParseInt(tmpStr, 10, 64)
if err != nil {
cfg.PoolCount = 1
} else {
if tmpStr, ok = conf.Get("common", "pool_count"); ok {
if v, err = strconv.ParseInt(tmpStr, 10, 64); err == nil {
cfg.PoolCount = int(v)
}
}
tmpStr, ok = conf.Get("common", "tcp_mux")
if ok && tmpStr == "false" {
if tmpStr, ok = conf.Get("common", "tcp_mux"); ok && tmpStr == "false" {
cfg.TcpMux = false
} else {
cfg.TcpMux = true
}
tmpStr, ok = conf.Get("common", "user")
if ok {
if tmpStr, ok = conf.Get("common", "user"); ok {
cfg.User = tmpStr
}
tmpStr, ok = conf.Get("common", "start")
if ok {
if tmpStr, ok = conf.Get("common", "dns_server"); ok {
cfg.DnsServer = tmpStr
}
if tmpStr, ok = conf.Get("common", "start"); ok {
proxyNames := strings.Split(tmpStr, ",")
for _, name := range proxyNames {
cfg.Start[strings.TrimSpace(name)] = struct{}{}
}
}
tmpStr, ok = conf.Get("common", "login_fail_exit")
if ok && tmpStr == "false" {
if tmpStr, ok = conf.Get("common", "login_fail_exit"); 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"
if tmpStr, ok = conf.Get("common", "protocol"); ok {
// Now it only support tcp and kcp and websocket.
if tmpStr != "tcp" && tmpStr != "kcp" && tmpStr != "websocket" {
err = fmt.Errorf("Parse conf error: invalid protocol")
return
}
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")
if tmpStr, ok = conf.Get("common", "heartbeat_timeout"); ok {
if v, err = strconv.ParseInt(tmpStr, 10, 64); err != nil {
err = fmt.Errorf("Parse conf error: invalid heartbeat_timeout")
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")
if tmpStr, ok = conf.Get("common", "heartbeat_interval"); ok {
if v, err = strconv.ParseInt(tmpStr, 10, 64); err != nil {
err = fmt.Errorf("Parse conf error: invalid heartbeat_interval")
return
} else {
cfg.HeartBeatInterval = v
}
}
return
}
func (cfg *ClientCommonConf) Check() (err error) {
if cfg.HeartBeatInterval <= 0 {
err = fmt.Errorf("Parse conf error: heartbeat_interval is incorrect")
err = fmt.Errorf("Parse conf error: invalid heartbeat_interval")
return
}
if cfg.HeartBeatTimeout < cfg.HeartBeatInterval {
err = fmt.Errorf("Parse conf error: heartbeat_timeout is incorrect, heartbeat_timeout is less than heartbeat_interval")
err = fmt.Errorf("Parse conf error: invalid heartbeat_timeout, heartbeat_timeout is less than heartbeat_interval")
return
}
return

View File

@@ -22,12 +22,14 @@ import (
"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
var (
proxyConfTypeMap map[string]reflect.Type
)
func init() {
proxyConfTypeMap = make(map[string]reflect.Type)
@@ -51,16 +53,16 @@ func NewConfByType(proxyType string) ProxyConf {
}
type ProxyConf interface {
GetName() string
GetBaseInfo() *BaseProxyConf
LoadFromMsg(pMsg *msg.NewProxy)
LoadFromFile(name string, conf ini.Section) error
UnMarshalToMsg(pMsg *msg.NewProxy)
Check() error
UnmarshalFromMsg(pMsg *msg.NewProxy)
UnmarshalFromIni(prefix string, name string, conf ini.Section) error
MarshalToMsg(pMsg *msg.NewProxy)
CheckForCli() error
CheckForSvr() error
Compare(conf ProxyConf) bool
}
func NewProxyConf(pMsg *msg.NewProxy) (cfg ProxyConf, err error) {
func NewProxyConfFromMsg(pMsg *msg.NewProxy) (cfg ProxyConf, err error) {
if pMsg.ProxyType == "" {
pMsg.ProxyType = consts.TcpProxy
}
@@ -70,12 +72,12 @@ func NewProxyConf(pMsg *msg.NewProxy) (cfg ProxyConf, err error) {
err = fmt.Errorf("proxy [%s] type [%s] error", pMsg.ProxyName, pMsg.ProxyType)
return
}
cfg.LoadFromMsg(pMsg)
err = cfg.Check()
cfg.UnmarshalFromMsg(pMsg)
err = cfg.CheckForSvr()
return
}
func NewProxyConfFromFile(name string, section ini.Section) (cfg ProxyConf, err error) {
func NewProxyConfFromIni(prefix string, name string, section ini.Section) (cfg ProxyConf, err error) {
proxyType := section["type"]
if proxyType == "" {
proxyType = consts.TcpProxy
@@ -86,7 +88,10 @@ func NewProxyConfFromFile(name string, section ini.Section) (cfg ProxyConf, err
err = fmt.Errorf("proxy [%s] type [%s] error", name, proxyType)
return
}
err = cfg.LoadFromFile(name, section)
if err = cfg.UnmarshalFromIni(prefix, name, section); err != nil {
return
}
err = cfg.CheckForCli()
return
}
@@ -95,12 +100,10 @@ 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
UseEncryption bool `json:"use_encryption"`
UseCompression bool `json:"use_compression"`
Group string `json:"group"`
GroupKey string `json:"group_key"`
}
func (cfg *BaseProxyConf) GetBaseInfo() *BaseProxyConf {
@@ -111,29 +114,29 @@ func (cfg *BaseProxyConf) compare(cmp *BaseProxyConf) bool {
if cfg.ProxyName != cmp.ProxyName ||
cfg.ProxyType != cmp.ProxyType ||
cfg.UseEncryption != cmp.UseEncryption ||
cfg.UseCompression != cmp.UseCompression {
cfg.UseCompression != cmp.UseCompression ||
cfg.Group != cmp.Group ||
cfg.GroupKey != cmp.GroupKey {
return false
}
return true
}
func (cfg *BaseProxyConf) LoadFromMsg(pMsg *msg.NewProxy) {
func (cfg *BaseProxyConf) UnmarshalFromMsg(pMsg *msg.NewProxy) {
cfg.ProxyName = pMsg.ProxyName
cfg.ProxyType = pMsg.ProxyType
cfg.UseEncryption = pMsg.UseEncryption
cfg.UseCompression = pMsg.UseCompression
cfg.Group = pMsg.Group
cfg.GroupKey = pMsg.GroupKey
}
func (cfg *BaseProxyConf) LoadFromFile(name string, section ini.Section) error {
func (cfg *BaseProxyConf) UnmarshalFromIni(prefix string, name string, section ini.Section) error {
var (
tmpStr string
ok bool
)
if ClientCommonCfg.User != "" {
cfg.ProxyName = ClientCommonCfg.User + "." + name
} else {
cfg.ProxyName = name
}
cfg.ProxyName = prefix + name
cfg.ProxyType = section["type"]
tmpStr, ok = section["use_encryption"]
@@ -145,43 +148,48 @@ func (cfg *BaseProxyConf) LoadFromFile(name string, section ini.Section) error {
if ok && tmpStr == "true" {
cfg.UseCompression = true
}
cfg.Group = section["group"]
cfg.GroupKey = section["group_key"]
return nil
}
func (cfg *BaseProxyConf) UnMarshalToMsg(pMsg *msg.NewProxy) {
func (cfg *BaseProxyConf) MarshalToMsg(pMsg *msg.NewProxy) {
pMsg.ProxyName = cfg.ProxyName
pMsg.ProxyType = cfg.ProxyType
pMsg.UseEncryption = cfg.UseEncryption
pMsg.UseCompression = cfg.UseCompression
pMsg.Group = cfg.Group
pMsg.GroupKey = cfg.GroupKey
}
// Bind info
type BindInfoConf struct {
BindAddr string `json:"bind_addr"`
RemotePort int64 `json:"remote_port"`
RemotePort int `json:"remote_port"`
}
func (cfg *BindInfoConf) compare(cmp *BindInfoConf) bool {
if cfg.BindAddr != cmp.BindAddr ||
cfg.RemotePort != cmp.RemotePort {
if cfg.RemotePort != cmp.RemotePort {
return false
}
return true
}
func (cfg *BindInfoConf) LoadFromMsg(pMsg *msg.NewProxy) {
cfg.BindAddr = ServerCommonCfg.ProxyBindAddr
func (cfg *BindInfoConf) UnmarshalFromMsg(pMsg *msg.NewProxy) {
cfg.RemotePort = pMsg.RemotePort
}
func (cfg *BindInfoConf) LoadFromFile(name string, section ini.Section) (err error) {
func (cfg *BindInfoConf) UnmarshalFromIni(prefix string, name string, section ini.Section) (err error) {
var (
tmpStr string
ok bool
v int64
)
if tmpStr, ok = section["remote_port"]; ok {
if cfg.RemotePort, err = strconv.ParseInt(tmpStr, 10, 64); err != nil {
if v, err = strconv.ParseInt(tmpStr, 10, 64); err != nil {
return fmt.Errorf("Parse conf error: proxy [%s] remote_port error", name)
} else {
cfg.RemotePort = int(v)
}
} else {
return fmt.Errorf("Parse conf error: proxy [%s] remote_port not found", name)
@@ -189,19 +197,10 @@ func (cfg *BindInfoConf) LoadFromFile(name string, section ini.Section) (err err
return nil
}
func (cfg *BindInfoConf) UnMarshalToMsg(pMsg *msg.NewProxy) {
func (cfg *BindInfoConf) MarshalToMsg(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"`
@@ -216,12 +215,12 @@ func (cfg *DomainConf) compare(cmp *DomainConf) bool {
return true
}
func (cfg *DomainConf) LoadFromMsg(pMsg *msg.NewProxy) {
func (cfg *DomainConf) UnmarshalFromMsg(pMsg *msg.NewProxy) {
cfg.CustomDomains = pMsg.CustomDomains
cfg.SubDomain = pMsg.SubDomain
}
func (cfg *DomainConf) LoadFromFile(name string, section ini.Section) (err error) {
func (cfg *DomainConf) UnmarshalFromIni(prefix string, name string, section ini.Section) (err error) {
var (
tmpStr string
ok bool
@@ -236,42 +235,60 @@ func (cfg *DomainConf) LoadFromFile(name string, section ini.Section) (err error
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) {
func (cfg *DomainConf) MarshalToMsg(pMsg *msg.NewProxy) {
pMsg.CustomDomains = cfg.CustomDomains
pMsg.SubDomain = cfg.SubDomain
}
func (cfg *DomainConf) check() (err error) {
if len(cfg.CustomDomains) == 0 && cfg.SubDomain == "" {
err = fmt.Errorf("custom_domains and subdomain should set at least one of them")
return
}
return
}
func (cfg *DomainConf) checkForCli() (err error) {
if err = cfg.check(); err != nil {
return
}
return
}
func (cfg *DomainConf) checkForSvr() (err error) {
if err = cfg.check(); err != nil {
return
}
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 subDomainHost != "" && len(strings.Split(subDomainHost, ".")) < len(strings.Split(domain, ".")) {
if strings.Contains(domain, subDomainHost) {
return fmt.Errorf("custom domain [%s] should not belong to subdomain_host [%s]", domain, subDomainHost)
}
}
}
if cfg.SubDomain != "" {
if ServerCommonCfg.SubDomainHost == "" {
return fmt.Errorf("subdomain is not supported because this feature is not enabled by frps")
if subDomainHost == "" {
return fmt.Errorf("subdomain is not supported because this feature is not enabled in remote frps")
}
if strings.Contains(cfg.SubDomain, ".") || strings.Contains(cfg.SubDomain, "*") {
return fmt.Errorf("'.' and '*' is not supported in subdomain")
}
}
return nil
return
}
// Local service info
type LocalSvrConf struct {
LocalIp string `json:"-"`
LocalPort int `json:"-"`
LocalIp string `json:"local_ip"`
LocalPort int `json:"local_port"`
Plugin string `json:"plugin"`
PluginParams map[string]string `json:"plugin_params"`
}
func (cfg *LocalSvrConf) compare(cmp *LocalSvrConf) bool {
@@ -279,30 +296,6 @@ func (cfg *LocalSvrConf) compare(cmp *LocalSvrConf) bool {
cfg.LocalPort != cmp.LocalPort {
return false
}
return true
}
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) compare(cmp *PluginConf) bool {
if cfg.Plugin != cmp.Plugin ||
len(cfg.PluginParams) != len(cmp.PluginParams) {
return false
@@ -316,7 +309,7 @@ func (cfg *PluginConf) compare(cmp *PluginConf) bool {
return true
}
func (cfg *PluginConf) LoadFromFile(name string, section ini.Section) (err error) {
func (cfg *LocalSvrConf) UnmarshalFromIni(prefix string, name string, section ini.Section) (err error) {
cfg.Plugin = section["plugin"]
cfg.PluginParams = make(map[string]string)
if cfg.Plugin != "" {
@@ -327,7 +320,17 @@ func (cfg *PluginConf) LoadFromFile(name string, section ini.Section) (err error
}
}
} else {
return fmt.Errorf("Parse conf error: proxy [%s] no plugin info found", name)
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
}
@@ -338,7 +341,6 @@ type TcpProxyConf struct {
BindInfoConf
LocalSvrConf
PluginConf
}
func (cfg *TcpProxyConf) Compare(cmp ProxyConf) bool {
@@ -349,43 +351,38 @@ func (cfg *TcpProxyConf) Compare(cmp ProxyConf) bool {
if !cfg.BaseProxyConf.compare(&cmpConf.BaseProxyConf) ||
!cfg.BindInfoConf.compare(&cmpConf.BindInfoConf) ||
!cfg.LocalSvrConf.compare(&cmpConf.LocalSvrConf) ||
!cfg.PluginConf.compare(&cmpConf.PluginConf) {
!cfg.LocalSvrConf.compare(&cmpConf.LocalSvrConf) {
return false
}
return true
}
func (cfg *TcpProxyConf) LoadFromMsg(pMsg *msg.NewProxy) {
cfg.BaseProxyConf.LoadFromMsg(pMsg)
cfg.BindInfoConf.LoadFromMsg(pMsg)
func (cfg *TcpProxyConf) UnmarshalFromMsg(pMsg *msg.NewProxy) {
cfg.BaseProxyConf.UnmarshalFromMsg(pMsg)
cfg.BindInfoConf.UnmarshalFromMsg(pMsg)
}
func (cfg *TcpProxyConf) LoadFromFile(name string, section ini.Section) (err error) {
if err = cfg.BaseProxyConf.LoadFromFile(name, section); err != nil {
func (cfg *TcpProxyConf) UnmarshalFromIni(prefix string, name string, section ini.Section) (err error) {
if err = cfg.BaseProxyConf.UnmarshalFromIni(prefix, name, section); err != nil {
return
}
if err = cfg.BindInfoConf.LoadFromFile(name, section); err != nil {
if err = cfg.BindInfoConf.UnmarshalFromIni(prefix, name, section); err != nil {
return
}
if err = cfg.PluginConf.LoadFromFile(name, section); err != nil {
if err = cfg.LocalSvrConf.LoadFromFile(name, section); err != nil {
return
}
if err = cfg.LocalSvrConf.UnmarshalFromIni(prefix, name, section); err != nil {
return
}
return
}
func (cfg *TcpProxyConf) UnMarshalToMsg(pMsg *msg.NewProxy) {
cfg.BaseProxyConf.UnMarshalToMsg(pMsg)
cfg.BindInfoConf.UnMarshalToMsg(pMsg)
func (cfg *TcpProxyConf) MarshalToMsg(pMsg *msg.NewProxy) {
cfg.BaseProxyConf.MarshalToMsg(pMsg)
cfg.BindInfoConf.MarshalToMsg(pMsg)
}
func (cfg *TcpProxyConf) Check() (err error) {
err = cfg.BindInfoConf.check()
return
}
func (cfg *TcpProxyConf) CheckForCli() error { return nil }
func (cfg *TcpProxyConf) CheckForSvr() error { return nil }
// UDP
type UdpProxyConf struct {
@@ -409,33 +406,32 @@ func (cfg *UdpProxyConf) Compare(cmp ProxyConf) bool {
return true
}
func (cfg *UdpProxyConf) LoadFromMsg(pMsg *msg.NewProxy) {
cfg.BaseProxyConf.LoadFromMsg(pMsg)
cfg.BindInfoConf.LoadFromMsg(pMsg)
func (cfg *UdpProxyConf) UnmarshalFromMsg(pMsg *msg.NewProxy) {
cfg.BaseProxyConf.UnmarshalFromMsg(pMsg)
cfg.BindInfoConf.UnmarshalFromMsg(pMsg)
}
func (cfg *UdpProxyConf) LoadFromFile(name string, section ini.Section) (err error) {
if err = cfg.BaseProxyConf.LoadFromFile(name, section); err != nil {
func (cfg *UdpProxyConf) UnmarshalFromIni(prefix string, name string, section ini.Section) (err error) {
if err = cfg.BaseProxyConf.UnmarshalFromIni(prefix, name, section); err != nil {
return
}
if err = cfg.BindInfoConf.LoadFromFile(name, section); err != nil {
if err = cfg.BindInfoConf.UnmarshalFromIni(prefix, name, section); err != nil {
return
}
if err = cfg.LocalSvrConf.LoadFromFile(name, section); err != nil {
if err = cfg.LocalSvrConf.UnmarshalFromIni(prefix, name, section); err != nil {
return
}
return
}
func (cfg *UdpProxyConf) UnMarshalToMsg(pMsg *msg.NewProxy) {
cfg.BaseProxyConf.UnMarshalToMsg(pMsg)
cfg.BindInfoConf.UnMarshalToMsg(pMsg)
func (cfg *UdpProxyConf) MarshalToMsg(pMsg *msg.NewProxy) {
cfg.BaseProxyConf.MarshalToMsg(pMsg)
cfg.BindInfoConf.MarshalToMsg(pMsg)
}
func (cfg *UdpProxyConf) Check() (err error) {
err = cfg.BindInfoConf.check()
return
}
func (cfg *UdpProxyConf) CheckForCli() error { return nil }
func (cfg *UdpProxyConf) CheckForSvr() error { return nil }
// HTTP
type HttpProxyConf struct {
@@ -443,12 +439,12 @@ type HttpProxyConf struct {
DomainConf
LocalSvrConf
PluginConf
Locations []string `json:"locations"`
HostHeaderRewrite string `json:"host_header_rewrite"`
HttpUser string `json:"-"`
HttpPwd string `json:"-"`
Locations []string `json:"locations"`
HttpUser string `json:"http_user"`
HttpPwd string `json:"http_pwd"`
HostHeaderRewrite string `json:"host_header_rewrite"`
Headers map[string]string `json:"headers"`
}
func (cfg *HttpProxyConf) Compare(cmp ProxyConf) bool {
@@ -460,37 +456,46 @@ func (cfg *HttpProxyConf) Compare(cmp ProxyConf) bool {
if !cfg.BaseProxyConf.compare(&cmpConf.BaseProxyConf) ||
!cfg.DomainConf.compare(&cmpConf.DomainConf) ||
!cfg.LocalSvrConf.compare(&cmpConf.LocalSvrConf) ||
!cfg.PluginConf.compare(&cmpConf.PluginConf) ||
strings.Join(cfg.Locations, " ") != strings.Join(cmpConf.Locations, " ") ||
cfg.HostHeaderRewrite != cmpConf.HostHeaderRewrite ||
cfg.HttpUser != cmpConf.HttpUser ||
cfg.HttpPwd != cmpConf.HttpPwd {
cfg.HttpPwd != cmpConf.HttpPwd ||
len(cfg.Headers) != len(cmpConf.Headers) {
return false
}
for k, v := range cfg.Headers {
if v2, ok := cmpConf.Headers[k]; !ok {
return false
} else {
if v != v2 {
return false
}
}
}
return true
}
func (cfg *HttpProxyConf) LoadFromMsg(pMsg *msg.NewProxy) {
cfg.BaseProxyConf.LoadFromMsg(pMsg)
cfg.DomainConf.LoadFromMsg(pMsg)
func (cfg *HttpProxyConf) UnmarshalFromMsg(pMsg *msg.NewProxy) {
cfg.BaseProxyConf.UnmarshalFromMsg(pMsg)
cfg.DomainConf.UnmarshalFromMsg(pMsg)
cfg.Locations = pMsg.Locations
cfg.HostHeaderRewrite = pMsg.HostHeaderRewrite
cfg.HttpUser = pMsg.HttpUser
cfg.HttpPwd = pMsg.HttpPwd
cfg.Headers = pMsg.Headers
}
func (cfg *HttpProxyConf) LoadFromFile(name string, section ini.Section) (err error) {
if err = cfg.BaseProxyConf.LoadFromFile(name, section); err != nil {
func (cfg *HttpProxyConf) UnmarshalFromIni(prefix string, name string, section ini.Section) (err error) {
if err = cfg.BaseProxyConf.UnmarshalFromIni(prefix, name, section); err != nil {
return
}
if err = cfg.DomainConf.LoadFromFile(name, section); err != nil {
if err = cfg.DomainConf.UnmarshalFromIni(prefix, name, section); err != nil {
return
}
if err = cfg.PluginConf.LoadFromFile(name, section); err != nil {
if err = cfg.LocalSvrConf.LoadFromFile(name, section); err != nil {
return
}
if err = cfg.LocalSvrConf.UnmarshalFromIni(prefix, name, section); err != nil {
return
}
var (
@@ -506,24 +511,42 @@ func (cfg *HttpProxyConf) LoadFromFile(name string, section ini.Section) (err er
cfg.HostHeaderRewrite = section["host_header_rewrite"]
cfg.HttpUser = section["http_user"]
cfg.HttpPwd = section["http_pwd"]
cfg.Headers = make(map[string]string)
for k, v := range section {
if strings.HasPrefix(k, "header_") {
cfg.Headers[strings.TrimPrefix(k, "header_")] = v
}
}
return
}
func (cfg *HttpProxyConf) UnMarshalToMsg(pMsg *msg.NewProxy) {
cfg.BaseProxyConf.UnMarshalToMsg(pMsg)
cfg.DomainConf.UnMarshalToMsg(pMsg)
func (cfg *HttpProxyConf) MarshalToMsg(pMsg *msg.NewProxy) {
cfg.BaseProxyConf.MarshalToMsg(pMsg)
cfg.DomainConf.MarshalToMsg(pMsg)
pMsg.Locations = cfg.Locations
pMsg.HostHeaderRewrite = cfg.HostHeaderRewrite
pMsg.HttpUser = cfg.HttpUser
pMsg.HttpPwd = cfg.HttpPwd
pMsg.Headers = cfg.Headers
}
func (cfg *HttpProxyConf) Check() (err error) {
if ServerCommonCfg.VhostHttpPort == 0 {
func (cfg *HttpProxyConf) CheckForCli() (err error) {
if err = cfg.DomainConf.checkForCli(); err != nil {
return
}
return
}
func (cfg *HttpProxyConf) CheckForSvr() (err error) {
if vhostHttpPort == 0 {
return fmt.Errorf("type [http] not support when vhost_http_port is not set")
}
err = cfg.DomainConf.check()
if err = cfg.DomainConf.checkForSvr(); err != nil {
err = fmt.Errorf("proxy [%s] domain conf check error: %v", cfg.ProxyName, err)
return
}
return
}
@@ -533,7 +556,6 @@ type HttpsProxyConf struct {
DomainConf
LocalSvrConf
PluginConf
}
func (cfg *HttpsProxyConf) Compare(cmp ProxyConf) bool {
@@ -544,43 +566,50 @@ func (cfg *HttpsProxyConf) Compare(cmp ProxyConf) bool {
if !cfg.BaseProxyConf.compare(&cmpConf.BaseProxyConf) ||
!cfg.DomainConf.compare(&cmpConf.DomainConf) ||
!cfg.LocalSvrConf.compare(&cmpConf.LocalSvrConf) ||
!cfg.PluginConf.compare(&cmpConf.PluginConf) {
!cfg.LocalSvrConf.compare(&cmpConf.LocalSvrConf) {
return false
}
return true
}
func (cfg *HttpsProxyConf) LoadFromMsg(pMsg *msg.NewProxy) {
cfg.BaseProxyConf.LoadFromMsg(pMsg)
cfg.DomainConf.LoadFromMsg(pMsg)
func (cfg *HttpsProxyConf) UnmarshalFromMsg(pMsg *msg.NewProxy) {
cfg.BaseProxyConf.UnmarshalFromMsg(pMsg)
cfg.DomainConf.UnmarshalFromMsg(pMsg)
}
func (cfg *HttpsProxyConf) LoadFromFile(name string, section ini.Section) (err error) {
if err = cfg.BaseProxyConf.LoadFromFile(name, section); err != nil {
func (cfg *HttpsProxyConf) UnmarshalFromIni(prefix string, name string, section ini.Section) (err error) {
if err = cfg.BaseProxyConf.UnmarshalFromIni(prefix, name, section); err != nil {
return
}
if err = cfg.DomainConf.LoadFromFile(name, section); err != nil {
if err = cfg.DomainConf.UnmarshalFromIni(prefix, name, section); err != nil {
return
}
if err = cfg.PluginConf.LoadFromFile(name, section); err != nil {
if err = cfg.LocalSvrConf.LoadFromFile(name, section); err != nil {
return
}
if err = cfg.LocalSvrConf.UnmarshalFromIni(prefix, name, section); err != nil {
return
}
return
}
func (cfg *HttpsProxyConf) UnMarshalToMsg(pMsg *msg.NewProxy) {
cfg.BaseProxyConf.UnMarshalToMsg(pMsg)
cfg.DomainConf.UnMarshalToMsg(pMsg)
func (cfg *HttpsProxyConf) MarshalToMsg(pMsg *msg.NewProxy) {
cfg.BaseProxyConf.MarshalToMsg(pMsg)
cfg.DomainConf.MarshalToMsg(pMsg)
}
func (cfg *HttpsProxyConf) Check() (err error) {
if ServerCommonCfg.VhostHttpsPort == 0 {
func (cfg *HttpsProxyConf) CheckForCli() (err error) {
if err = cfg.DomainConf.checkForCli(); err != nil {
return
}
return
}
func (cfg *HttpsProxyConf) CheckForSvr() (err error) {
if vhostHttpsPort == 0 {
return fmt.Errorf("type [https] not support when vhost_https_port is not set")
}
err = cfg.DomainConf.check()
if err = cfg.DomainConf.checkForSvr(); err != nil {
err = fmt.Errorf("proxy [%s] domain conf check error: %v", cfg.ProxyName, err)
return
}
return
}
@@ -593,7 +622,6 @@ type StcpProxyConf struct {
// used in role server
LocalSvrConf
PluginConf
// used in role visitor
ServerName string `json:"server_name"`
@@ -609,7 +637,6 @@ func (cfg *StcpProxyConf) Compare(cmp ProxyConf) bool {
if !cfg.BaseProxyConf.compare(&cmpConf.BaseProxyConf) ||
!cfg.LocalSvrConf.compare(&cmpConf.LocalSvrConf) ||
!cfg.PluginConf.compare(&cmpConf.PluginConf) ||
cfg.Role != cmpConf.Role ||
cfg.Sk != cmpConf.Sk ||
cfg.ServerName != cmpConf.ServerName ||
@@ -621,27 +648,29 @@ func (cfg *StcpProxyConf) Compare(cmp ProxyConf) bool {
}
// Only for role server.
func (cfg *StcpProxyConf) LoadFromMsg(pMsg *msg.NewProxy) {
cfg.BaseProxyConf.LoadFromMsg(pMsg)
func (cfg *StcpProxyConf) UnmarshalFromMsg(pMsg *msg.NewProxy) {
cfg.BaseProxyConf.UnmarshalFromMsg(pMsg)
cfg.Sk = pMsg.Sk
}
func (cfg *StcpProxyConf) LoadFromFile(name string, section ini.Section) (err error) {
if err = cfg.BaseProxyConf.LoadFromFile(name, section); err != nil {
func (cfg *StcpProxyConf) UnmarshalFromIni(prefix string, name string, section ini.Section) (err error) {
if err = cfg.BaseProxyConf.UnmarshalFromIni(prefix, name, section); err != nil {
return
}
tmpStr := section["role"]
if tmpStr == "" {
tmpStr = "server"
}
if tmpStr == "server" || tmpStr == "visitor" {
cfg.Role = tmpStr
} else {
return fmt.Errorf("Parse conf error: incorrect role [%s]", tmpStr)
return fmt.Errorf("Parse conf error: proxy [%s] incorrect role [%s]", name, tmpStr)
}
cfg.Sk = section["sk"]
if tmpStr == "visitor" {
prefix := section["prefix"]
cfg.ServerName = prefix + section["server_name"]
if cfg.BindAddr = section["bind_addr"]; cfg.BindAddr == "" {
cfg.BindAddr = "127.0.0.1"
@@ -655,21 +684,37 @@ func (cfg *StcpProxyConf) LoadFromFile(name string, section ini.Section) (err er
return fmt.Errorf("Parse conf error: proxy [%s] bind_port not found", name)
}
} else {
if err = cfg.PluginConf.LoadFromFile(name, section); err != nil {
if err = cfg.LocalSvrConf.LoadFromFile(name, section); err != nil {
return
}
if err = cfg.LocalSvrConf.UnmarshalFromIni(prefix, name, section); err != nil {
return
}
}
return
}
func (cfg *StcpProxyConf) UnMarshalToMsg(pMsg *msg.NewProxy) {
cfg.BaseProxyConf.UnMarshalToMsg(pMsg)
func (cfg *StcpProxyConf) MarshalToMsg(pMsg *msg.NewProxy) {
cfg.BaseProxyConf.MarshalToMsg(pMsg)
pMsg.Sk = cfg.Sk
}
func (cfg *StcpProxyConf) Check() (err error) {
func (cfg *StcpProxyConf) CheckForCli() (err error) {
if cfg.Role != "server" && cfg.Role != "visitor" {
err = fmt.Errorf("role should be 'server' or 'visitor'")
return
}
if cfg.Role == "visitor" {
if cfg.BindAddr == "" {
err = fmt.Errorf("bind_addr shouldn't be empty")
return
}
if cfg.BindPort == 0 {
err = fmt.Errorf("bind_port should be set")
return
}
}
return
}
func (cfg *StcpProxyConf) CheckForSvr() (err error) {
return
}
@@ -682,7 +727,6 @@ type XtcpProxyConf struct {
// used in role server
LocalSvrConf
PluginConf
// used in role visitor
ServerName string `json:"server_name"`
@@ -698,7 +742,6 @@ func (cfg *XtcpProxyConf) Compare(cmp ProxyConf) bool {
if !cfg.BaseProxyConf.compare(&cmpConf.BaseProxyConf) ||
!cfg.LocalSvrConf.compare(&cmpConf.LocalSvrConf) ||
!cfg.PluginConf.compare(&cmpConf.PluginConf) ||
cfg.Role != cmpConf.Role ||
cfg.Sk != cmpConf.Sk ||
cfg.ServerName != cmpConf.ServerName ||
@@ -710,27 +753,29 @@ func (cfg *XtcpProxyConf) Compare(cmp ProxyConf) bool {
}
// Only for role server.
func (cfg *XtcpProxyConf) LoadFromMsg(pMsg *msg.NewProxy) {
cfg.BaseProxyConf.LoadFromMsg(pMsg)
func (cfg *XtcpProxyConf) UnmarshalFromMsg(pMsg *msg.NewProxy) {
cfg.BaseProxyConf.UnmarshalFromMsg(pMsg)
cfg.Sk = pMsg.Sk
}
func (cfg *XtcpProxyConf) LoadFromFile(name string, section ini.Section) (err error) {
if err = cfg.BaseProxyConf.LoadFromFile(name, section); err != nil {
func (cfg *XtcpProxyConf) UnmarshalFromIni(prefix string, name string, section ini.Section) (err error) {
if err = cfg.BaseProxyConf.UnmarshalFromIni(prefix, name, section); err != nil {
return
}
tmpStr := section["role"]
if tmpStr == "" {
tmpStr = "server"
}
if tmpStr == "server" || tmpStr == "visitor" {
cfg.Role = tmpStr
} else {
return fmt.Errorf("Parse conf error: incorrect role [%s]", tmpStr)
return fmt.Errorf("Parse conf error: proxy [%s] incorrect role [%s]", name, tmpStr)
}
cfg.Sk = section["sk"]
if tmpStr == "visitor" {
prefix := section["prefix"]
cfg.ServerName = prefix + section["server_name"]
if cfg.BindAddr = section["bind_addr"]; cfg.BindAddr == "" {
cfg.BindAddr = "127.0.0.1"
@@ -744,27 +789,75 @@ func (cfg *XtcpProxyConf) LoadFromFile(name string, section ini.Section) (err er
return fmt.Errorf("Parse conf error: proxy [%s] bind_port not found", name)
}
} else {
if err = cfg.PluginConf.LoadFromFile(name, section); err != nil {
if err = cfg.LocalSvrConf.LoadFromFile(name, section); err != nil {
return
}
if err = cfg.LocalSvrConf.UnmarshalFromIni(prefix, name, section); err != nil {
return
}
}
return
}
func (cfg *XtcpProxyConf) UnMarshalToMsg(pMsg *msg.NewProxy) {
cfg.BaseProxyConf.UnMarshalToMsg(pMsg)
func (cfg *XtcpProxyConf) MarshalToMsg(pMsg *msg.NewProxy) {
cfg.BaseProxyConf.MarshalToMsg(pMsg)
pMsg.Sk = cfg.Sk
}
func (cfg *XtcpProxyConf) Check() (err error) {
func (cfg *XtcpProxyConf) CheckForCli() (err error) {
if cfg.Role != "server" && cfg.Role != "visitor" {
err = fmt.Errorf("role should be 'server' or 'visitor'")
return
}
if cfg.Role == "visitor" {
if cfg.BindAddr == "" {
err = fmt.Errorf("bind_addr shouldn't be empty")
return
}
if cfg.BindPort == 0 {
err = fmt.Errorf("bind_port should be set")
return
}
}
return
}
func (cfg *XtcpProxyConf) CheckForSvr() (err error) {
return
}
func ParseRangeSection(name string, section ini.Section) (sections map[string]ini.Section, err error) {
localPorts, errRet := util.ParseRangeNumbers(section["local_port"])
if errRet != nil {
err = fmt.Errorf("Parse conf error: range section [%s] local_port invalid, %v", name, errRet)
return
}
remotePorts, errRet := util.ParseRangeNumbers(section["remote_port"])
if errRet != nil {
err = fmt.Errorf("Parse conf error: range section [%s] remote_port invalid, %v", name, errRet)
return
}
if len(localPorts) != len(remotePorts) {
err = fmt.Errorf("Parse conf error: range section [%s] local ports number should be same with remote ports number", name)
return
}
if len(localPorts) == 0 {
err = fmt.Errorf("Parse conf error: range section [%s] local_port and remote_port is necessary", name)
return
}
sections = make(map[string]ini.Section)
for i, port := range localPorts {
subName := fmt.Sprintf("%s_%d", name, i)
subSection := copySection(section)
subSection["local_port"] = fmt.Sprintf("%d", port)
subSection["remote_port"] = fmt.Sprintf("%d", remotePorts[i])
sections[subName] = subSection
}
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{}) (
func LoadProxyConfFromIni(prefix string, conf ini.File, startProxy map[string]struct{}) (
proxyConfs map[string]ProxyConf, visitorConfs map[string]ProxyConf, err error) {
if prefix != "" {
@@ -778,22 +871,49 @@ func LoadProxyConfFromFile(prefix string, conf ini.File, startProxy map[string]s
proxyConfs = make(map[string]ProxyConf)
visitorConfs = make(map[string]ProxyConf)
for name, section := range conf {
if name == "common" {
continue
}
_, shouldStart := startProxy[name]
if name != "common" && (startAll || shouldStart) {
// some proxy or visotr configure may be used this prefix
section["prefix"] = prefix
cfg, err := NewProxyConfFromFile(name, section)
if !startAll && !shouldStart {
continue
}
subSections := make(map[string]ini.Section)
if strings.HasPrefix(name, "range:") {
// range section
rangePrefix := strings.TrimSpace(strings.TrimPrefix(name, "range:"))
subSections, err = ParseRangeSection(rangePrefix, section)
if err != nil {
return
}
} else {
subSections[name] = section
}
for subName, subSection := range subSections {
cfg, err := NewProxyConfFromIni(prefix, subName, subSection)
if err != nil {
return proxyConfs, visitorConfs, err
}
role := section["role"]
role := subSection["role"]
if role == "visitor" {
visitorConfs[prefix+name] = cfg
visitorConfs[prefix+subName] = cfg
} else {
proxyConfs[prefix+name] = cfg
proxyConfs[prefix+subName] = cfg
}
}
}
return
}
func copySection(section ini.Section) (out ini.Section) {
out = make(ini.Section)
for k, v := range section {
out[k] = v
}
return
}

View File

@@ -19,182 +19,212 @@ import (
"strconv"
"strings"
"github.com/fatedier/frp/utils/util"
ini "github.com/vaughan0/go-ini"
"github.com/fatedier/frp/utils/util"
)
var ServerCommonCfg *ServerCommonConf
var (
// server global configure used for generate proxy conf used in frps
proxyBindAddr string
subDomainHost string
vhostHttpPort int
vhostHttpsPort int
)
func InitServerCfg(cfg *ServerCommonConf) {
proxyBindAddr = cfg.ProxyBindAddr
subDomainHost = cfg.SubDomainHost
vhostHttpPort = cfg.VhostHttpPort
vhostHttpsPort = cfg.VhostHttpsPort
}
// common config
type ServerCommonConf struct {
ConfigFile string
BindAddr string
BindPort int64
BindUdpPort int64
KcpBindPort int64
ProxyBindAddr string
BindAddr string `json:"bind_addr"`
BindPort int `json:"bind_port"`
BindUdpPort int `json:"bind_udp_port"`
KcpBindPort int `json:"kcp_bind_port"`
ProxyBindAddr string `json:"proxy_bind_addr"`
// If VhostHttpPort equals 0, don't listen a public port for http protocol.
VhostHttpPort int64
VhostHttpPort int `json:"vhost_http_port"`
// if VhostHttpsPort equals 0, don't listen a public port for https protocol
VhostHttpsPort int64
DashboardAddr string
VhostHttpsPort int `json:"vhost_http_port"`
VhostHttpTimeout int64 `json:"vhost_http_timeout"`
DashboardAddr string `json:"dashboard_addr"`
// 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
DashboardPort int `json:"dashboard_port"`
DashboardUser string `json:"dashboard_user"`
DashboardPwd string `json:"dashboard_pwd"`
AssetsDir string `json:"asserts_dir"`
LogFile string `json:"log_file"`
LogWay string `json:"log_way"` // console or file
LogLevel string `json:"log_level"`
LogMaxDays int64 `json:"log_max_days"`
Token string `json:"token"`
AuthTimeout int64 `json:"auth_timeout"`
SubDomainHost string `json:"subdomain_host"`
TcpMux bool `json:"tcp_mux"`
// 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
AllowPorts map[int]struct{}
MaxPoolCount int64 `json:"max_pool_count"`
MaxPortsPerClient int64 `json:"max_ports_per_client"`
HeartBeatTimeout int64 `json:"heart_beat_timeout"`
UserConnTimeout int64 `json:"user_conn_timeout"`
}
func GetDefaultServerCommonConf() *ServerCommonConf {
func GetDefaultServerConf() *ServerCommonConf {
return &ServerCommonConf{
ConfigFile: "./frps.ini",
BindAddr: "0.0.0.0",
BindPort: 7000,
BindUdpPort: 0,
KcpBindPort: 0,
ProxyBindAddr: "0.0.0.0",
VhostHttpPort: 0,
VhostHttpsPort: 0,
DashboardAddr: "0.0.0.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,
BindAddr: "0.0.0.0",
BindPort: 7000,
BindUdpPort: 0,
KcpBindPort: 0,
ProxyBindAddr: "0.0.0.0",
VhostHttpPort: 0,
VhostHttpsPort: 0,
VhostHttpTimeout: 60,
DashboardAddr: "0.0.0.0",
DashboardPort: 0,
DashboardUser: "admin",
DashboardPwd: "admin",
AssetsDir: "",
LogFile: "console",
LogWay: "console",
LogLevel: "info",
LogMaxDays: 3,
Token: "",
AuthTimeout: 900,
SubDomainHost: "",
TcpMux: true,
AllowPorts: make(map[int]struct{}),
MaxPoolCount: 5,
MaxPortsPerClient: 0,
HeartBeatTimeout: 90,
UserConnTimeout: 10,
}
}
// Load server common configure.
func LoadServerCommonConf(conf ini.File) (cfg *ServerCommonConf, err error) {
func UnmarshalServerConfFromIni(defaultCfg *ServerCommonConf, content string) (cfg *ServerCommonConf, err error) {
cfg = defaultCfg
if cfg == nil {
cfg = GetDefaultServerConf()
}
conf, err := ini.Load(strings.NewReader(content))
if err != nil {
err = fmt.Errorf("parse ini conf file error: %v", err)
return nil, err
}
var (
tmpStr string
ok bool
v int64
)
cfg = GetDefaultServerCommonConf()
tmpStr, ok = conf.Get("common", "bind_addr")
if ok {
if tmpStr, ok = conf.Get("common", "bind_addr"); 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
if tmpStr, ok = conf.Get("common", "bind_port"); ok {
if v, err = strconv.ParseInt(tmpStr, 10, 64); err != nil {
err = fmt.Errorf("Parse conf error: invalid bind_port")
return
} else {
cfg.BindPort = int(v)
}
}
tmpStr, ok = conf.Get("common", "bind_udp_port")
if ok {
v, err = strconv.ParseInt(tmpStr, 10, 64)
if err == nil {
cfg.BindUdpPort = v
if tmpStr, ok = conf.Get("common", "bind_udp_port"); ok {
if v, err = strconv.ParseInt(tmpStr, 10, 64); err != nil {
err = fmt.Errorf("Parse conf error: invalid bind_udp_port")
return
} else {
cfg.BindUdpPort = int(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
if tmpStr, ok = conf.Get("common", "kcp_bind_port"); ok {
if v, err = strconv.ParseInt(tmpStr, 10, 64); err != nil {
err = fmt.Errorf("Parse conf error: invalid kcp_bind_port")
return
} else {
cfg.KcpBindPort = int(v)
}
}
tmpStr, ok = conf.Get("common", "proxy_bind_addr")
if ok {
if tmpStr, ok = conf.Get("common", "proxy_bind_addr"); ok {
cfg.ProxyBindAddr = tmpStr
} else {
cfg.ProxyBindAddr = cfg.BindAddr
}
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")
if tmpStr, ok = conf.Get("common", "vhost_http_port"); ok {
if v, err = strconv.ParseInt(tmpStr, 10, 64); err != nil {
err = fmt.Errorf("Parse conf error: invalid vhost_http_port")
return
} else {
cfg.VhostHttpPort = int(v)
}
} 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")
if tmpStr, ok = conf.Get("common", "vhost_https_port"); ok {
if v, err = strconv.ParseInt(tmpStr, 10, 64); err != nil {
err = fmt.Errorf("Parse conf error: invalid vhost_https_port")
return
} else {
cfg.VhostHttpsPort = int(v)
}
} else {
cfg.VhostHttpsPort = 0
}
tmpStr, ok = conf.Get("common", "dashboard_addr")
if ok {
if tmpStr, ok = conf.Get("common", "vhost_http_timeout"); ok {
v, errRet := strconv.ParseInt(tmpStr, 10, 64)
if errRet != nil || v < 0 {
err = fmt.Errorf("Parse conf error: invalid vhost_http_timeout")
return
} else {
cfg.VhostHttpTimeout = v
}
}
if tmpStr, ok = conf.Get("common", "dashboard_addr"); ok {
cfg.DashboardAddr = tmpStr
} else {
cfg.DashboardAddr = cfg.BindAddr
}
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")
if tmpStr, ok = conf.Get("common", "dashboard_port"); ok {
if v, err = strconv.ParseInt(tmpStr, 10, 64); err != nil {
err = fmt.Errorf("Parse conf error: invalid dashboard_port")
return
} else {
cfg.DashboardPort = int(v)
}
} else {
cfg.DashboardPort = 0
}
tmpStr, ok = conf.Get("common", "dashboard_user")
if ok {
if tmpStr, ok = conf.Get("common", "dashboard_user"); ok {
cfg.DashboardUser = tmpStr
}
tmpStr, ok = conf.Get("common", "dashboard_pwd")
if ok {
if tmpStr, ok = conf.Get("common", "dashboard_pwd"); ok {
cfg.DashboardPwd = tmpStr
}
tmpStr, ok = conf.Get("common", "assets_dir")
if ok {
if tmpStr, ok = conf.Get("common", "assets_dir"); ok {
cfg.AssetsDir = tmpStr
}
tmpStr, ok = conf.Get("common", "log_file")
if ok {
if tmpStr, ok = conf.Get("common", "log_file"); ok {
cfg.LogFile = tmpStr
if cfg.LogFile == "console" {
cfg.LogWay = "console"
@@ -203,51 +233,59 @@ func LoadServerCommonConf(conf ini.File) (cfg *ServerCommonConf, err error) {
}
}
tmpStr, ok = conf.Get("common", "log_level")
if ok {
if tmpStr, ok = conf.Get("common", "log_level"); ok {
cfg.LogLevel = tmpStr
}
tmpStr, ok = conf.Get("common", "log_max_days")
if ok {
if tmpStr, ok = conf.Get("common", "log_max_days"); 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
cfg.Token, _ = conf.Get("common", "token")
if allowPortsStr, ok := conf.Get("common", "allow_ports"); ok {
// e.g. 1000-2000,2001,2002,3000-4000
ports, errRet := util.ParseRangeNumbers(allowPortsStr)
if errRet != nil {
err = fmt.Errorf("Parse conf error: allow_ports: %v", errRet)
return
}
for _, port := range ports {
cfg.AllowPorts[int(port)] = struct{}{}
}
}
// 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)
if tmpStr, ok = conf.Get("common", "max_pool_count"); ok {
if v, err = strconv.ParseInt(tmpStr, 10, 64); err != nil {
err = fmt.Errorf("Parse conf error: invalid max_pool_count")
return
} else {
if v < 0 {
err = fmt.Errorf("Parse conf error: invalid max_pool_count")
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 {
if tmpStr, ok = conf.Get("common", "max_ports_per_client"); ok {
if v, err = strconv.ParseInt(tmpStr, 10, 64); err != nil {
err = fmt.Errorf("Parse conf error: invalid max_ports_per_client")
return
} else {
if v < 0 {
err = fmt.Errorf("Parse conf error: invalid max_ports_per_client")
return
}
cfg.MaxPortsPerClient = v
}
}
if tmpStr, ok = conf.Get("common", "authentication_timeout"); ok {
v, errRet := strconv.ParseInt(tmpStr, 10, 64)
if errRet != nil {
err = fmt.Errorf("Parse conf error: authentication_timeout is incorrect")
@@ -257,20 +295,17 @@ func LoadServerCommonConf(conf ini.File) (cfg *ServerCommonConf, err error) {
}
}
tmpStr, ok = conf.Get("common", "subdomain_host")
if ok {
if tmpStr, ok = conf.Get("common", "subdomain_host"); ok {
cfg.SubDomainHost = strings.ToLower(strings.TrimSpace(tmpStr))
}
tmpStr, ok = conf.Get("common", "tcp_mux")
if ok && tmpStr == "false" {
if tmpStr, ok = conf.Get("common", "tcp_mux"); ok && tmpStr == "false" {
cfg.TcpMux = false
} else {
cfg.TcpMux = true
}
tmpStr, ok = conf.Get("common", "heartbeat_timeout")
if ok {
if tmpStr, ok = conf.Get("common", "heartbeat_timeout"); ok {
v, errRet := strconv.ParseInt(tmpStr, 10, 64)
if errRet != nil {
err = fmt.Errorf("Parse conf error: heartbeat_timeout is incorrect")
@@ -281,3 +316,7 @@ func LoadServerCommonConf(conf ini.File) (cfg *ServerCommonConf, err error) {
}
return
}
func (cfg *ServerCommonConf) Check() (err error) {
return
}

View File

@@ -14,8 +14,11 @@
package errors
import "errors"
import (
"errors"
)
var (
ErrMsgType = errors.New("message type error")
ErrMsgType = errors.New("message type error")
ErrCtlClosed = errors.New("control is closed")
)

View File

@@ -1,4 +1,4 @@
// Copyright 2016 fatedier, fatedier@gmail.com
// Copyright 2018 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.
@@ -12,40 +12,35 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package pool
package msg
import (
"testing"
"io"
"github.com/stretchr/testify/assert"
jsonMsg "github.com/fatedier/golib/msg/json"
)
func TestPutBuf(t *testing.T) {
buf := make([]byte, 512)
PutBuf(buf)
type Message = jsonMsg.Message
buf = make([]byte, 1025)
PutBuf(buf)
var (
msgCtl *jsonMsg.MsgCtl
)
buf = make([]byte, 2*1025)
PutBuf(buf)
buf = make([]byte, 5*1025)
PutBuf(buf)
func init() {
msgCtl = jsonMsg.NewMsgCtl()
for typeByte, msg := range msgTypeMap {
msgCtl.RegisterMsg(typeByte, msg)
}
}
func TestGetBuf(t *testing.T) {
assert := assert.New(t)
buf := GetBuf(200)
assert.Len(buf, 200)
buf = GetBuf(1025)
assert.Len(buf, 1025)
buf = GetBuf(2 * 1024)
assert.Len(buf, 2*1024)
buf = GetBuf(5 * 2000)
assert.Len(buf, 5*2000)
func ReadMsg(c io.Reader) (msg Message, err error) {
return msgCtl.ReadMsg(c)
}
func ReadMsgInto(c io.Reader, msg Message) (err error) {
return msgCtl.ReadMsgInto(c, msg)
}
func WriteMsg(c io.Writer, msg interface{}) (err error) {
return msgCtl.WriteMsg(c, msg)
}

View File

@@ -14,10 +14,7 @@
package msg
import (
"net"
"reflect"
)
import "net"
const (
TypeLogin = 'o'
@@ -40,39 +37,26 @@ const (
)
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[TypeNewVisitorConn] = reflect.TypeOf(NewVisitorConn{})
TypeMap[TypeNewVisitorConnResp] = reflect.TypeOf(NewVisitorConnResp{})
TypeMap[TypePing] = reflect.TypeOf(Ping{})
TypeMap[TypePong] = reflect.TypeOf(Pong{})
TypeMap[TypeUdpPacket] = reflect.TypeOf(UdpPacket{})
TypeMap[TypeNatHoleVisitor] = reflect.TypeOf(NatHoleVisitor{})
TypeMap[TypeNatHoleClient] = reflect.TypeOf(NatHoleClient{})
TypeMap[TypeNatHoleResp] = reflect.TypeOf(NatHoleResp{})
TypeMap[TypeNatHoleSid] = reflect.TypeOf(NatHoleSid{})
for k, v := range TypeMap {
TypeStringMap[v] = k
msgTypeMap = map[byte]interface{}{
TypeLogin: Login{},
TypeLoginResp: LoginResp{},
TypeNewProxy: NewProxy{},
TypeNewProxyResp: NewProxyResp{},
TypeCloseProxy: CloseProxy{},
TypeNewWorkConn: NewWorkConn{},
TypeReqWorkConn: ReqWorkConn{},
TypeStartWorkConn: StartWorkConn{},
TypeNewVisitorConn: NewVisitorConn{},
TypeNewVisitorConnResp: NewVisitorConnResp{},
TypePing: Ping{},
TypePong: Pong{},
TypeUdpPacket: UdpPacket{},
TypeNatHoleVisitor: NatHoleVisitor{},
TypeNatHoleClient: NatHoleClient{},
TypeNatHoleResp: NatHoleResp{},
TypeNatHoleSid: NatHoleSid{},
}
}
// 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 {
@@ -92,7 +76,7 @@ type Login struct {
type LoginResp struct {
Version string `json:"version"`
RunId string `json:"run_id"`
ServerUdpPort int64 `json:"server_udp_port"`
ServerUdpPort int `json:"server_udp_port"`
Error string `json:"error"`
}
@@ -102,25 +86,29 @@ type NewProxy struct {
ProxyType string `json:"proxy_type"`
UseEncryption bool `json:"use_encryption"`
UseCompression bool `json:"use_compression"`
Group string `json:"group"`
GroupKey string `json:"group_key"`
// tcp and udp only
RemotePort int64 `json:"remote_port"`
RemotePort int `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"`
CustomDomains []string `json:"custom_domains"`
SubDomain string `json:"subdomain"`
Locations []string `json:"locations"`
HttpUser string `json:"http_user"`
HttpPwd string `json:"http_pwd"`
HostHeaderRewrite string `json:"host_header_rewrite"`
Headers map[string]string `json:"headers"`
// stcp
Sk string `json:"sk"`
}
type NewProxyResp struct {
ProxyName string `json:"proxy_name"`
Error string `json:"error"`
ProxyName string `json:"proxy_name"`
RemoteAddr string `json:"remote_addr"`
Error string `json:"error"`
}
type CloseProxy struct {
@@ -178,6 +166,7 @@ type NatHoleResp struct {
Sid string `json:"sid"`
VisitorAddr string `json:"visitor_addr"`
ClientAddr string `json:"client_addr"`
Error string `json:"error"`
}
type NatHoleSid struct {

View File

@@ -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 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)
}

View File

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

View File

@@ -17,16 +17,15 @@ 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"
frpIo "github.com/fatedier/golib/io"
gnet "github.com/fatedier/golib/net"
)
const PluginHttpProxy = "http_proxy"
@@ -35,47 +34,6 @@ 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
@@ -106,23 +64,25 @@ 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, realConn)
}
func (hp *HttpProxy) Handle(conn io.ReadWriteCloser, realConn frpNet.Conn) {
wrapConn := frpNet.WrapReadWriteCloserToConn(conn, realConn)
sc, rd := frpNet.NewShareConn(wrapConn)
request, err := http.ReadRequest(bufio.NewReader(rd))
sc, rd := gnet.NewSharedConn(wrapConn)
firstBytes := make([]byte, 7)
_, err := rd.Read(firstBytes)
if err != nil {
wrapConn.Close()
return
}
if request.Method == http.MethodConnect {
hp.handleConnectReq(request, frpIo.WrapReadWriteCloser(rd, wrapConn, nil))
if strings.ToUpper(string(firstBytes)) == "CONNECT" {
bufRd := bufio.NewReader(sc)
request, err := http.ReadRequest(bufRd)
if err != nil {
wrapConn.Close()
return
}
hp.handleConnectReq(request, frpIo.WrapReadWriteCloser(bufRd, wrapConn, wrapConn.Close))
return
}

View File

@@ -17,6 +17,12 @@ package plugin
import (
"fmt"
"io"
"net"
"sync"
frpNet "github.com/fatedier/frp/utils/net"
"github.com/fatedier/golib/errors"
)
// Creators is used for create plugins to handle connections.
@@ -40,6 +46,47 @@ func Create(name string, params map[string]string) (p Plugin, err error) {
type Plugin interface {
Name() string
Handle(conn io.ReadWriteCloser)
Handle(conn io.ReadWriteCloser, realConn frpNet.Conn)
Close() error
}
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)
}

View File

@@ -32,27 +32,30 @@ func init() {
type Socks5Plugin struct {
Server *gosocks5.Server
user string
passwd string
}
func NewSocks5Plugin(params map[string]string) (p Plugin, err error) {
sp := &Socks5Plugin{}
sp.Server, err = gosocks5.New(&gosocks5.Config{
user := params["plugin_user"]
passwd := params["plugin_passwd"]
cfg := &gosocks5.Config{
Logger: log.New(ioutil.Discard, "", log.LstdFlags),
})
}
if user != "" || passwd != "" {
cfg.Credentials = gosocks5.StaticCredentials(map[string]string{user: passwd})
}
sp := &Socks5Plugin{}
sp.Server, err = gosocks5.New(cfg)
p = sp
return
}
func (sp *Socks5Plugin) Handle(conn io.ReadWriteCloser) {
func (sp *Socks5Plugin) Handle(conn io.ReadWriteCloser, realConn frpNet.Conn) {
defer conn.Close()
var wrapConn frpNet.Conn
if realConn, ok := conn.(frpNet.Conn); ok {
wrapConn = realConn
} else {
wrapConn = frpNet.WrapReadWriteCloserToConn(conn, realConn)
}
wrapConn := frpNet.WrapReadWriteCloserToConn(conn, realConn)
sp.Server.ServeConn(wrapConn)
}

View File

@@ -0,0 +1,88 @@
// Copyright 2018 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 (
"io"
"net/http"
frpNet "github.com/fatedier/frp/utils/net"
"github.com/gorilla/mux"
)
const PluginStaticFile = "static_file"
func init() {
Register(PluginStaticFile, NewStaticFilePlugin)
}
type StaticFilePlugin struct {
localPath string
stripPrefix string
httpUser string
httpPasswd string
l *Listener
s *http.Server
}
func NewStaticFilePlugin(params map[string]string) (Plugin, error) {
localPath := params["plugin_local_path"]
stripPrefix := params["plugin_strip_prefix"]
httpUser := params["plugin_http_user"]
httpPasswd := params["plugin_http_passwd"]
listener := NewProxyListener()
sp := &StaticFilePlugin{
localPath: localPath,
stripPrefix: stripPrefix,
httpUser: httpUser,
httpPasswd: httpPasswd,
l: listener,
}
var prefix string
if stripPrefix != "" {
prefix = "/" + stripPrefix + "/"
} else {
prefix = "/"
}
router := mux.NewRouter()
router.Use(frpNet.NewHttpAuthMiddleware(httpUser, httpPasswd).Middleware)
router.PathPrefix(prefix).Handler(frpNet.MakeHttpGzipHandler(http.StripPrefix(prefix, http.FileServer(http.Dir(localPath))))).Methods("GET")
sp.s = &http.Server{
Handler: router,
}
go sp.s.Serve(listener)
return sp, nil
}
func (sp *StaticFilePlugin) Handle(conn io.ReadWriteCloser, realConn frpNet.Conn) {
wrapConn := frpNet.WrapReadWriteCloserToConn(conn, realConn)
sp.l.PutConn(wrapConn)
}
func (sp *StaticFilePlugin) Name() string {
return PluginStaticFile
}
func (sp *StaticFilePlugin) Close() error {
sp.s.Close()
sp.l.Close()
return nil
}

View File

@@ -19,7 +19,9 @@ import (
"io"
"net"
frpIo "github.com/fatedier/frp/utils/io"
frpNet "github.com/fatedier/frp/utils/net"
frpIo "github.com/fatedier/golib/io"
)
const PluginUnixDomainSocket = "unix_domain_socket"
@@ -51,7 +53,7 @@ func NewUnixDomainSocketPlugin(params map[string]string) (p Plugin, err error) {
return
}
func (uds *UnixDomainSocketPlugin) Handle(conn io.ReadWriteCloser) {
func (uds *UnixDomainSocketPlugin) Handle(conn io.ReadWriteCloser, realConn frpNet.Conn) {
localConn, err := net.DialUnix("unix", nil, uds.UnixAddr)
if err != nil {
return

View File

@@ -21,8 +21,9 @@ import (
"time"
"github.com/fatedier/frp/models/msg"
"github.com/fatedier/frp/utils/errors"
"github.com/fatedier/frp/utils/pool"
"github.com/fatedier/golib/errors"
"github.com/fatedier/golib/pool"
)
func NewUdpPacket(buf []byte, laddr, raddr *net.UDPAddr) *msg.UdpPacket {
@@ -82,6 +83,7 @@ func Forwarder(dstAddr *net.UDPAddr, readCh <-chan *msg.UdpPacket, sendCh chan<-
mu.Lock()
delete(udpConnMap, addr)
mu.Unlock()
udpConn.Close()
}()
buf := pool.GetBuf(1500)

View File

@@ -14,8 +14,8 @@ make -f ./Makefile.cross-compiles
rm -rf ./packages
mkdir ./packages
os_all='linux windows darwin'
arch_all='386 amd64 arm mips64 mips64le mips mipsle'
os_all='linux windows darwin freebsd'
arch_all='386 amd64 arm arm64 mips64 mips64le mips mipsle'
for os in $os_all; do
for arch in $arch_all; do

View File

@@ -17,17 +17,21 @@ package server
import (
"fmt"
"io"
"runtime/debug"
"sync"
"time"
"github.com/fatedier/frp/g"
"github.com/fatedier/frp/models/config"
"github.com/fatedier/frp/models/consts"
frpErr "github.com/fatedier/frp/models/errors"
"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"
"github.com/fatedier/golib/control/shutdown"
"github.com/fatedier/golib/crypto"
"github.com/fatedier/golib/errors"
)
type Control struct {
@@ -55,6 +59,9 @@ type Control struct {
// pool count
poolCount int
// ports used, for limitations
portsUsedNum int
// last time got the Ping message
lastPing time.Time
@@ -84,6 +91,7 @@ func NewControl(svr *Service, ctlConn net.Conn, loginMsg *msg.Login) *Control {
workConnCh: make(chan net.Conn, loginMsg.PoolCount+10),
proxies: make(map[string]Proxy),
poolCount: loginMsg.PoolCount,
portsUsedNum: 0,
lastPing: time.Now(),
runId: loginMsg.RunId,
status: consts.Working,
@@ -99,7 +107,7 @@ func (ctl *Control) Start() {
loginRespMsg := &msg.LoginResp{
Version: version.Full(),
RunId: ctl.runId,
ServerUdpPort: config.ServerCommonCfg.BindUdpPort,
ServerUdpPort: g.GlbServerCfg.BindUdpPort,
Error: "",
}
msg.WriteMsg(ctl.conn, loginRespMsg)
@@ -118,6 +126,7 @@ func (ctl *Control) RegisterWorkConn(conn net.Conn) {
defer func() {
if err := recover(); err != nil {
ctl.conn.Error("panic error: %v", err)
ctl.conn.Error(string(debug.Stack()))
}
}()
@@ -138,6 +147,7 @@ func (ctl *Control) GetWorkConn() (workConn net.Conn, err error) {
defer func() {
if err := recover(); err != nil {
ctl.conn.Error("panic error: %v", err)
ctl.conn.Error(string(debug.Stack()))
}
}()
@@ -146,7 +156,7 @@ func (ctl *Control) GetWorkConn() (workConn net.Conn, err error) {
select {
case workConn, ok = <-ctl.workConnCh:
if !ok {
err = errors.ErrCtlClosed
err = frpErr.ErrCtlClosed
return
}
ctl.conn.Debug("get work connection from pool")
@@ -163,12 +173,12 @@ func (ctl *Control) GetWorkConn() (workConn net.Conn, err error) {
select {
case workConn, ok = <-ctl.workConnCh:
if !ok {
err = errors.ErrCtlClosed
err = frpErr.ErrCtlClosed
ctl.conn.Warn("no work connections avaiable, %v", err)
return
}
case <-time.After(time.Duration(config.ServerCommonCfg.UserConnTimeout) * time.Second):
case <-time.After(time.Duration(g.GlbServerCfg.UserConnTimeout) * time.Second):
err = fmt.Errorf("timeout trying to get work connection")
ctl.conn.Warn("%v", err)
return
@@ -192,13 +202,14 @@ func (ctl *Control) writer() {
defer func() {
if err := recover(); err != nil {
ctl.conn.Error("panic error: %v", err)
ctl.conn.Error(string(debug.Stack()))
}
}()
defer ctl.allShutdown.Start()
defer ctl.writerShutdown.Done()
encWriter, err := crypto.NewWriter(ctl.conn, []byte(config.ServerCommonCfg.PrivilegeToken))
encWriter, err := crypto.NewWriter(ctl.conn, []byte(g.GlbServerCfg.Token))
if err != nil {
ctl.conn.Error("crypto new writer error: %v", err)
ctl.allShutdown.Start()
@@ -221,13 +232,14 @@ func (ctl *Control) reader() {
defer func() {
if err := recover(); err != nil {
ctl.conn.Error("panic error: %v", err)
ctl.conn.Error(string(debug.Stack()))
}
}()
defer ctl.allShutdown.Start()
defer ctl.readerShutdown.Done()
encReader := crypto.NewReader(ctl.conn, []byte(config.ServerCommonCfg.PrivilegeToken))
encReader := crypto.NewReader(ctl.conn, []byte(g.GlbServerCfg.Token))
for {
if m, err := msg.ReadMsg(encReader); err != nil {
if err == io.EOF {
@@ -247,27 +259,29 @@ func (ctl *Control) stoper() {
defer func() {
if err := recover(); err != nil {
ctl.conn.Error("panic error: %v", err)
ctl.conn.Error(string(debug.Stack()))
}
}()
ctl.allShutdown.WaitStart()
close(ctl.readCh)
ctl.managerShutdown.WaitDown()
ctl.managerShutdown.WaitDone()
close(ctl.sendCh)
ctl.writerShutdown.WaitDown()
ctl.writerShutdown.WaitDone()
ctl.conn.Close()
ctl.readerShutdown.WaitDown()
ctl.readerShutdown.WaitDone()
ctl.mu.Lock()
defer ctl.mu.Unlock()
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())
@@ -284,6 +298,7 @@ func (ctl *Control) manager() {
defer func() {
if err := recover(); err != nil {
ctl.conn.Error("panic error: %v", err)
ctl.conn.Error(string(debug.Stack()))
}
}()
@@ -296,9 +311,9 @@ func (ctl *Control) manager() {
for {
select {
case <-heartbeat.C:
if time.Since(ctl.lastPing) > time.Duration(config.ServerCommonCfg.HeartBeatTimeout)*time.Second {
if time.Since(ctl.lastPing) > time.Duration(g.GlbServerCfg.HeartBeatTimeout)*time.Second {
ctl.conn.Warn("heartbeat timeout")
ctl.allShutdown.Start()
return
}
case rawMsg, ok := <-ctl.readCh:
if !ok {
@@ -308,7 +323,7 @@ func (ctl *Control) manager() {
switch m := rawMsg.(type) {
case *msg.NewProxy:
// register proxy in this control
err := ctl.RegisterProxy(m)
remoteAddr, err := ctl.RegisterProxy(m)
resp := &msg.NewProxyResp{
ProxyName: m.ProxyName,
}
@@ -316,6 +331,7 @@ func (ctl *Control) manager() {
resp.Error = err.Error()
ctl.conn.Warn("new proxy [%s] error: %v", m.ProxyName, err)
} else {
resp.RemoteAddr = remoteAddr
ctl.conn.Info("new proxy [%s] success", m.ProxyName)
StatsNewProxy(m.ProxyName, m.ProxyType)
}
@@ -332,24 +348,44 @@ func (ctl *Control) manager() {
}
}
func (ctl *Control) RegisterProxy(pxyMsg *msg.NewProxy) (err error) {
func (ctl *Control) RegisterProxy(pxyMsg *msg.NewProxy) (remoteAddr string, err error) {
var pxyConf config.ProxyConf
// Load configures from NewProxy message and check.
pxyConf, err = config.NewProxyConf(pxyMsg)
pxyConf, err = config.NewProxyConfFromMsg(pxyMsg)
if err != nil {
return err
return
}
// 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
return remoteAddr, err
}
err = pxy.Run()
// Check ports used number in each client
if g.GlbServerCfg.MaxPortsPerClient > 0 {
ctl.mu.Lock()
if ctl.portsUsedNum+pxy.GetUsedPortsNum() > int(g.GlbServerCfg.MaxPortsPerClient) {
ctl.mu.Unlock()
err = fmt.Errorf("exceed the max_ports_per_client")
return
}
ctl.portsUsedNum = ctl.portsUsedNum + pxy.GetUsedPortsNum()
ctl.mu.Unlock()
defer func() {
if err != nil {
ctl.mu.Lock()
ctl.portsUsedNum = ctl.portsUsedNum - pxy.GetUsedPortsNum()
ctl.mu.Unlock()
}
}()
}
remoteAddr, err = pxy.Run()
if err != nil {
return err
return
}
defer func() {
if err != nil {
@@ -359,27 +395,32 @@ func (ctl *Control) RegisterProxy(pxyMsg *msg.NewProxy) (err error) {
err = ctl.svr.RegisterProxy(pxyMsg.ProxyName, pxy)
if err != nil {
return err
return
}
ctl.mu.Lock()
ctl.proxies[pxy.GetName()] = pxy
ctl.mu.Unlock()
return nil
return
}
func (ctl *Control) CloseProxy(closeMsg *msg.CloseProxy) (err error) {
ctl.mu.Lock()
defer ctl.mu.Unlock()
pxy, ok := ctl.proxies[closeMsg.ProxyName]
if !ok {
ctl.mu.Unlock()
return
}
if g.GlbServerCfg.MaxPortsPerClient > 0 {
ctl.portsUsedNum = ctl.portsUsedNum - pxy.GetUsedPortsNum()
}
pxy.Close()
ctl.svr.DelProxy(pxy.GetName())
delete(ctl.proxies, closeMsg.ProxyName)
ctl.mu.Unlock()
StatsCloseProxy(pxy.GetName(), pxy.GetConf().GetBaseInfo().ProxyType)
return
}

View File

@@ -21,10 +21,10 @@ import (
"time"
"github.com/fatedier/frp/assets"
"github.com/fatedier/frp/models/config"
"github.com/fatedier/frp/g"
frpNet "github.com/fatedier/frp/utils/net"
"github.com/julienschmidt/httprouter"
"github.com/gorilla/mux"
)
var (
@@ -32,28 +32,26 @@ var (
httpServerWriteTimeout = 10 * time.Second
)
func RunDashboardServer(addr string, port int64) (err error) {
func RunDashboardServer(addr string, port int) (err error) {
// url router
router := httprouter.New()
router := mux.NewRouter()
user, passwd := config.ServerCommonCfg.DashboardUser, config.ServerCommonCfg.DashboardPwd
user, passwd := g.GlbServerCfg.DashboardUser, g.GlbServerCfg.DashboardPwd
router.Use(frpNet.NewHttpAuthMiddleware(user, passwd).Middleware)
// api, see dashboard_api.go
router.GET("/api/serverinfo", frpNet.HttprouterBasicAuth(apiServerInfo, user, passwd))
router.GET("/api/proxy/tcp", frpNet.HttprouterBasicAuth(apiProxyTcp, user, passwd))
router.GET("/api/proxy/udp", frpNet.HttprouterBasicAuth(apiProxyUdp, user, passwd))
router.GET("/api/proxy/http", frpNet.HttprouterBasicAuth(apiProxyHttp, user, passwd))
router.GET("/api/proxy/https", frpNet.HttprouterBasicAuth(apiProxyHttps, user, passwd))
router.GET("/api/proxy/traffic/:name", frpNet.HttprouterBasicAuth(apiProxyTraffic, user, passwd))
router.HandleFunc("/api/serverinfo", apiServerInfo).Methods("GET")
router.HandleFunc("/api/proxy/{type}", apiProxyByType).Methods("GET")
router.HandleFunc("/api/proxy/{type}/{name}", apiProxyByTypeAndName).Methods("GET")
router.HandleFunc("/api/traffic/{name}", apiProxyTraffic).Methods("GET")
// view
router.Handler("GET", "/favicon.ico", http.FileServer(assets.FileSystem))
router.Handler("GET", "/static/*filepath", frpNet.MakeHttpGzipHandler(
frpNet.NewHttpBasicAuthWraper(http.StripPrefix("/static/", http.FileServer(assets.FileSystem)), user, passwd)))
router.Handle("/favicon.ico", http.FileServer(assets.FileSystem)).Methods("GET")
router.PathPrefix("/static/").Handler(frpNet.MakeHttpGzipHandler(http.StripPrefix("/static/", http.FileServer(assets.FileSystem)))).Methods("GET")
router.HandlerFunc("GET", "/", frpNet.HttpBasicAuth(func(w http.ResponseWriter, r *http.Request) {
router.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, "/static/", http.StatusMovedPermanently)
}, user, passwd))
})
address := fmt.Sprintf("%s:%d", addr, port)
server := &http.Server{

View File

@@ -18,12 +18,13 @@ import (
"encoding/json"
"net/http"
"github.com/fatedier/frp/g"
"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"
"github.com/gorilla/mux"
)
type GeneralResponse struct {
@@ -35,13 +36,17 @@ type GeneralResponse struct {
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"`
Version string `json:"version"`
BindPort int `json:"bind_port"`
BindUdpPort int `json:"bind_udp_port"`
VhostHttpPort int `json:"vhost_http_port"`
VhostHttpsPort int `json:"vhost_https_port"`
KcpBindPort int `json:"kcp_bind_port"`
AuthTimeout int64 `json:"auth_timeout"`
SubdomainHost string `json:"subdomain_host"`
MaxPoolCount int64 `json:"max_pool_count"`
MaxPortsPerClient int64 `json:"max_ports_per_client"`
HeartBeatTimeout int64 `json:"heart_beat_timeout"`
TotalTrafficIn int64 `json:"total_traffic_in"`
TotalTrafficOut int64 `json:"total_traffic_out"`
@@ -50,26 +55,30 @@ type ServerInfoResp struct {
ProxyTypeCounts map[string]int64 `json:"proxy_type_count"`
}
func apiServerInfo(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
func apiServerInfo(w http.ResponseWriter, r *http.Request) {
var (
buf []byte
res ServerInfoResp
)
defer func() {
log.Info("Http response [/api/serverinfo]: code [%d]", res.Code)
log.Info("Http response [%s]: code [%d]", r.URL.Path, res.Code)
}()
log.Info("Http request: [/api/serverinfo]")
cfg := config.ServerCommonCfg
log.Info("Http request: [%s]", r.URL.Path)
cfg := &g.GlbServerCfg.ServerCommonConf
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,
Version: version.Full(),
BindPort: cfg.BindPort,
BindUdpPort: cfg.BindUdpPort,
VhostHttpPort: cfg.VhostHttpPort,
VhostHttpsPort: cfg.VhostHttpsPort,
KcpBindPort: cfg.KcpBindPort,
AuthTimeout: cfg.AuthTimeout,
SubdomainHost: cfg.SubDomainHost,
MaxPoolCount: cfg.MaxPoolCount,
MaxPortsPerClient: cfg.MaxPortsPerClient,
HeartBeatTimeout: cfg.HeartBeatTimeout,
TotalTrafficIn: serverStats.TotalTrafficIn,
TotalTrafficOut: serverStats.TotalTrafficOut,
@@ -82,16 +91,69 @@ func apiServerInfo(w http.ResponseWriter, r *http.Request, _ httprouter.Params)
w.Write(buf)
}
type BaseOutConf struct {
config.BaseProxyConf
}
type TcpOutConf struct {
BaseOutConf
RemotePort int `json:"remote_port"`
}
type UdpOutConf struct {
BaseOutConf
RemotePort int `json:"remote_port"`
}
type HttpOutConf struct {
BaseOutConf
config.DomainConf
Locations []string `json:"locations"`
HostHeaderRewrite string `json:"host_header_rewrite"`
}
type HttpsOutConf struct {
BaseOutConf
config.DomainConf
}
type StcpOutConf struct {
BaseOutConf
}
type XtcpOutConf struct {
BaseOutConf
}
func getConfByType(proxyType string) interface{} {
switch proxyType {
case consts.TcpProxy:
return &TcpOutConf{}
case consts.UdpProxy:
return &UdpOutConf{}
case consts.HttpProxy:
return &HttpOutConf{}
case consts.HttpsProxy:
return &HttpsOutConf{}
case consts.StcpProxy:
return &StcpOutConf{}
case consts.XtcpProxy:
return &XtcpOutConf{}
default:
return nil
}
}
// 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"`
Name string `json:"name"`
Conf interface{} `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 {
@@ -99,72 +161,27 @@ type GetProxyInfoResp struct {
Proxies []*ProxyStatsInfo `json:"proxies"`
}
// api/proxy/tcp
func apiProxyTcp(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
// api/proxy/:type
func apiProxyByType(w http.ResponseWriter, r *http.Request) {
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]")
params := mux.Vars(r)
proxyType := params["type"]
res.Proxies = getProxyStatsByType(consts.TcpProxy)
defer func() {
log.Info("Http response [%s]: code [%d]", r.URL.Path, res.Code)
log.Info(r.URL.Path)
log.Info(r.URL.RawPath)
}()
log.Info("Http request: [%s]", r.URL.Path)
res.Proxies = getProxyStatsByType(proxyType)
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) {
@@ -173,7 +190,16 @@ func getProxyStatsByType(proxyType string) (proxyInfos []*ProxyStatsInfo) {
for _, ps := range proxyStats {
proxyInfo := &ProxyStatsInfo{}
if pxy, ok := ServerService.pxyManager.GetByName(ps.Name); ok {
proxyInfo.Conf = pxy.GetConf()
content, err := json.Marshal(pxy.GetConf())
if err != nil {
log.Warn("marshal proxy [%s] conf info error: %v", ps.Name, err)
continue
}
proxyInfo.Conf = getConfByType(ps.Type)
if err = json.Unmarshal(content, &proxyInfo.Conf); err != nil {
log.Warn("unmarshal proxy [%s] conf info error: %v", ps.Name, err)
continue
}
proxyInfo.Status = consts.Online
} else {
proxyInfo.Status = consts.Offline
@@ -189,7 +215,78 @@ func getProxyStatsByType(proxyType string) (proxyInfos []*ProxyStatsInfo) {
return
}
// api/proxy/traffic/:name
// Get proxy info by name.
type GetProxyStatsResp struct {
GeneralResponse
Name string `json:"name"`
Conf interface{} `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"`
}
// api/proxy/:type/:name
func apiProxyByTypeAndName(w http.ResponseWriter, r *http.Request) {
var (
buf []byte
res GetProxyStatsResp
)
params := mux.Vars(r)
proxyType := params["type"]
name := params["name"]
defer func() {
log.Info("Http response [%s]: code [%d]", r.URL.Path, res.Code)
}()
log.Info("Http request: [%s]", r.URL.Path)
res = getProxyStatsByTypeAndName(proxyType, name)
buf, _ = json.Marshal(&res)
w.Write(buf)
}
func getProxyStatsByTypeAndName(proxyType string, proxyName string) (proxyInfo GetProxyStatsResp) {
proxyInfo.Name = proxyName
ps := StatsGetProxiesByTypeAndName(proxyType, proxyName)
if ps == nil {
proxyInfo.Code = 1
proxyInfo.Msg = "no proxy info found"
} else {
if pxy, ok := ServerService.pxyManager.GetByName(proxyName); ok {
content, err := json.Marshal(pxy.GetConf())
if err != nil {
log.Warn("marshal proxy [%s] conf info error: %v", ps.Name, err)
proxyInfo.Code = 2
proxyInfo.Msg = "parse conf error"
return
}
proxyInfo.Conf = getConfByType(ps.Type)
if err = json.Unmarshal(content, &proxyInfo.Conf); err != nil {
log.Warn("unmarshal proxy [%s] conf info error: %v", ps.Name, err)
proxyInfo.Code = 2
proxyInfo.Msg = "parse conf error"
return
}
proxyInfo.Status = consts.Online
} else {
proxyInfo.Status = consts.Offline
}
proxyInfo.TodayTrafficIn = ps.TodayTrafficIn
proxyInfo.TodayTrafficOut = ps.TodayTrafficOut
proxyInfo.CurConns = ps.CurConns
proxyInfo.LastStartTime = ps.LastStartTime
proxyInfo.LastCloseTime = ps.LastCloseTime
}
return
}
// api/traffic/:name
type GetProxyTrafficResp struct {
GeneralResponse
@@ -198,17 +295,18 @@ type GetProxyTrafficResp struct {
TrafficOut []int64 `json:"traffic_out"`
}
func apiProxyTraffic(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
func apiProxyTraffic(w http.ResponseWriter, r *http.Request) {
var (
buf []byte
res GetProxyTrafficResp
)
name := params.ByName("name")
params := mux.Vars(r)
name := params["name"]
defer func() {
log.Info("Http response [/api/proxy/traffic/:name]: code [%d]", res.Code)
log.Info("Http response [%s]: code [%d]", r.URL.Path, res.Code)
}()
log.Info("Http request: [/api/proxy/traffic/:name]")
log.Info("Http request: [%s]", r.URL.Path)
res.Name = name
proxyTrafficInfo := StatsGetProxyTraffic(name)

View File

@@ -1,10 +1,10 @@
// Copyright 2014 beego Author. All Rights Reserved.
// Copyright 2018 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
// 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,
@@ -12,14 +12,14 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package logs
package group
import (
"testing"
"errors"
)
func TestConn(t *testing.T) {
log := NewLogger(1000)
log.SetLogger("conn", `{"net":"tcp","addr":":7020"}`)
log.Informational("informational")
}
var (
ErrGroupAuthFailed = errors.New("group auth failed")
ErrGroupParamsInvalid = errors.New("group params invalid")
ErrListenerClosed = errors.New("group listener closed")
)

200
server/group/tcp.go Normal file
View File

@@ -0,0 +1,200 @@
// Copyright 2018 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 group
import (
"fmt"
"net"
"sync"
"github.com/fatedier/frp/server/ports"
gerr "github.com/fatedier/golib/errors"
)
type TcpGroupListener struct {
groupName string
group *TcpGroup
addr net.Addr
closeCh chan struct{}
}
func newTcpGroupListener(name string, group *TcpGroup, addr net.Addr) *TcpGroupListener {
return &TcpGroupListener{
groupName: name,
group: group,
addr: addr,
closeCh: make(chan struct{}),
}
}
func (ln *TcpGroupListener) Accept() (c net.Conn, err error) {
var ok bool
select {
case <-ln.closeCh:
return nil, ErrListenerClosed
case c, ok = <-ln.group.Accept():
if !ok {
return nil, ErrListenerClosed
}
return c, nil
}
}
func (ln *TcpGroupListener) Addr() net.Addr {
return ln.addr
}
func (ln *TcpGroupListener) Close() (err error) {
close(ln.closeCh)
ln.group.CloseListener(ln)
return
}
type TcpGroup struct {
group string
groupKey string
addr string
port int
realPort int
acceptCh chan net.Conn
index uint64
tcpLn net.Listener
lns []*TcpGroupListener
ctl *TcpGroupCtl
mu sync.Mutex
}
func NewTcpGroup(ctl *TcpGroupCtl) *TcpGroup {
return &TcpGroup{
lns: make([]*TcpGroupListener, 0),
ctl: ctl,
acceptCh: make(chan net.Conn),
}
}
func (tg *TcpGroup) Listen(proxyName string, group string, groupKey string, addr string, port int) (ln *TcpGroupListener, realPort int, err error) {
tg.mu.Lock()
defer tg.mu.Unlock()
if len(tg.lns) == 0 {
realPort, err = tg.ctl.portManager.Acquire(proxyName, port)
if err != nil {
return
}
tcpLn, errRet := net.Listen("tcp", fmt.Sprintf("%s:%d", addr, port))
if errRet != nil {
err = errRet
return
}
ln = newTcpGroupListener(group, tg, tcpLn.Addr())
tg.group = group
tg.groupKey = groupKey
tg.addr = addr
tg.port = port
tg.realPort = realPort
tg.tcpLn = tcpLn
tg.lns = append(tg.lns, ln)
if tg.acceptCh == nil {
tg.acceptCh = make(chan net.Conn)
}
go tg.worker()
} else {
if tg.group != group || tg.addr != addr || tg.port != port {
err = ErrGroupParamsInvalid
return
}
if tg.groupKey != groupKey {
err = ErrGroupAuthFailed
return
}
ln = newTcpGroupListener(group, tg, tg.lns[0].Addr())
realPort = tg.realPort
tg.lns = append(tg.lns, ln)
}
return
}
func (tg *TcpGroup) worker() {
for {
c, err := tg.tcpLn.Accept()
if err != nil {
return
}
err = gerr.PanicToError(func() {
tg.acceptCh <- c
})
if err != nil {
return
}
}
}
func (tg *TcpGroup) Accept() <-chan net.Conn {
return tg.acceptCh
}
func (tg *TcpGroup) CloseListener(ln *TcpGroupListener) {
tg.mu.Lock()
defer tg.mu.Unlock()
for i, tmpLn := range tg.lns {
if tmpLn == ln {
tg.lns = append(tg.lns[:i], tg.lns[i+1:]...)
break
}
}
if len(tg.lns) == 0 {
close(tg.acceptCh)
tg.tcpLn.Close()
tg.ctl.portManager.Release(tg.realPort)
tg.ctl.RemoveGroup(tg.group)
}
}
type TcpGroupCtl struct {
groups map[string]*TcpGroup
portManager *ports.PortManager
mu sync.Mutex
}
func NewTcpGroupCtl(portManager *ports.PortManager) *TcpGroupCtl {
return &TcpGroupCtl{
groups: make(map[string]*TcpGroup),
portManager: portManager,
}
}
func (tgc *TcpGroupCtl) Listen(proxyNanme string, group string, groupKey string,
addr string, port int) (l net.Listener, realPort int, err error) {
tgc.mu.Lock()
defer tgc.mu.Unlock()
if tcpGroup, ok := tgc.groups[group]; ok {
return tcpGroup.Listen(proxyNanme, group, groupKey, addr, port)
} else {
tcpGroup = NewTcpGroup(tgc)
tgc.groups[group] = tcpGroup
return tcpGroup.Listen(proxyNanme, group, groupKey, addr, port)
}
}
func (tgc *TcpGroupCtl) RemoveGroup(group string) {
tgc.mu.Lock()
defer tgc.mu.Unlock()
delete(tgc.groups, group)
}

View File

@@ -19,9 +19,10 @@ import (
"io"
"sync"
frpIo "github.com/fatedier/frp/utils/io"
frpNet "github.com/fatedier/frp/utils/net"
"github.com/fatedier/frp/utils/util"
frpIo "github.com/fatedier/golib/io"
)
type ControlManager struct {

View File

@@ -18,7 +18,7 @@ import (
"sync"
"time"
"github.com/fatedier/frp/models/config"
"github.com/fatedier/frp/g"
"github.com/fatedier/frp/utils/log"
"github.com/fatedier/frp/utils/metric"
)
@@ -92,19 +92,19 @@ func StatsClearUselessInfo() {
}
func StatsNewClient() {
if config.ServerCommonCfg.DashboardPort != 0 {
if g.GlbServerCfg.DashboardPort != 0 {
globalStats.ClientCounts.Inc(1)
}
}
func StatsCloseClient() {
if config.ServerCommonCfg.DashboardPort != 0 {
if g.GlbServerCfg.DashboardPort != 0 {
globalStats.ClientCounts.Dec(1)
}
}
func StatsNewProxy(name string, proxyType string) {
if config.ServerCommonCfg.DashboardPort != 0 {
if g.GlbServerCfg.DashboardPort != 0 {
globalStats.mu.Lock()
defer globalStats.mu.Unlock()
counter, ok := globalStats.ProxyTypeCounts[proxyType]
@@ -130,7 +130,7 @@ func StatsNewProxy(name string, proxyType string) {
}
func StatsCloseProxy(proxyName string, proxyType string) {
if config.ServerCommonCfg.DashboardPort != 0 {
if g.GlbServerCfg.DashboardPort != 0 {
globalStats.mu.Lock()
defer globalStats.mu.Unlock()
if counter, ok := globalStats.ProxyTypeCounts[proxyType]; ok {
@@ -143,7 +143,7 @@ func StatsCloseProxy(proxyName string, proxyType string) {
}
func StatsOpenConnection(name string) {
if config.ServerCommonCfg.DashboardPort != 0 {
if g.GlbServerCfg.DashboardPort != 0 {
globalStats.CurConns.Inc(1)
globalStats.mu.Lock()
@@ -157,7 +157,7 @@ func StatsOpenConnection(name string) {
}
func StatsCloseConnection(name string) {
if config.ServerCommonCfg.DashboardPort != 0 {
if g.GlbServerCfg.DashboardPort != 0 {
globalStats.CurConns.Dec(1)
globalStats.mu.Lock()
@@ -171,7 +171,7 @@ func StatsCloseConnection(name string) {
}
func StatsAddTrafficIn(name string, trafficIn int64) {
if config.ServerCommonCfg.DashboardPort != 0 {
if g.GlbServerCfg.DashboardPort != 0 {
globalStats.TotalTrafficIn.Inc(trafficIn)
globalStats.mu.Lock()
@@ -186,7 +186,7 @@ func StatsAddTrafficIn(name string, trafficIn int64) {
}
func StatsAddTrafficOut(name string, trafficOut int64) {
if config.ServerCommonCfg.DashboardPort != 0 {
if g.GlbServerCfg.DashboardPort != 0 {
globalStats.TotalTrafficOut.Inc(trafficOut)
globalStats.mu.Lock()
@@ -263,6 +263,37 @@ func StatsGetProxiesByType(proxyType string) []*ProxyStats {
return res
}
func StatsGetProxiesByTypeAndName(proxyType string, proxyName string) (res *ProxyStats) {
globalStats.mu.Lock()
defer globalStats.mu.Unlock()
for name, proxyStats := range globalStats.ProxyStatistics {
if proxyStats.ProxyType != proxyType {
continue
}
if name != proxyName {
continue
}
res = &ProxyStats{
Name: name,
Type: proxyStats.ProxyType,
TodayTrafficIn: proxyStats.TrafficIn.TodayCount(),
TodayTrafficOut: proxyStats.TrafficOut.TodayCount(),
CurConns: proxyStats.CurConns.Count(),
}
if !proxyStats.LastStartTime.IsZero() {
res.LastStartTime = proxyStats.LastStartTime.Format("01-02 15:04:05")
}
if !proxyStats.LastCloseTime.IsZero() {
res.LastCloseTime = proxyStats.LastCloseTime.Format("01-02 15:04:05")
}
break
}
return
}
type ProxyTrafficInfo struct {
Name string
TrafficIn []int64

View File

@@ -8,10 +8,11 @@ import (
"time"
"github.com/fatedier/frp/models/msg"
"github.com/fatedier/frp/utils/errors"
"github.com/fatedier/frp/utils/log"
"github.com/fatedier/frp/utils/pool"
"github.com/fatedier/frp/utils/util"
"github.com/fatedier/golib/errors"
"github.com/fatedier/golib/pool"
)
// Timeout seconds.
@@ -105,10 +106,21 @@ func (nc *NatHoleController) HandleVisitor(m *msg.NatHoleVisitor, raddr *net.UDP
}
nc.mu.Lock()
clientCfg, ok := nc.clientCfgs[m.ProxyName]
if !ok || m.SignKey != util.GetAuthKey(clientCfg.Sk, m.Timestamp) {
if !ok {
nc.mu.Unlock()
errInfo := fmt.Sprintf("xtcp server for [%s] doesn't exist", m.ProxyName)
log.Debug(errInfo)
nc.listener.WriteToUDP(nc.GenNatHoleResponse(nil, errInfo), raddr)
return
}
if m.SignKey != util.GetAuthKey(clientCfg.Sk, m.Timestamp) {
nc.mu.Unlock()
errInfo := fmt.Sprintf("xtcp connection of [%s] auth failed", m.ProxyName)
log.Debug(errInfo)
nc.listener.WriteToUDP(nc.GenNatHoleResponse(nil, errInfo), raddr)
return
}
nc.sessions[sid] = session
nc.mu.Unlock()
log.Trace("handle visitor message, sid [%s]", sid)
@@ -129,7 +141,7 @@ func (nc *NatHoleController) HandleVisitor(m *msg.NatHoleVisitor, raddr *net.UDP
// Wait client connections.
select {
case <-session.NotifyCh:
resp := nc.GenNatHoleResponse(raddr, session)
resp := nc.GenNatHoleResponse(session, "")
log.Trace("send nat hole response to visitor")
nc.listener.WriteToUDP(resp, raddr)
case <-time.After(time.Duration(NatHoleTimeout) * time.Second):
@@ -148,16 +160,27 @@ func (nc *NatHoleController) HandleClient(m *msg.NatHoleClient, raddr *net.UDPAd
session.ClientAddr = raddr
session.NotifyCh <- struct{}{}
resp := nc.GenNatHoleResponse(raddr, session)
resp := nc.GenNatHoleResponse(session, "")
log.Trace("send nat hole response to client")
nc.listener.WriteToUDP(resp, raddr)
}
func (nc *NatHoleController) GenNatHoleResponse(raddr *net.UDPAddr, session *NatHoleSession) []byte {
func (nc *NatHoleController) GenNatHoleResponse(session *NatHoleSession, errInfo string) []byte {
var (
sid string
visitorAddr string
clientAddr string
)
if session != nil {
sid = session.Sid
visitorAddr = session.VisitorAddr.String()
clientAddr = session.ClientAddr.String()
}
m := &msg.NatHoleResp{
Sid: session.Sid,
VisitorAddr: session.VisitorAddr.String(),
ClientAddr: session.ClientAddr.String(),
Sid: sid,
VisitorAddr: visitorAddr,
ClientAddr: clientAddr,
Error: errInfo,
}
b := bytes.NewBuffer(nil)
err := msg.WriteMsg(b, m)

180
server/ports/ports.go Normal file
View File

@@ -0,0 +1,180 @@
package ports
import (
"errors"
"fmt"
"net"
"sync"
"time"
)
const (
MinPort = 1
MaxPort = 65535
MaxPortReservedDuration = time.Duration(24) * time.Hour
CleanReservedPortsInterval = time.Hour
)
var (
ErrPortAlreadyUsed = errors.New("port already used")
ErrPortNotAllowed = errors.New("port not allowed")
ErrPortUnAvailable = errors.New("port unavailable")
ErrNoAvailablePort = errors.New("no available port")
)
type PortCtx struct {
ProxyName string
Port int
Closed bool
UpdateTime time.Time
}
type PortManager struct {
reservedPorts map[string]*PortCtx
usedPorts map[int]*PortCtx
freePorts map[int]struct{}
bindAddr string
netType string
mu sync.Mutex
}
func NewPortManager(netType string, bindAddr string, allowPorts map[int]struct{}) *PortManager {
pm := &PortManager{
reservedPorts: make(map[string]*PortCtx),
usedPorts: make(map[int]*PortCtx),
freePorts: make(map[int]struct{}),
bindAddr: bindAddr,
netType: netType,
}
if len(allowPorts) > 0 {
for port, _ := range allowPorts {
pm.freePorts[port] = struct{}{}
}
} else {
for i := MinPort; i <= MaxPort; i++ {
pm.freePorts[i] = struct{}{}
}
}
go pm.cleanReservedPortsWorker()
return pm
}
func (pm *PortManager) Acquire(name string, port int) (realPort int, err error) {
portCtx := &PortCtx{
ProxyName: name,
Closed: false,
UpdateTime: time.Now(),
}
var ok bool
pm.mu.Lock()
defer func() {
if err == nil {
portCtx.Port = realPort
}
pm.mu.Unlock()
}()
// check reserved ports first
if port == 0 {
if ctx, ok := pm.reservedPorts[name]; ok {
if pm.isPortAvailable(ctx.Port) {
realPort = ctx.Port
pm.usedPorts[realPort] = portCtx
pm.reservedPorts[name] = portCtx
delete(pm.freePorts, realPort)
return
}
}
}
if port == 0 {
// get random port
count := 0
maxTryTimes := 5
for k, _ := range pm.freePorts {
count++
if count > maxTryTimes {
break
}
if pm.isPortAvailable(k) {
realPort = k
pm.usedPorts[realPort] = portCtx
pm.reservedPorts[name] = portCtx
delete(pm.freePorts, realPort)
break
}
}
if realPort == 0 {
err = ErrNoAvailablePort
}
} else {
// specified port
if _, ok = pm.freePorts[port]; ok {
if pm.isPortAvailable(port) {
realPort = port
pm.usedPorts[realPort] = portCtx
pm.reservedPorts[name] = portCtx
delete(pm.freePorts, realPort)
} else {
err = ErrPortUnAvailable
}
} else {
if _, ok = pm.usedPorts[port]; ok {
err = ErrPortAlreadyUsed
} else {
err = ErrPortNotAllowed
}
}
}
return
}
func (pm *PortManager) isPortAvailable(port int) bool {
if pm.netType == "udp" {
addr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", pm.bindAddr, port))
if err != nil {
return false
}
l, err := net.ListenUDP("udp", addr)
if err != nil {
return false
}
l.Close()
return true
} else {
l, err := net.Listen(pm.netType, fmt.Sprintf("%s:%d", pm.bindAddr, port))
if err != nil {
return false
}
l.Close()
return true
}
}
func (pm *PortManager) Release(port int) {
pm.mu.Lock()
defer pm.mu.Unlock()
if ctx, ok := pm.usedPorts[port]; ok {
pm.freePorts[port] = struct{}{}
delete(pm.usedPorts, port)
ctx.Closed = true
ctx.UpdateTime = time.Now()
}
}
// Release reserved port if it isn't used in last 24 hours.
func (pm *PortManager) cleanReservedPortsWorker() {
for {
time.Sleep(CleanReservedPortsInterval)
pm.mu.Lock()
for name, ctx := range pm.reservedPorts {
if ctx.Closed && time.Since(ctx.UpdateTime) > MaxPortReservedDuration {
delete(pm.reservedPorts, name)
}
}
pm.mu.Unlock()
}
}

View File

@@ -19,34 +19,41 @@ import (
"fmt"
"io"
"net"
"strings"
"sync"
"time"
"github.com/fatedier/frp/g"
"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/util"
"github.com/fatedier/frp/utils/vhost"
"github.com/fatedier/golib/errors"
frpIo "github.com/fatedier/golib/io"
)
type Proxy interface {
Run() error
Run() (remoteAddr string, err error)
GetControl() *Control
GetName() string
GetConf() config.ProxyConf
GetWorkConnFromPool() (workConn frpNet.Conn, err error)
GetUsedPortsNum() int
Close()
log.Logger
}
type BaseProxy struct {
name string
ctl *Control
listeners []frpNet.Listener
mu sync.RWMutex
name string
ctl *Control
listeners []frpNet.Listener
usedPortsNum int
mu sync.RWMutex
log.Logger
}
@@ -58,6 +65,10 @@ func (pxy *BaseProxy) GetControl() *Control {
return pxy.ctl
}
func (pxy *BaseProxy) GetUsedPortsNum() int {
return pxy.usedPortsNum
}
func (pxy *BaseProxy) Close() {
pxy.Info("proxy closing")
for _, l := range pxy.listeners {
@@ -117,13 +128,14 @@ func (pxy *BaseProxy) startListenHandler(p Proxy, handler func(Proxy, frpNet.Con
func NewProxy(ctl *Control, pxyConf config.ProxyConf) (pxy Proxy, err error) {
basePxy := BaseProxy{
name: pxyConf.GetName(),
name: pxyConf.GetBaseInfo().ProxyName,
ctl: ctl,
listeners: make([]frpNet.Listener, 0),
Logger: log.NewPrefixLogger(ctl.runId),
}
switch cfg := pxyConf.(type) {
case *config.TcpProxyConf:
basePxy.usedPortsNum = 1
pxy = &TcpProxy{
BaseProxy: basePxy,
cfg: cfg,
@@ -139,6 +151,7 @@ func NewProxy(ctl *Control, pxyConf config.ProxyConf) (pxy Proxy, err error) {
cfg: cfg,
}
case *config.UdpProxyConf:
basePxy.usedPortsNum = 1
pxy = &UdpProxy{
BaseProxy: basePxy,
cfg: cfg,
@@ -163,19 +176,51 @@ func NewProxy(ctl *Control, pxyConf config.ProxyConf) (pxy Proxy, err error) {
type TcpProxy struct {
BaseProxy
cfg *config.TcpProxyConf
realPort int
}
func (pxy *TcpProxy) Run() error {
listener, err := frpNet.ListenTcp(config.ServerCommonCfg.ProxyBindAddr, pxy.cfg.RemotePort)
if err != nil {
return err
func (pxy *TcpProxy) Run() (remoteAddr string, err error) {
if pxy.cfg.Group != "" {
l, realPort, errRet := pxy.ctl.svr.tcpGroupCtl.Listen(pxy.name, pxy.cfg.Group, pxy.cfg.GroupKey, g.GlbServerCfg.ProxyBindAddr, pxy.cfg.RemotePort)
if errRet != nil {
err = errRet
return
}
defer func() {
if err != nil {
l.Close()
}
}()
pxy.realPort = realPort
listener := frpNet.WrapLogListener(l)
listener.AddLogPrefix(pxy.name)
pxy.listeners = append(pxy.listeners, listener)
pxy.Info("tcp proxy listen port [%d] in group [%s]", pxy.cfg.RemotePort, pxy.cfg.Group)
} else {
pxy.realPort, err = pxy.ctl.svr.tcpPortManager.Acquire(pxy.name, pxy.cfg.RemotePort)
if err != nil {
return
}
defer func() {
if err != nil {
pxy.ctl.svr.tcpPortManager.Release(pxy.realPort)
}
}()
listener, errRet := frpNet.ListenTcp(g.GlbServerCfg.ProxyBindAddr, pxy.realPort)
if errRet != nil {
err = errRet
return
}
listener.AddLogPrefix(pxy.name)
pxy.listeners = append(pxy.listeners, listener)
pxy.Info("tcp proxy listen port [%d]", pxy.cfg.RemotePort)
}
listener.AddLogPrefix(pxy.name)
pxy.listeners = append(pxy.listeners, listener)
pxy.Info("tcp proxy listen port [%d]", pxy.cfg.RemotePort)
pxy.cfg.RemotePort = pxy.realPort
remoteAddr = fmt.Sprintf(":%d", pxy.realPort)
pxy.startListenHandler(pxy, HandleUserTcpConnection)
return nil
return
}
func (pxy *TcpProxy) GetConf() config.ProxyConf {
@@ -184,6 +229,9 @@ func (pxy *TcpProxy) GetConf() config.ProxyConf {
func (pxy *TcpProxy) Close() {
pxy.BaseProxy.Close()
if pxy.cfg.Group == "" {
pxy.ctl.svr.tcpPortManager.Release(pxy.realPort)
}
}
type HttpProxy struct {
@@ -193,9 +241,10 @@ type HttpProxy struct {
closeFuncs []func()
}
func (pxy *HttpProxy) Run() (err error) {
func (pxy *HttpProxy) Run() (remoteAddr string, err error) {
routeConfig := vhost.VhostRouteConfig{
RewriteHost: pxy.cfg.HostHeaderRewrite,
Headers: pxy.cfg.Headers,
Username: pxy.cfg.HttpUser,
Password: pxy.cfg.HttpPwd,
CreateConnFn: pxy.GetRealConn,
@@ -205,16 +254,19 @@ func (pxy *HttpProxy) Run() (err error) {
if len(locations) == 0 {
locations = []string{""}
}
addrs := make([]string, 0)
for _, domain := range pxy.cfg.CustomDomains {
routeConfig.Domain = domain
for _, location := range locations {
routeConfig.Location = location
err := pxy.ctl.svr.httpReverseProxy.Register(routeConfig)
err = pxy.ctl.svr.httpReverseProxy.Register(routeConfig)
if err != nil {
return err
return
}
tmpDomain := routeConfig.Domain
tmpLocation := routeConfig.Location
addrs = append(addrs, util.CanonicalAddr(tmpDomain, int(g.GlbServerCfg.VhostHttpPort)))
pxy.closeFuncs = append(pxy.closeFuncs, func() {
pxy.ctl.svr.httpReverseProxy.UnRegister(tmpDomain, tmpLocation)
})
@@ -223,21 +275,23 @@ func (pxy *HttpProxy) Run() (err error) {
}
if pxy.cfg.SubDomain != "" {
routeConfig.Domain = pxy.cfg.SubDomain + "." + config.ServerCommonCfg.SubDomainHost
routeConfig.Domain = pxy.cfg.SubDomain + "." + g.GlbServerCfg.SubDomainHost
for _, location := range locations {
routeConfig.Location = location
err := pxy.ctl.svr.httpReverseProxy.Register(routeConfig)
err = pxy.ctl.svr.httpReverseProxy.Register(routeConfig)
if err != nil {
return err
return
}
tmpDomain := routeConfig.Domain
tmpLocation := routeConfig.Location
addrs = append(addrs, util.CanonicalAddr(tmpDomain, g.GlbServerCfg.VhostHttpPort))
pxy.closeFuncs = append(pxy.closeFuncs, func() {
pxy.ctl.svr.httpReverseProxy.UnRegister(tmpDomain, tmpLocation)
})
pxy.Info("http proxy listen for host [%s] location [%s]", routeConfig.Domain, routeConfig.Location)
}
}
remoteAddr = strings.Join(addrs, ",")
return
}
@@ -254,7 +308,7 @@ func (pxy *HttpProxy) GetRealConn() (workConn frpNet.Conn, err error) {
var rwc io.ReadWriteCloser = tmpConn
if pxy.cfg.UseEncryption {
rwc, err = frpIo.WithEncryption(rwc, []byte(config.ServerCommonCfg.PrivilegeToken))
rwc, err = frpIo.WithEncryption(rwc, []byte(g.GlbServerCfg.Token))
if err != nil {
pxy.Error("create encryption stream error: %v", err)
return
@@ -264,9 +318,18 @@ func (pxy *HttpProxy) GetRealConn() (workConn frpNet.Conn, err error) {
rwc = frpIo.WithCompression(rwc)
}
workConn = frpNet.WrapReadWriteCloserToConn(rwc, tmpConn)
workConn = frpNet.WrapStatsConn(workConn, pxy.updateStatsAfterClosedConn)
StatsOpenConnection(pxy.GetName())
return
}
func (pxy *HttpProxy) updateStatsAfterClosedConn(totalRead, totalWrite int64) {
name := pxy.GetName()
StatsCloseConnection(name)
StatsAddTrafficIn(name, totalWrite)
StatsAddTrafficOut(name, totalRead)
}
func (pxy *HttpProxy) Close() {
pxy.BaseProxy.Close()
for _, closeFn := range pxy.closeFuncs {
@@ -279,32 +342,38 @@ type HttpsProxy struct {
cfg *config.HttpsProxyConf
}
func (pxy *HttpsProxy) Run() (err error) {
func (pxy *HttpsProxy) Run() (remoteAddr string, err error) {
routeConfig := &vhost.VhostRouteConfig{}
addrs := make([]string, 0)
for _, domain := range pxy.cfg.CustomDomains {
routeConfig.Domain = domain
l, err := pxy.ctl.svr.VhostHttpsMuxer.Listen(routeConfig)
if err != nil {
return err
l, errRet := pxy.ctl.svr.VhostHttpsMuxer.Listen(routeConfig)
if errRet != nil {
err = errRet
return
}
l.AddLogPrefix(pxy.name)
pxy.Info("https proxy listen for host [%s]", routeConfig.Domain)
pxy.listeners = append(pxy.listeners, l)
addrs = append(addrs, util.CanonicalAddr(routeConfig.Domain, g.GlbServerCfg.VhostHttpsPort))
}
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
routeConfig.Domain = pxy.cfg.SubDomain + "." + g.GlbServerCfg.SubDomainHost
l, errRet := pxy.ctl.svr.VhostHttpsMuxer.Listen(routeConfig)
if errRet != nil {
err = errRet
return
}
l.AddLogPrefix(pxy.name)
pxy.Info("https proxy listen for host [%s]", routeConfig.Domain)
pxy.listeners = append(pxy.listeners, l)
addrs = append(addrs, util.CanonicalAddr(routeConfig.Domain, int(g.GlbServerCfg.VhostHttpsPort)))
}
pxy.startListenHandler(pxy, HandleUserTcpConnection)
remoteAddr = strings.Join(addrs, ",")
return
}
@@ -321,17 +390,18 @@ type StcpProxy struct {
cfg *config.StcpProxyConf
}
func (pxy *StcpProxy) Run() error {
listener, err := pxy.ctl.svr.visitorManager.Listen(pxy.GetName(), pxy.cfg.Sk)
if err != nil {
return err
func (pxy *StcpProxy) Run() (remoteAddr string, err error) {
listener, errRet := pxy.ctl.svr.visitorManager.Listen(pxy.GetName(), pxy.cfg.Sk)
if errRet != nil {
err = errRet
return
}
listener.AddLogPrefix(pxy.name)
pxy.listeners = append(pxy.listeners, listener)
pxy.Info("stcp proxy custom listen success")
pxy.startListenHandler(pxy, HandleUserTcpConnection)
return nil
return
}
func (pxy *StcpProxy) GetConf() config.ProxyConf {
@@ -350,10 +420,11 @@ type XtcpProxy struct {
closeCh chan struct{}
}
func (pxy *XtcpProxy) Run() error {
func (pxy *XtcpProxy) Run() (remoteAddr string, err error) {
if pxy.ctl.svr.natHoleController == nil {
pxy.Error("udp port for xtcp is not specified.")
return fmt.Errorf("xtcp is not supported in frps")
err = fmt.Errorf("xtcp is not supported in frps")
return
}
sidCh := pxy.ctl.svr.natHoleController.ListenClient(pxy.GetName(), pxy.cfg.Sk)
go func() {
@@ -362,21 +433,21 @@ func (pxy *XtcpProxy) Run() error {
case <-pxy.closeCh:
break
case sid := <-sidCh:
workConn, err := pxy.GetWorkConnFromPool()
if err != nil {
workConn, errRet := pxy.GetWorkConnFromPool()
if errRet != nil {
continue
}
m := &msg.NatHoleSid{
Sid: sid,
}
err = msg.WriteMsg(workConn, m)
if err != nil {
pxy.Warn("write nat hole sid package error, %v", err)
errRet = msg.WriteMsg(workConn, m)
if errRet != nil {
pxy.Warn("write nat hole sid package error, %v", errRet)
}
}
}
}()
return nil
return
}
func (pxy *XtcpProxy) GetConf() config.ProxyConf {
@@ -395,6 +466,8 @@ type UdpProxy struct {
BaseProxy
cfg *config.UdpProxyConf
realPort int
// udpConn is the listener of udp packages
udpConn *net.UDPConn
@@ -414,15 +487,29 @@ type UdpProxy struct {
isClosed bool
}
func (pxy *UdpProxy) Run() (err error) {
addr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", config.ServerCommonCfg.ProxyBindAddr, pxy.cfg.RemotePort))
func (pxy *UdpProxy) Run() (remoteAddr string, err error) {
pxy.realPort, err = pxy.ctl.svr.udpPortManager.Acquire(pxy.name, pxy.cfg.RemotePort)
if err != nil {
return err
return
}
udpConn, err := net.ListenUDP("udp", addr)
if err != nil {
defer func() {
if err != nil {
pxy.ctl.svr.udpPortManager.Release(pxy.realPort)
}
}()
remoteAddr = fmt.Sprintf(":%d", pxy.realPort)
pxy.cfg.RemotePort = pxy.realPort
addr, errRet := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", g.GlbServerCfg.ProxyBindAddr, pxy.realPort))
if errRet != nil {
err = errRet
return
}
udpConn, errRet := net.ListenUDP("udp", addr)
if errRet != nil {
err = errRet
pxy.Warn("listen udp port error: %v", err)
return err
return
}
pxy.Info("udp proxy listen port [%d]", pxy.cfg.RemotePort)
@@ -537,7 +624,7 @@ func (pxy *UdpProxy) Run() (err error) {
udp.ForwardUserConn(udpConn, pxy.readCh, pxy.sendCh)
pxy.Close()
}()
return nil
return remoteAddr, nil
}
func (pxy *UdpProxy) GetConf() config.ProxyConf {
@@ -561,6 +648,7 @@ func (pxy *UdpProxy) Close() {
close(pxy.readCh)
close(pxy.sendCh)
}
pxy.ctl.svr.udpPortManager.Release(pxy.realPort)
}
// HandleUserTcpConnection is used for incoming tcp user connections.
@@ -578,7 +666,7 @@ func HandleUserTcpConnection(pxy Proxy, userConn frpNet.Conn) {
var local io.ReadWriteCloser = workConn
cfg := pxy.GetConf().GetBaseInfo()
if cfg.UseEncryption {
local, err = frpIo.WithEncryption(local, []byte(config.ServerCommonCfg.PrivilegeToken))
local, err = frpIo.WithEncryption(local, []byte(g.GlbServerCfg.Token))
if err != nil {
pxy.Error("create encryption stream error: %v", err)
return

View File

@@ -15,21 +15,26 @@
package server
import (
"bytes"
"fmt"
"io/ioutil"
"net"
"net/http"
"time"
"github.com/fatedier/frp/assets"
"github.com/fatedier/frp/models/config"
"github.com/fatedier/frp/g"
"github.com/fatedier/frp/models/msg"
"github.com/fatedier/frp/server/group"
"github.com/fatedier/frp/server/ports"
"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"
"github.com/fatedier/golib/net/mux"
fmux "github.com/hashicorp/yamux"
)
const (
@@ -38,39 +43,57 @@ const (
var ServerService *Service
// Server service.
// Server service
type Service struct {
// Accept connections from client.
// Dispatch connections to different handlers listen on same port
muxer *mux.Mux
// Accept connections from client
listener frpNet.Listener
// Accept connections using kcp.
// Accept connections using kcp
kcpListener frpNet.Listener
// For https proxies, route requests to different clients by hostname and other infomation.
// Accept connections using websocket
websocketListener frpNet.Listener
// For https proxies, route requests to different clients by hostname and other infomation
VhostHttpsMuxer *vhost.HttpsMuxer
httpReverseProxy *vhost.HttpReverseProxy
// Manage all controllers.
// Manage all controllers
ctlManager *ControlManager
// Manage all proxies.
// Manage all proxies
pxyManager *ProxyManager
// Manage all visitor listeners.
// Manage all visitor listeners
visitorManager *VisitorManager
// Controller for nat hole connections.
// Manage all tcp ports
tcpPortManager *ports.PortManager
// Manage all udp ports
udpPortManager *ports.PortManager
// Tcp Group Controller
tcpGroupCtl *group.TcpGroupCtl
// Controller for nat hole connections
natHoleController *NatHoleController
}
func NewService() (svr *Service, err error) {
cfg := &g.GlbServerCfg.ServerCommonConf
svr = &Service{
ctlManager: NewControlManager(),
pxyManager: NewProxyManager(),
visitorManager: NewVisitorManager(),
tcpPortManager: ports.NewPortManager("tcp", cfg.ProxyBindAddr, cfg.AllowPorts),
udpPortManager: ports.NewPortManager("udp", cfg.ProxyBindAddr, cfg.AllowPorts),
}
cfg := config.ServerCommonCfg
svr.tcpGroupCtl = group.NewTcpGroupCtl(svr.tcpPortManager)
// Init assets.
err = assets.Load(cfg.AssetsDir)
@@ -79,12 +102,31 @@ func NewService() (svr *Service, err error) {
return
}
var (
httpMuxOn bool
httpsMuxOn bool
)
if cfg.BindAddr == cfg.ProxyBindAddr {
if cfg.BindPort == cfg.VhostHttpPort {
httpMuxOn = true
}
if cfg.BindPort == cfg.VhostHttpsPort {
httpsMuxOn = true
}
}
// Listen for accepting connections from client.
svr.listener, err = frpNet.ListenTcp(cfg.BindAddr, cfg.BindPort)
ln, err := net.Listen("tcp", fmt.Sprintf("%s:%d", cfg.BindAddr, cfg.BindPort))
if err != nil {
err = fmt.Errorf("Create server listener error, %v", err)
return
}
svr.muxer = mux.NewMux(ln)
go svr.muxer.Serve()
ln = svr.muxer.DefaultListener()
svr.listener = frpNet.WrapLogListener(ln)
log.Info("frps tcp listen on %s:%d", cfg.BindAddr, cfg.BindPort)
// Listen for accepting connections from client using kcp protocol.
@@ -97,9 +139,18 @@ func NewService() (svr *Service, err error) {
log.Info("frps kcp listen on udp %s:%d", cfg.BindAddr, cfg.KcpBindPort)
}
// Listen for accepting connections from client using websocket protocol.
websocketPrefix := []byte("GET " + frpNet.FrpWebsocketPath)
websocketLn := svr.muxer.Listen(0, uint32(len(websocketPrefix)), func(data []byte) bool {
return bytes.Equal(data, websocketPrefix)
})
svr.websocketListener = frpNet.NewWebsocketListener(websocketLn)
// Create http vhost muxer.
if cfg.VhostHttpPort > 0 {
rp := vhost.NewHttpReverseProxy()
rp := vhost.NewHttpReverseProxy(vhost.HttpReverseProxyOptions{
ResponseHeaderTimeoutS: cfg.VhostHttpTimeout,
})
svr.httpReverseProxy = rp
address := fmt.Sprintf("%s:%d", cfg.ProxyBindAddr, cfg.VhostHttpPort)
@@ -108,10 +159,14 @@ func NewService() (svr *Service, err error) {
Handler: rp,
}
var l net.Listener
l, err = net.Listen("tcp", address)
if err != nil {
err = fmt.Errorf("Create vhost http listener error, %v", err)
return
if httpMuxOn {
l = svr.muxer.ListenHttp(1)
} else {
l, err = net.Listen("tcp", address)
if err != nil {
err = fmt.Errorf("Create vhost http listener error, %v", err)
return
}
}
go server.Serve(l)
log.Info("http service listen on %s:%d", cfg.ProxyBindAddr, cfg.VhostHttpPort)
@@ -119,13 +174,18 @@ func NewService() (svr *Service, err error) {
// Create https vhost muxer.
if cfg.VhostHttpsPort > 0 {
var l frpNet.Listener
l, err = frpNet.ListenTcp(cfg.ProxyBindAddr, cfg.VhostHttpsPort)
if err != nil {
err = fmt.Errorf("Create vhost https listener error, %v", err)
return
var l net.Listener
if httpsMuxOn {
l = svr.muxer.ListenHttps(1)
} else {
l, err = net.Listen("tcp", fmt.Sprintf("%s:%d", cfg.ProxyBindAddr, cfg.VhostHttpsPort))
if err != nil {
err = fmt.Errorf("Create server listener error, %v", err)
return
}
}
svr.VhostHttpsMuxer, err = vhost.NewHttpsMuxer(l, 30*time.Second)
svr.VhostHttpsMuxer, err = vhost.NewHttpsMuxer(frpNet.WrapLogListener(l), 30*time.Second)
if err != nil {
err = fmt.Errorf("Create vhost httpsMuxer error, %v", err)
return
@@ -155,6 +215,7 @@ func NewService() (svr *Service, err error) {
}
log.Info("Dashboard listen on %s:%d", cfg.DashboardAddr, cfg.DashboardPort)
}
return
}
@@ -162,11 +223,13 @@ func (svr *Service) Run() {
if svr.natHoleController != nil {
go svr.natHoleController.Run()
}
if config.ServerCommonCfg.KcpBindPort > 0 {
if g.GlbServerCfg.KcpBindPort > 0 {
go svr.HandleListener(svr.kcpListener)
}
svr.HandleListener(svr.listener)
go svr.HandleListener(svr.websocketListener)
svr.HandleListener(svr.listener)
}
func (svr *Service) HandleListener(l frpNet.Listener) {
@@ -225,8 +288,10 @@ func (svr *Service) HandleListener(l frpNet.Listener) {
}
}
if config.ServerCommonCfg.TcpMux {
session, err := smux.Server(frpConn, nil)
if g.GlbServerCfg.TcpMux {
fmuxCfg := fmux.DefaultConfig()
fmuxCfg.LogOutput = ioutil.Discard
session, err := fmux.Server(frpConn, fmuxCfg)
if err != nil {
log.Warn("Failed to create mux connection: %v", err)
frpConn.Close()
@@ -236,7 +301,7 @@ func (svr *Service) HandleListener(l frpNet.Listener) {
for {
stream, err := session.AcceptStream()
if err != nil {
log.Warn("Accept new mux stream error: %v", err)
log.Debug("Accept new mux stream error: %v", err)
session.Close()
return
}
@@ -262,11 +327,11 @@ func (svr *Service) RegisterControl(ctlConn frpNet.Conn, loginMsg *msg.Login) (e
// Check auth.
nowTime := time.Now().Unix()
if config.ServerCommonCfg.AuthTimeout != 0 && nowTime-loginMsg.Timestamp > config.ServerCommonCfg.AuthTimeout {
if g.GlbServerCfg.AuthTimeout != 0 && nowTime-loginMsg.Timestamp > g.GlbServerCfg.AuthTimeout {
err = fmt.Errorf("authorization timeout")
return
}
if util.GetAuthKey(config.ServerCommonCfg.PrivilegeToken, loginMsg.Timestamp) != loginMsg.PrivilegeKey {
if util.GetAuthKey(g.GlbServerCfg.Token, loginMsg.Timestamp) != loginMsg.PrivilegeKey {
err = fmt.Errorf("authorization failed")
return
}
@@ -283,7 +348,7 @@ func (svr *Service) RegisterControl(ctlConn frpNet.Conn, loginMsg *msg.Login) (e
ctl := NewControl(svr, ctlConn, loginMsg)
if oldCtl := svr.ctlManager.Add(loginMsg.RunId, ctl); oldCtl != nil {
oldCtl.allShutdown.WaitDown()
oldCtl.allShutdown.WaitDone()
}
ctlConn.AddLogPrefix(loginMsg.RunId)

View File

@@ -10,5 +10,11 @@ if [ -n "${pid}" ]; then
kill ${pid}
fi
pid=`ps aux|grep './../bin/frpc -c ./conf/auto_test_frpc_visitor.ini'|grep -v grep|awk {'print $2'}`
if [ -n "${pid}" ]; then
kill ${pid}
fi
rm -f ./frps.log
rm -f ./frpc.log
rm -f ./frpc_visitor.log

View File

@@ -1,35 +1,193 @@
[common]
server_addr = 0.0.0.0
server_addr = 127.0.0.1
server_port = 10700
log_file = ./frpc.log
# debug, info, warn, error
log_level = debug
privilege_token = 123456
token = 123456
admin_port = 10600
admin_user = abc
admin_pwd = abc
[echo]
[tcp_normal]
type = tcp
local_ip = 127.0.0.1
local_port = 10701
remote_port = 10711
use_encryption = true
use_compression = true
remote_port = 10801
[web]
type = http
[tcp_ec]
type = tcp
local_ip = 127.0.0.1
local_port = 10702
local_port = 10701
remote_port = 10901
use_encryption = true
use_compression = true
custom_domains = 127.0.0.1
[udp]
[tcp_group1]
type = tcp
local_ip = 127.0.0.1
local_port = 10701
remote_port = 10802
group = test1
group_key = 123
[tcp_group2]
type = tcp
local_ip = 127.0.0.1
local_port = 10702
remote_port = 10802
group = test1
group_key = 123
[udp_normal]
type = udp
local_ip = 127.0.0.1
local_port = 10703
remote_port = 10712
local_port = 10702
remote_port = 10802
[udp_ec]
type = udp
local_ip = 127.0.0.1
local_port = 10702
remote_port = 10902
use_encryption = true
use_compression = true
[unix_domain]
type = tcp
remote_port = 10704
remote_port = 10803
plugin = unix_domain_socket
plugin_unix_path = /tmp/frp_echo_server.sock
[stcp]
type = stcp
sk = abcdefg
local_ip = 127.0.0.1
local_port = 10701
[stcp_ec]
type = stcp
sk = abc
local_ip = 127.0.0.1
local_port = 10701
use_encryption = true
use_compression = true
[web01]
type = http
local_ip = 127.0.0.1
local_port = 10704
custom_domains = 127.0.0.1
[web02]
type = http
local_ip = 127.0.0.1
local_port = 10704
custom_domains = test2.frp.com
host_header_rewrite = test2.frp.com
use_encryption = true
use_compression = true
[web03]
type = http
local_ip = 127.0.0.1
local_port = 10704
custom_domains = test3.frp.com
use_encryption = true
use_compression = true
host_header_rewrite = test3.frp.com
locations = /,/foo
[web04]
type = http
local_ip = 127.0.0.1
local_port = 10704
custom_domains = test3.frp.com
use_encryption = true
use_compression = true
host_header_rewrite = test3.frp.com
locations = /bar
[web05]
type = http
local_ip = 127.0.0.1
local_port = 10704
custom_domains = test5.frp.com
host_header_rewrite = test5.frp.com
use_encryption = true
use_compression = true
http_user = test
http_user = test
[web06]
type = http
local_ip = 127.0.0.1
local_port = 10704
custom_domains = test6.frp.com
host_header_rewrite = test6.frp.com
header_X-From-Where = frp
[subhost01]
type = http
local_ip = 127.0.0.1
local_port = 10704
subdomain = test01
[subhost02]
type = http
local_ip = 127.0.0.1
local_port = 10704
subdomain = test02
[tcp_port_not_allowed]
type = tcp
local_ip = 127.0.0.1
local_port = 10701
remote_port = 20001
[tcp_port_unavailable]
type =tcp
local_ip = 127.0.0.1
local_port = 10701
remote_port = 10700
[tcp_port_normal]
type = tcp
local_ip = 127.0.0.1
local_port = 10701
remote_port = 20002
[tcp_random_port]
type = tcp
local_ip = 127.0.0.1
local_port = 10701
remote_port = 0
[udp_port_not_allowed]
type = udp
local_ip = 127.0.0.1
local_port = 10702
remote_port = 20001
[udp_port_normal]
type = udp
local_ip = 127.0.0.1
local_port = 10702
remote_port = 20002
[udp_random_port]
type = udp
local_ip = 127.0.0.1
local_port = 10702
remote_port = 0
[http_proxy]
type = tcp
plugin = http_proxy
remote_port = 0
[range:range_tcp]
type = tcp
local_ip = 127.0.0.1
local_port = 30000-30001,30003
remote_port = 30000-30001,30003

View File

@@ -0,0 +1,25 @@
[common]
server_addr = 0.0.0.0
server_port = 10700
log_file = ./frpc_visitor.log
# debug, info, warn, error
log_level = debug
token = 123456
[stcp_visitor]
type = stcp
role = visitor
server_name = stcp
sk = abcdefg
bind_addr = 127.0.0.1
bind_port = 10805
[stcp_ec_visitor]
type = stcp
role = visitor
server_name = stcp_ec
sk = abc
bind_addr = 127.0.0.1
bind_port = 10905
use_encryption = true
use_compression = true

View File

@@ -1,7 +1,9 @@
[common]
bind_addr = 0.0.0.0
bind_port = 10700
vhost_http_port = 10710
vhost_http_port = 10804
log_file = ./frps.log
log_level = debug
privilege_token = 123456
token = 123456
allow_ports = 10000-20000,20002,30000-50000
subdomain_host = sub.com

View File

@@ -1,7 +1,6 @@
package tests
import (
"bufio"
"fmt"
"io"
"net"
@@ -11,8 +10,8 @@ import (
frpNet "github.com/fatedier/frp/utils/net"
)
func StartEchoServer() {
l, err := frpNet.ListenTcp("127.0.0.1", 10701)
func StartTcpEchoServer() {
l, err := frpNet.ListenTcp("127.0.0.1", TEST_TCP_PORT)
if err != nil {
fmt.Printf("echo server listen error: %v\n", err)
return
@@ -29,8 +28,26 @@ func StartEchoServer() {
}
}
func StartTcpEchoServer2() {
l, err := frpNet.ListenTcp("127.0.0.1", TEST_TCP2_PORT)
if err != nil {
fmt.Printf("echo server2 listen error: %v\n", err)
return
}
for {
c, err := l.Accept()
if err != nil {
fmt.Printf("echo server2 accept error: %v\n", err)
return
}
go echoWorker2(c)
}
}
func StartUdpEchoServer() {
l, err := frpNet.ListenUDP("127.0.0.1", 10703)
l, err := frpNet.ListenUDP("127.0.0.1", TEST_UDP_PORT)
if err != nil {
fmt.Printf("udp echo server listen error: %v\n", err)
return
@@ -48,7 +65,7 @@ func StartUdpEchoServer() {
}
func StartUnixDomainServer() {
unixPath := "/tmp/frp_echo_server.sock"
unixPath := TEST_UNIX_DOMAIN_ADDR
os.Remove(unixPath)
syscall.Umask(0)
l, err := net.Listen("unix", unixPath)
@@ -69,17 +86,42 @@ func StartUnixDomainServer() {
}
func echoWorker(c net.Conn) {
br := bufio.NewReader(c)
buf := make([]byte, 2048)
for {
buf, err := br.ReadString('\n')
if err == io.EOF {
break
}
n, err := c.Read(buf)
if err != nil {
fmt.Printf("echo server read error: %v\n", err)
return
if err == io.EOF {
c.Close()
break
} else {
fmt.Printf("echo server read error: %v\n", err)
return
}
}
c.Write([]byte(buf + "\n"))
c.Write(buf[:n])
}
}
func echoWorker2(c net.Conn) {
buf := make([]byte, 2048)
for {
n, err := c.Read(buf)
if err != nil {
if err == io.EOF {
c.Close()
break
} else {
fmt.Printf("echo server read error: %v\n", err)
return
}
}
var w []byte
w = append(w, buf[:n]...)
w = append(w, buf[:n]...)
c.Write(w)
}
}

View File

@@ -1,119 +1,337 @@
package tests
import (
"bufio"
"bytes"
"fmt"
"io/ioutil"
"net"
"net/http"
"net/url"
"strings"
"testing"
"time"
frpNet "github.com/fatedier/frp/utils/net"
"github.com/gorilla/websocket"
"github.com/stretchr/testify/assert"
"github.com/fatedier/frp/client"
"github.com/fatedier/frp/server/ports"
gnet "github.com/fatedier/golib/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"
SERVER_ADDR = "127.0.0.1"
ADMIN_ADDR = "127.0.0.1:10600"
ADMIN_USER = "abc"
ADMIN_PWD = "abc"
TEST_STR = "frp is a fast reverse proxy to help you expose a local server behind a NAT or firewall to the internet."
TEST_TCP_PORT int = 10701
TEST_TCP2_PORT int = 10702
TEST_TCP_FRP_PORT int = 10801
TEST_TCP2_FRP_PORT int = 10802
TEST_TCP_EC_FRP_PORT int = 10901
TEST_TCP_ECHO_STR string = "tcp type:" + TEST_STR
TEST_UDP_PORT int = 10702
TEST_UDP_FRP_PORT int = 10802
TEST_UDP_EC_FRP_PORT int = 10902
TEST_UDP_ECHO_STR string = "udp type:" + TEST_STR
TEST_UNIX_DOMAIN_ADDR string = "/tmp/frp_echo_server.sock"
TEST_UNIX_DOMAIN_FRP_PORT int = 10803
TEST_UNIX_DOMAIN_STR string = "unix domain type:" + TEST_STR
TEST_HTTP_PORT int = 10704
TEST_HTTP_FRP_PORT int = 10804
TEST_HTTP_NORMAL_STR string = "http normal string: " + TEST_STR
TEST_HTTP_FOO_STR string = "http foo string: " + TEST_STR
TEST_HTTP_BAR_STR string = "http bar string: " + TEST_STR
TEST_STCP_FRP_PORT int = 10805
TEST_STCP_EC_FRP_PORT int = 10905
TEST_STCP_ECHO_STR string = "stcp type:" + TEST_STR
ProxyTcpPortNotAllowed string = "tcp_port_not_allowed"
ProxyTcpPortUnavailable string = "tcp_port_unavailable"
ProxyTcpPortNormal string = "tcp_port_normal"
ProxyTcpRandomPort string = "tcp_random_port"
ProxyUdpPortNotAllowed string = "udp_port_not_allowed"
ProxyUdpPortNormal string = "udp_port_normal"
ProxyUdpRandomPort string = "udp_random_port"
ProxyHttpProxy string = "http_proxy"
ProxyRangeTcpPrefix string = "range_tcp"
)
func init() {
go StartEchoServer()
go StartTcpEchoServer()
go StartTcpEchoServer2()
go StartUdpEchoServer()
go StartHttpServer()
go StartUnixDomainServer()
go StartHttpServer()
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)
func TestTcp(t *testing.T) {
assert := assert.New(t)
// Normal
addr := fmt.Sprintf("127.0.0.1:%d", TEST_TCP_FRP_PORT)
res, err := sendTcpMsg(addr, TEST_TCP_ECHO_STR)
assert.NoError(err)
assert.Equal(TEST_TCP_ECHO_STR, res)
c.Write([]byte(ECHO_TEST_STR + "\n"))
// Encrytion and compression
addr = fmt.Sprintf("127.0.0.1:%d", TEST_TCP_EC_FRP_PORT)
res, err = sendTcpMsg(addr, TEST_TCP_ECHO_STR)
assert.NoError(err)
assert.Equal(TEST_TCP_ECHO_STR, res)
}
br := bufio.NewReader(c)
buf, err := br.ReadString('\n')
if err != nil {
t.Fatalf("read from echo server error: %v", err)
}
func TestUdp(t *testing.T) {
assert := assert.New(t)
// Normal
addr := fmt.Sprintf("127.0.0.1:%d", TEST_UDP_FRP_PORT)
res, err := sendUdpMsg(addr, TEST_UDP_ECHO_STR)
assert.NoError(err)
assert.Equal(TEST_UDP_ECHO_STR, res)
if ECHO_TEST_STR != buf {
t.Fatalf("content error, send [%s], get [%s]", strings.Trim(ECHO_TEST_STR, "\n"), strings.Trim(buf, "\n"))
// Encrytion and compression
addr = fmt.Sprintf("127.0.0.1:%d", TEST_UDP_EC_FRP_PORT)
res, err = sendUdpMsg(addr, TEST_UDP_ECHO_STR)
assert.NoError(err)
assert.Equal(TEST_UDP_ECHO_STR, res)
}
func TestUnixDomain(t *testing.T) {
assert := assert.New(t)
// Normal
addr := fmt.Sprintf("127.0.0.1:%d", TEST_UNIX_DOMAIN_FRP_PORT)
res, err := sendTcpMsg(addr, TEST_UNIX_DOMAIN_STR)
if assert.NoError(err) {
assert.Equal(TEST_UNIX_DOMAIN_STR, res)
}
}
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)
func TestStcp(t *testing.T) {
assert := assert.New(t)
// Normal
addr := fmt.Sprintf("127.0.0.1:%d", TEST_STCP_FRP_PORT)
res, err := sendTcpMsg(addr, TEST_STCP_ECHO_STR)
if assert.NoError(err) {
assert.Equal(TEST_STCP_ECHO_STR, res)
}
if res.StatusCode == 200 {
body, err := ioutil.ReadAll(res.Body)
if err != nil {
t.Fatalf("read from http server error: %v", err)
// Encrytion and compression
addr = fmt.Sprintf("127.0.0.1:%d", TEST_STCP_EC_FRP_PORT)
res, err = sendTcpMsg(addr, TEST_STCP_ECHO_STR)
if assert.NoError(err) {
assert.Equal(TEST_STCP_ECHO_STR, res)
}
}
func TestHttp(t *testing.T) {
assert := assert.New(t)
// web01
code, body, _, err := sendHttpMsg("GET", fmt.Sprintf("http://127.0.0.1:%d", TEST_HTTP_FRP_PORT), "", nil, "")
if assert.NoError(err) {
assert.Equal(200, code)
assert.Equal(TEST_HTTP_NORMAL_STR, body)
}
// web02
code, body, _, err = sendHttpMsg("GET", fmt.Sprintf("http://127.0.0.1:%d", TEST_HTTP_FRP_PORT), "test2.frp.com", nil, "")
if assert.NoError(err) {
assert.Equal(200, code)
assert.Equal(TEST_HTTP_NORMAL_STR, body)
}
// error host header
code, body, _, err = sendHttpMsg("GET", fmt.Sprintf("http://127.0.0.1:%d", TEST_HTTP_FRP_PORT), "errorhost.frp.com", nil, "")
if assert.NoError(err) {
assert.Equal(404, code)
}
// web03
code, body, _, err = sendHttpMsg("GET", fmt.Sprintf("http://127.0.0.1:%d", TEST_HTTP_FRP_PORT), "test3.frp.com", nil, "")
if assert.NoError(err) {
assert.Equal(200, code)
assert.Equal(TEST_HTTP_NORMAL_STR, body)
}
code, body, _, err = sendHttpMsg("GET", fmt.Sprintf("http://127.0.0.1:%d/foo", TEST_HTTP_FRP_PORT), "test3.frp.com", nil, "")
if assert.NoError(err) {
assert.Equal(200, code)
assert.Equal(TEST_HTTP_FOO_STR, body)
}
// web04
code, body, _, err = sendHttpMsg("GET", fmt.Sprintf("http://127.0.0.1:%d/bar", TEST_HTTP_FRP_PORT), "test3.frp.com", nil, "")
if assert.NoError(err) {
assert.Equal(200, code)
assert.Equal(TEST_HTTP_BAR_STR, body)
}
// web05
code, body, _, err = sendHttpMsg("GET", fmt.Sprintf("http://127.0.0.1:%d", TEST_HTTP_FRP_PORT), "test5.frp.com", nil, "")
if assert.NoError(err) {
assert.Equal(401, code)
}
headers := make(map[string]string)
headers["Authorization"] = basicAuth("test", "test")
code, body, _, err = sendHttpMsg("GET", fmt.Sprintf("http://127.0.0.1:%d", TEST_HTTP_FRP_PORT), "test5.frp.com", headers, "")
if assert.NoError(err) {
assert.Equal(401, code)
}
// web06
var header http.Header
code, body, header, err = sendHttpMsg("GET", fmt.Sprintf("http://127.0.0.1:%d", TEST_HTTP_FRP_PORT), "test6.frp.com", nil, "")
if assert.NoError(err) {
assert.Equal(200, code)
assert.Equal(TEST_HTTP_NORMAL_STR, body)
assert.Equal("true", header.Get("X-Header-Set"))
}
// subhost01
code, body, _, err = sendHttpMsg("GET", fmt.Sprintf("http://127.0.0.1:%d", TEST_HTTP_FRP_PORT), "test01.sub.com", nil, "")
if assert.NoError(err) {
assert.Equal(200, code)
assert.Equal("test01.sub.com", body)
}
// subhost02
code, body, _, err = sendHttpMsg("GET", fmt.Sprintf("http://127.0.0.1:%d", TEST_HTTP_FRP_PORT), "test02.sub.com", nil, "")
if assert.NoError(err) {
assert.Equal(200, code)
assert.Equal("test02.sub.com", body)
}
}
func TestWebSocket(t *testing.T) {
assert := assert.New(t)
u := url.URL{Scheme: "ws", Host: fmt.Sprintf("%s:%d", "127.0.0.1", TEST_HTTP_FRP_PORT), Path: "/ws"}
c, _, err := websocket.DefaultDialer.Dial(u.String(), nil)
assert.NoError(err)
defer c.Close()
err = c.WriteMessage(websocket.TextMessage, []byte(TEST_HTTP_NORMAL_STR))
assert.NoError(err)
_, msg, err := c.ReadMessage()
assert.NoError(err)
assert.Equal(TEST_HTTP_NORMAL_STR, string(msg))
}
func TestAllowPorts(t *testing.T) {
assert := assert.New(t)
// Port not allowed
status, err := getProxyStatus(ProxyTcpPortNotAllowed)
if assert.NoError(err) {
assert.Equal(client.ProxyStatusStartErr, status.Status)
assert.True(strings.Contains(status.Err, ports.ErrPortNotAllowed.Error()))
}
status, err = getProxyStatus(ProxyUdpPortNotAllowed)
if assert.NoError(err) {
assert.Equal(client.ProxyStatusStartErr, status.Status)
assert.True(strings.Contains(status.Err, ports.ErrPortNotAllowed.Error()))
}
status, err = getProxyStatus(ProxyTcpPortUnavailable)
if assert.NoError(err) {
assert.Equal(client.ProxyStatusStartErr, status.Status)
assert.True(strings.Contains(status.Err, ports.ErrPortUnAvailable.Error()))
}
// Port normal
status, err = getProxyStatus(ProxyTcpPortNormal)
if assert.NoError(err) {
assert.Equal(client.ProxyStatusRunning, status.Status)
}
status, err = getProxyStatus(ProxyUdpPortNormal)
if assert.NoError(err) {
assert.Equal(client.ProxyStatusRunning, status.Status)
}
}
func TestRandomPort(t *testing.T) {
assert := assert.New(t)
// tcp
status, err := getProxyStatus(ProxyTcpRandomPort)
if assert.NoError(err) {
addr := status.RemoteAddr
res, err := sendTcpMsg(addr, TEST_TCP_ECHO_STR)
assert.NoError(err)
assert.Equal(TEST_TCP_ECHO_STR, res)
}
// udp
status, err = getProxyStatus(ProxyUdpRandomPort)
if assert.NoError(err) {
addr := status.RemoteAddr
res, err := sendUdpMsg(addr, TEST_UDP_ECHO_STR)
assert.NoError(err)
assert.Equal(TEST_UDP_ECHO_STR, res)
}
}
func TestPluginHttpProxy(t *testing.T) {
assert := assert.New(t)
status, err := getProxyStatus(ProxyHttpProxy)
if assert.NoError(err) {
assert.Equal(client.ProxyStatusRunning, status.Status)
// http proxy
addr := status.RemoteAddr
code, body, _, err := sendHttpMsg("GET", fmt.Sprintf("http://127.0.0.1:%d", TEST_HTTP_FRP_PORT),
"", nil, "http://"+addr)
if assert.NoError(err) {
assert.Equal(200, code)
assert.Equal(TEST_HTTP_NORMAL_STR, body)
}
bodystr := string(body)
if bodystr != HTTP_RES_STR {
t.Fatalf("content from http server error [%s], correct string is [%s]", bodystr, HTTP_RES_STR)
// connect method
conn, err := gnet.DialTcpByProxy("http://"+addr, fmt.Sprintf("127.0.0.1:%d", TEST_TCP_FRP_PORT))
if assert.NoError(err) {
res, err := sendTcpMsgByConn(conn, TEST_TCP_ECHO_STR)
assert.NoError(err)
assert.Equal(TEST_TCP_ECHO_STR, res)
}
} 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)
}
func TestRangePortsMapping(t *testing.T) {
assert := assert.New(t)
if string(bytes.TrimSpace(data[:n])) != "hello frp" {
t.Fatalf("message got from udp server error, get %s", string(data[:n-1]))
for i := 0; i < 3; i++ {
name := fmt.Sprintf("%s_%d", ProxyRangeTcpPrefix, i)
status, err := getProxyStatus(name)
if assert.NoError(err) {
assert.Equal(client.ProxyStatusRunning, status.Status)
}
}
}
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)
func TestGroup(t *testing.T) {
assert := assert.New(t)
c.Write([]byte(ECHO_TEST_STR + "\n"))
var (
p1 int
p2 int
)
addr := fmt.Sprintf("127.0.0.1:%d", TEST_TCP2_FRP_PORT)
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"))
for i := 0; i < 6; i++ {
res, err := sendTcpMsg(addr, TEST_TCP_ECHO_STR)
assert.NoError(err)
switch res {
case TEST_TCP_ECHO_STR:
p1++
case TEST_TCP_ECHO_STR + TEST_TCP_ECHO_STR:
p2++
}
}
assert.True(p1 > 0 && p2 > 0, "group proxies load balancing")
}

View File

@@ -2,14 +2,74 @@ package tests
import (
"fmt"
"log"
"net/http"
"regexp"
"strings"
"github.com/gorilla/websocket"
)
var upgrader = websocket.Upgrader{}
func StartHttpServer() {
http.HandleFunc("/", request)
http.ListenAndServe(fmt.Sprintf("0.0.0.0:%d", 10702), nil)
http.HandleFunc("/", handleHttp)
http.HandleFunc("/ws", handleWebSocket)
http.ListenAndServe(fmt.Sprintf("0.0.0.0:%d", TEST_HTTP_PORT), nil)
}
func request(w http.ResponseWriter, r *http.Request) {
w.Write([]byte(HTTP_RES_STR))
func handleWebSocket(w http.ResponseWriter, r *http.Request) {
c, err := upgrader.Upgrade(w, r, nil)
if err != nil {
log.Print("upgrade:", err)
return
}
defer c.Close()
for {
mt, message, err := c.ReadMessage()
if err != nil {
break
}
err = c.WriteMessage(mt, message)
if err != nil {
log.Println("write:", err)
break
}
}
}
func handleHttp(w http.ResponseWriter, r *http.Request) {
if r.Header.Get("X-From-Where") == "frp" {
w.Header().Set("X-Header-Set", "true")
}
match, err := regexp.Match(`.*\.sub\.com`, []byte(r.Host))
if err != nil {
w.WriteHeader(500)
return
}
if match {
w.WriteHeader(200)
w.Write([]byte(r.Host))
return
}
if strings.Contains(r.Host, "127.0.0.1") || strings.Contains(r.Host, "test2.frp.com") ||
strings.Contains(r.Host, "test5.frp.com") || strings.Contains(r.Host, "test6.frp.com") {
w.WriteHeader(200)
w.Write([]byte(TEST_HTTP_NORMAL_STR))
} else if strings.Contains(r.Host, "test3.frp.com") {
w.WriteHeader(200)
if strings.Contains(r.URL.Path, "foo") {
w.Write([]byte(TEST_HTTP_FOO_STR))
} else if strings.Contains(r.URL.Path, "bar") {
w.Write([]byte(TEST_HTTP_BAR_STR))
} else {
w.Write([]byte(TEST_HTTP_NORMAL_STR))
}
} else {
w.WriteHeader(404)
}
return
}

View File

@@ -3,6 +3,7 @@
./../bin/frps -c ./conf/auto_test_frps.ini &
sleep 1
./../bin/frpc -c ./conf/auto_test_frpc.ini &
./../bin/frpc -c ./conf/auto_test_frpc_visitor.ini &
# wait until proxies are connected
sleep 2

183
tests/util.go Normal file
View File

@@ -0,0 +1,183 @@
package tests
import (
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"net"
"net/http"
"net/url"
"strings"
"time"
"github.com/fatedier/frp/client"
frpNet "github.com/fatedier/frp/utils/net"
)
func getProxyStatus(name string) (status *client.ProxyStatusResp, err error) {
req, err := http.NewRequest("GET", "http://"+ADMIN_ADDR+"/api/status", nil)
if err != nil {
return status, err
}
authStr := "Basic " + base64.StdEncoding.EncodeToString([]byte(ADMIN_USER+":"+ADMIN_PWD))
req.Header.Add("Authorization", authStr)
resp, err := http.DefaultClient.Do(req)
if err != nil {
return status, err
} else {
if resp.StatusCode != 200 {
return status, fmt.Errorf("admin api status code [%d]", resp.StatusCode)
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return status, err
}
allStatus := &client.StatusResp{}
err = json.Unmarshal(body, &allStatus)
if err != nil {
return status, fmt.Errorf("unmarshal http response error: %s", strings.TrimSpace(string(body)))
}
for _, s := range allStatus.Tcp {
if s.Name == name {
return &s, nil
}
}
for _, s := range allStatus.Udp {
if s.Name == name {
return &s, nil
}
}
for _, s := range allStatus.Http {
if s.Name == name {
return &s, nil
}
}
for _, s := range allStatus.Https {
if s.Name == name {
return &s, nil
}
}
for _, s := range allStatus.Stcp {
if s.Name == name {
return &s, nil
}
}
for _, s := range allStatus.Xtcp {
if s.Name == name {
return &s, nil
}
}
}
return status, errors.New("no proxy status found")
}
func sendTcpMsg(addr string, msg string) (res string, err error) {
c, err := frpNet.ConnectTcpServer(addr)
if err != nil {
err = fmt.Errorf("connect to tcp server error: %v", err)
return
}
defer c.Close()
return sendTcpMsgByConn(c, msg)
}
func sendTcpMsgByConn(c net.Conn, msg string) (res string, err error) {
timer := time.Now().Add(5 * time.Second)
c.SetDeadline(timer)
c.Write([]byte(msg))
buf := make([]byte, 2048)
n, errRet := c.Read(buf)
if errRet != nil {
err = fmt.Errorf("read from tcp server error: %v", errRet)
return
}
return string(buf[:n]), nil
}
func sendUdpMsg(addr string, msg string) (res string, err error) {
udpAddr, errRet := net.ResolveUDPAddr("udp", addr)
if errRet != nil {
err = fmt.Errorf("resolve udp addr error: %v", err)
return
}
conn, errRet := net.DialUDP("udp", nil, udpAddr)
if errRet != nil {
err = fmt.Errorf("dial udp server error: %v", err)
return
}
defer conn.Close()
_, err = conn.Write([]byte(msg))
if err != nil {
err = fmt.Errorf("write to udp server error: %v", err)
return
}
buf := make([]byte, 2048)
n, errRet := conn.Read(buf)
if errRet != nil {
err = fmt.Errorf("read from udp server error: %v", err)
return
}
return string(buf[:n]), nil
}
func sendHttpMsg(method, urlStr string, host string, headers map[string]string, proxy string) (code int, body string, header http.Header, err error) {
req, errRet := http.NewRequest(method, urlStr, nil)
if errRet != nil {
err = errRet
return
}
if host != "" {
req.Host = host
}
for k, v := range headers {
req.Header.Set(k, v)
}
tr := &http.Transport{
DialContext: (&net.Dialer{
Timeout: 30 * time.Second,
KeepAlive: 30 * time.Second,
DualStack: true,
}).DialContext,
MaxIdleConns: 100,
IdleConnTimeout: 90 * time.Second,
TLSHandshakeTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
}
if len(proxy) != 0 {
tr.Proxy = func(req *http.Request) (*url.URL, error) {
return url.Parse(proxy)
}
}
client := http.Client{
Transport: tr,
}
resp, errRet := client.Do(req)
if errRet != nil {
err = errRet
return
}
code = resp.StatusCode
header = resp.Header
buf, errRet := ioutil.ReadAll(resp.Body)
if errRet != nil {
err = errRet
return
}
body = string(buf)
return
}
func basicAuth(username, passwd string) string {
auth := username + ":" + passwd
return "Basic " + base64.StdEncoding.EncodeToString([]byte(auth))
}

View File

@@ -1,41 +0,0 @@
// 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()))
}

View File

@@ -1,16 +0,0 @@
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")
}

View File

@@ -1,145 +0,0 @@
// 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)
}

View File

@@ -15,17 +15,17 @@
package net
import (
"bytes"
"errors"
"fmt"
"io"
"net"
"sync"
"sync/atomic"
"time"
"github.com/fatedier/frp/utils/log"
kcp "github.com/xtaci/kcp-go"
gnet "github.com/fatedier/golib/net"
kcp "github.com/fatedier/kcp-go"
)
// Conn is the interface of connections used in frp.
@@ -96,6 +96,75 @@ 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")}
}
type CloseNotifyConn struct {
net.Conn
log.Logger
// 1 means closed
closeFlag int32
closeFn func()
}
// closeFn will be only called once
func WrapCloseNotifyConn(c net.Conn, closeFn func()) Conn {
return &CloseNotifyConn{
Conn: c,
Logger: log.NewPrefixLogger(""),
closeFn: closeFn,
}
}
func (cc *CloseNotifyConn) Close() (err error) {
pflag := atomic.SwapInt32(&cc.closeFlag, 1)
if pflag == 0 {
err = cc.Close()
if cc.closeFn != nil {
cc.closeFn()
}
}
return
}
type StatsConn struct {
Conn
closed int64 // 1 means closed
totalRead int64
totalWrite int64
statsFunc func(totalRead, totalWrite int64)
}
func WrapStatsConn(conn Conn, statsFunc func(total, totalWrite int64)) *StatsConn {
return &StatsConn{
Conn: conn,
statsFunc: statsFunc,
}
}
func (statsConn *StatsConn) Read(p []byte) (n int, err error) {
n, err = statsConn.Conn.Read(p)
statsConn.totalRead += int64(n)
return
}
func (statsConn *StatsConn) Write(p []byte) (n int, err error) {
n, err = statsConn.Conn.Write(p)
statsConn.totalWrite += int64(n)
return
}
func (statsConn *StatsConn) Close() (err error) {
old := atomic.SwapInt64(&statsConn.closed, 1)
if old != 1 {
err = statsConn.Conn.Close()
if statsConn.statsFunc != nil {
statsConn.statsFunc(statsConn.totalRead, statsConn.totalWrite)
}
}
return
}
func ConnectServer(protocol string, addr string) (c Conn, err error) {
switch protocol {
case "tcp":
@@ -121,56 +190,20 @@ func ConnectServer(protocol string, addr string) (c Conn, err error) {
}
}
func ConnectServerByHttpProxy(httpProxy string, protocol string, addr string) (c Conn, err error) {
func ConnectServerByProxy(proxyUrl string, protocol string, addr string) (c Conn, err error) {
switch protocol {
case "tcp":
return ConnectTcpServerByHttpProxy(httpProxy, addr)
var conn net.Conn
if conn, err = gnet.DialTcpByProxy(proxyUrl, addr); err != nil {
return
}
return WrapConn(conn), nil
case "kcp":
// http proxy is not supported for kcp
return ConnectServer(protocol, addr)
case "websocket":
return ConnectWebsocketServer(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
}

View File

@@ -19,8 +19,6 @@ import (
"io"
"net/http"
"strings"
"github.com/julienschmidt/httprouter"
)
type HttpAuthWraper struct {
@@ -47,6 +45,31 @@ func (aw *HttpAuthWraper) ServeHTTP(w http.ResponseWriter, r *http.Request) {
}
}
type HttpAuthMiddleware struct {
user string
passwd string
}
func NewHttpAuthMiddleware(user, passwd string) *HttpAuthMiddleware {
return &HttpAuthMiddleware{
user: user,
passwd: passwd,
}
}
func (authMid *HttpAuthMiddleware) Middleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
reqUser, reqPasswd, hasAuth := r.BasicAuth()
if (authMid.user == "" && authMid.passwd == "") ||
(hasAuth && reqUser == authMid.user && reqPasswd == authMid.passwd) {
next.ServeHTTP(w, r)
} else {
w.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`)
http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
}
})
}
func HttpBasicAuth(h http.HandlerFunc, user, passwd string) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
reqUser, reqPasswd, hasAuth := r.BasicAuth()
@@ -60,19 +83,6 @@ func HttpBasicAuth(h http.HandlerFunc, user, passwd string) http.HandlerFunc {
}
}
func HttprouterBasicAuth(h httprouter.Handle, user, passwd string) httprouter.Handle {
return func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
reqUser, reqPasswd, hasAuth := r.BasicAuth()
if (user == "" && passwd == "") ||
(hasAuth && reqUser == user && reqPasswd == passwd) {
h(w, r, ps)
} else {
w.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`)
http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
}
}
}
type HttpGzipWraper struct {
h http.Handler
}

View File

@@ -31,7 +31,7 @@ type KcpListener struct {
log.Logger
}
func ListenKcp(bindAddr string, bindPort int64) (l *KcpListener, err error) {
func ListenKcp(bindAddr string, bindPort int) (l *KcpListener, err error) {
listener, err := kcp.ListenWithOptions(fmt.Sprintf("%s:%d", bindAddr, bindPort), nil, 10, 3)
if err != nil {
return l, err

View File

@@ -19,8 +19,9 @@ import (
"net"
"sync"
"github.com/fatedier/frp/utils/errors"
"github.com/fatedier/frp/utils/log"
"github.com/fatedier/golib/errors"
)
type Listener interface {

View File

@@ -15,12 +15,8 @@
package net
import (
"bufio"
"encoding/base64"
"fmt"
"net"
"net/http"
"net/url"
"github.com/fatedier/frp/utils/log"
)
@@ -33,7 +29,7 @@ type TcpListener struct {
log.Logger
}
func ListenTcp(bindAddr string, bindPort int64) (l *TcpListener, err error) {
func ListenTcp(bindAddr string, bindPort int) (l *TcpListener, err error) {
tcpAddr, err := net.ResolveTCPAddr("tcp", fmt.Sprintf("%s:%d", bindAddr, bindPort))
if err != nil {
return l, err
@@ -93,7 +89,7 @@ type TcpConn struct {
log.Logger
}
func NewTcpConn(conn *net.TCPConn) (c *TcpConn) {
func NewTcpConn(conn net.Conn) (c *TcpConn) {
c = &TcpConn{
Conn: conn,
Logger: log.NewPrefixLogger(""),
@@ -113,54 +109,3 @@ func ConnectTcpServer(addr string) (c Conn, err error) {
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
}

View File

@@ -22,7 +22,8 @@ import (
"time"
"github.com/fatedier/frp/utils/log"
"github.com/fatedier/frp/utils/pool"
"github.com/fatedier/golib/pool"
)
type UdpPacket struct {
@@ -167,7 +168,7 @@ type UdpListener struct {
log.Logger
}
func ListenUDP(bindAddr string, bindPort int64) (l *UdpListener, err error) {
func ListenUDP(bindAddr string, bindPort int) (l *UdpListener, err error) {
udpAddr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", bindAddr, bindPort))
if err != nil {
return l, err

105
utils/net/websocket.go Normal file
View File

@@ -0,0 +1,105 @@
package net
import (
"errors"
"fmt"
"net"
"net/http"
"net/url"
"time"
"github.com/fatedier/frp/utils/log"
"golang.org/x/net/websocket"
)
var (
ErrWebsocketListenerClosed = errors.New("websocket listener closed")
)
const (
FrpWebsocketPath = "/~!frp"
)
type WebsocketListener struct {
net.Addr
ln net.Listener
accept chan Conn
log.Logger
server *http.Server
httpMutex *http.ServeMux
}
// ln: tcp listener for websocket connections
func NewWebsocketListener(ln net.Listener) (wl *WebsocketListener) {
wl = &WebsocketListener{
Addr: ln.Addr(),
accept: make(chan Conn),
Logger: log.NewPrefixLogger(""),
}
muxer := http.NewServeMux()
muxer.Handle(FrpWebsocketPath, websocket.Handler(func(c *websocket.Conn) {
notifyCh := make(chan struct{})
conn := WrapCloseNotifyConn(c, func() {
close(notifyCh)
})
wl.accept <- conn
<-notifyCh
}))
wl.server = &http.Server{
Addr: ln.Addr().String(),
Handler: muxer,
}
go wl.server.Serve(ln)
return
}
func ListenWebsocket(bindAddr string, bindPort int) (*WebsocketListener, error) {
tcpLn, err := net.Listen("tcp", fmt.Sprintf("%s:%d", bindAddr, bindPort))
if err != nil {
return nil, err
}
l := NewWebsocketListener(tcpLn)
return l, nil
}
func (p *WebsocketListener) Accept() (Conn, error) {
c, ok := <-p.accept
if !ok {
return nil, ErrWebsocketListenerClosed
}
return c, nil
}
func (p *WebsocketListener) Close() error {
return p.server.Close()
}
// addr: domain:port
func ConnectWebsocketServer(addr string) (Conn, error) {
addr = "ws://" + addr + FrpWebsocketPath
uri, err := url.Parse(addr)
if err != nil {
return nil, err
}
origin := "http://" + uri.Host
cfg, err := websocket.NewConfig(addr, origin)
if err != nil {
return nil, err
}
cfg.Dialer = &net.Dialer{
Timeout: 10 * time.Second,
}
conn, err := websocket.DialConfig(cfg)
if err != nil {
return nil, err
}
c := WrapConn(conn)
return c, nil
}

View File

@@ -1,21 +0,0 @@
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()
}

View File

@@ -48,65 +48,56 @@ func GetAuthKey(token string, timestamp int64) (key string) {
return hex.EncodeToString(data)
}
// for example: rangeStr is "1000-2000,2001,2002,3000-4000", return an array as port ranges.
func GetPortRanges(rangeStr string) (portRanges [][2]int64, err error) {
// for example: 1000-2000,2001,2002,3000-4000
rangeArray := strings.Split(rangeStr, ",")
for _, portRangeStr := range rangeArray {
func CanonicalAddr(host string, port int) (addr string) {
if port == 80 || port == 443 {
addr = host
} else {
addr = fmt.Sprintf("%s:%d", host, port)
}
return
}
func ParseRangeNumbers(rangeStr string) (numbers []int64, err error) {
rangeStr = strings.TrimSpace(rangeStr)
numbers = make([]int64, 0)
// e.g. 1000-2000,2001,2002,3000-4000
numRanges := strings.Split(rangeStr, ",")
for _, numRangeStr := range numRanges {
// 1000-2000 or 2001
portArray := strings.Split(portRangeStr, "-")
numArray := strings.Split(numRangeStr, "-")
// length: only 1 or 2 is correct
rangeType := len(portArray)
rangeType := len(numArray)
if rangeType == 1 {
singlePort, err := strconv.ParseInt(portArray[0], 10, 64)
if err != nil {
return [][2]int64{}, err
// single number
singleNum, errRet := strconv.ParseInt(strings.TrimSpace(numArray[0]), 10, 64)
if errRet != nil {
err = fmt.Errorf("range number is invalid, %v", errRet)
return
}
portRanges = append(portRanges, [2]int64{singlePort, singlePort})
numbers = append(numbers, singleNum)
} else if rangeType == 2 {
min, err := strconv.ParseInt(portArray[0], 10, 64)
if err != nil {
return [][2]int64{}, err
// range numbers
min, errRet := strconv.ParseInt(strings.TrimSpace(numArray[0]), 10, 64)
if errRet != nil {
err = fmt.Errorf("range number is invalid, %v", errRet)
return
}
max, err := strconv.ParseInt(portArray[1], 10, 64)
if err != nil {
return [][2]int64{}, err
max, errRet := strconv.ParseInt(strings.TrimSpace(numArray[1]), 10, 64)
if errRet != nil {
err = fmt.Errorf("range number is invalid, %v", errRet)
return
}
if max < min {
return [][2]int64{}, fmt.Errorf("range incorrect")
err = fmt.Errorf("range number is invalid")
return
}
portRanges = append(portRanges, [2]int64{min, max})
} else {
return [][2]int64{}, fmt.Errorf("format error")
}
}
return portRanges, nil
}
func ContainsPort(portRanges [][2]int64, port int64) bool {
for _, pr := range portRanges {
if port >= pr[0] && port <= pr[1] {
return true
}
}
return false
}
func PortRangesCut(portRanges [][2]int64, port int64) [][2]int64 {
var tmpRanges [][2]int64
for _, pr := range portRanges {
if port >= pr[0] && port <= pr[1] {
leftRange := [2]int64{pr[0], port - 1}
rightRange := [2]int64{port + 1, pr[1]}
if leftRange[0] <= leftRange[1] {
tmpRanges = append(tmpRanges, leftRange)
}
if rightRange[0] <= rightRange[1] {
tmpRanges = append(tmpRanges, rightRange)
for i := min; i <= max; i++ {
numbers = append(numbers, i)
}
} else {
tmpRanges = append(tmpRanges, pr)
err = fmt.Errorf("range number is invalid")
return
}
}
return tmpRanges
return
}

View File

@@ -21,66 +21,28 @@ func TestGetAuthKey(t *testing.T) {
assert.Equal("6df41a43725f0c770fd56379e12acf8c", key)
}
func TestGetPortRanges(t *testing.T) {
func TestParseRangeNumbers(t *testing.T) {
assert := assert.New(t)
rangesStr := "2000-3000,3001,4000-50000"
expect := [][2]int64{
[2]int64{2000, 3000},
[2]int64{3001, 3001},
[2]int64{4000, 50000},
numbers, err := ParseRangeNumbers("2-5")
if assert.NoError(err) {
assert.Equal([]int64{2, 3, 4, 5}, numbers)
}
actual, err := GetPortRanges(rangesStr)
assert.Nil(err)
t.Log(actual)
assert.Equal(expect, actual)
}
func TestContainsPort(t *testing.T) {
assert := assert.New(t)
rangesStr := "2000-3000,3001,4000-50000"
portRanges, err := GetPortRanges(rangesStr)
assert.Nil(err)
type Case struct {
Port int64
Answer bool
}
cases := []Case{
Case{
Port: 3001,
Answer: true,
},
Case{
Port: 3002,
Answer: false,
},
Case{
Port: 44444,
Answer: true,
},
}
for _, elem := range cases {
ok := ContainsPort(portRanges, elem.Port)
assert.Equal(elem.Answer, ok)
}
}
func TestPortRangesCut(t *testing.T) {
assert := assert.New(t)
rangesStr := "2000-3000,3001,4000-50000"
portRanges, err := GetPortRanges(rangesStr)
assert.Nil(err)
expect := [][2]int64{
[2]int64{2000, 3000},
[2]int64{3001, 3001},
[2]int64{4000, 44443},
[2]int64{44445, 50000},
}
actual := PortRangesCut(portRanges, 44444)
t.Log(actual)
assert.Equal(expect, actual)
numbers, err = ParseRangeNumbers("1")
if assert.NoError(err) {
assert.Equal([]int64{1}, numbers)
}
numbers, err = ParseRangeNumbers("3-5,8")
if assert.NoError(err) {
assert.Equal([]int64{3, 4, 5, 8}, numbers)
}
numbers, err = ParseRangeNumbers(" 3-5,8, 10-12 ")
if assert.NoError(err) {
assert.Equal([]int64{3, 4, 5, 8, 10, 11, 12}, numbers)
}
_, err = ParseRangeNumbers("3-a")
assert.Error(err)
}

View File

@@ -19,7 +19,7 @@ import (
"strings"
)
var version string = "0.14.1"
var version string = "0.21.0"
func Full() string {
return version
@@ -48,8 +48,8 @@ func Minor(v string) int64 {
// add every case there if server will not accept client's protocol and return false
func Compat(client string) (ok bool, msg string) {
if LessThan(client, "0.10.0") {
return false, "Please upgrade your frpc version to at least 0.10.0"
if LessThan(client, "0.18.0") {
return false, "Please upgrade your frpc version to at least 0.18.0"
}
return true, ""
}

View File

@@ -61,5 +61,5 @@ func TestCompact(t *testing.T) {
assert.True(ok)
ok, _ = Compat("0.10.0")
assert.True(ok)
assert.False(ok)
}

View File

@@ -26,7 +26,9 @@ import (
"time"
frpNet "github.com/fatedier/frp/utils/net"
"github.com/fatedier/frp/utils/pool"
gnet "github.com/fatedier/golib/net"
"github.com/fatedier/golib/pool"
)
type HttpMuxer struct {
@@ -35,11 +37,11 @@ type HttpMuxer struct {
func GetHttpRequestInfo(c frpNet.Conn) (_ frpNet.Conn, _ map[string]string, err error) {
reqInfoMap := make(map[string]string, 0)
sc, rd := frpNet.NewShareConn(c)
sc, rd := gnet.NewSharedConn(c)
request, err := http.ReadRequest(bufio.NewReader(rd))
if err != nil {
return sc, reqInfoMap, err
return nil, reqInfoMap, err
}
// hostName
tmpArr := strings.Split(request.Host, ":")
@@ -53,7 +55,7 @@ func GetHttpRequestInfo(c frpNet.Conn) (_ frpNet.Conn, _ map[string]string, err
reqInfoMap["Authorization"] = authStr
}
request.Body.Close()
return sc, reqInfoMap, nil
return frpNet.WrapConn(sc), reqInfoMap, nil
}
func NewHttpMuxer(listener frpNet.Listener, timeout time.Duration) (*HttpMuxer, error) {
@@ -62,14 +64,14 @@ func NewHttpMuxer(listener frpNet.Listener, timeout time.Duration) (*HttpMuxer,
}
func ModifyHttpRequest(c frpNet.Conn, rewriteHost string) (_ frpNet.Conn, err error) {
sc, rd := frpNet.NewShareConn(c)
sc, rd := gnet.NewSharedConn(c)
var buff []byte
remoteIP := strings.Split(c.RemoteAddr().String(), ":")[0]
if buff, err = hostNameRewrite(rd, rewriteHost, remoteIP); err != nil {
return sc, err
return nil, err
}
err = sc.WriteBuff(buff)
return sc, err
err = sc.ResetBuf(buff)
return frpNet.WrapConn(sc), err
}
func hostNameRewrite(request io.Reader, rewriteHost string, remoteIP string) (_ []byte, err error) {

View File

@@ -21,7 +21,9 @@ import (
"time"
frpNet "github.com/fatedier/frp/utils/net"
"github.com/fatedier/frp/utils/pool"
gnet "github.com/fatedier/golib/net"
"github.com/fatedier/golib/pool"
)
const (
@@ -55,14 +57,17 @@ func readHandshake(rd io.Reader) (host string, err error) {
data := pool.GetBuf(1024)
origin := data
defer pool.PutBuf(origin)
length, err := rd.Read(data)
_, err = io.ReadFull(rd, data[:47])
if err != nil {
return
}
length, err := rd.Read(data[47:])
if err != nil {
return
} else {
if length < 47 {
err = fmt.Errorf("readHandshake: proto length[%d] is too short", length)
return
}
length += 47
}
data = data[:length]
if uint8(data[5]) != typeClientHello {
@@ -108,7 +113,7 @@ func readHandshake(rd io.Reader) (host string, err error) {
return
}
if len(data) < 2 {
err = fmt.Errorf("readHandshake: extension dataLen[%d] is too short")
err = fmt.Errorf("readHandshake: extension dataLen[%d] is too short", len(data))
return
}
@@ -177,14 +182,14 @@ func readHandshake(rd io.Reader) (host string, err error) {
return
}
func GetHttpsHostname(c frpNet.Conn) (sc frpNet.Conn, _ map[string]string, err error) {
func GetHttpsHostname(c frpNet.Conn) (_ frpNet.Conn, _ map[string]string, err error) {
reqInfoMap := make(map[string]string, 0)
sc, rd := frpNet.NewShareConn(c)
sc, rd := gnet.NewSharedConn(c)
host, err := readHandshake(rd)
if err != nil {
return sc, reqInfoMap, err
return nil, reqInfoMap, err
}
reqInfoMap["Host"] = host
reqInfoMap["Scheme"] = "https"
return sc, reqInfoMap, nil
return frpNet.WrapConn(sc), reqInfoMap, nil
}

View File

@@ -26,12 +26,11 @@ import (
"time"
frpLog "github.com/fatedier/frp/utils/log"
"github.com/fatedier/frp/utils/pool"
"github.com/fatedier/golib/pool"
)
var (
responseHeaderTimeout = time.Duration(30) * time.Second
ErrRouterConfigConflict = errors.New("router config conflict")
ErrNoDomain = errors.New("no such domain")
)
@@ -46,32 +45,45 @@ func getHostFromAddr(addr string) (host string) {
return
}
type HttpReverseProxyOptions struct {
ResponseHeaderTimeoutS int64
}
type HttpReverseProxy struct {
proxy *ReverseProxy
tr *http.Transport
vhostRouter *VhostRouters
cfgMu sync.RWMutex
responseHeaderTimeout time.Duration
cfgMu sync.RWMutex
}
func NewHttpReverseProxy() *HttpReverseProxy {
func NewHttpReverseProxy(option HttpReverseProxyOptions) *HttpReverseProxy {
if option.ResponseHeaderTimeoutS <= 0 {
option.ResponseHeaderTimeoutS = 60
}
rp := &HttpReverseProxy{
vhostRouter: NewVhostRouters(),
responseHeaderTimeout: time.Duration(option.ResponseHeaderTimeoutS) * time.Second,
vhostRouter: NewVhostRouters(),
}
proxy := &ReverseProxy{
Director: func(req *http.Request) {
req.URL.Scheme = "http"
url := req.Context().Value("url").(string)
host := getHostFromAddr(req.Context().Value("host").(string))
host = rp.GetRealHost(host, url)
oldHost := getHostFromAddr(req.Context().Value("host").(string))
host := rp.GetRealHost(oldHost, url)
if host != "" {
req.Host = host
}
req.URL.Host = req.Host
headers := rp.GetHeaders(oldHost, url)
for k, v := range headers {
req.Header.Set(k, v)
}
},
Transport: &http.Transport{
ResponseHeaderTimeout: responseHeaderTimeout,
ResponseHeaderTimeout: rp.responseHeaderTimeout,
DisableKeepAlives: true,
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
url := ctx.Value("url").(string)
@@ -79,6 +91,11 @@ func NewHttpReverseProxy() *HttpReverseProxy {
return rp.CreateConnection(host, url)
},
},
WebSocketDialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
url := ctx.Value("url").(string)
host := getHostFromAddr(ctx.Value("host").(string))
return rp.CreateConnection(host, url)
},
BufferPool: newWrapPool(),
ErrorLog: log.New(newWrapLogger(), "", 0),
}
@@ -112,6 +129,14 @@ func (rp *HttpReverseProxy) GetRealHost(domain string, location string) (host st
return
}
func (rp *HttpReverseProxy) GetHeaders(domain string, location string) (headers map[string]string) {
vr, ok := rp.getVhost(domain, location)
if ok {
headers = vr.payload.(*VhostRouteConfig).Headers
}
return
}
func (rp *HttpReverseProxy) CreateConnection(domain string, location string) (net.Conn, error) {
vr, ok := rp.getVhost(domain, location)
if ok {

View File

@@ -16,6 +16,8 @@ import (
"strings"
"sync"
"time"
frpIo "github.com/fatedier/golib/io"
)
// onExitFlushLoop is a callback set by tests to detect the state of the
@@ -59,6 +61,8 @@ type ReverseProxy struct {
// modifies the Response from the backend.
// If it returns an error, the proxy returns a StatusBadGateway error.
ModifyResponse func(*http.Response) error
WebSocketDialContext func(ctx context.Context, network, addr string) (net.Conn, error)
}
// A BufferPool is an interface for getting and returning temporary
@@ -139,6 +143,48 @@ var hopHeaders = []string{
}
func (p *ReverseProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
if IsWebsocketRequest(req) {
p.serveWebSocket(rw, req)
} else {
p.serveHTTP(rw, req)
}
}
func (p *ReverseProxy) serveWebSocket(rw http.ResponseWriter, req *http.Request) {
if p.WebSocketDialContext == nil {
rw.WriteHeader(500)
return
}
req = req.WithContext(context.WithValue(req.Context(), "url", req.URL.Path))
req = req.WithContext(context.WithValue(req.Context(), "host", req.Host))
targetConn, err := p.WebSocketDialContext(req.Context(), "tcp", "")
if err != nil {
rw.WriteHeader(501)
return
}
defer targetConn.Close()
p.Director(req)
hijacker, ok := rw.(http.Hijacker)
if !ok {
rw.WriteHeader(500)
return
}
conn, _, errHijack := hijacker.Hijack()
if errHijack != nil {
rw.WriteHeader(500)
return
}
defer conn.Close()
req.Write(targetConn)
frpIo.Join(conn, targetConn)
}
func (p *ReverseProxy) serveHTTP(rw http.ResponseWriter, req *http.Request) {
transport := p.Transport
if transport == nil {
transport = http.DefaultTransport
@@ -368,3 +414,16 @@ func (m *maxLatencyWriter) flushLoop() {
}
func (m *maxLatencyWriter) stop() { m.done <- true }
func IsWebsocketRequest(req *http.Request) bool {
containsHeader := func(name, value string) bool {
items := strings.Split(req.Header.Get(name), ",")
for _, item := range items {
if value == strings.ToLower(strings.TrimSpace(item)) {
return true
}
}
return false
}
return containsHeader("Connection", "upgrade") && containsHeader("Upgrade", "websocket")
}

View File

@@ -52,16 +52,13 @@ func (r *VhostRouters) Del(domain, location string) {
if !found {
return
}
for i, vr := range vrs {
if vr.location == location {
if len(vrs) > i+1 {
r.RouterByDomain[domain] = append(vrs[:i], vrs[i+1:]...)
} else {
r.RouterByDomain[domain] = vrs[:i]
}
newVrs := make([]*VhostRouter, 0)
for _, vr := range vrs {
if vr.location != location {
newVrs = append(newVrs, vr)
}
}
r.RouterByDomain[domain] = newVrs
}
func (r *VhostRouters) Get(host, path string) (vr *VhostRouter, exist bool) {

View File

@@ -18,9 +18,10 @@ import (
"sync"
"time"
"github.com/fatedier/frp/utils/errors"
"github.com/fatedier/frp/utils/log"
frpNet "github.com/fatedier/frp/utils/net"
"github.com/fatedier/golib/errors"
)
type muxFunc func(frpNet.Conn) (frpNet.Conn, map[string]string, error)
@@ -58,6 +59,7 @@ type VhostRouteConfig struct {
RewriteHost string
Username string
Password string
Headers map[string]string
CreateConnFn CreateConnFunc
}

View File

@@ -1,119 +0,0 @@
package socks5
import (
"bytes"
"testing"
)
func TestNoAuth(t *testing.T) {
req := bytes.NewBuffer(nil)
req.Write([]byte{1, NoAuth})
var resp bytes.Buffer
s, _ := New(&Config{})
ctx, err := s.authenticate(&resp, req)
if err != nil {
t.Fatalf("err: %v", err)
}
if ctx.Method != NoAuth {
t.Fatal("Invalid Context Method")
}
out := resp.Bytes()
if !bytes.Equal(out, []byte{socks5Version, NoAuth}) {
t.Fatalf("bad: %v", out)
}
}
func TestPasswordAuth_Valid(t *testing.T) {
req := bytes.NewBuffer(nil)
req.Write([]byte{2, NoAuth, UserPassAuth})
req.Write([]byte{1, 3, 'f', 'o', 'o', 3, 'b', 'a', 'r'})
var resp bytes.Buffer
cred := StaticCredentials{
"foo": "bar",
}
cator := UserPassAuthenticator{Credentials: cred}
s, _ := New(&Config{AuthMethods: []Authenticator{cator}})
ctx, err := s.authenticate(&resp, req)
if err != nil {
t.Fatalf("err: %v", err)
}
if ctx.Method != UserPassAuth {
t.Fatal("Invalid Context Method")
}
val, ok := ctx.Payload["Username"]
if !ok {
t.Fatal("Missing key Username in auth context's payload")
}
if val != "foo" {
t.Fatal("Invalid Username in auth context's payload")
}
out := resp.Bytes()
if !bytes.Equal(out, []byte{socks5Version, UserPassAuth, 1, authSuccess}) {
t.Fatalf("bad: %v", out)
}
}
func TestPasswordAuth_Invalid(t *testing.T) {
req := bytes.NewBuffer(nil)
req.Write([]byte{2, NoAuth, UserPassAuth})
req.Write([]byte{1, 3, 'f', 'o', 'o', 3, 'b', 'a', 'z'})
var resp bytes.Buffer
cred := StaticCredentials{
"foo": "bar",
}
cator := UserPassAuthenticator{Credentials: cred}
s, _ := New(&Config{AuthMethods: []Authenticator{cator}})
ctx, err := s.authenticate(&resp, req)
if err != UserAuthFailed {
t.Fatalf("err: %v", err)
}
if ctx != nil {
t.Fatal("Invalid Context Method")
}
out := resp.Bytes()
if !bytes.Equal(out, []byte{socks5Version, UserPassAuth, 1, authFailure}) {
t.Fatalf("bad: %v", out)
}
}
func TestNoSupportedAuth(t *testing.T) {
req := bytes.NewBuffer(nil)
req.Write([]byte{1, NoAuth})
var resp bytes.Buffer
cred := StaticCredentials{
"foo": "bar",
}
cator := UserPassAuthenticator{Credentials: cred}
s, _ := New(&Config{AuthMethods: []Authenticator{cator}})
ctx, err := s.authenticate(&resp, req)
if err != NoSupportedAuth {
t.Fatalf("err: %v", err)
}
if ctx != nil {
t.Fatal("Invalid Context Method")
}
out := resp.Bytes()
if !bytes.Equal(out, []byte{socks5Version, noAcceptable}) {
t.Fatalf("bad: %v", out)
}
}

View File

@@ -1,24 +0,0 @@
package socks5
import (
"testing"
)
func TestStaticCredentials(t *testing.T) {
creds := StaticCredentials{
"foo": "bar",
"baz": "",
}
if !creds.Valid("foo", "bar") {
t.Fatalf("expect valid")
}
if !creds.Valid("baz", "") {
t.Fatalf("expect valid")
}
if creds.Valid("foo", "") {
t.Fatalf("expect invalid")
}
}

View File

@@ -1,169 +0,0 @@
package socks5
import (
"bytes"
"encoding/binary"
"io"
"log"
"net"
"os"
"strings"
"testing"
)
type MockConn struct {
buf bytes.Buffer
}
func (m *MockConn) Write(b []byte) (int, error) {
return m.buf.Write(b)
}
func (m *MockConn) RemoteAddr() net.Addr {
return &net.TCPAddr{IP: []byte{127, 0, 0, 1}, Port: 65432}
}
func TestRequest_Connect(t *testing.T) {
// Create a local listener
l, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
t.Fatalf("err: %v", err)
}
go func() {
conn, err := l.Accept()
if err != nil {
t.Fatalf("err: %v", err)
}
defer conn.Close()
buf := make([]byte, 4)
if _, err := io.ReadAtLeast(conn, buf, 4); err != nil {
t.Fatalf("err: %v", err)
}
if !bytes.Equal(buf, []byte("ping")) {
t.Fatalf("bad: %v", buf)
}
conn.Write([]byte("pong"))
}()
lAddr := l.Addr().(*net.TCPAddr)
// Make server
s := &Server{config: &Config{
Rules: PermitAll(),
Resolver: DNSResolver{},
Logger: log.New(os.Stdout, "", log.LstdFlags),
}}
// Create the connect request
buf := bytes.NewBuffer(nil)
buf.Write([]byte{5, 1, 0, 1, 127, 0, 0, 1})
port := []byte{0, 0}
binary.BigEndian.PutUint16(port, uint16(lAddr.Port))
buf.Write(port)
// Send a ping
buf.Write([]byte("ping"))
// Handle the request
resp := &MockConn{}
req, err := NewRequest(buf)
if err != nil {
t.Fatalf("err: %v", err)
}
if err := s.handleRequest(req, resp); err != nil {
t.Fatalf("err: %v", err)
}
// Verify response
out := resp.buf.Bytes()
expected := []byte{
5,
0,
0,
1,
127, 0, 0, 1,
0, 0,
'p', 'o', 'n', 'g',
}
// Ignore the port for both
out[8] = 0
out[9] = 0
if !bytes.Equal(out, expected) {
t.Fatalf("bad: %v %v", out, expected)
}
}
func TestRequest_Connect_RuleFail(t *testing.T) {
// Create a local listener
l, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
t.Fatalf("err: %v", err)
}
go func() {
conn, err := l.Accept()
if err != nil {
t.Fatalf("err: %v", err)
}
defer conn.Close()
buf := make([]byte, 4)
if _, err := io.ReadAtLeast(conn, buf, 4); err != nil {
t.Fatalf("err: %v", err)
}
if !bytes.Equal(buf, []byte("ping")) {
t.Fatalf("bad: %v", buf)
}
conn.Write([]byte("pong"))
}()
lAddr := l.Addr().(*net.TCPAddr)
// Make server
s := &Server{config: &Config{
Rules: PermitNone(),
Resolver: DNSResolver{},
Logger: log.New(os.Stdout, "", log.LstdFlags),
}}
// Create the connect request
buf := bytes.NewBuffer(nil)
buf.Write([]byte{5, 1, 0, 1, 127, 0, 0, 1})
port := []byte{0, 0}
binary.BigEndian.PutUint16(port, uint16(lAddr.Port))
buf.Write(port)
// Send a ping
buf.Write([]byte("ping"))
// Handle the request
resp := &MockConn{}
req, err := NewRequest(buf)
if err != nil {
t.Fatalf("err: %v", err)
}
if err := s.handleRequest(req, resp); !strings.Contains(err.Error(), "blocked by rules") {
t.Fatalf("err: %v", err)
}
// Verify response
out := resp.buf.Bytes()
expected := []byte{
5,
2,
0,
1,
0, 0, 0, 0,
0, 0,
}
if !bytes.Equal(out, expected) {
t.Fatalf("bad: %v %v", out, expected)
}
}

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