Compare commits

...

109 Commits

Author SHA1 Message Date
fatedier
d5ce4d4916 Merge pull request #1000 from fatedier/dev
bump version to v0.22.0
2018-12-09 22:35:26 +08:00
fatedier
4f0ee5980d Merge pull request #998 from fatedier/health
frpc: support health check
2018-12-09 22:10:13 +08:00
fatedier
146956ac6e bump version to v0.22.0 2018-12-09 22:06:56 +08:00
fatedier
35278ad17f mv folders 2018-12-09 22:06:22 +08:00
fatedier
aea9f9fbcc health: add more ci cases and fix bugs 2018-12-09 21:56:46 +08:00
fatedier
08c17c3247 frpc: support health check 2018-12-07 18:40:17 +08:00
fatedier
6934a18f95 Merge pull request #981 from bighamx/patch-1
Update README_zh.md
2018-11-21 14:56:44 +08:00
SpanishBigHam
5165b0821f Update README_zh.md
fix typo
2018-11-21 14:51:33 +08:00
fatedier
0aec869513 Merge pull request #974 from acrogenesis/patch-1
Fix setting basic auth password doc
2018-11-19 10:55:21 +08:00
Adrian Rangel
826b9db5f2 fix setting basic auth password doc 2018-11-15 22:53:35 -06:00
fatedier
89d1a1fb2b Merge pull request #968 from fatedier/health
support health check and code refactor
2018-11-09 10:06:47 +08:00
fatedier
450e0b7148 Merge pull request #967 from fatedier/fix
update golib, fix #959
2018-11-08 09:50:35 +08:00
fatedier
a1ac002694 update golib, fix #959 2018-11-07 21:10:47 +08:00
fatedier
951d33d47c Merge pull request #966 from fatedier/new_health
refactor code
2018-11-06 23:16:56 +08:00
fatedier
b33ea9274c client/control: refactor code 2018-11-06 18:35:05 +08:00
fatedier
3b3f3dc2b5 Merge pull request #947 from HaraldNordgren/master
Bump Travis versions
2018-10-29 10:16:58 +08:00
fatedier
ec0b59732c Merge pull request #955 from muesli/readme-fixes
Grammar fixes and improved README
2018-10-25 10:43:02 +08:00
Christian Muehlhaeuser
bae1ecdc69 Grammar fixes and improved README
I think this reads nicer and more accurately describes the HTTP reverse proxying.
2018-10-25 00:57:50 +02:00
Harald Nordgren
5c2ab5a749 Bump Travis versions 2018-10-19 21:01:21 +02:00
fatedier
1a8ac148ca fix xtcp visitor panic 2018-10-18 13:55:51 +08:00
fatedier
698219b621 frpc: support health check 2018-09-11 18:33:02 +08:00
fatedier
229740524e Merge pull request #891 from mark86092/bump-docker-to-1-10
Upgrade dockerfile golang to 1.10
2018-08-12 22:04:49 +08:00
Yen-chi Chen
cbeeac06a5 Upgrade golang to 1.10 2018-08-12 15:03:48 +08:00
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
082447f517 frpc: support health check 2018-07-16 01:21:29 +08:00
fatedier
cc6486addb add more cmd test 2018-07-12 16:49:16 +08:00
fatedier
57417c83ae add ci case of reload and reconnect 2018-07-12 15:23:34 +08:00
fatedier
d74b45be5d more ci 2018-07-12 00:31:21 +08:00
fatedier
0d02f291e3 refactor ci test 2018-07-11 23:27:47 +08:00
fatedier
42ee536dae add module comments for vgo 2018-06-27 11:46:53 +08:00
fatedier
c33b5152e7 split visitors from proxies and add health check config 2018-06-25 18:22:35 +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
1370 changed files with 21676 additions and 312913 deletions

View File

@@ -3,6 +3,7 @@ language: go
go: go:
- 1.10.x - 1.10.x
- 1.11.x
install: install:
- make - make

View File

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

251
Gopkg.lock generated Normal file
View File

@@ -0,0 +1,251 @@
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
[[projects]]
digest = "1:5a91fc342af1f94bce8b760a80d5b709fe53ea10c870a5daf1dc7e9fada8525f"
name = "github.com/armon/go-socks5"
packages = ["."]
pruneopts = "UT"
revision = "e75332964ef517daa070d7c38a9466a0d687e0a5"
[[projects]]
digest = "1:a2c1d0e43bd3baaa071d1b9ed72c27d78169b2b269f71c105ac4ba34b1be4a39"
name = "github.com/davecgh/go-spew"
packages = ["spew"]
pruneopts = "UT"
revision = "346938d642f2ec3594ed81d874461961cd0faa76"
version = "v1.1.0"
[[projects]]
digest = "1:0f8ca5fa815e8058bfbf5d0e4ad0c2f8334d68cac86e3bfee94b4e3031e9f69f"
name = "github.com/fatedier/beego"
packages = ["logs"]
pruneopts = "UT"
revision = "6c6a4f5bd5eb5a39f7e289b8f345b55f75e7e3e8"
[[projects]]
digest = "1:edb90bd03be19aa95d375ed6eb5d681538e0a3f7d2a057b69bc2ca6e5217477a"
name = "github.com/fatedier/golib"
packages = [
"control/shutdown",
"crypto",
"errors",
"io",
"msg/json",
"net",
"net/mux",
"pool",
]
pruneopts = "UT"
revision = "ff8cd814b04901d617b7fffaca6fedb81067821d"
[[projects]]
branch = "frp"
digest = "1:6621826f49b587c0d6f868e1c56d2bbbc1d75597347d97419b3b027e8a753bdb"
name = "github.com/fatedier/kcp-go"
packages = ["."]
pruneopts = "UT"
revision = "cd167d2f15f451b0f33780ce862fca97adc0331e"
[[projects]]
digest = "1:29a5ab9fa9e845fd8e8726f31b187d710afd271ef1eb32085fe3d604b7e06382"
name = "github.com/golang/snappy"
packages = ["."]
pruneopts = "UT"
revision = "553a641470496b2327abcac10b36396bd98e45c9"
[[projects]]
digest = "1:c79fb010be38a59d657c48c6ba1d003a8aa651fa56b579d959d74573b7dff8e1"
name = "github.com/gorilla/context"
packages = ["."]
pruneopts = "UT"
revision = "08b5f424b9271eedf6f9f0ce86cb9396ed337a42"
version = "v1.1.1"
[[projects]]
digest = "1:e73f5b0152105f18bc131fba127d9949305c8693f8a762588a82a48f61756f5f"
name = "github.com/gorilla/mux"
packages = ["."]
pruneopts = "UT"
revision = "e3702bed27f0d39777b0b37b664b6280e8ef8fbf"
version = "v1.6.2"
[[projects]]
digest = "1:43dd08a10854b2056e615d1b1d22ac94559d822e1f8b6fcc92c1a1057e85188e"
name = "github.com/gorilla/websocket"
packages = ["."]
pruneopts = "UT"
revision = "ea4d1f681babbce9545c9c5f3d5194a789c89f5b"
version = "v1.2.0"
[[projects]]
digest = "1:6074024c54115955afc83ee5064367523bbc55e4eb0e9cf145e43c9c0371918c"
name = "github.com/hashicorp/yamux"
packages = ["."]
pruneopts = "UT"
revision = "2658be15c5f05e76244154714161f17e3e77de2e"
[[projects]]
digest = "1:870d441fe217b8e689d7949fef6e43efbc787e50f200cb1e70dbca9204a1d6be"
name = "github.com/inconshreveable/mousetrap"
packages = ["."]
pruneopts = "UT"
revision = "76626ae9c91c4f2a10f34cad8ce83ea42c93bb75"
version = "v1.0"
[[projects]]
digest = "1:40e195917a951a8bf867cd05de2a46aaf1806c50cf92eebf4c16f78cd196f747"
name = "github.com/pkg/errors"
packages = ["."]
pruneopts = "UT"
revision = "645ef00459ed84a119197bfb8d8205042c6df63d"
version = "v0.8.0"
[[projects]]
digest = "1:0028cb19b2e4c3112225cd871870f2d9cf49b9b4276531f03438a88e94be86fe"
name = "github.com/pmezard/go-difflib"
packages = ["difflib"]
pruneopts = "UT"
revision = "792786c7400a136282c1664665ae0a8db921c6c2"
version = "v1.0.0"
[[projects]]
digest = "1:bc91590d3e20673d5e33267fc140e7dadddde0b84f2e9030547ba86859d2d13e"
name = "github.com/rakyll/statik"
packages = ["fs"]
pruneopts = "UT"
revision = "fd36b3595eb2ec8da4b8153b107f7ea08504899d"
version = "v0.1.1"
[[projects]]
digest = "1:4c01929c0b1665523b469482fc8241a04519bd5bfc97a1c113367cfadebab07b"
name = "github.com/rodaine/table"
packages = ["."]
pruneopts = "UT"
revision = "212a2ad1c462ed4d5b5511ea2b480a573281dbbd"
version = "v1.0.0"
[[projects]]
digest = "1:645cabccbb4fa8aab25a956cbcbdf6a6845ca736b2c64e197ca7cbb9d210b939"
name = "github.com/spf13/cobra"
packages = ["."]
pruneopts = "UT"
revision = "ef82de70bb3f60c65fb8eebacbb2d122ef517385"
version = "v0.0.3"
[[projects]]
digest = "1:9424f440bba8f7508b69414634aef3b2b3a877e522d8a4624692412805407bb7"
name = "github.com/spf13/pflag"
packages = ["."]
pruneopts = "UT"
revision = "583c0c0531f06d5278b7d917446061adc344b5cd"
version = "v1.0.1"
[[projects]]
digest = "1:f85e109eda8f6080877185d1c39e98dd8795e1780c08beca28304b87fd855a1c"
name = "github.com/stretchr/testify"
packages = ["assert"]
pruneopts = "UT"
revision = "12b6f73e6084dad08a7c6e575284b177ecafbc71"
version = "v1.2.1"
[[projects]]
branch = "master"
digest = "1:710ccf83337a9ca27abe968c3e58fdf16bd69d76b9870dadafc511e94fc33d7f"
name = "github.com/templexxx/cpufeat"
packages = ["."]
pruneopts = "UT"
revision = "3794dfbfb04749f896b521032f69383f24c3687e"
[[projects]]
digest = "1:7bf0e709c5dd92c937e6f59a76056fe0a89cfe2f52ce25493c6337d23781af0a"
name = "github.com/templexxx/reedsolomon"
packages = ["."]
pruneopts = "UT"
revision = "5e06b81a1c7628d9c8d4fb7c3c4e401e37db39b4"
version = "0.1.1"
[[projects]]
digest = "1:a0a269bea865974fc4d583373c984a5aa60cf98b5aa4f3e1b5de527891d37845"
name = "github.com/templexxx/xor"
packages = ["."]
pruneopts = "UT"
revision = "0af8e873c554da75f37f2049cdffda804533d44c"
version = "0.1.2"
[[projects]]
digest = "1:97293f3bd0b9f81484da18dba66a20de340307b43835a91157aaaee484c80e9b"
name = "github.com/tjfoc/gmsm"
packages = ["sm4"]
pruneopts = "UT"
revision = "98aa888b79d8de04afe0fccf45ed10594efc858b"
version = "v1.1"
[[projects]]
digest = "1:8f70510b21fd07eba5bd4e0f84d49d932ea74c8b0ea20a5807e9492cc819928c"
name = "github.com/vaughan0/go-ini"
packages = ["."]
pruneopts = "UT"
revision = "a98ad7ee00ec53921f08832bc06ecf7fd600e6a1"
[[projects]]
digest = "1:a14b2b0fb9cc2d9ed073aac9834ff93ded673b94fedee4eead3cd9a65e80a40b"
name = "golang.org/x/crypto"
packages = [
"blowfish",
"cast5",
"pbkdf2",
"salsa20",
"salsa20/salsa",
"tea",
"twofish",
"xtea",
]
pruneopts = "UT"
revision = "4ec37c66abab2c7e02ae775328b2ff001c3f025a"
[[projects]]
branch = "master"
digest = "1:4781de952463c8e97ab707c03c73f5f53296be672d1bceac9323393a7b6e7e0a"
name = "golang.org/x/net"
packages = [
"bpf",
"context",
"internal/iana",
"internal/socket",
"internal/socks",
"ipv4",
"proxy",
"websocket",
]
pruneopts = "UT"
revision = "dfa909b99c79129e1100513e5cd36307665e5723"
[solve-meta]
analyzer-name = "dep"
analyzer-version = 1
input-imports = [
"github.com/armon/go-socks5",
"github.com/fatedier/beego/logs",
"github.com/fatedier/golib/control/shutdown",
"github.com/fatedier/golib/crypto",
"github.com/fatedier/golib/errors",
"github.com/fatedier/golib/io",
"github.com/fatedier/golib/msg/json",
"github.com/fatedier/golib/net",
"github.com/fatedier/golib/net/mux",
"github.com/fatedier/golib/pool",
"github.com/fatedier/kcp-go",
"github.com/gorilla/mux",
"github.com/gorilla/websocket",
"github.com/hashicorp/yamux",
"github.com/rakyll/statik/fs",
"github.com/rodaine/table",
"github.com/spf13/cobra",
"github.com/stretchr/testify/assert",
"github.com/vaughan0/go-ini",
"golang.org/x/net/ipv4",
"golang.org/x/net/websocket",
]
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"
revision = "ff8cd814b04901d617b7fffaca6fedb81067821d"
[[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 PATH := $(GOPATH)/bin:$(PATH)
export GO15VENDOREXPERIMENT := 1
all: fmt build all: fmt build
@@ -27,24 +26,18 @@ frpc:
test: gotest test: gotest
gotest: gotest:
go test -v ./assets/... go test -v --cover ./assets/...
go test -v ./client/... go test -v --cover ./client/...
go test -v ./cmd/... go test -v --cover ./cmd/...
go test -v ./models/... go test -v --cover ./models/...
go test -v ./server/... go test -v --cover ./server/...
go test -v ./utils/... go test -v --cover ./utils/...
ci: ci:
cd ./tests && ./run_test.sh && cd - go test -count=1 -p=1 -v ./tests/...
go test -v ./tests/...
cd ./tests && ./clean_test.sh && cd -
cic:
cd ./tests && ./clean_test.sh && cd -
alltest: gotest ci alltest: gotest ci
clean: clean:
rm -f ./bin/frpc rm -f ./bin/frpc
rm -f ./bin/frps rm -f ./bin/frps
cd ./tests && ./clean_test.sh && cd -

View File

@@ -1,5 +1,4 @@
export PATH := $(GOPATH)/bin:$(PATH) export PATH := $(GOPATH)/bin:$(PATH)
export GO15VENDOREXPERIMENT := 1
LDFLAGS := -s -w LDFLAGS := -s -w
all: build all: build
@@ -19,6 +18,8 @@ app:
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=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 ./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=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 ./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=386 go build -ldflags "$(LDFLAGS)" -o ./frps_windows_386.exe ./cmd/frps
env CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -ldflags "$(LDFLAGS)" -o ./frpc_windows_amd64.exe ./cmd/frpc env CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -ldflags "$(LDFLAGS)" -o ./frpc_windows_amd64.exe ./cmd/frpc

View File

@@ -6,7 +6,7 @@
## What is frp? ## What is frp?
frp is a fast reverse proxy to help you expose a local server behind a NAT or firewall to the internet. Now, it supports tcp, udp, http and https protocol when requests can be forwarded by domains to backward web services. frp is a fast reverse proxy to help you expose a local server behind a NAT or firewall to the internet. As of now, it supports tcp & udp, as well as http and https protocols, where requests can be forwarded to internal services by domain name.
## Table of Contents ## Table of Contents
@@ -23,7 +23,6 @@ frp is a fast reverse proxy to help you expose a local server behind a NAT or fi
* [Expose a simple http file server](#expose-a-simple-http-file-server) * [Expose a simple http file server](#expose-a-simple-http-file-server)
* [Expose your service in security](#expose-your-service-in-security) * [Expose your service in security](#expose-your-service-in-security)
* [P2P Mode](#p2p-mode) * [P2P Mode](#p2p-mode)
* [Connect website through frpc's network](#connect-website-through-frpcs-network)
* [Features](#features) * [Features](#features)
* [Configuration File](#configuration-file) * [Configuration File](#configuration-file)
* [Dashboard](#dashboard) * [Dashboard](#dashboard)
@@ -32,10 +31,13 @@ frp is a fast reverse proxy to help you expose a local server behind a NAT or fi
* [Hot-Reload frpc configuration](#hot-reload-frpc-configuration) * [Hot-Reload frpc configuration](#hot-reload-frpc-configuration)
* [Get proxy status from client](#get-proxy-status-from-client) * [Get proxy status from client](#get-proxy-status-from-client)
* [Port White List](#port-white-list) * [Port White List](#port-white-list)
* [Port Reuse](#port-reuse)
* [TCP Stream Multiplexing](#tcp-stream-multiplexing) * [TCP Stream Multiplexing](#tcp-stream-multiplexing)
* [Support KCP Protocol](#support-kcp-protocol) * [Support KCP Protocol](#support-kcp-protocol)
* [Connection Pool](#connection-pool) * [Connection Pool](#connection-pool)
* [Load balancing](#load-balancing)
* [Rewriting the Host Header](#rewriting-the-host-header) * [Rewriting the Host Header](#rewriting-the-host-header)
* [Set Headers In HTTP Request](#set-headers-in-http-request)
* [Get Real IP](#get-real-ip) * [Get Real IP](#get-real-ip)
* [Password protecting your web service](#password-protecting-your-web-service) * [Password protecting your web service](#password-protecting-your-web-service)
* [Custom subdomain names](#custom-subdomain-names) * [Custom subdomain names](#custom-subdomain-names)
@@ -333,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` `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 ## Features
### Configuration File ### Configuration File
@@ -434,6 +416,12 @@ allow_ports = 2000-3000,3001,3003,4000-50000
`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 ### TCP Stream Multiplexing
frp support tcp stream multiplexing since v0.10.0 like HTTP2 Multiplexing. All user requests to same frpc can use only one tcp connection. frp support tcp stream multiplexing since v0.10.0 like HTTP2 Multiplexing. All user requests to same frpc can use only one tcp connection.
@@ -497,9 +485,35 @@ This feature is fit for a large number of short connections.
pool_count = 1 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 ### 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 ```ini
# frpc.ini # frpc.ini
@@ -510,7 +524,24 @@ custom_domains = test.yourdomain.com
host_header_rewrite = dev.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 ### Get Real IP
@@ -535,7 +566,7 @@ type = http
local_port = 80 local_port = 80
custom_domains = test.yourdomain.com custom_domains = test.yourdomain.com
http_user = abc http_user = abc
http_passwd = abc http_pwd = abc
``` ```
Visit `http://test.yourdomain.com` and now you need to input username and password. Visit `http://test.yourdomain.com` and now you need to input username and password.
@@ -612,7 +643,7 @@ local_port = 6000-6006,6007
remote_port = 6000-6006,6007 remote_port = 6000-6006,6007
``` ```
frpc will generate 6 proxies like `test_tcp_0, test_tcp_1 ... test_tcp_5`. frpc will generate 8 proxies like `test_tcp_0, test_tcp_1 ... test_tcp_7`.
### Plugin ### Plugin
@@ -640,7 +671,6 @@ plugin_http_passwd = abc
* Log http request information in frps. * Log http request information in frps.
* Direct reverse proxy, like haproxy. * Direct reverse proxy, like haproxy.
* Load balance to different service in frpc.
* kubernetes ingress support. * kubernetes ingress support.
## Contributing ## Contributing

View File

@@ -21,7 +21,6 @@ frp 是一个可用于内网穿透的高性能的反向代理应用,支持 tcp
* [对外提供简单的文件访问服务](#对外提供简单的文件访问服务) * [对外提供简单的文件访问服务](#对外提供简单的文件访问服务)
* [安全地暴露内网服务](#安全地暴露内网服务) * [安全地暴露内网服务](#安全地暴露内网服务)
* [点对点内网穿透](#点对点内网穿透) * [点对点内网穿透](#点对点内网穿透)
* [通过 frpc 所在机器访问外网](#通过-frpc-所在机器访问外网)
* [功能说明](#功能说明) * [功能说明](#功能说明)
* [配置文件](#配置文件) * [配置文件](#配置文件)
* [Dashboard](#dashboard) * [Dashboard](#dashboard)
@@ -30,10 +29,13 @@ frp 是一个可用于内网穿透的高性能的反向代理应用,支持 tcp
* [客户端热加载配置文件](#客户端热加载配置文件) * [客户端热加载配置文件](#客户端热加载配置文件)
* [客户端查看代理状态](#客户端查看代理状态) * [客户端查看代理状态](#客户端查看代理状态)
* [端口白名单](#端口白名单) * [端口白名单](#端口白名单)
* [端口复用](#端口复用)
* [TCP 多路复用](#tcp-多路复用) * [TCP 多路复用](#tcp-多路复用)
* [底层通信可选 kcp 协议](#底层通信可选-kcp-协议) * [底层通信可选 kcp 协议](#底层通信可选-kcp-协议)
* [连接池](#连接池) * [连接池](#连接池)
* [负载均衡](#负载均衡)
* [修改 Host Header](#修改-host-header) * [修改 Host Header](#修改-host-header)
* [设置 HTTP 请求的 header](#设置-http-请求的-header)
* [获取用户真实 IP](#获取用户真实-ip) * [获取用户真实 IP](#获取用户真实-ip)
* [通过密码保护你的 web 服务](#通过密码保护你的-web-服务) * [通过密码保护你的 web 服务](#通过密码保护你的-web-服务)
* [自定义二级域名](#自定义二级域名) * [自定义二级域名](#自定义二级域名)
@@ -348,28 +350,6 @@ frp 提供了一种新的代理类型 **xtcp** 用于应对在希望传输大量
`ssh -oPort=6000 test@127.0.0.1` `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 机器的网络访问互联网。
## 功能说明 ## 功能说明
### 配置文件 ### 配置文件
@@ -461,6 +441,14 @@ allow_ports = 2000-3000,3001,3003,4000-50000
`allow_ports` 可以配置允许使用的某个指定端口或者是一个范围内的所有端口,以 `,` 分隔,指定的范围以 `-` 分隔。 `allow_ports` 可以配置允许使用的某个指定端口或者是一个范围内的所有端口,以 `,` 分隔,指定的范围以 `-` 分隔。
### 端口复用
目前 frps 中的 `vhost_http_port` 和 `vhost_https_port` 支持配置成和 `bind_port` 为同一个端口frps 会对连接的协议进行分析,之后进行不同的处理。
例如在某些限制较严格的网络环境中,可以将 `bind_port` 和 `vhost_https_port` 都设置为 443。
后续会尝试允许多个 proxy 绑定同一个远端端口的不同协议。
### TCP 多路复用 ### TCP 多路复用
从 v0.10.0 版本开始,客户端和服务器端之间的连接支持多路复用,不再需要为每一个用户请求创建一个连接,使连接建立的延迟降低,并且避免了大量文件描述符的占用,使 frp 可以承载更高的并发数。 从 v0.10.0 版本开始,客户端和服务器端之间的连接支持多路复用,不再需要为每一个用户请求创建一个连接,使连接建立的延迟降低,并且避免了大量文件描述符的占用,使 frp 可以承载更高的并发数。
@@ -524,6 +512,32 @@ tcp_mux = false
pool_count = 1 pool_count = 1
``` ```
### 负载均衡
可以将多个相同类型的 proxy 加入到同一个 group 中,从而实现负载均衡的功能。
目前只支持 tcp 类型的 proxy。
```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
```
用户连接 frps 服务器的 80 端口frps 会将接收到的用户连接随机分发给其中一个存活的 proxy。这样可以在一台 frpc 机器挂掉后仍然有其他节点能够提供服务。
要求 `group_key` 相同,做权限验证,且 `remote_port` 相同。
### 修改 Host Header ### 修改 Host Header
通常情况下 frp 不会修改转发的任何数据。但有一些后端服务会根据 http 请求 header 中的 host 字段来展现不同的网站,例如 nginx 的虚拟主机服务,启用 host-header 的修改功能可以动态修改 http 请求中的 host 字段。该功能仅限于 http 类型的代理。 通常情况下 frp 不会修改转发的任何数据。但有一些后端服务会根据 http 请求 header 中的 host 字段来展现不同的网站,例如 nginx 的虚拟主机服务,启用 host-header 的修改功能可以动态修改 http 请求中的 host 字段。该功能仅限于 http 类型的代理。
@@ -539,6 +553,22 @@ host_header_rewrite = dev.yourdomain.com
原来 http 请求中的 host 字段 `test.yourdomain.com` 转发到后端服务时会被替换为 `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 ### 获取用户真实 IP
目前只有 **http** 类型的代理支持这一功能,可以通过用户请求的 header 中的 `X-Forwarded-For` 和 `X-Real-IP` 来获取用户真实 IP。 目前只有 **http** 类型的代理支持这一功能,可以通过用户请求的 header 中的 `X-Forwarded-For` 和 `X-Real-IP` 来获取用户真实 IP。
@@ -589,7 +619,7 @@ local_port = 80
subdomain = test subdomain = test
``` ```
frps 和 fprc 都启动成功后,通过 `test.frps.com` 就可以访问到内网的 web 服务。 frps 和 frpc 都启动成功后,通过 `test.frps.com` 就可以访问到内网的 web 服务。
需要注意的是如果 frps 配置了 `subdomain_host`,则 `custom_domains` 中不能是属于 `subdomain_host` 的子域名或者泛域名。 需要注意的是如果 frps 配置了 `subdomain_host`,则 `custom_domains` 中不能是属于 `subdomain_host` 的子域名或者泛域名。
@@ -651,7 +681,7 @@ local_port = 6000-6006,6007
remote_port = 6000-6006,6007 remote_port = 6000-6006,6007
``` ```
实际连接成功后会创建 6 个 proxy命名为 `test_tcp_0, test_tcp_1 ... test_tcp_5`。 实际连接成功后会创建 8 个 proxy命名为 `test_tcp_0, test_tcp_1 ... test_tcp_7`。
### 插件 ### 插件
@@ -681,7 +711,6 @@ plugin_http_passwd = abc
* frps 记录 http 请求日志。 * frps 记录 http 请求日志。
* frps 支持直接反向代理,类似 haproxy。 * frps 支持直接反向代理,类似 haproxy。
* frpc 支持负载均衡到后端不同服务。
* 集成对 k8s 等平台的支持。 * 集成对 k8s 等平台的支持。
## 为 frp 做贡献 ## 为 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?facf06d98c7e1aea259d"></script><script type="text/javascript" src="vendor.js?a05a344be2b42183469b"></script><script type="text/javascript" src="index.js?a914c2dc7a5bb16ad443"></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 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,a,c){for(var u,i,f,l=0,s=[];l<t.length;l++)i=t[l],o[i]&&s.push(o[i][0]),o[i]=0;for(u in a)Object.prototype.hasOwnProperty.call(a,u)&&(e[u]=a[u]);for(r&&r(t,a,c);s.length;)s.shift()();if(c)for(l=0;l<c.length;l++)f=n(n.s=c[l]);return f};var t={},o={2:0};n.e=function(e){function r(){u.onerror=u.onload=null,clearTimeout(i);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 a=new Promise(function(n,r){t=o[e]=[n,r]});t[2]=a;var c=document.getElementsByTagName("head")[0],u=document.createElement("script");u.type="text/javascript",u.charset="utf-8",u.async=!0,u.timeout=12e4,n.nc&&u.setAttribute("nonce",n.nc),u.src=n.p+""+e+".js?"+{0:"a914c2dc7a5bb16ad443",1:"a05a344be2b42183469b"}[e];var i=setTimeout(r,12e4);return u.onerror=u.onload=r,c.appendChild(u),a},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}}([]); !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

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

View File

@@ -22,9 +22,9 @@ import (
"sort" "sort"
"strings" "strings"
"github.com/julienschmidt/httprouter"
ini "github.com/vaughan0/go-ini" ini "github.com/vaughan0/go-ini"
"github.com/fatedier/frp/client/proxy"
"github.com/fatedier/frp/g" "github.com/fatedier/frp/g"
"github.com/fatedier/frp/models/config" "github.com/fatedier/frp/models/config"
"github.com/fatedier/frp/utils/log" "github.com/fatedier/frp/utils/log"
@@ -40,7 +40,7 @@ type ReloadResp struct {
GeneralResponse GeneralResponse
} }
func (svr *Service) apiReload(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { func (svr *Service) apiReload(w http.ResponseWriter, r *http.Request) {
var ( var (
buf []byte buf []byte
res ReloadResp res ReloadResp
@@ -78,7 +78,7 @@ func (svr *Service) apiReload(w http.ResponseWriter, r *http.Request, _ httprout
return return
} }
pxyCfgs, visitorCfgs, err := config.LoadProxyConfFromIni(g.GlbClientCfg.User, conf, newCommonCfg.Start) pxyCfgs, visitorCfgs, err := config.LoadAllConfFromIni(g.GlbClientCfg.User, conf, newCommonCfg.Start)
if err != nil { if err != nil {
res.Code = 3 res.Code = 3
res.Msg = err.Error() res.Msg = err.Error()
@@ -86,7 +86,7 @@ func (svr *Service) apiReload(w http.ResponseWriter, r *http.Request, _ httprout
return return
} }
err = svr.ctl.reloadConf(pxyCfgs, visitorCfgs) err = svr.ctl.ReloadConf(pxyCfgs, visitorCfgs)
if err != nil { if err != nil {
res.Code = 4 res.Code = 4
res.Msg = err.Error() res.Msg = err.Error()
@@ -122,7 +122,7 @@ 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) 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 (a ByProxyStatusResp) Less(i, j int) bool { return strings.Compare(a[i].Name, a[j].Name) < 0 }
func NewProxyStatusResp(status *ProxyStatus) ProxyStatusResp { func NewProxyStatusResp(status *proxy.ProxyStatus) ProxyStatusResp {
psr := ProxyStatusResp{ psr := ProxyStatusResp{
Name: status.Name, Name: status.Name,
Type: status.Type, Type: status.Type,
@@ -176,7 +176,7 @@ func NewProxyStatusResp(status *ProxyStatus) ProxyStatusResp {
} }
// api/status // api/status
func (svr *Service) apiStatus(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { func (svr *Service) apiStatus(w http.ResponseWriter, r *http.Request) {
var ( var (
buf []byte buf []byte
res StatusResp res StatusResp

View File

@@ -17,35 +17,32 @@ package client
import ( import (
"fmt" "fmt"
"io" "io"
"runtime" "runtime/debug"
"sync" "sync"
"time" "time"
"github.com/fatedier/frp/client/proxy"
"github.com/fatedier/frp/g" "github.com/fatedier/frp/g"
"github.com/fatedier/frp/models/config" "github.com/fatedier/frp/models/config"
"github.com/fatedier/frp/models/msg" "github.com/fatedier/frp/models/msg"
"github.com/fatedier/frp/utils/crypto"
"github.com/fatedier/frp/utils/log" "github.com/fatedier/frp/utils/log"
frpNet "github.com/fatedier/frp/utils/net" frpNet "github.com/fatedier/frp/utils/net"
"github.com/fatedier/frp/utils/shutdown"
"github.com/fatedier/frp/utils/util"
"github.com/fatedier/frp/utils/version"
"github.com/fatedier/golib/control/shutdown"
"github.com/fatedier/golib/crypto"
fmux "github.com/hashicorp/yamux" fmux "github.com/hashicorp/yamux"
) )
const (
connReadTimeout time.Duration = 10 * time.Second
)
type Control struct { type Control struct {
// frpc service // uniq id got from frps, attach it in loginMsg
svr *Service runId string
// login message to server, only used // manage all proxies
loginMsg *msg.Login pxyCfgs map[string]config.ProxyConf
pm *proxy.ProxyManager
pm *ProxyManager // manage all visitors
vm *VisitorManager
// control connection // control connection
conn frpNet.Conn conn frpNet.Conn
@@ -59,14 +56,10 @@ type Control struct {
// read from this channel to get the next message sent by server // read from this channel to get the next message sent by server
readCh chan (msg.Message) readCh chan (msg.Message)
// run id got from server
runId string
// if we call close() in control, do not reconnect to server
exit bool
// goroutines can block by reading from this channel, it will be closed only in reader() when control connection is closed // goroutines can block by reading from this channel, it will be closed only in reader() when control connection is closed
closedCh chan int closedCh chan struct{}
closedDoneCh chan struct{}
// last time got the Pong message // last time got the Pong message
lastPong time.Time lastPong time.Time
@@ -80,54 +73,37 @@ type Control struct {
log.Logger log.Logger
} }
func NewControl(svr *Service, pxyCfgs map[string]config.ProxyConf, visitorCfgs map[string]config.ProxyConf) *Control { func NewControl(runId string, conn frpNet.Conn, session *fmux.Session, pxyCfgs map[string]config.ProxyConf, visitorCfgs map[string]config.VisitorConf) *Control {
loginMsg := &msg.Login{
Arch: runtime.GOARCH,
Os: runtime.GOOS,
PoolCount: g.GlbClientCfg.PoolCount,
User: g.GlbClientCfg.User,
Version: version.Full(),
}
ctl := &Control{ ctl := &Control{
svr: svr, runId: runId,
loginMsg: loginMsg, conn: conn,
session: session,
pxyCfgs: pxyCfgs,
sendCh: make(chan msg.Message, 100), sendCh: make(chan msg.Message, 100),
readCh: make(chan msg.Message, 100), readCh: make(chan msg.Message, 100),
closedCh: make(chan int), closedCh: make(chan struct{}),
closedDoneCh: make(chan struct{}),
readerShutdown: shutdown.New(), readerShutdown: shutdown.New(),
writerShutdown: shutdown.New(), writerShutdown: shutdown.New(),
msgHandlerShutdown: shutdown.New(), msgHandlerShutdown: shutdown.New(),
Logger: log.NewPrefixLogger(""), Logger: log.NewPrefixLogger(""),
} }
ctl.pm = NewProxyManager(ctl, ctl.sendCh, "") ctl.pm = proxy.NewProxyManager(ctl.sendCh, runId)
ctl.pm.Reload(pxyCfgs, visitorCfgs, false)
ctl.vm = NewVisitorManager(ctl)
ctl.vm.Reload(visitorCfgs)
return ctl return ctl
} }
func (ctl *Control) Run() (err error) { func (ctl *Control) Run() {
for {
err = ctl.login()
if err != nil {
ctl.Warn("login to server failed: %v", err)
// if login_fail_exit is true, just exit this program
// otherwise sleep a while and continues relogin to server
if g.GlbClientCfg.LoginFailExit {
return
} else {
time.Sleep(10 * time.Second)
}
} else {
break
}
}
go ctl.worker() go ctl.worker()
// start all local visitors and send NewProxy message for all configured proxies // start all proxies
ctl.pm.Reset(ctl.sendCh, ctl.runId) ctl.pm.Reload(ctl.pxyCfgs)
ctl.pm.CheckAndStartProxy([]string{ProxyStatusNew})
return nil // start all visitors
go ctl.vm.Run()
return
} }
func (ctl *Control) HandleReqWorkConn(inMsg *msg.ReqWorkConn) { func (ctl *Control) HandleReqWorkConn(inMsg *msg.ReqWorkConn) {
@@ -169,80 +145,16 @@ func (ctl *Control) HandleNewProxyResp(inMsg *msg.NewProxyResp) {
} }
func (ctl *Control) Close() error { func (ctl *Control) Close() error {
ctl.mu.Lock() ctl.conn.Close()
defer ctl.mu.Unlock()
ctl.exit = true
ctl.pm.CloseProxies()
return nil return nil
} }
// login send a login message to server and wait for a loginResp message. // ClosedDoneCh returns a channel which will be closed after all resources are released
func (ctl *Control) login() (err error) { func (ctl *Control) ClosedDoneCh() <-chan struct{} {
if ctl.conn != nil { return ctl.closedDoneCh
ctl.conn.Close()
}
if ctl.session != nil {
ctl.session.Close()
}
conn, err := frpNet.ConnectServerByHttpProxy(g.GlbClientCfg.HttpProxy, g.GlbClientCfg.Protocol,
fmt.Sprintf("%s:%d", g.GlbClientCfg.ServerAddr, g.GlbClientCfg.ServerPort))
if err != nil {
return err
}
defer func() {
if err != nil {
conn.Close()
}
}()
if g.GlbClientCfg.TcpMux {
session, errRet := fmux.Client(conn, nil)
if errRet != nil {
return errRet
}
stream, errRet := session.OpenStream()
if errRet != nil {
session.Close()
return errRet
}
conn = frpNet.WrapConn(stream)
ctl.session = session
}
now := time.Now().Unix()
ctl.loginMsg.PrivilegeKey = util.GetAuthKey(g.GlbClientCfg.Token, now)
ctl.loginMsg.Timestamp = now
ctl.loginMsg.RunId = ctl.runId
if err = msg.WriteMsg(conn, ctl.loginMsg); err != nil {
return err
}
var loginRespMsg msg.LoginResp
conn.SetReadDeadline(time.Now().Add(connReadTimeout))
if err = msg.ReadMsgInto(conn, &loginRespMsg); err != nil {
return err
}
conn.SetReadDeadline(time.Time{})
if loginRespMsg.Error != "" {
err = fmt.Errorf("%s", loginRespMsg.Error)
ctl.Error("%s", loginRespMsg.Error)
return err
}
ctl.conn = conn
// update runId got from server
ctl.runId = loginRespMsg.RunId
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)
return nil
} }
// connectServer return a new connection to frps
func (ctl *Control) connectServer() (conn frpNet.Conn, err error) { func (ctl *Control) connectServer() (conn frpNet.Conn, err error) {
if g.GlbClientCfg.TcpMux { if g.GlbClientCfg.TcpMux {
stream, errRet := ctl.session.OpenStream() stream, errRet := ctl.session.OpenStream()
@@ -253,7 +165,7 @@ func (ctl *Control) connectServer() (conn frpNet.Conn, err error) {
} }
conn = frpNet.WrapConn(stream) conn = frpNet.WrapConn(stream)
} else { } else {
conn, err = frpNet.ConnectServerByHttpProxy(g.GlbClientCfg.HttpProxy, g.GlbClientCfg.Protocol, conn, err = frpNet.ConnectServerByProxy(g.GlbClientCfg.HttpProxy, g.GlbClientCfg.Protocol,
fmt.Sprintf("%s:%d", g.GlbClientCfg.ServerAddr, g.GlbClientCfg.ServerPort)) fmt.Sprintf("%s:%d", g.GlbClientCfg.ServerAddr, g.GlbClientCfg.ServerPort))
if err != nil { if err != nil {
ctl.Warn("start new connection to server error: %v", err) ctl.Warn("start new connection to server error: %v", err)
@@ -268,6 +180,7 @@ func (ctl *Control) reader() {
defer func() { defer func() {
if err := recover(); err != nil { if err := recover(); err != nil {
ctl.Error("panic error: %v", err) ctl.Error("panic error: %v", err)
ctl.Error(string(debug.Stack()))
} }
}() }()
defer ctl.readerShutdown.Done() defer ctl.readerShutdown.Done()
@@ -316,6 +229,7 @@ func (ctl *Control) msgHandler() {
defer func() { defer func() {
if err := recover(); err != nil { if err := recover(); err != nil {
ctl.Error("panic error: %v", err) ctl.Error("panic error: %v", err)
ctl.Error(string(debug.Stack()))
} }
}() }()
defer ctl.msgHandlerShutdown.Done() defer ctl.msgHandlerShutdown.Done()
@@ -358,87 +272,32 @@ func (ctl *Control) msgHandler() {
} }
} }
// controler keep watching closedCh, start a new connection if previous control connection is closed. // If controler is notified by closedCh, reader and writer and handler will exit
// If controler is notified by closedCh, reader and writer and handler will exit, then recall these functions.
func (ctl *Control) worker() { func (ctl *Control) worker() {
go ctl.msgHandler() go ctl.msgHandler()
go ctl.reader() go ctl.reader()
go ctl.writer() go ctl.writer()
var err error select {
maxDelayTime := 20 * time.Second case <-ctl.closedCh:
delayTime := time.Second // close related channels and wait until other goroutines done
close(ctl.readCh)
ctl.readerShutdown.WaitDone()
ctl.msgHandlerShutdown.WaitDone()
checkInterval := 60 * time.Second close(ctl.sendCh)
checkProxyTicker := time.NewTicker(checkInterval) ctl.writerShutdown.WaitDone()
for {
select {
case <-checkProxyTicker.C:
// 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 and wait until other goroutines done
close(ctl.readCh)
ctl.readerShutdown.WaitDone()
ctl.msgHandlerShutdown.WaitDone()
close(ctl.sendCh) ctl.pm.Close()
ctl.writerShutdown.WaitDone() ctl.vm.Close()
ctl.pm.CloseProxies() close(ctl.closedDoneCh)
// if ctl.exit is true, just exit return
ctl.mu.RLock()
exit := ctl.exit
ctl.mu.RUnlock()
if exit {
return
}
// loop util reconnecting to server success
for {
ctl.Info("try to reconnect to server...")
err = ctl.login()
if err != nil {
ctl.Warn("reconnect to server error: %v", err)
time.Sleep(delayTime)
delayTime = delayTime * 2
if delayTime > maxDelayTime {
delayTime = maxDelayTime
}
continue
}
// reconnect success, init delayTime
delayTime = time.Second
break
}
// init related channels and variables
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.msgHandler()
go ctl.writer()
go ctl.reader()
// start all configured proxies
ctl.pm.CheckAndStartProxy([]string{ProxyStatusNew, ProxyStatusClosed})
checkProxyTicker.Stop()
checkProxyTicker = time.NewTicker(checkInterval)
}
}
} }
} }
func (ctl *Control) reloadConf(pxyCfgs map[string]config.ProxyConf, visitorCfgs map[string]config.ProxyConf) error { func (ctl *Control) ReloadConf(pxyCfgs map[string]config.ProxyConf, visitorCfgs map[string]config.VisitorConf) error {
err := ctl.pm.Reload(pxyCfgs, visitorCfgs, true) ctl.vm.Reload(visitorCfgs)
return err ctl.pm.Reload(pxyCfgs)
return nil
} }

28
client/event/event.go Normal file
View File

@@ -0,0 +1,28 @@
package event
import (
"errors"
"github.com/fatedier/frp/models/msg"
)
type EventType int
const (
EvStartProxy EventType = iota
EvCloseProxy
)
var (
ErrPayloadType = errors.New("error payload type")
)
type EventHandler func(evType EventType, payload interface{}) error
type StartProxyPayload struct {
NewProxyMsg *msg.NewProxy
}
type CloseProxyPayload struct {
CloseProxyMsg *msg.CloseProxy
}

178
client/health/health.go Normal file
View File

@@ -0,0 +1,178 @@
// 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 health
import (
"context"
"errors"
"fmt"
"net"
"net/http"
"time"
"github.com/fatedier/frp/utils/log"
)
var (
ErrHealthCheckType = errors.New("error health check type")
)
type HealthCheckMonitor struct {
checkType string
interval time.Duration
timeout time.Duration
maxFailedTimes int
// For tcp
addr string
// For http
url string
failedTimes uint64
statusOK bool
statusNormalFn func()
statusFailedFn func()
ctx context.Context
cancel context.CancelFunc
l log.Logger
}
func NewHealthCheckMonitor(checkType string, intervalS int, timeoutS int, maxFailedTimes int, addr string, url string,
statusNormalFn func(), statusFailedFn func()) *HealthCheckMonitor {
if intervalS <= 0 {
intervalS = 10
}
if timeoutS <= 0 {
timeoutS = 3
}
if maxFailedTimes <= 0 {
maxFailedTimes = 1
}
ctx, cancel := context.WithCancel(context.Background())
return &HealthCheckMonitor{
checkType: checkType,
interval: time.Duration(intervalS) * time.Second,
timeout: time.Duration(timeoutS) * time.Second,
maxFailedTimes: maxFailedTimes,
addr: addr,
url: url,
statusOK: false,
statusNormalFn: statusNormalFn,
statusFailedFn: statusFailedFn,
ctx: ctx,
cancel: cancel,
}
}
func (monitor *HealthCheckMonitor) SetLogger(l log.Logger) {
monitor.l = l
}
func (monitor *HealthCheckMonitor) Start() {
go monitor.checkWorker()
}
func (monitor *HealthCheckMonitor) Stop() {
monitor.cancel()
}
func (monitor *HealthCheckMonitor) checkWorker() {
for {
ctx, cancel := context.WithDeadline(monitor.ctx, time.Now().Add(monitor.timeout))
err := monitor.doCheck(ctx)
// check if this monitor has been closed
select {
case <-ctx.Done():
cancel()
return
default:
cancel()
}
if err == nil {
if monitor.l != nil {
monitor.l.Trace("do one health check success")
}
if !monitor.statusOK && monitor.statusNormalFn != nil {
if monitor.l != nil {
monitor.l.Info("health check status change to success")
}
monitor.statusOK = true
monitor.statusNormalFn()
}
} else {
if monitor.l != nil {
monitor.l.Warn("do one health check failed: %v", err)
}
monitor.failedTimes++
if monitor.statusOK && int(monitor.failedTimes) >= monitor.maxFailedTimes && monitor.statusFailedFn != nil {
if monitor.l != nil {
monitor.l.Warn("health check status change to failed")
}
monitor.statusOK = false
monitor.statusFailedFn()
}
}
time.Sleep(monitor.interval)
}
}
func (monitor *HealthCheckMonitor) doCheck(ctx context.Context) error {
switch monitor.checkType {
case "tcp":
return monitor.doTcpCheck(ctx)
case "http":
return monitor.doHttpCheck(ctx)
default:
return ErrHealthCheckType
}
}
func (monitor *HealthCheckMonitor) doTcpCheck(ctx context.Context) error {
// if tcp address is not specified, always return nil
if monitor.addr == "" {
return nil
}
var d net.Dialer
conn, err := d.DialContext(ctx, "tcp", monitor.addr)
if err != nil {
return err
}
conn.Close()
return nil
}
func (monitor *HealthCheckMonitor) doHttpCheck(ctx context.Context) error {
req, err := http.NewRequest("GET", monitor.url, nil)
if err != nil {
return err
}
resp, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
if resp.StatusCode/100 != 2 {
return fmt.Errorf("do http health check, StatusCode is [%d] not 2xx", resp.StatusCode)
}
return nil
}

View File

@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
package client package proxy
import ( import (
"bytes" "bytes"
@@ -27,14 +27,15 @@ import (
"github.com/fatedier/frp/models/msg" "github.com/fatedier/frp/models/msg"
"github.com/fatedier/frp/models/plugin" "github.com/fatedier/frp/models/plugin"
"github.com/fatedier/frp/models/proto/udp" "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" "github.com/fatedier/frp/utils/log"
frpNet "github.com/fatedier/frp/utils/net" 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. // Proxy defines how to handle work connections for different proxy type.
type Proxy interface { type Proxy interface {
Run() error Run() error
@@ -271,6 +272,12 @@ func (pxy *XtcpProxy) InWorkConn(conn frpNet.Conn) {
} }
clientConn.SetReadDeadline(time.Time{}) clientConn.SetReadDeadline(time.Time{})
clientConn.Close() 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) pxy.Trace("get natHoleRespMsg, sid [%s], client address [%s]", natHoleRespMsg.Sid, natHoleRespMsg.ClientAddr)
// Send sid to visitor udp address. // Send sid to visitor udp address.

View File

@@ -0,0 +1,138 @@
package proxy
import (
"fmt"
"sync"
"github.com/fatedier/frp/client/event"
"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"
)
type ProxyManager struct {
sendCh chan (msg.Message)
proxies map[string]*ProxyWrapper
closed bool
mu sync.RWMutex
logPrefix string
log.Logger
}
func NewProxyManager(msgSendCh chan (msg.Message), logPrefix string) *ProxyManager {
return &ProxyManager{
proxies: make(map[string]*ProxyWrapper),
sendCh: msgSendCh,
closed: false,
logPrefix: logPrefix,
Logger: log.NewPrefixLogger(logPrefix),
}
}
func (pm *ProxyManager) StartProxy(name string, remoteAddr string, serverRespErr string) error {
pm.mu.RLock()
pxy, ok := pm.proxies[name]
pm.mu.RUnlock()
if !ok {
return fmt.Errorf("proxy [%s] not found", name)
}
err := pxy.SetRunningStatus(remoteAddr, serverRespErr)
if err != nil {
return err
}
return nil
}
func (pm *ProxyManager) Close() {
pm.mu.RLock()
defer pm.mu.RUnlock()
for _, pxy := range pm.proxies {
pxy.Stop()
}
}
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) HandleEvent(evType event.EventType, payload interface{}) error {
var m msg.Message
switch e := payload.(type) {
case *event.StartProxyPayload:
m = e.NewProxyMsg
case *event.CloseProxyPayload:
m = e.CloseProxyMsg
default:
return event.ErrPayloadType
}
err := errors.PanicToError(func() {
pm.sendCh <- m
})
return err
}
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
}
func (pm *ProxyManager) Reload(pxyCfgs map[string]config.ProxyConf) {
pm.mu.Lock()
defer pm.mu.Unlock()
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.Stop()
}
}
if len(delPxyNames) > 0 {
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.HandleEvent, pm.logPrefix)
pm.proxies[name] = pxy
addPxyNames = append(addPxyNames, name)
pxy.Start()
}
}
if len(addPxyNames) > 0 {
pm.Info("proxy added: %v", addPxyNames)
}
}

View File

@@ -0,0 +1,244 @@
package proxy
import (
"fmt"
"sync"
"sync/atomic"
"time"
"github.com/fatedier/frp/client/event"
"github.com/fatedier/frp/client/health"
"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"
ProxyStatusWaitStart = "wait start"
ProxyStatusStartErr = "start error"
ProxyStatusRunning = "running"
ProxyStatusCheckFailed = "check failed"
ProxyStatusClosed = "closed"
)
var (
statusCheckInterval time.Duration = 3 * time.Second
waitResponseTimeout = 20 * time.Second
startErrTimeout = 30 * time.Second
)
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"`
}
type ProxyWrapper struct {
ProxyStatus
// underlying proxy
pxy Proxy
// if ProxyConf has healcheck config
// monitor will watch if it is alive
monitor *health.HealthCheckMonitor
// event handler
handler event.EventHandler
health uint32
lastSendStartMsg time.Time
lastStartErr time.Time
closeCh chan struct{}
healthNotifyCh chan struct{}
mu sync.RWMutex
log.Logger
}
func NewProxyWrapper(cfg config.ProxyConf, eventHandler event.EventHandler, logPrefix string) *ProxyWrapper {
baseInfo := cfg.GetBaseInfo()
pw := &ProxyWrapper{
ProxyStatus: ProxyStatus{
Name: baseInfo.ProxyName,
Type: baseInfo.ProxyType,
Status: ProxyStatusNew,
Cfg: cfg,
},
closeCh: make(chan struct{}),
healthNotifyCh: make(chan struct{}),
handler: eventHandler,
Logger: log.NewPrefixLogger(logPrefix),
}
pw.AddLogPrefix(pw.Name)
if baseInfo.HealthCheckType != "" {
pw.health = 1 // means failed
pw.monitor = health.NewHealthCheckMonitor(baseInfo.HealthCheckType, baseInfo.HealthCheckIntervalS,
baseInfo.HealthCheckTimeoutS, baseInfo.HealthCheckMaxFailed, baseInfo.HealthCheckAddr,
baseInfo.HealthCheckUrl, pw.statusNormalCallback, pw.statusFailedCallback)
pw.monitor.SetLogger(pw.Logger)
pw.Trace("enable health check monitor")
}
pw.pxy = NewProxy(pw.Cfg)
return pw
}
func (pw *ProxyWrapper) SetRunningStatus(remoteAddr string, respErr string) error {
pw.mu.Lock()
defer pw.mu.Unlock()
if pw.Status != ProxyStatusWaitStart {
return fmt.Errorf("status not wait start, ignore start message")
}
pw.RemoteAddr = remoteAddr
if respErr != "" {
pw.Status = ProxyStatusStartErr
pw.Err = respErr
pw.lastStartErr = time.Now()
return fmt.Errorf(pw.Err)
}
if err := pw.pxy.Run(); err != nil {
pw.Status = ProxyStatusStartErr
pw.Err = err.Error()
pw.lastStartErr = time.Now()
return err
}
pw.Status = ProxyStatusRunning
pw.Err = ""
return nil
}
func (pw *ProxyWrapper) Start() {
go pw.checkWorker()
if pw.monitor != nil {
go pw.monitor.Start()
}
}
func (pw *ProxyWrapper) Stop() {
pw.mu.Lock()
defer pw.mu.Unlock()
close(pw.closeCh)
close(pw.healthNotifyCh)
pw.pxy.Close()
if pw.monitor != nil {
pw.monitor.Stop()
}
pw.Status = ProxyStatusClosed
pw.handler(event.EvCloseProxy, &event.CloseProxyPayload{
CloseProxyMsg: &msg.CloseProxy{
ProxyName: pw.Name,
},
})
}
func (pw *ProxyWrapper) checkWorker() {
if pw.monitor != nil {
// let monitor do check request first
time.Sleep(500 * time.Millisecond)
}
for {
// check proxy status
now := time.Now()
if atomic.LoadUint32(&pw.health) == 0 {
pw.mu.Lock()
if pw.Status == ProxyStatusNew ||
pw.Status == ProxyStatusCheckFailed ||
(pw.Status == ProxyStatusWaitStart && now.After(pw.lastSendStartMsg.Add(waitResponseTimeout))) ||
(pw.Status == ProxyStatusStartErr && now.After(pw.lastStartErr.Add(startErrTimeout))) {
pw.Trace("change status from [%s] to [%s]", pw.Status, ProxyStatusWaitStart)
pw.Status = ProxyStatusWaitStart
var newProxyMsg msg.NewProxy
pw.Cfg.MarshalToMsg(&newProxyMsg)
pw.lastSendStartMsg = now
pw.handler(event.EvStartProxy, &event.StartProxyPayload{
NewProxyMsg: &newProxyMsg,
})
}
pw.mu.Unlock()
} else {
pw.mu.Lock()
if pw.Status == ProxyStatusRunning || pw.Status == ProxyStatusWaitStart {
pw.handler(event.EvCloseProxy, &event.CloseProxyPayload{
CloseProxyMsg: &msg.CloseProxy{
ProxyName: pw.Name,
},
})
pw.Trace("change status from [%s] to [%s]", pw.Status, ProxyStatusCheckFailed)
pw.Status = ProxyStatusCheckFailed
}
pw.mu.Unlock()
}
select {
case <-pw.closeCh:
return
case <-time.After(statusCheckInterval):
case <-pw.healthNotifyCh:
}
}
}
func (pw *ProxyWrapper) statusNormalCallback() {
atomic.StoreUint32(&pw.health, 0)
errors.PanicToError(func() {
select {
case pw.healthNotifyCh <- struct{}{}:
default:
}
})
pw.Info("health check success")
}
func (pw *ProxyWrapper) statusFailedCallback() {
atomic.StoreUint32(&pw.health, 1)
errors.PanicToError(func() {
select {
case pw.healthNotifyCh <- struct{}{}:
default:
}
})
pw.Info("health check failed")
}
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) 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
}

View File

@@ -1,364 +0,0 @@
package client
import (
"fmt"
"sync"
"github.com/fatedier/frp/models/config"
"github.com/fatedier/frp/models/msg"
"github.com/fatedier/frp/utils/errors"
"github.com/fatedier/frp/utils/log"
frpNet "github.com/fatedier/frp/utils/net"
)
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,35 +15,85 @@
package client package client
import ( import (
"fmt"
"io/ioutil"
"runtime"
"sync"
"sync/atomic"
"time"
"github.com/fatedier/frp/g" "github.com/fatedier/frp/g"
"github.com/fatedier/frp/models/config" "github.com/fatedier/frp/models/config"
"github.com/fatedier/frp/models/msg"
"github.com/fatedier/frp/utils/log" "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"
fmux "github.com/hashicorp/yamux"
) )
type Service struct { type Service struct {
// manager control connection with server // uniq id got from frps, attach it in loginMsg
ctl *Control runId string
// manager control connection with server
ctl *Control
ctlMu sync.RWMutex
pxyCfgs map[string]config.ProxyConf
visitorCfgs map[string]config.VisitorConf
cfgMu sync.RWMutex
exit uint32 // 0 means not exit
closedCh chan int closedCh chan int
} }
func NewService(pxyCfgs map[string]config.ProxyConf, visitorCfgs map[string]config.ProxyConf) (svr *Service) { func NewService(pxyCfgs map[string]config.ProxyConf, visitorCfgs map[string]config.VisitorConf) (svr *Service) {
svr = &Service{ svr = &Service{
closedCh: make(chan int), pxyCfgs: pxyCfgs,
visitorCfgs: visitorCfgs,
exit: 0,
closedCh: make(chan int),
} }
ctl := NewControl(svr, pxyCfgs, visitorCfgs)
svr.ctl = ctl
return return
} }
func (svr *Service) GetController() *Control {
svr.ctlMu.RLock()
defer svr.ctlMu.RUnlock()
return svr.ctl
}
func (svr *Service) Run() error { func (svr *Service) Run() error {
err := svr.ctl.Run() // first login
if err != nil { for {
return err conn, session, err := svr.login()
if err != nil {
log.Warn("login to server failed: %v", err)
// if login_fail_exit is true, just exit this program
// otherwise sleep a while and try again to connect to server
if g.GlbClientCfg.LoginFailExit {
return err
} else {
time.Sleep(10 * time.Second)
}
} else {
// login success
ctl := NewControl(svr.runId, conn, session, svr.pxyCfgs, svr.visitorCfgs)
ctl.Run()
svr.ctlMu.Lock()
svr.ctl = ctl
svr.ctlMu.Unlock()
break
}
} }
go svr.keepControllerWorking()
if g.GlbClientCfg.AdminPort != 0 { if g.GlbClientCfg.AdminPort != 0 {
err = svr.RunAdminServer(g.GlbClientCfg.AdminAddr, g.GlbClientCfg.AdminPort) err := svr.RunAdminServer(g.GlbClientCfg.AdminAddr, g.GlbClientCfg.AdminPort)
if err != nil { if err != nil {
log.Warn("run admin server error: %v", err) log.Warn("run admin server error: %v", err)
} }
@@ -54,6 +104,119 @@ func (svr *Service) Run() error {
return nil return nil
} }
func (svr *Service) Close() { func (svr *Service) keepControllerWorking() {
svr.ctl.Close() maxDelayTime := 20 * time.Second
delayTime := time.Second
for {
<-svr.ctl.ClosedDoneCh()
if atomic.LoadUint32(&svr.exit) != 0 {
return
}
for {
log.Info("try to reconnect to server...")
conn, session, err := svr.login()
if err != nil {
log.Warn("reconnect to server error: %v", err)
time.Sleep(delayTime)
delayTime = delayTime * 2
if delayTime > maxDelayTime {
delayTime = maxDelayTime
}
continue
}
// reconnect success, init delayTime
delayTime = time.Second
ctl := NewControl(svr.runId, conn, session, svr.pxyCfgs, svr.visitorCfgs)
ctl.Run()
svr.ctlMu.Lock()
svr.ctl = ctl
svr.ctlMu.Unlock()
break
}
}
}
// login creates a connection to frps and registers it self as a client
// conn: control connection
// session: if it's not nil, using tcp mux
func (svr *Service) login() (conn frpNet.Conn, session *fmux.Session, err error) {
conn, err = frpNet.ConnectServerByProxy(g.GlbClientCfg.HttpProxy, g.GlbClientCfg.Protocol,
fmt.Sprintf("%s:%d", g.GlbClientCfg.ServerAddr, g.GlbClientCfg.ServerPort))
if err != nil {
return
}
defer func() {
if err != nil {
conn.Close()
}
}()
if g.GlbClientCfg.TcpMux {
fmuxCfg := fmux.DefaultConfig()
fmuxCfg.LogOutput = ioutil.Discard
session, err = fmux.Client(conn, fmuxCfg)
if err != nil {
return
}
stream, errRet := session.OpenStream()
if errRet != nil {
session.Close()
err = errRet
return
}
conn = frpNet.WrapConn(stream)
}
now := time.Now().Unix()
loginMsg := &msg.Login{
Arch: runtime.GOARCH,
Os: runtime.GOOS,
PoolCount: g.GlbClientCfg.PoolCount,
User: g.GlbClientCfg.User,
Version: version.Full(),
PrivilegeKey: util.GetAuthKey(g.GlbClientCfg.Token, now),
Timestamp: now,
RunId: svr.runId,
}
if err = msg.WriteMsg(conn, loginMsg); err != nil {
return
}
var loginRespMsg msg.LoginResp
conn.SetReadDeadline(time.Now().Add(10 * time.Second))
if err = msg.ReadMsgInto(conn, &loginRespMsg); err != nil {
return
}
conn.SetReadDeadline(time.Time{})
if loginRespMsg.Error != "" {
err = fmt.Errorf("%s", loginRespMsg.Error)
log.Error("%s", loginRespMsg.Error)
return
}
svr.runId = loginRespMsg.RunId
g.GlbClientCfg.ServerUdpPort = loginRespMsg.ServerUdpPort
log.Info("login to server success, get run id [%s], server udp port [%d]", loginRespMsg.RunId, loginRespMsg.ServerUdpPort)
return
}
func (svr *Service) ReloadConf(pxyCfgs map[string]config.ProxyConf, visitorCfgs map[string]config.VisitorConf) error {
svr.cfgMu.Lock()
svr.pxyCfgs = pxyCfgs
svr.visitorCfgs = visitorCfgs
svr.cfgMu.Unlock()
return svr.ctl.ReloadConf(pxyCfgs, visitorCfgs)
}
func (svr *Service) Close() {
atomic.StoreUint32(&svr.exit, 1)
svr.ctl.Close()
close(svr.closedCh)
} }

View File

@@ -29,11 +29,12 @@ import (
"github.com/fatedier/frp/g" "github.com/fatedier/frp/g"
"github.com/fatedier/frp/models/config" "github.com/fatedier/frp/models/config"
"github.com/fatedier/frp/models/msg" "github.com/fatedier/frp/models/msg"
frpIo "github.com/fatedier/frp/utils/io"
"github.com/fatedier/frp/utils/log" "github.com/fatedier/frp/utils/log"
frpNet "github.com/fatedier/frp/utils/net" frpNet "github.com/fatedier/frp/utils/net"
"github.com/fatedier/frp/utils/pool"
"github.com/fatedier/frp/utils/util" "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. // Visitor is used for forward traffics from local port tot remote service.
@@ -43,18 +44,18 @@ type Visitor interface {
log.Logger log.Logger
} }
func NewVisitor(ctl *Control, pxyConf config.ProxyConf) (visitor Visitor) { func NewVisitor(ctl *Control, cfg config.VisitorConf) (visitor Visitor) {
baseVisitor := BaseVisitor{ baseVisitor := BaseVisitor{
ctl: ctl, ctl: ctl,
Logger: log.NewPrefixLogger(pxyConf.GetBaseInfo().ProxyName), Logger: log.NewPrefixLogger(cfg.GetBaseInfo().ProxyName),
} }
switch cfg := pxyConf.(type) { switch cfg := cfg.(type) {
case *config.StcpProxyConf: case *config.StcpVisitorConf:
visitor = &StcpVisitor{ visitor = &StcpVisitor{
BaseVisitor: baseVisitor, BaseVisitor: baseVisitor,
cfg: cfg, cfg: cfg,
} }
case *config.XtcpProxyConf: case *config.XtcpVisitorConf:
visitor = &XtcpVisitor{ visitor = &XtcpVisitor{
BaseVisitor: baseVisitor, BaseVisitor: baseVisitor,
cfg: cfg, cfg: cfg,
@@ -74,7 +75,7 @@ type BaseVisitor struct {
type StcpVisitor struct { type StcpVisitor struct {
BaseVisitor BaseVisitor
cfg *config.StcpProxyConf cfg *config.StcpVisitorConf
} }
func (sv *StcpVisitor) Run() (err error) { func (sv *StcpVisitor) Run() (err error) {
@@ -161,7 +162,7 @@ func (sv *StcpVisitor) handleConn(userConn frpNet.Conn) {
type XtcpVisitor struct { type XtcpVisitor struct {
BaseVisitor BaseVisitor
cfg *config.XtcpProxyConf cfg *config.XtcpVisitorConf
} }
func (sv *XtcpVisitor) Run() (err error) { func (sv *XtcpVisitor) Run() (err error) {
@@ -182,7 +183,7 @@ func (sv *XtcpVisitor) worker() {
for { for {
conn, err := sv.l.Accept() conn, err := sv.l.Accept()
if err != nil { if err != nil {
sv.Warn("stcp local listener closed") sv.Warn("xtcp local listener closed")
return return
} }
@@ -201,7 +202,16 @@ func (sv *XtcpVisitor) handleConn(userConn frpNet.Conn) {
raddr, err := net.ResolveUDPAddr("udp", raddr, err := net.ResolveUDPAddr("udp",
fmt.Sprintf("%s:%d", g.GlbClientCfg.ServerAddr, g.GlbClientCfg.ServerUdpPort)) fmt.Sprintf("%s:%d", g.GlbClientCfg.ServerAddr, g.GlbClientCfg.ServerUdpPort))
if err != nil {
sv.Error("resolve server UDP addr error")
return
}
visitorConn, err := net.DialUDP("udp", nil, raddr) visitorConn, err := net.DialUDP("udp", nil, raddr)
if err != nil {
sv.Warn("dial server udp addr error: %v", err)
return
}
defer visitorConn.Close() defer visitorConn.Close()
now := time.Now().Unix() now := time.Now().Unix()
@@ -234,6 +244,11 @@ func (sv *XtcpVisitor) handleConn(userConn frpNet.Conn) {
visitorConn.SetReadDeadline(time.Time{}) visitorConn.SetReadDeadline(time.Time{})
pool.PutBuf(buf) 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) sv.Trace("get natHoleRespMsg, sid [%s], client address [%s]", natHoleRespMsg.Sid, natHoleRespMsg.ClientAddr)
// Close visitorConn, so we can use it's local address. // Close visitorConn, so we can use it's local address.

123
client/visitor_manager.go Normal file
View File

@@ -0,0 +1,123 @@
// 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 client
import (
"sync"
"time"
"github.com/fatedier/frp/models/config"
"github.com/fatedier/frp/utils/log"
)
type VisitorManager struct {
ctl *Control
cfgs map[string]config.VisitorConf
visitors map[string]Visitor
checkInterval time.Duration
mu sync.Mutex
}
func NewVisitorManager(ctl *Control) *VisitorManager {
return &VisitorManager{
ctl: ctl,
cfgs: make(map[string]config.VisitorConf),
visitors: make(map[string]Visitor),
checkInterval: 10 * time.Second,
}
}
func (vm *VisitorManager) Run() {
for {
time.Sleep(vm.checkInterval)
vm.mu.Lock()
for _, cfg := range vm.cfgs {
name := cfg.GetBaseInfo().ProxyName
if _, exist := vm.visitors[name]; !exist {
log.Info("try to start visitor [%s]", name)
vm.startVisitor(cfg)
}
}
vm.mu.Unlock()
}
}
// Hold lock before calling this function.
func (vm *VisitorManager) startVisitor(cfg config.VisitorConf) (err error) {
name := cfg.GetBaseInfo().ProxyName
visitor := NewVisitor(vm.ctl, cfg)
err = visitor.Run()
if err != nil {
visitor.Warn("start error: %v", err)
} else {
vm.visitors[name] = visitor
visitor.Info("start visitor success")
}
return
}
func (vm *VisitorManager) Reload(cfgs map[string]config.VisitorConf) {
vm.mu.Lock()
defer vm.mu.Unlock()
delNames := make([]string, 0)
for name, oldCfg := range vm.cfgs {
del := false
cfg, ok := cfgs[name]
if !ok {
del = true
} else {
if !oldCfg.Compare(cfg) {
del = true
}
}
if del {
delNames = append(delNames, name)
delete(vm.cfgs, name)
if visitor, ok := vm.visitors[name]; ok {
visitor.Close()
}
delete(vm.visitors, name)
}
}
if len(delNames) > 0 {
log.Info("visitor removed: %v", delNames)
}
addNames := make([]string, 0)
for name, cfg := range cfgs {
if _, ok := vm.cfgs[name]; !ok {
vm.cfgs[name] = cfg
addNames = append(addNames, name)
vm.startVisitor(cfg)
}
}
if len(addNames) > 0 {
log.Info("visitor added: %v", addNames)
}
return
}
func (vm *VisitorManager) Close() {
vm.mu.Lock()
defer vm.mu.Unlock()
for _, v := range vm.visitors {
v.Close()
}
}

View File

@@ -12,12 +12,16 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
package main package main // "github.com/fatedier/frp/cmd/frpc"
import ( import (
"github.com/fatedier/frp/cmd/frpc/sub" "github.com/fatedier/frp/cmd/frpc/sub"
"github.com/fatedier/golib/crypto"
) )
func main() { func main() {
crypto.DefaultSalt = "frp"
sub.Execute() sub.Execute()
} }

View File

@@ -40,8 +40,8 @@ func init() {
httpCmd.PersistentFlags().StringVarP(&customDomains, "custom_domain", "d", "", "custom domain") httpCmd.PersistentFlags().StringVarP(&customDomains, "custom_domain", "d", "", "custom domain")
httpCmd.PersistentFlags().StringVarP(&subDomain, "sd", "", "", "sub domain") httpCmd.PersistentFlags().StringVarP(&subDomain, "sd", "", "", "sub domain")
httpCmd.PersistentFlags().StringVarP(&locations, "locations", "", "", "locations") httpCmd.PersistentFlags().StringVarP(&locations, "locations", "", "", "locations")
httpCmd.PersistentFlags().StringVarP(&httpUser, "http_user", "", "admin", "http auth user") httpCmd.PersistentFlags().StringVarP(&httpUser, "http_user", "", "", "http auth user")
httpCmd.PersistentFlags().StringVarP(&httpPwd, "http_pwd", "", "admin", "http auth password") httpCmd.PersistentFlags().StringVarP(&httpPwd, "http_pwd", "", "", "http auth password")
httpCmd.PersistentFlags().StringVarP(&hostHeaderRewrite, "host_header_rewrite", "", "", "host header rewrite") httpCmd.PersistentFlags().StringVarP(&hostHeaderRewrite, "host_header_rewrite", "", "", "host header rewrite")
httpCmd.PersistentFlags().BoolVarP(&useEncryption, "ue", "", false, "use encryption") httpCmd.PersistentFlags().BoolVarP(&useEncryption, "ue", "", false, "use encryption")
httpCmd.PersistentFlags().BoolVarP(&useCompression, "uc", "", false, "use compression") httpCmd.PersistentFlags().BoolVarP(&useCompression, "uc", "", false, "use compression")

View File

@@ -69,6 +69,7 @@ var (
sk string sk string
serverName string serverName string
bindAddr string bindAddr string
bindPort int
) )
func init() { func init() {
@@ -165,6 +166,11 @@ func parseClientCommonCfgFromCmd() (err error) {
g.GlbClientCfg.LogLevel = logLevel g.GlbClientCfg.LogLevel = logLevel
g.GlbClientCfg.LogFile = logFile g.GlbClientCfg.LogFile = logFile
g.GlbClientCfg.LogMaxDays = int64(logMaxDays) g.GlbClientCfg.LogMaxDays = int64(logMaxDays)
if logFile == "console" {
g.GlbClientCfg.LogWay = "console"
} else {
g.GlbClientCfg.LogWay = "file"
}
return nil return nil
} }
@@ -179,7 +185,7 @@ func runClient(cfgFilePath string) (err error) {
return err return err
} }
pxyCfgs, visitorCfgs, err := config.LoadProxyConfFromIni(g.GlbClientCfg.User, conf, g.GlbClientCfg.Start) pxyCfgs, visitorCfgs, err := config.LoadAllConfFromIni(g.GlbClientCfg.User, conf, g.GlbClientCfg.Start)
if err != nil { if err != nil {
return err return err
} }
@@ -188,7 +194,7 @@ func runClient(cfgFilePath string) (err error) {
return return
} }
func startService(pxyCfgs map[string]config.ProxyConf, visitorCfgs map[string]config.ProxyConf) (err error) { func startService(pxyCfgs map[string]config.ProxyConf, visitorCfgs map[string]config.VisitorConf) (err error) {
log.InitLog(g.GlbClientCfg.LogWay, g.GlbClientCfg.LogFile, g.GlbClientCfg.LogLevel, g.GlbClientCfg.LogMaxDays) log.InitLog(g.GlbClientCfg.LogWay, g.GlbClientCfg.LogFile, g.GlbClientCfg.LogLevel, g.GlbClientCfg.LogMaxDays)
if g.GlbClientCfg.DnsServer != "" { if g.GlbClientCfg.DnsServer != "" {
s := g.GlbClientCfg.DnsServer s := g.GlbClientCfg.DnsServer

View File

@@ -40,6 +40,7 @@ func init() {
stcpCmd.PersistentFlags().StringVarP(&localIp, "local_ip", "i", "127.0.0.1", "local ip") 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().IntVarP(&localPort, "local_port", "l", 0, "local port")
stcpCmd.PersistentFlags().StringVarP(&bindAddr, "bind_addr", "", "", "bind addr") 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(&useEncryption, "ue", "", false, "use encryption")
stcpCmd.PersistentFlags().BoolVarP(&useCompression, "uc", "", false, "use compression") stcpCmd.PersistentFlags().BoolVarP(&useCompression, "uc", "", false, "use compression")
@@ -56,47 +57,57 @@ var stcpCmd = &cobra.Command{
os.Exit(1) os.Exit(1)
} }
cfg := &config.StcpProxyConf{} proxyConfs := make(map[string]config.ProxyConf)
visitorConfs := make(map[string]config.VisitorConf)
var prefix string var prefix string
if user != "" { if user != "" {
prefix = 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.UseEncryption = useEncryption
cfg.UseCompression = useCompression
err = cfg.CheckForCli() if role == "server" {
cfg := &config.StcpProxyConf{}
cfg.ProxyName = prefix + proxyName
cfg.ProxyType = consts.StcpProxy
cfg.UseEncryption = useEncryption
cfg.UseCompression = useCompression
cfg.Role = role
cfg.Sk = sk
cfg.LocalIp = localIp
cfg.LocalPort = localPort
err = cfg.CheckForCli()
if err != nil {
fmt.Println(err)
os.Exit(1)
}
proxyConfs[cfg.ProxyName] = cfg
} else if role == "visitor" {
cfg := &config.StcpVisitorConf{}
cfg.ProxyName = prefix + proxyName
cfg.ProxyType = consts.StcpProxy
cfg.UseEncryption = useEncryption
cfg.UseCompression = useCompression
cfg.Role = role
cfg.Sk = sk
cfg.ServerName = serverName
cfg.BindAddr = bindAddr
cfg.BindPort = bindPort
err = cfg.Check()
if err != nil {
fmt.Println(err)
os.Exit(1)
}
visitorConfs[cfg.ProxyName] = cfg
} else {
fmt.Println("invalid role")
os.Exit(1)
}
err = startService(proxyConfs, visitorConfs)
if err != nil { if err != nil {
fmt.Println(err) fmt.Println(err)
os.Exit(1) 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 return nil
}, },
} }

View File

@@ -40,6 +40,7 @@ func init() {
xtcpCmd.PersistentFlags().StringVarP(&localIp, "local_ip", "i", "127.0.0.1", "local ip") 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().IntVarP(&localPort, "local_port", "l", 0, "local port")
xtcpCmd.PersistentFlags().StringVarP(&bindAddr, "bind_addr", "", "", "bind addr") 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(&useEncryption, "ue", "", false, "use encryption")
xtcpCmd.PersistentFlags().BoolVarP(&useCompression, "uc", "", false, "use compression") xtcpCmd.PersistentFlags().BoolVarP(&useCompression, "uc", "", false, "use compression")
@@ -56,47 +57,57 @@ var xtcpCmd = &cobra.Command{
os.Exit(1) os.Exit(1)
} }
cfg := &config.XtcpProxyConf{} proxyConfs := make(map[string]config.ProxyConf)
visitorConfs := make(map[string]config.VisitorConf)
var prefix string var prefix string
if user != "" { if user != "" {
prefix = 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.UseEncryption = useEncryption
cfg.UseCompression = useCompression
err = cfg.CheckForCli() if role == "server" {
cfg := &config.XtcpProxyConf{}
cfg.ProxyName = prefix + proxyName
cfg.ProxyType = consts.StcpProxy
cfg.UseEncryption = useEncryption
cfg.UseCompression = useCompression
cfg.Role = role
cfg.Sk = sk
cfg.LocalIp = localIp
cfg.LocalPort = localPort
err = cfg.CheckForCli()
if err != nil {
fmt.Println(err)
os.Exit(1)
}
proxyConfs[cfg.ProxyName] = cfg
} else if role == "visitor" {
cfg := &config.XtcpVisitorConf{}
cfg.ProxyName = prefix + proxyName
cfg.ProxyType = consts.StcpProxy
cfg.UseEncryption = useEncryption
cfg.UseCompression = useCompression
cfg.Role = role
cfg.Sk = sk
cfg.ServerName = serverName
cfg.BindAddr = bindAddr
cfg.BindPort = bindPort
err = cfg.Check()
if err != nil {
fmt.Println(err)
os.Exit(1)
}
visitorConfs[cfg.ProxyName] = cfg
} else {
fmt.Println("invalid role")
os.Exit(1)
}
err = startService(proxyConfs, visitorConfs)
if err != nil { if err != nil {
fmt.Println(err) fmt.Println(err)
os.Exit(1) 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 return nil
}, },
} }

View File

@@ -12,8 +12,14 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
package main package main // "github.com/fatedier/frp/cmd/frps"
import (
"github.com/fatedier/golib/crypto"
)
func main() { func main() {
crypto.DefaultSalt = "frp"
Execute() Execute()
} }

View File

@@ -25,6 +25,7 @@ import (
"github.com/fatedier/frp/models/config" "github.com/fatedier/frp/models/config"
"github.com/fatedier/frp/server" "github.com/fatedier/frp/server"
"github.com/fatedier/frp/utils/log" "github.com/fatedier/frp/utils/log"
"github.com/fatedier/frp/utils/util"
"github.com/fatedier/frp/utils/version" "github.com/fatedier/frp/utils/version"
) )
@@ -44,13 +45,13 @@ var (
proxyBindAddr string proxyBindAddr string
vhostHttpPort int vhostHttpPort int
vhostHttpsPort int vhostHttpsPort int
vhostHttpTimeout int64
dashboardAddr string dashboardAddr string
dashboardPort int dashboardPort int
dashboardUser string dashboardUser string
dashboardPwd string dashboardPwd string
assetsDir string assetsDir string
logFile string logFile string
logWay string
logLevel string logLevel string
logMaxDays int64 logMaxDays int64
token string token string
@@ -73,17 +74,18 @@ func init() {
rootCmd.PersistentFlags().StringVarP(&proxyBindAddr, "proxy_bind_addr", "", "0.0.0.0", "proxy bind address") 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(&vhostHttpPort, "vhost_http_port", "", 0, "vhost http port")
rootCmd.PersistentFlags().IntVarP(&vhostHttpsPort, "vhost_https_port", "", 0, "vhost https 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().StringVarP(&dashboardAddr, "dashboard_addr", "", "0.0.0.0", "dasboard address")
rootCmd.PersistentFlags().IntVarP(&dashboardPort, "dashboard_port", "", 0, "dashboard port") rootCmd.PersistentFlags().IntVarP(&dashboardPort, "dashboard_port", "", 0, "dashboard port")
rootCmd.PersistentFlags().StringVarP(&dashboardUser, "dashboard_user", "", "admin", "dashboard user") rootCmd.PersistentFlags().StringVarP(&dashboardUser, "dashboard_user", "", "admin", "dashboard user")
rootCmd.PersistentFlags().StringVarP(&dashboardPwd, "dashboard_pwd", "", "admin", "dashboard password") rootCmd.PersistentFlags().StringVarP(&dashboardPwd, "dashboard_pwd", "", "admin", "dashboard password")
rootCmd.PersistentFlags().StringVarP(&logFile, "log_file", "", "console", "log file") 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().StringVarP(&logLevel, "log_level", "", "info", "log level")
rootCmd.PersistentFlags().Int64VarP(&logMaxDays, "log_max_days", "", 3, "log_max_days") rootCmd.PersistentFlags().Int64VarP(&logMaxDays, "log_max_days", "", 3, "log_max_days")
rootCmd.PersistentFlags().StringVarP(&token, "token", "", "", "auth token") rootCmd.PersistentFlags().StringVarP(&token, "token", "t", "", "auth token")
rootCmd.PersistentFlags().Int64VarP(&authTimeout, "auth_timeout", "", 900, "auth timeout") rootCmd.PersistentFlags().Int64VarP(&authTimeout, "auth_timeout", "", 900, "auth timeout")
rootCmd.PersistentFlags().StringVarP(&subDomainHost, "subdomain_host", "", "", "subdomain host") 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") rootCmd.PersistentFlags().Int64VarP(&maxPortsPerClient, "max_ports_per_client", "", 0, "max ports per client")
} }
@@ -165,18 +167,36 @@ func parseServerCommonCfgFromCmd() (err error) {
g.GlbServerCfg.ProxyBindAddr = proxyBindAddr g.GlbServerCfg.ProxyBindAddr = proxyBindAddr
g.GlbServerCfg.VhostHttpPort = vhostHttpPort g.GlbServerCfg.VhostHttpPort = vhostHttpPort
g.GlbServerCfg.VhostHttpsPort = vhostHttpsPort g.GlbServerCfg.VhostHttpsPort = vhostHttpsPort
g.GlbServerCfg.VhostHttpTimeout = vhostHttpTimeout
g.GlbServerCfg.DashboardAddr = dashboardAddr g.GlbServerCfg.DashboardAddr = dashboardAddr
g.GlbServerCfg.DashboardPort = dashboardPort g.GlbServerCfg.DashboardPort = dashboardPort
g.GlbServerCfg.DashboardUser = dashboardUser g.GlbServerCfg.DashboardUser = dashboardUser
g.GlbServerCfg.DashboardPwd = dashboardPwd g.GlbServerCfg.DashboardPwd = dashboardPwd
g.GlbServerCfg.LogFile = logFile g.GlbServerCfg.LogFile = logFile
g.GlbServerCfg.LogWay = logWay
g.GlbServerCfg.LogLevel = logLevel g.GlbServerCfg.LogLevel = logLevel
g.GlbServerCfg.LogMaxDays = logMaxDays g.GlbServerCfg.LogMaxDays = logMaxDays
g.GlbServerCfg.Token = token g.GlbServerCfg.Token = token
g.GlbServerCfg.AuthTimeout = authTimeout g.GlbServerCfg.AuthTimeout = authTimeout
g.GlbServerCfg.SubDomainHost = subDomainHost 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 g.GlbServerCfg.MaxPortsPerClient = maxPortsPerClient
if logFile == "console" {
g.GlbClientCfg.LogWay = "console"
} else {
g.GlbClientCfg.LogWay = "file"
}
return return
} }

View File

@@ -5,9 +5,10 @@
server_addr = 0.0.0.0 server_addr = 0.0.0.0
server_port = 7000 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 # it only works when protocol is tcp
# http_proxy = http://user:passwd@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 # console or real logFile path like ./frpc.log
log_file = ./frpc.log log_file = ./frpc.log
@@ -24,7 +25,7 @@ token = 12345678
admin_addr = 127.0.0.1 admin_addr = 127.0.0.1
admin_port = 7400 admin_port = 7400
admin_user = admin admin_user = admin
admin_passwd = admin admin_pwd = admin
# connections will be established in advance, default value is zero # connections will be established in advance, default value is zero
pool_count = 5 pool_count = 5
@@ -40,11 +41,11 @@ user = your_name
login_fail_exit = true login_fail_exit = true
# communication protocol used to connect to server # 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 protocol = tcp
# specify a dns server, so frpc will use this instead of default one # specify a dns server, so frpc will use this instead of default one
dns_server = 8.8.8.8 # dns_server = 8.8.8.8
# proxy names you want to start divided by ',' # proxy names you want to start divided by ','
# default is empty, means all proxies # default is empty, means all proxies
@@ -55,10 +56,10 @@ dns_server = 8.8.8.8
# heartbeat_interval = 30 # heartbeat_interval = 30
# heartbeat_timeout = 90 # heartbeat_timeout = 90
# ssh is the proxy name same as server's configuration # '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 # if user in [common] section is not empty, it will be changed to {user}.{proxy} such as 'your_name.ssh'
[ssh] [ssh]
# tcp | udp | http | https, default is tcp # tcp | udp | http | https | stcp | xtcp, default is tcp
type = tcp type = tcp
local_ip = 127.0.0.1 local_ip = 127.0.0.1
local_port = 22 local_port = 22
@@ -68,12 +69,22 @@ use_encryption = false
use_compression = false use_compression = false
# remote port listen by frps # remote port listen by frps
remote_port = 6001 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
# enable health check for the backend service, it support 'tcp' and 'http' now
# frpc will connect local service's port to detect it's healthy status
health_check_type = tcp
health_check_interval_s = 10
health_check_max_failed = 1
health_check_timeout_s = 3
[ssh_random] [ssh_random]
type = tcp type = tcp
local_ip = 127.0.0.1 local_ip = 127.0.0.1
local_port = 22 local_port = 22
# if remote_port is 0, frps will assgin a random port for you # if remote_port is 0, frps will assign a random port for you
remote_port = 0 remote_port = 0
# if you want to expose multiple ports, add 'range:' prefix to the section name # if you want to expose multiple ports, add 'range:' prefix to the section name
@@ -119,13 +130,20 @@ custom_domains = web02.yourdomain.com
# locations is only available for http type # locations is only available for http type
locations = /,/pic locations = /,/pic
host_header_rewrite = example.com host_header_rewrite = example.com
# params with prefix "header_" will be used to update http request headers
header_X-From-Where = frp
health_check_type = http
# frpc will send a GET http request '/status' to local http service
# http service is alive when it return 2xx http response code
health_check_url = /status
health_check_interval_s = 10
[web02] [web02]
type = https type = https
local_ip = 127.0.0.1 local_ip = 127.0.0.1
local_port = 8000 local_port = 8000
use_encryption = false use_encryption = false
use_compression = false use_compression = false
subdomain = web01 subdomain = web01
custom_domains = web02.yourdomain.com custom_domains = web02.yourdomain.com
@@ -135,7 +153,7 @@ remote_port = 6003
# if plugin is defined, local_ip and local_port is useless # if plugin is defined, local_ip and local_port is useless
# plugin will handle connections got from frps # plugin will handle connections got from frps
plugin = unix_domain_socket 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_unix_path = /var/run/docker.sock
[plugin_http_proxy] [plugin_http_proxy]

View File

@@ -16,9 +16,13 @@ kcp_bind_port = 7000
# proxy_bind_addr = 127.0.0.1 # proxy_bind_addr = 127.0.0.1
# if you want to support virtual host, you must set the http port for listening (optional) # 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_http_port = 80
vhost_https_port = 443 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 # set dashboard_addr and dashboard_port to view dashboard of frps
# dashboard_addr's default value is same with bind_addr # dashboard_addr's default value is same with bind_addr
# dashboard is available only if dashboard_port is set # dashboard is available only if dashboard_port is set
@@ -27,7 +31,7 @@ dashboard_port = 7500
# dashboard user and passwd 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_user = admin
dashboard_passwd = admin dashboard_pwd = admin
# dashboard assets directory(only for debug mode) # dashboard assets directory(only for debug mode)
# assets_dir = ./static # assets_dir = ./static

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
```

81
glide.lock generated
View File

@@ -1,81 +0,0 @@
hash: e2a62cbc49d9da8ff95682f5c0b7731a7047afdd139acddb691c51ea98f726e1
updated: 2018-04-25T02:41:38.15698+08:00
imports:
- name: github.com/armon/go-socks5
version: e75332964ef517daa070d7c38a9466a0d687e0a5
- name: github.com/davecgh/go-spew
version: 346938d642f2ec3594ed81d874461961cd0faa76
subpackages:
- spew
- 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/gorilla/websocket
version: ea4d1f681babbce9545c9c5f3d5194a789c89f5b
- name: github.com/hashicorp/yamux
version: 2658be15c5f05e76244154714161f17e3e77de2e
- name: github.com/inconshreveable/mousetrap
version: 76626ae9c91c4f2a10f34cad8ce83ea42c93bb75
- 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/rodaine/table
version: 212a2ad1c462ed4d5b5511ea2b480a573281dbbd
- name: github.com/spf13/cobra
version: a1f051bc3eba734da4772d60e2d677f47cf93ef4
- name: github.com/spf13/pflag
version: 583c0c0531f06d5278b7d917446061adc344b5cd
- 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: 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,74 +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/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: 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
- package: github.com/rodaine/table
version: v1.0.0
- package: github.com/gorilla/websocket
version: v1.2.0
- package: github.com/hashicorp/yamux
- package: github.com/spf13/cobra
version: v0.0.2

View File

@@ -186,9 +186,10 @@ func UnmarshalClientConfFromIni(defaultCfg *ClientCommonConf, content string) (c
} }
if tmpStr, ok = conf.Get("common", "protocol"); ok { if tmpStr, ok = conf.Get("common", "protocol"); ok {
// Now it only support tcp and kcp. // Now it only support tcp and kcp and websocket.
if tmpStr != "kcp" { if tmpStr != "tcp" && tmpStr != "kcp" && tmpStr != "websocket" {
tmpStr = "tcp" err = fmt.Errorf("Parse conf error: invalid protocol")
return
} }
cfg.Protocol = tmpStr cfg.Protocol = tmpStr
} }

View File

@@ -91,7 +91,9 @@ func NewProxyConfFromIni(prefix string, name string, section ini.Section) (cfg P
if err = cfg.UnmarshalFromIni(prefix, name, section); err != nil { if err = cfg.UnmarshalFromIni(prefix, name, section); err != nil {
return return
} }
err = cfg.CheckForCli() if err = cfg.CheckForCli(); err != nil {
return
}
return return
} }
@@ -100,8 +102,13 @@ type BaseProxyConf struct {
ProxyName string `json:"proxy_name"` ProxyName string `json:"proxy_name"`
ProxyType string `json:"proxy_type"` ProxyType string `json:"proxy_type"`
UseEncryption bool `json:"use_encryption"` UseEncryption bool `json:"use_encryption"`
UseCompression bool `json:"use_compression"` UseCompression bool `json:"use_compression"`
Group string `json:"group"`
GroupKey string `json:"group_key"`
LocalSvrConf
HealthCheckConf // only used for client
} }
func (cfg *BaseProxyConf) GetBaseInfo() *BaseProxyConf { func (cfg *BaseProxyConf) GetBaseInfo() *BaseProxyConf {
@@ -112,7 +119,15 @@ func (cfg *BaseProxyConf) compare(cmp *BaseProxyConf) bool {
if cfg.ProxyName != cmp.ProxyName || if cfg.ProxyName != cmp.ProxyName ||
cfg.ProxyType != cmp.ProxyType || cfg.ProxyType != cmp.ProxyType ||
cfg.UseEncryption != cmp.UseEncryption || cfg.UseEncryption != cmp.UseEncryption ||
cfg.UseCompression != cmp.UseCompression { cfg.UseCompression != cmp.UseCompression ||
cfg.Group != cmp.Group ||
cfg.GroupKey != cmp.GroupKey {
return false
}
if !cfg.LocalSvrConf.compare(&cmp.LocalSvrConf) {
return false
}
if !cfg.HealthCheckConf.compare(&cmp.HealthCheckConf) {
return false return false
} }
return true return true
@@ -123,6 +138,8 @@ func (cfg *BaseProxyConf) UnmarshalFromMsg(pMsg *msg.NewProxy) {
cfg.ProxyType = pMsg.ProxyType cfg.ProxyType = pMsg.ProxyType
cfg.UseEncryption = pMsg.UseEncryption cfg.UseEncryption = pMsg.UseEncryption
cfg.UseCompression = pMsg.UseCompression cfg.UseCompression = pMsg.UseCompression
cfg.Group = pMsg.Group
cfg.GroupKey = pMsg.GroupKey
} }
func (cfg *BaseProxyConf) UnmarshalFromIni(prefix string, name string, section ini.Section) error { func (cfg *BaseProxyConf) UnmarshalFromIni(prefix string, name string, section ini.Section) error {
@@ -142,6 +159,28 @@ func (cfg *BaseProxyConf) UnmarshalFromIni(prefix string, name string, section i
if ok && tmpStr == "true" { if ok && tmpStr == "true" {
cfg.UseCompression = true cfg.UseCompression = true
} }
cfg.Group = section["group"]
cfg.GroupKey = section["group_key"]
if err := cfg.LocalSvrConf.UnmarshalFromIni(prefix, name, section); err != nil {
return err
}
if err := cfg.HealthCheckConf.UnmarshalFromIni(prefix, name, section); err != nil {
return err
}
if cfg.HealthCheckType == "tcp" && cfg.Plugin == "" {
cfg.HealthCheckAddr = cfg.LocalIp + fmt.Sprintf(":%d", cfg.LocalPort)
}
if cfg.HealthCheckType == "http" && cfg.Plugin == "" && cfg.HealthCheckUrl != "" {
s := fmt.Sprintf("http://%s:%d", cfg.LocalIp, cfg.LocalPort)
if !strings.HasPrefix(cfg.HealthCheckUrl, "/") {
s += "/"
}
cfg.HealthCheckUrl = s + cfg.HealthCheckUrl
}
return nil return nil
} }
@@ -150,6 +189,18 @@ func (cfg *BaseProxyConf) MarshalToMsg(pMsg *msg.NewProxy) {
pMsg.ProxyType = cfg.ProxyType pMsg.ProxyType = cfg.ProxyType
pMsg.UseEncryption = cfg.UseEncryption pMsg.UseEncryption = cfg.UseEncryption
pMsg.UseCompression = cfg.UseCompression pMsg.UseCompression = cfg.UseCompression
pMsg.Group = cfg.Group
pMsg.GroupKey = cfg.GroupKey
}
func (cfg *BaseProxyConf) checkForCli() (err error) {
if err = cfg.LocalSvrConf.checkForCli(); err != nil {
return
}
if err = cfg.HealthCheckConf.checkForCli(); err != nil {
return
}
return nil
} }
// Bind info // Bind info
@@ -262,7 +313,7 @@ func (cfg *DomainConf) checkForSvr() (err error) {
if cfg.SubDomain != "" { if cfg.SubDomain != "" {
if subDomainHost == "" { if subDomainHost == "" {
return fmt.Errorf("subdomain is not supported because this feature is not enabled by frps") 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, "*") { if strings.Contains(cfg.SubDomain, ".") || strings.Contains(cfg.SubDomain, "*") {
return fmt.Errorf("'.' and '*' is not supported in subdomain") return fmt.Errorf("'.' and '*' is not supported in subdomain")
@@ -324,12 +375,83 @@ func (cfg *LocalSvrConf) UnmarshalFromIni(prefix string, name string, section in
return return
} }
func (cfg *LocalSvrConf) checkForCli() (err error) {
if cfg.Plugin == "" {
if cfg.LocalIp == "" {
err = fmt.Errorf("local ip or plugin is required")
return
}
if cfg.LocalPort <= 0 {
err = fmt.Errorf("error local_port")
return
}
}
return
}
// Health check info
type HealthCheckConf struct {
HealthCheckType string `json:"health_check_type"` // tcp | http
HealthCheckTimeoutS int `json:"health_check_timeout_s"`
HealthCheckMaxFailed int `json:"health_check_max_failed"`
HealthCheckIntervalS int `json:"health_check_interval_s"`
HealthCheckUrl string `json:"health_check_url"`
// local_ip + local_port
HealthCheckAddr string `json:"-"`
}
func (cfg *HealthCheckConf) compare(cmp *HealthCheckConf) bool {
if cfg.HealthCheckType != cmp.HealthCheckType ||
cfg.HealthCheckTimeoutS != cmp.HealthCheckTimeoutS ||
cfg.HealthCheckMaxFailed != cmp.HealthCheckMaxFailed ||
cfg.HealthCheckIntervalS != cmp.HealthCheckIntervalS ||
cfg.HealthCheckUrl != cmp.HealthCheckUrl {
return false
}
return true
}
func (cfg *HealthCheckConf) UnmarshalFromIni(prefix string, name string, section ini.Section) (err error) {
cfg.HealthCheckType = section["health_check_type"]
cfg.HealthCheckUrl = section["health_check_url"]
if tmpStr, ok := section["health_check_timeout_s"]; ok {
if cfg.HealthCheckTimeoutS, err = strconv.Atoi(tmpStr); err != nil {
return fmt.Errorf("Parse conf error: proxy [%s] health_check_timeout_s error", name)
}
}
if tmpStr, ok := section["health_check_max_failed"]; ok {
if cfg.HealthCheckMaxFailed, err = strconv.Atoi(tmpStr); err != nil {
return fmt.Errorf("Parse conf error: proxy [%s] health_check_max_failed error", name)
}
}
if tmpStr, ok := section["health_check_interval_s"]; ok {
if cfg.HealthCheckIntervalS, err = strconv.Atoi(tmpStr); err != nil {
return fmt.Errorf("Parse conf error: proxy [%s] health_check_interval_s error", name)
}
}
return
}
func (cfg *HealthCheckConf) checkForCli() error {
if cfg.HealthCheckType != "" && cfg.HealthCheckType != "tcp" && cfg.HealthCheckType != "http" {
return fmt.Errorf("unsupport health check type")
}
if cfg.HealthCheckType != "" {
if cfg.HealthCheckType == "http" && cfg.HealthCheckUrl == "" {
return fmt.Errorf("health_check_url is required for health check type 'http'")
}
}
return nil
}
// TCP // TCP
type TcpProxyConf struct { type TcpProxyConf struct {
BaseProxyConf BaseProxyConf
BindInfoConf BindInfoConf
LocalSvrConf
} }
func (cfg *TcpProxyConf) Compare(cmp ProxyConf) bool { func (cfg *TcpProxyConf) Compare(cmp ProxyConf) bool {
@@ -339,8 +461,7 @@ func (cfg *TcpProxyConf) Compare(cmp ProxyConf) bool {
} }
if !cfg.BaseProxyConf.compare(&cmpConf.BaseProxyConf) || if !cfg.BaseProxyConf.compare(&cmpConf.BaseProxyConf) ||
!cfg.BindInfoConf.compare(&cmpConf.BindInfoConf) || !cfg.BindInfoConf.compare(&cmpConf.BindInfoConf) {
!cfg.LocalSvrConf.compare(&cmpConf.LocalSvrConf) {
return false return false
} }
return true return true
@@ -358,9 +479,6 @@ func (cfg *TcpProxyConf) UnmarshalFromIni(prefix string, name string, section in
if err = cfg.BindInfoConf.UnmarshalFromIni(prefix, name, section); err != nil { if err = cfg.BindInfoConf.UnmarshalFromIni(prefix, name, section); err != nil {
return return
} }
if err = cfg.LocalSvrConf.UnmarshalFromIni(prefix, name, section); err != nil {
return
}
return return
} }
@@ -369,7 +487,12 @@ func (cfg *TcpProxyConf) MarshalToMsg(pMsg *msg.NewProxy) {
cfg.BindInfoConf.MarshalToMsg(pMsg) cfg.BindInfoConf.MarshalToMsg(pMsg)
} }
func (cfg *TcpProxyConf) CheckForCli() error { return nil } func (cfg *TcpProxyConf) CheckForCli() (err error) {
if err = cfg.BaseProxyConf.checkForCli(); err != nil {
return err
}
return
}
func (cfg *TcpProxyConf) CheckForSvr() error { return nil } func (cfg *TcpProxyConf) CheckForSvr() error { return nil }
@@ -377,8 +500,6 @@ func (cfg *TcpProxyConf) CheckForSvr() error { return nil }
type UdpProxyConf struct { type UdpProxyConf struct {
BaseProxyConf BaseProxyConf
BindInfoConf BindInfoConf
LocalSvrConf
} }
func (cfg *UdpProxyConf) Compare(cmp ProxyConf) bool { func (cfg *UdpProxyConf) Compare(cmp ProxyConf) bool {
@@ -388,8 +509,7 @@ func (cfg *UdpProxyConf) Compare(cmp ProxyConf) bool {
} }
if !cfg.BaseProxyConf.compare(&cmpConf.BaseProxyConf) || if !cfg.BaseProxyConf.compare(&cmpConf.BaseProxyConf) ||
!cfg.BindInfoConf.compare(&cmpConf.BindInfoConf) || !cfg.BindInfoConf.compare(&cmpConf.BindInfoConf) {
!cfg.LocalSvrConf.compare(&cmpConf.LocalSvrConf) {
return false return false
} }
return true return true
@@ -407,9 +527,6 @@ func (cfg *UdpProxyConf) UnmarshalFromIni(prefix string, name string, section in
if err = cfg.BindInfoConf.UnmarshalFromIni(prefix, name, section); err != nil { if err = cfg.BindInfoConf.UnmarshalFromIni(prefix, name, section); err != nil {
return return
} }
if err = cfg.LocalSvrConf.UnmarshalFromIni(prefix, name, section); err != nil {
return
}
return return
} }
@@ -418,7 +535,12 @@ func (cfg *UdpProxyConf) MarshalToMsg(pMsg *msg.NewProxy) {
cfg.BindInfoConf.MarshalToMsg(pMsg) cfg.BindInfoConf.MarshalToMsg(pMsg)
} }
func (cfg *UdpProxyConf) CheckForCli() error { return nil } func (cfg *UdpProxyConf) CheckForCli() (err error) {
if err = cfg.BaseProxyConf.checkForCli(); err != nil {
return
}
return
}
func (cfg *UdpProxyConf) CheckForSvr() error { return nil } func (cfg *UdpProxyConf) CheckForSvr() error { return nil }
@@ -427,12 +549,11 @@ type HttpProxyConf struct {
BaseProxyConf BaseProxyConf
DomainConf DomainConf
LocalSvrConf Locations []string `json:"locations"`
HttpUser string `json:"http_user"`
Locations []string `json:"locations"` HttpPwd string `json:"http_pwd"`
HostHeaderRewrite string `json:"host_header_rewrite"` HostHeaderRewrite string `json:"host_header_rewrite"`
HttpUser string `json:"http_user"` Headers map[string]string `json:"headers"`
HttpPwd string `json:"http_pwd"`
} }
func (cfg *HttpProxyConf) Compare(cmp ProxyConf) bool { func (cfg *HttpProxyConf) Compare(cmp ProxyConf) bool {
@@ -443,13 +564,23 @@ func (cfg *HttpProxyConf) Compare(cmp ProxyConf) bool {
if !cfg.BaseProxyConf.compare(&cmpConf.BaseProxyConf) || if !cfg.BaseProxyConf.compare(&cmpConf.BaseProxyConf) ||
!cfg.DomainConf.compare(&cmpConf.DomainConf) || !cfg.DomainConf.compare(&cmpConf.DomainConf) ||
!cfg.LocalSvrConf.compare(&cmpConf.LocalSvrConf) ||
strings.Join(cfg.Locations, " ") != strings.Join(cmpConf.Locations, " ") || strings.Join(cfg.Locations, " ") != strings.Join(cmpConf.Locations, " ") ||
cfg.HostHeaderRewrite != cmpConf.HostHeaderRewrite || cfg.HostHeaderRewrite != cmpConf.HostHeaderRewrite ||
cfg.HttpUser != cmpConf.HttpUser || cfg.HttpUser != cmpConf.HttpUser ||
cfg.HttpPwd != cmpConf.HttpPwd { cfg.HttpPwd != cmpConf.HttpPwd ||
len(cfg.Headers) != len(cmpConf.Headers) {
return false 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 return true
} }
@@ -461,6 +592,7 @@ func (cfg *HttpProxyConf) UnmarshalFromMsg(pMsg *msg.NewProxy) {
cfg.HostHeaderRewrite = pMsg.HostHeaderRewrite cfg.HostHeaderRewrite = pMsg.HostHeaderRewrite
cfg.HttpUser = pMsg.HttpUser cfg.HttpUser = pMsg.HttpUser
cfg.HttpPwd = pMsg.HttpPwd cfg.HttpPwd = pMsg.HttpPwd
cfg.Headers = pMsg.Headers
} }
func (cfg *HttpProxyConf) UnmarshalFromIni(prefix string, name string, section ini.Section) (err error) { func (cfg *HttpProxyConf) UnmarshalFromIni(prefix string, name string, section ini.Section) (err error) {
@@ -470,9 +602,6 @@ func (cfg *HttpProxyConf) UnmarshalFromIni(prefix string, name string, section i
if err = cfg.DomainConf.UnmarshalFromIni(prefix, name, section); err != nil { if err = cfg.DomainConf.UnmarshalFromIni(prefix, name, section); err != nil {
return return
} }
if err = cfg.LocalSvrConf.UnmarshalFromIni(prefix, name, section); err != nil {
return
}
var ( var (
tmpStr string tmpStr string
@@ -487,6 +616,13 @@ func (cfg *HttpProxyConf) UnmarshalFromIni(prefix string, name string, section i
cfg.HostHeaderRewrite = section["host_header_rewrite"] cfg.HostHeaderRewrite = section["host_header_rewrite"]
cfg.HttpUser = section["http_user"] cfg.HttpUser = section["http_user"]
cfg.HttpPwd = section["http_pwd"] 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 return
} }
@@ -498,9 +634,13 @@ func (cfg *HttpProxyConf) MarshalToMsg(pMsg *msg.NewProxy) {
pMsg.HostHeaderRewrite = cfg.HostHeaderRewrite pMsg.HostHeaderRewrite = cfg.HostHeaderRewrite
pMsg.HttpUser = cfg.HttpUser pMsg.HttpUser = cfg.HttpUser
pMsg.HttpPwd = cfg.HttpPwd pMsg.HttpPwd = cfg.HttpPwd
pMsg.Headers = cfg.Headers
} }
func (cfg *HttpProxyConf) CheckForCli() (err error) { func (cfg *HttpProxyConf) CheckForCli() (err error) {
if err = cfg.BaseProxyConf.checkForCli(); err != nil {
return
}
if err = cfg.DomainConf.checkForCli(); err != nil { if err = cfg.DomainConf.checkForCli(); err != nil {
return return
} }
@@ -509,9 +649,10 @@ func (cfg *HttpProxyConf) CheckForCli() (err error) {
func (cfg *HttpProxyConf) CheckForSvr() (err error) { func (cfg *HttpProxyConf) CheckForSvr() (err error) {
if vhostHttpPort == 0 { if vhostHttpPort == 0 {
err = fmt.Errorf("type [http] not support when vhost_http_port is not set") return fmt.Errorf("type [http] not support when vhost_http_port is not set")
} }
if err = cfg.DomainConf.checkForSvr(); err != nil { if err = cfg.DomainConf.checkForSvr(); err != nil {
err = fmt.Errorf("proxy [%s] domain conf check error: %v", cfg.ProxyName, err)
return return
} }
return return
@@ -521,8 +662,6 @@ func (cfg *HttpProxyConf) CheckForSvr() (err error) {
type HttpsProxyConf struct { type HttpsProxyConf struct {
BaseProxyConf BaseProxyConf
DomainConf DomainConf
LocalSvrConf
} }
func (cfg *HttpsProxyConf) Compare(cmp ProxyConf) bool { func (cfg *HttpsProxyConf) Compare(cmp ProxyConf) bool {
@@ -532,8 +671,7 @@ func (cfg *HttpsProxyConf) Compare(cmp ProxyConf) bool {
} }
if !cfg.BaseProxyConf.compare(&cmpConf.BaseProxyConf) || if !cfg.BaseProxyConf.compare(&cmpConf.BaseProxyConf) ||
!cfg.DomainConf.compare(&cmpConf.DomainConf) || !cfg.DomainConf.compare(&cmpConf.DomainConf) {
!cfg.LocalSvrConf.compare(&cmpConf.LocalSvrConf) {
return false return false
} }
return true return true
@@ -551,9 +689,6 @@ func (cfg *HttpsProxyConf) UnmarshalFromIni(prefix string, name string, section
if err = cfg.DomainConf.UnmarshalFromIni(prefix, name, section); err != nil { if err = cfg.DomainConf.UnmarshalFromIni(prefix, name, section); err != nil {
return return
} }
if err = cfg.LocalSvrConf.UnmarshalFromIni(prefix, name, section); err != nil {
return
}
return return
} }
@@ -563,6 +698,9 @@ func (cfg *HttpsProxyConf) MarshalToMsg(pMsg *msg.NewProxy) {
} }
func (cfg *HttpsProxyConf) CheckForCli() (err error) { func (cfg *HttpsProxyConf) CheckForCli() (err error) {
if err = cfg.BaseProxyConf.checkForCli(); err != nil {
return
}
if err = cfg.DomainConf.checkForCli(); err != nil { if err = cfg.DomainConf.checkForCli(); err != nil {
return return
} }
@@ -574,6 +712,7 @@ func (cfg *HttpsProxyConf) CheckForSvr() (err error) {
return fmt.Errorf("type [https] not support when vhost_https_port is not set") return fmt.Errorf("type [https] not support when vhost_https_port is not set")
} }
if err = cfg.DomainConf.checkForSvr(); err != nil { if err = cfg.DomainConf.checkForSvr(); err != nil {
err = fmt.Errorf("proxy [%s] domain conf check error: %v", cfg.ProxyName, err)
return return
} }
return return
@@ -585,14 +724,6 @@ type StcpProxyConf struct {
Role string `json:"role"` Role string `json:"role"`
Sk string `json:"sk"` Sk string `json:"sk"`
// used in role server
LocalSvrConf
// used in role visitor
ServerName string `json:"server_name"`
BindAddr string `json:"bind_addr"`
BindPort int `json:"bind_port"`
} }
func (cfg *StcpProxyConf) Compare(cmp ProxyConf) bool { func (cfg *StcpProxyConf) Compare(cmp ProxyConf) bool {
@@ -602,12 +733,8 @@ func (cfg *StcpProxyConf) Compare(cmp ProxyConf) bool {
} }
if !cfg.BaseProxyConf.compare(&cmpConf.BaseProxyConf) || if !cfg.BaseProxyConf.compare(&cmpConf.BaseProxyConf) ||
!cfg.LocalSvrConf.compare(&cmpConf.LocalSvrConf) ||
cfg.Role != cmpConf.Role || cfg.Role != cmpConf.Role ||
cfg.Sk != cmpConf.Sk || cfg.Sk != cmpConf.Sk {
cfg.ServerName != cmpConf.ServerName ||
cfg.BindAddr != cmpConf.BindAddr ||
cfg.BindPort != cmpConf.BindPort {
return false return false
} }
return true return true
@@ -624,36 +751,15 @@ func (cfg *StcpProxyConf) UnmarshalFromIni(prefix string, name string, section i
return return
} }
tmpStr := section["role"] cfg.Role = section["role"]
if tmpStr == "" { if cfg.Role != "server" {
tmpStr = "server" return fmt.Errorf("Parse conf error: proxy [%s] incorrect role [%s]", name, cfg.Role)
}
if tmpStr == "server" || tmpStr == "visitor" {
cfg.Role = tmpStr
} else {
return fmt.Errorf("Parse conf error: proxy [%s] incorrect role [%s]", name, tmpStr)
} }
cfg.Sk = section["sk"] cfg.Sk = section["sk"]
if tmpStr == "visitor" { if err = cfg.LocalSvrConf.UnmarshalFromIni(prefix, name, section); err != nil {
prefix := section["prefix"] return
cfg.ServerName = prefix + section["server_name"]
if cfg.BindAddr = section["bind_addr"]; cfg.BindAddr == "" {
cfg.BindAddr = "127.0.0.1"
}
if tmpStr, ok := section["bind_port"]; ok {
if cfg.BindPort, err = strconv.Atoi(tmpStr); err != nil {
return fmt.Errorf("Parse conf error: proxy [%s] bind_port error", name)
}
} else {
return fmt.Errorf("Parse conf error: proxy [%s] bind_port not found", name)
}
} else {
if err = cfg.LocalSvrConf.UnmarshalFromIni(prefix, name, section); err != nil {
return
}
} }
return return
} }
@@ -664,15 +770,12 @@ func (cfg *StcpProxyConf) MarshalToMsg(pMsg *msg.NewProxy) {
} }
func (cfg *StcpProxyConf) CheckForCli() (err error) { func (cfg *StcpProxyConf) CheckForCli() (err error) {
if cfg.Role != "server" && cfg.Role != "visitor" { if err = cfg.BaseProxyConf.checkForCli(); err != nil {
err = fmt.Errorf("role should be 'server' or 'visitor'")
return return
} }
if cfg.Role == "visitor" { if cfg.Role != "server" {
if cfg.BindAddr == "" { err = fmt.Errorf("role should be 'server'")
err = fmt.Errorf("bind_addr shouldn't be empty") return
return
}
} }
return return
} }
@@ -687,14 +790,6 @@ type XtcpProxyConf struct {
Role string `json:"role"` Role string `json:"role"`
Sk string `json:"sk"` Sk string `json:"sk"`
// used in role server
LocalSvrConf
// used in role visitor
ServerName string `json:"server_name"`
BindAddr string `json:"bind_addr"`
BindPort int `json:"bind_port"`
} }
func (cfg *XtcpProxyConf) Compare(cmp ProxyConf) bool { func (cfg *XtcpProxyConf) Compare(cmp ProxyConf) bool {
@@ -706,10 +801,7 @@ func (cfg *XtcpProxyConf) Compare(cmp ProxyConf) bool {
if !cfg.BaseProxyConf.compare(&cmpConf.BaseProxyConf) || if !cfg.BaseProxyConf.compare(&cmpConf.BaseProxyConf) ||
!cfg.LocalSvrConf.compare(&cmpConf.LocalSvrConf) || !cfg.LocalSvrConf.compare(&cmpConf.LocalSvrConf) ||
cfg.Role != cmpConf.Role || cfg.Role != cmpConf.Role ||
cfg.Sk != cmpConf.Sk || cfg.Sk != cmpConf.Sk {
cfg.ServerName != cmpConf.ServerName ||
cfg.BindAddr != cmpConf.BindAddr ||
cfg.BindPort != cmpConf.BindPort {
return false return false
} }
return true return true
@@ -726,36 +818,15 @@ func (cfg *XtcpProxyConf) UnmarshalFromIni(prefix string, name string, section i
return return
} }
tmpStr := section["role"] cfg.Role = section["role"]
if tmpStr == "" { if cfg.Role != "server" {
tmpStr = "server" return fmt.Errorf("Parse conf error: proxy [%s] incorrect role [%s]", name, cfg.Role)
}
if tmpStr == "server" || tmpStr == "visitor" {
cfg.Role = tmpStr
} else {
return fmt.Errorf("Parse conf error: proxy [%s] incorrect role [%s]", name, tmpStr)
} }
cfg.Sk = section["sk"] cfg.Sk = section["sk"]
if tmpStr == "visitor" { if err = cfg.LocalSvrConf.UnmarshalFromIni(prefix, name, section); err != nil {
prefix := section["prefix"] return
cfg.ServerName = prefix + section["server_name"]
if cfg.BindAddr = section["bind_addr"]; cfg.BindAddr == "" {
cfg.BindAddr = "127.0.0.1"
}
if tmpStr, ok := section["bind_port"]; ok {
if cfg.BindPort, err = strconv.Atoi(tmpStr); err != nil {
return fmt.Errorf("Parse conf error: proxy [%s] bind_port error", name)
}
} else {
return fmt.Errorf("Parse conf error: proxy [%s] bind_port not found", name)
}
} else {
if err = cfg.LocalSvrConf.UnmarshalFromIni(prefix, name, section); err != nil {
return
}
} }
return return
} }
@@ -766,15 +837,12 @@ func (cfg *XtcpProxyConf) MarshalToMsg(pMsg *msg.NewProxy) {
} }
func (cfg *XtcpProxyConf) CheckForCli() (err error) { func (cfg *XtcpProxyConf) CheckForCli() (err error) {
if cfg.Role != "server" && cfg.Role != "visitor" { if err = cfg.BaseProxyConf.checkForCli(); err != nil {
err = fmt.Errorf("role should be 'server' or 'visitor'")
return return
} }
if cfg.Role == "visitor" { if cfg.Role != "server" {
if cfg.BindAddr == "" { err = fmt.Errorf("role should be 'server'")
err = fmt.Errorf("bind_addr shouldn't be empty") return
return
}
} }
return return
} }
@@ -817,8 +885,8 @@ func ParseRangeSection(name string, section ini.Section) (sections map[string]in
// if len(startProxy) is 0, start all // if len(startProxy) is 0, start all
// otherwise just start proxies in startProxy map // otherwise just start proxies in startProxy map
func LoadProxyConfFromIni(prefix string, conf ini.File, startProxy map[string]struct{}) ( func LoadAllConfFromIni(prefix string, conf ini.File, startProxy map[string]struct{}) (
proxyConfs map[string]ProxyConf, visitorConfs map[string]ProxyConf, err error) { proxyConfs map[string]ProxyConf, visitorConfs map[string]VisitorConf, err error) {
if prefix != "" { if prefix != "" {
prefix += "." prefix += "."
@@ -829,7 +897,7 @@ func LoadProxyConfFromIni(prefix string, conf ini.File, startProxy map[string]st
startAll = false startAll = false
} }
proxyConfs = make(map[string]ProxyConf) proxyConfs = make(map[string]ProxyConf)
visitorConfs = make(map[string]ProxyConf) visitorConfs = make(map[string]VisitorConf)
for name, section := range conf { for name, section := range conf {
if name == "common" { if name == "common" {
continue continue
@@ -854,16 +922,27 @@ func LoadProxyConfFromIni(prefix string, conf ini.File, startProxy map[string]st
} }
for subName, subSection := range subSections { for subName, subSection := range subSections {
cfg, err := NewProxyConfFromIni(prefix, subName, subSection) if subSection["role"] == "" {
if err != nil { subSection["role"] = "server"
return proxyConfs, visitorConfs, err
} }
role := subSection["role"] role := subSection["role"]
if role == "visitor" { if role == "server" {
cfg, errRet := NewProxyConfFromIni(prefix, subName, subSection)
if errRet != nil {
err = errRet
return
}
proxyConfs[prefix+subName] = cfg
} else if role == "visitor" {
cfg, errRet := NewVisitorConfFromIni(prefix, subName, subSection)
if errRet != nil {
err = errRet
return
}
visitorConfs[prefix+subName] = cfg visitorConfs[prefix+subName] = cfg
} else { } else {
proxyConfs[prefix+subName] = cfg err = fmt.Errorf("role should be 'server' or 'visitor'")
return
} }
} }
} }

View File

@@ -51,8 +51,11 @@ type ServerCommonConf struct {
VhostHttpPort int `json:"vhost_http_port"` VhostHttpPort int `json:"vhost_http_port"`
// if VhostHttpsPort equals 0, don't listen a public port for https protocol // if VhostHttpsPort equals 0, don't listen a public port for https protocol
VhostHttpsPort int `json:"vhost_http_port"` VhostHttpsPort int `json:"vhost_http_port"`
DashboardAddr string `json:"dashboard_addr"`
VhostHttpTimeout int64 `json:"vhost_http_timeout"`
DashboardAddr string `json:"dashboard_addr"`
// if DashboardPort equals 0, dashboard is not available // if DashboardPort equals 0, dashboard is not available
DashboardPort int `json:"dashboard_port"` DashboardPort int `json:"dashboard_port"`
@@ -84,6 +87,7 @@ func GetDefaultServerConf() *ServerCommonConf {
ProxyBindAddr: "0.0.0.0", ProxyBindAddr: "0.0.0.0",
VhostHttpPort: 0, VhostHttpPort: 0,
VhostHttpsPort: 0, VhostHttpsPort: 0,
VhostHttpTimeout: 60,
DashboardAddr: "0.0.0.0", DashboardAddr: "0.0.0.0",
DashboardPort: 0, DashboardPort: 0,
DashboardUser: "admin", DashboardUser: "admin",
@@ -181,6 +185,16 @@ func UnmarshalServerConfFromIni(defaultCfg *ServerCommonConf, content string) (c
cfg.VhostHttpsPort = 0 cfg.VhostHttpsPort = 0
} }
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 { if tmpStr, ok = conf.Get("common", "dashboard_addr"); ok {
cfg.DashboardAddr = tmpStr cfg.DashboardAddr = tmpStr
} else { } else {

213
models/config/visitor.go Normal file
View File

@@ -0,0 +1,213 @@
// 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 config
import (
"fmt"
"reflect"
"strconv"
"github.com/fatedier/frp/models/consts"
ini "github.com/vaughan0/go-ini"
)
var (
visitorConfTypeMap map[string]reflect.Type
)
func init() {
visitorConfTypeMap = make(map[string]reflect.Type)
visitorConfTypeMap[consts.StcpProxy] = reflect.TypeOf(StcpVisitorConf{})
visitorConfTypeMap[consts.XtcpProxy] = reflect.TypeOf(XtcpVisitorConf{})
}
type VisitorConf interface {
GetBaseInfo() *BaseVisitorConf
Compare(cmp VisitorConf) bool
UnmarshalFromIni(prefix string, name string, section ini.Section) error
Check() error
}
func NewVisitorConfByType(cfgType string) VisitorConf {
v, ok := visitorConfTypeMap[cfgType]
if !ok {
return nil
}
cfg := reflect.New(v).Interface().(VisitorConf)
return cfg
}
func NewVisitorConfFromIni(prefix string, name string, section ini.Section) (cfg VisitorConf, err error) {
cfgType := section["type"]
if cfgType == "" {
err = fmt.Errorf("visitor [%s] type shouldn't be empty", name)
return
}
cfg = NewVisitorConfByType(cfgType)
if cfg == nil {
err = fmt.Errorf("visitor [%s] type [%s] error", name, cfgType)
return
}
if err = cfg.UnmarshalFromIni(prefix, name, section); err != nil {
return
}
if err = cfg.Check(); err != nil {
return
}
return
}
type BaseVisitorConf struct {
ProxyName string `json:"proxy_name"`
ProxyType string `json:"proxy_type"`
UseEncryption bool `json:"use_encryption"`
UseCompression bool `json:"use_compression"`
Role string `json:"role"`
Sk string `json:"sk"`
ServerName string `json:"server_name"`
BindAddr string `json:"bind_addr"`
BindPort int `json:"bind_port"`
}
func (cfg *BaseVisitorConf) GetBaseInfo() *BaseVisitorConf {
return cfg
}
func (cfg *BaseVisitorConf) compare(cmp *BaseVisitorConf) bool {
if cfg.ProxyName != cmp.ProxyName ||
cfg.ProxyType != cmp.ProxyType ||
cfg.UseEncryption != cmp.UseEncryption ||
cfg.UseCompression != cmp.UseCompression ||
cfg.Role != cmp.Role ||
cfg.Sk != cmp.Sk ||
cfg.ServerName != cmp.ServerName ||
cfg.BindAddr != cmp.BindAddr ||
cfg.BindPort != cmp.BindPort {
return false
}
return true
}
func (cfg *BaseVisitorConf) check() (err error) {
if cfg.Role != "visitor" {
err = fmt.Errorf("invalid role")
return
}
if cfg.BindAddr == "" {
err = fmt.Errorf("bind_addr shouldn't be empty")
return
}
if cfg.BindPort <= 0 {
err = fmt.Errorf("bind_port is required")
return
}
return
}
func (cfg *BaseVisitorConf) UnmarshalFromIni(prefix string, name string, section ini.Section) (err error) {
var (
tmpStr string
ok bool
)
cfg.ProxyName = prefix + name
cfg.ProxyType = section["type"]
if tmpStr, ok = section["use_encryption"]; ok && tmpStr == "true" {
cfg.UseEncryption = true
}
if tmpStr, ok = section["use_compression"]; ok && tmpStr == "true" {
cfg.UseCompression = true
}
cfg.Role = section["role"]
if cfg.Role != "visitor" {
return fmt.Errorf("Parse conf error: proxy [%s] incorrect role [%s]", name, cfg.Role)
}
cfg.Sk = section["sk"]
cfg.ServerName = prefix + section["server_name"]
if cfg.BindAddr = section["bind_addr"]; cfg.BindAddr == "" {
cfg.BindAddr = "127.0.0.1"
}
if tmpStr, ok = section["bind_port"]; ok {
if cfg.BindPort, err = strconv.Atoi(tmpStr); err != nil {
return fmt.Errorf("Parse conf error: proxy [%s] bind_port incorrect", name)
}
} else {
return fmt.Errorf("Parse conf error: proxy [%s] bind_port not found", name)
}
return nil
}
type StcpVisitorConf struct {
BaseVisitorConf
}
func (cfg *StcpVisitorConf) Compare(cmp VisitorConf) bool {
cmpConf, ok := cmp.(*StcpVisitorConf)
if !ok {
return false
}
if !cfg.BaseVisitorConf.compare(&cmpConf.BaseVisitorConf) {
return false
}
return true
}
func (cfg *StcpVisitorConf) UnmarshalFromIni(prefix string, name string, section ini.Section) (err error) {
if err = cfg.BaseVisitorConf.UnmarshalFromIni(prefix, name, section); err != nil {
return
}
return
}
func (cfg *StcpVisitorConf) Check() (err error) {
if err = cfg.BaseVisitorConf.check(); err != nil {
return
}
return
}
type XtcpVisitorConf struct {
BaseVisitorConf
}
func (cfg *XtcpVisitorConf) Compare(cmp VisitorConf) bool {
cmpConf, ok := cmp.(*XtcpVisitorConf)
if !ok {
return false
}
if !cfg.BaseVisitorConf.compare(&cmpConf.BaseVisitorConf) {
return false
}
return true
}
func (cfg *XtcpVisitorConf) UnmarshalFromIni(prefix string, name string, section ini.Section) (err error) {
if err = cfg.BaseVisitorConf.UnmarshalFromIni(prefix, name, section); err != nil {
return
}
return
}
func (cfg *XtcpVisitorConf) Check() (err error) {
if err = cfg.BaseVisitorConf.check(); err != nil {
return
}
return
}

View File

@@ -14,8 +14,11 @@
package errors package errors
import "errors" import (
"errors"
)
var ( 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"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with 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 // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
package pool package msg
import ( import (
"testing" "io"
"github.com/stretchr/testify/assert" jsonMsg "github.com/fatedier/golib/msg/json"
) )
func TestPutBuf(t *testing.T) { type Message = jsonMsg.Message
buf := make([]byte, 512)
PutBuf(buf)
buf = make([]byte, 1025) var (
PutBuf(buf) msgCtl *jsonMsg.MsgCtl
)
buf = make([]byte, 2*1025) func init() {
PutBuf(buf) msgCtl = jsonMsg.NewMsgCtl()
for typeByte, msg := range msgTypeMap {
buf = make([]byte, 5*1025) msgCtl.RegisterMsg(typeByte, msg)
PutBuf(buf) }
} }
func TestGetBuf(t *testing.T) { func ReadMsg(c io.Reader) (msg Message, err error) {
assert := assert.New(t) return msgCtl.ReadMsg(c)
}
buf := GetBuf(200)
assert.Len(buf, 200) func ReadMsgInto(c io.Reader, msg Message) (err error) {
return msgCtl.ReadMsgInto(c, msg)
buf = GetBuf(1025) }
assert.Len(buf, 1025)
func WriteMsg(c io.Writer, msg interface{}) (err error) {
buf = GetBuf(2 * 1024) return msgCtl.WriteMsg(c, msg)
assert.Len(buf, 2*1024)
buf = GetBuf(5 * 2000)
assert.Len(buf, 5*2000)
} }

View File

@@ -14,10 +14,7 @@
package msg package msg
import ( import "net"
"net"
"reflect"
)
const ( const (
TypeLogin = 'o' TypeLogin = 'o'
@@ -40,39 +37,26 @@ const (
) )
var ( var (
TypeMap map[byte]reflect.Type msgTypeMap = map[byte]interface{}{
TypeStringMap map[reflect.Type]byte TypeLogin: Login{},
) TypeLoginResp: LoginResp{},
TypeNewProxy: NewProxy{},
func init() { TypeNewProxyResp: NewProxyResp{},
TypeMap = make(map[byte]reflect.Type) TypeCloseProxy: CloseProxy{},
TypeStringMap = make(map[reflect.Type]byte) TypeNewWorkConn: NewWorkConn{},
TypeReqWorkConn: ReqWorkConn{},
TypeMap[TypeLogin] = reflect.TypeOf(Login{}) TypeStartWorkConn: StartWorkConn{},
TypeMap[TypeLoginResp] = reflect.TypeOf(LoginResp{}) TypeNewVisitorConn: NewVisitorConn{},
TypeMap[TypeNewProxy] = reflect.TypeOf(NewProxy{}) TypeNewVisitorConnResp: NewVisitorConnResp{},
TypeMap[TypeNewProxyResp] = reflect.TypeOf(NewProxyResp{}) TypePing: Ping{},
TypeMap[TypeCloseProxy] = reflect.TypeOf(CloseProxy{}) TypePong: Pong{},
TypeMap[TypeNewWorkConn] = reflect.TypeOf(NewWorkConn{}) TypeUdpPacket: UdpPacket{},
TypeMap[TypeReqWorkConn] = reflect.TypeOf(ReqWorkConn{}) TypeNatHoleVisitor: NatHoleVisitor{},
TypeMap[TypeStartWorkConn] = reflect.TypeOf(StartWorkConn{}) TypeNatHoleClient: NatHoleClient{},
TypeMap[TypeNewVisitorConn] = reflect.TypeOf(NewVisitorConn{}) TypeNatHoleResp: NatHoleResp{},
TypeMap[TypeNewVisitorConnResp] = reflect.TypeOf(NewVisitorConnResp{}) TypeNatHoleSid: NatHoleSid{},
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
} }
} )
// Message wraps socket packages for communicating between frpc and frps.
type Message interface{}
// When frpc start, client send this message to login to server. // When frpc start, client send this message to login to server.
type Login struct { type Login struct {
@@ -102,17 +86,20 @@ type NewProxy struct {
ProxyType string `json:"proxy_type"` ProxyType string `json:"proxy_type"`
UseEncryption bool `json:"use_encryption"` UseEncryption bool `json:"use_encryption"`
UseCompression bool `json:"use_compression"` UseCompression bool `json:"use_compression"`
Group string `json:"group"`
GroupKey string `json:"group_key"`
// tcp and udp only // tcp and udp only
RemotePort int `json:"remote_port"` RemotePort int `json:"remote_port"`
// http and https only // http and https only
CustomDomains []string `json:"custom_domains"` CustomDomains []string `json:"custom_domains"`
SubDomain string `json:"subdomain"` SubDomain string `json:"subdomain"`
Locations []string `json:"locations"` Locations []string `json:"locations"`
HostHeaderRewrite string `json:"host_header_rewrite"` HttpUser string `json:"http_user"`
HttpUser string `json:"http_user"` HttpPwd string `json:"http_pwd"`
HttpPwd string `json:"http_pwd"` HostHeaderRewrite string `json:"host_header_rewrite"`
Headers map[string]string `json:"headers"`
// stcp // stcp
Sk string `json:"sk"` Sk string `json:"sk"`
@@ -179,6 +166,7 @@ type NatHoleResp struct {
Sid string `json:"sid"` Sid string `json:"sid"`
VisitorAddr string `json:"visitor_addr"` VisitorAddr string `json:"visitor_addr"`
ClientAddr string `json:"client_addr"` ClientAddr string `json:"client_addr"`
Error string `json:"error"`
} }
type NatHoleSid struct { 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

@@ -22,8 +22,10 @@ import (
"net/http" "net/http"
"strings" "strings"
frpIo "github.com/fatedier/frp/utils/io"
frpNet "github.com/fatedier/frp/utils/net" frpNet "github.com/fatedier/frp/utils/net"
frpIo "github.com/fatedier/golib/io"
gnet "github.com/fatedier/golib/net"
) )
const PluginHttpProxy = "http_proxy" const PluginHttpProxy = "http_proxy"
@@ -65,15 +67,22 @@ func (hp *HttpProxy) Name() string {
func (hp *HttpProxy) Handle(conn io.ReadWriteCloser, realConn frpNet.Conn) { func (hp *HttpProxy) Handle(conn io.ReadWriteCloser, realConn frpNet.Conn) {
wrapConn := frpNet.WrapReadWriteCloserToConn(conn, realConn) wrapConn := frpNet.WrapReadWriteCloserToConn(conn, realConn)
sc, rd := frpNet.NewShareConn(wrapConn) sc, rd := gnet.NewSharedConn(wrapConn)
request, err := http.ReadRequest(bufio.NewReader(rd)) firstBytes := make([]byte, 7)
_, err := rd.Read(firstBytes)
if err != nil { if err != nil {
wrapConn.Close() wrapConn.Close()
return return
} }
if request.Method == http.MethodConnect { if strings.ToUpper(string(firstBytes)) == "CONNECT" {
hp.handleConnectReq(request, frpIo.WrapReadWriteCloser(rd, wrapConn, nil)) 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 return
} }

View File

@@ -20,8 +20,9 @@ import (
"net" "net"
"sync" "sync"
"github.com/fatedier/frp/utils/errors"
frpNet "github.com/fatedier/frp/utils/net" frpNet "github.com/fatedier/frp/utils/net"
"github.com/fatedier/golib/errors"
) )
// Creators is used for create plugins to handle connections. // Creators is used for create plugins to handle connections.

View File

@@ -18,9 +18,9 @@ import (
"io" "io"
"net/http" "net/http"
"github.com/julienschmidt/httprouter"
frpNet "github.com/fatedier/frp/utils/net" frpNet "github.com/fatedier/frp/utils/net"
"github.com/gorilla/mux"
) )
const PluginStaticFile = "static_file" const PluginStaticFile = "static_file"
@@ -61,9 +61,10 @@ func NewStaticFilePlugin(params map[string]string) (Plugin, error) {
} else { } else {
prefix = "/" prefix = "/"
} }
router := httprouter.New()
router.Handler("GET", prefix+"*filepath", frpNet.MakeHttpGzipHandler( router := mux.NewRouter()
frpNet.NewHttpBasicAuthWraper(http.StripPrefix(prefix, http.FileServer(http.Dir(localPath))), httpUser, httpPasswd))) 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{ sp.s = &http.Server{
Handler: router, Handler: router,
} }

View File

@@ -19,8 +19,9 @@ import (
"io" "io"
"net" "net"
frpIo "github.com/fatedier/frp/utils/io"
frpNet "github.com/fatedier/frp/utils/net" frpNet "github.com/fatedier/frp/utils/net"
frpIo "github.com/fatedier/golib/io"
) )
const PluginUnixDomainSocket = "unix_domain_socket" const PluginUnixDomainSocket = "unix_domain_socket"

View File

@@ -21,8 +21,9 @@ import (
"time" "time"
"github.com/fatedier/frp/models/msg" "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 { func NewUdpPacket(buf []byte, laddr, raddr *net.UDPAddr) *msg.UdpPacket {

View File

@@ -15,7 +15,7 @@ rm -rf ./packages
mkdir ./packages mkdir ./packages
os_all='linux windows darwin freebsd' os_all='linux windows darwin freebsd'
arch_all='386 amd64 arm mips64 mips64le mips mipsle' arch_all='386 amd64 arm arm64 mips64 mips64le mips mipsle'
for os in $os_all; do for os in $os_all; do
for arch in $arch_all; do for arch in $arch_all; do

View File

@@ -17,18 +17,21 @@ package server
import ( import (
"fmt" "fmt"
"io" "io"
"runtime/debug"
"sync" "sync"
"time" "time"
"github.com/fatedier/frp/g" "github.com/fatedier/frp/g"
"github.com/fatedier/frp/models/config" "github.com/fatedier/frp/models/config"
"github.com/fatedier/frp/models/consts" "github.com/fatedier/frp/models/consts"
frpErr "github.com/fatedier/frp/models/errors"
"github.com/fatedier/frp/models/msg" "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/net"
"github.com/fatedier/frp/utils/shutdown"
"github.com/fatedier/frp/utils/version" "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 { type Control struct {
@@ -123,6 +126,7 @@ func (ctl *Control) RegisterWorkConn(conn net.Conn) {
defer func() { defer func() {
if err := recover(); err != nil { if err := recover(); err != nil {
ctl.conn.Error("panic error: %v", err) ctl.conn.Error("panic error: %v", err)
ctl.conn.Error(string(debug.Stack()))
} }
}() }()
@@ -143,6 +147,7 @@ func (ctl *Control) GetWorkConn() (workConn net.Conn, err error) {
defer func() { defer func() {
if err := recover(); err != nil { if err := recover(); err != nil {
ctl.conn.Error("panic error: %v", err) ctl.conn.Error("panic error: %v", err)
ctl.conn.Error(string(debug.Stack()))
} }
}() }()
@@ -151,7 +156,7 @@ func (ctl *Control) GetWorkConn() (workConn net.Conn, err error) {
select { select {
case workConn, ok = <-ctl.workConnCh: case workConn, ok = <-ctl.workConnCh:
if !ok { if !ok {
err = errors.ErrCtlClosed err = frpErr.ErrCtlClosed
return return
} }
ctl.conn.Debug("get work connection from pool") ctl.conn.Debug("get work connection from pool")
@@ -168,7 +173,7 @@ func (ctl *Control) GetWorkConn() (workConn net.Conn, err error) {
select { select {
case workConn, ok = <-ctl.workConnCh: case workConn, ok = <-ctl.workConnCh:
if !ok { if !ok {
err = errors.ErrCtlClosed err = frpErr.ErrCtlClosed
ctl.conn.Warn("no work connections avaiable, %v", err) ctl.conn.Warn("no work connections avaiable, %v", err)
return return
} }
@@ -197,6 +202,7 @@ func (ctl *Control) writer() {
defer func() { defer func() {
if err := recover(); err != nil { if err := recover(); err != nil {
ctl.conn.Error("panic error: %v", err) ctl.conn.Error("panic error: %v", err)
ctl.conn.Error(string(debug.Stack()))
} }
}() }()
@@ -226,6 +232,7 @@ func (ctl *Control) reader() {
defer func() { defer func() {
if err := recover(); err != nil { if err := recover(); err != nil {
ctl.conn.Error("panic error: %v", err) ctl.conn.Error("panic error: %v", err)
ctl.conn.Error(string(debug.Stack()))
} }
}() }()
@@ -252,6 +259,7 @@ func (ctl *Control) stoper() {
defer func() { defer func() {
if err := recover(); err != nil { if err := recover(); err != nil {
ctl.conn.Error("panic error: %v", err) ctl.conn.Error("panic error: %v", err)
ctl.conn.Error(string(debug.Stack()))
} }
}() }()
@@ -290,6 +298,7 @@ func (ctl *Control) manager() {
defer func() { defer func() {
if err := recover(); err != nil { if err := recover(); err != nil {
ctl.conn.Error("panic error: %v", err) ctl.conn.Error("panic error: %v", err)
ctl.conn.Error(string(debug.Stack()))
} }
}() }()
@@ -304,7 +313,6 @@ func (ctl *Control) manager() {
case <-heartbeat.C: case <-heartbeat.C:
if time.Since(ctl.lastPing) > time.Duration(g.GlbServerCfg.HeartBeatTimeout)*time.Second { if time.Since(ctl.lastPing) > time.Duration(g.GlbServerCfg.HeartBeatTimeout)*time.Second {
ctl.conn.Warn("heartbeat timeout") ctl.conn.Warn("heartbeat timeout")
ctl.allShutdown.Start()
return return
} }
case rawMsg, ok := <-ctl.readCh: case rawMsg, ok := <-ctl.readCh:

View File

@@ -24,7 +24,7 @@ import (
"github.com/fatedier/frp/g" "github.com/fatedier/frp/g"
frpNet "github.com/fatedier/frp/utils/net" frpNet "github.com/fatedier/frp/utils/net"
"github.com/julienschmidt/httprouter" "github.com/gorilla/mux"
) )
var ( var (
@@ -34,30 +34,24 @@ var (
func RunDashboardServer(addr string, port int) (err error) { func RunDashboardServer(addr string, port int) (err error) {
// url router // url router
router := httprouter.New() router := mux.NewRouter()
user, passwd := g.GlbServerCfg.DashboardUser, g.GlbServerCfg.DashboardPwd user, passwd := g.GlbServerCfg.DashboardUser, g.GlbServerCfg.DashboardPwd
router.Use(frpNet.NewHttpAuthMiddleware(user, passwd).Middleware)
// api, see dashboard_api.go // api, see dashboard_api.go
router.GET("/api/serverinfo", frpNet.HttprouterBasicAuth(apiServerInfo, user, passwd)) router.HandleFunc("/api/serverinfo", apiServerInfo).Methods("GET")
router.GET("/api/proxy/tcp/:name", frpNet.HttprouterBasicAuth(apiProxyTcpByName, user, passwd)) router.HandleFunc("/api/proxy/{type}", apiProxyByType).Methods("GET")
router.GET("/api/proxy/udp/:name", frpNet.HttprouterBasicAuth(apiProxyUdpByName, user, passwd)) router.HandleFunc("/api/proxy/{type}/{name}", apiProxyByTypeAndName).Methods("GET")
router.GET("/api/proxy/http/:name", frpNet.HttprouterBasicAuth(apiProxyHttpByName, user, passwd)) router.HandleFunc("/api/traffic/{name}", apiProxyTraffic).Methods("GET")
router.GET("/api/proxy/https/:name", frpNet.HttprouterBasicAuth(apiProxyHttpsByName, 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))
// view // view
router.Handler("GET", "/favicon.ico", http.FileServer(assets.FileSystem)) router.Handle("/favicon.ico", http.FileServer(assets.FileSystem)).Methods("GET")
router.Handler("GET", "/static/*filepath", frpNet.MakeHttpGzipHandler( router.PathPrefix("/static/").Handler(frpNet.MakeHttpGzipHandler(http.StripPrefix("/static/", http.FileServer(assets.FileSystem)))).Methods("GET")
frpNet.NewHttpBasicAuthWraper(http.StripPrefix("/static/", http.FileServer(assets.FileSystem)), user, passwd)))
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) http.Redirect(w, r, "/static/", http.StatusMovedPermanently)
}, user, passwd)) })
address := fmt.Sprintf("%s:%d", addr, port) address := fmt.Sprintf("%s:%d", addr, port)
server := &http.Server{ server := &http.Server{

View File

@@ -24,7 +24,7 @@ import (
"github.com/fatedier/frp/utils/log" "github.com/fatedier/frp/utils/log"
"github.com/fatedier/frp/utils/version" "github.com/fatedier/frp/utils/version"
"github.com/julienschmidt/httprouter" "github.com/gorilla/mux"
) )
type GeneralResponse struct { type GeneralResponse struct {
@@ -36,13 +36,17 @@ type GeneralResponse struct {
type ServerInfoResp struct { type ServerInfoResp struct {
GeneralResponse GeneralResponse
Version string `json:"version"` Version string `json:"version"`
VhostHttpPort int `json:"vhost_http_port"` BindPort int `json:"bind_port"`
VhostHttpsPort int `json:"vhost_https_port"` BindUdpPort int `json:"bind_udp_port"`
AuthTimeout int64 `json:"auth_timeout"` VhostHttpPort int `json:"vhost_http_port"`
SubdomainHost string `json:"subdomain_host"` VhostHttpsPort int `json:"vhost_https_port"`
MaxPoolCount int64 `json:"max_pool_count"` KcpBindPort int `json:"kcp_bind_port"`
HeartBeatTimeout int64 `json:"heart_beat_timeout"` 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"` TotalTrafficIn int64 `json:"total_traffic_in"`
TotalTrafficOut int64 `json:"total_traffic_out"` TotalTrafficOut int64 `json:"total_traffic_out"`
@@ -51,26 +55,30 @@ type ServerInfoResp struct {
ProxyTypeCounts map[string]int64 `json:"proxy_type_count"` 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 ( var (
buf []byte buf []byte
res ServerInfoResp res ServerInfoResp
) )
defer func() { 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]") log.Info("Http request: [%s]", r.URL.Path)
cfg := &g.GlbServerCfg.ServerCommonConf cfg := &g.GlbServerCfg.ServerCommonConf
serverStats := StatsGetServer() serverStats := StatsGetServer()
res = ServerInfoResp{ res = ServerInfoResp{
Version: version.Full(), Version: version.Full(),
VhostHttpPort: cfg.VhostHttpPort, BindPort: cfg.BindPort,
VhostHttpsPort: cfg.VhostHttpsPort, BindUdpPort: cfg.BindUdpPort,
AuthTimeout: cfg.AuthTimeout, VhostHttpPort: cfg.VhostHttpPort,
SubdomainHost: cfg.SubDomainHost, VhostHttpsPort: cfg.VhostHttpsPort,
MaxPoolCount: cfg.MaxPoolCount, KcpBindPort: cfg.KcpBindPort,
HeartBeatTimeout: cfg.HeartBeatTimeout, AuthTimeout: cfg.AuthTimeout,
SubdomainHost: cfg.SubDomainHost,
MaxPoolCount: cfg.MaxPoolCount,
MaxPortsPerClient: cfg.MaxPortsPerClient,
HeartBeatTimeout: cfg.HeartBeatTimeout,
TotalTrafficIn: serverStats.TotalTrafficIn, TotalTrafficIn: serverStats.TotalTrafficIn,
TotalTrafficOut: serverStats.TotalTrafficOut, TotalTrafficOut: serverStats.TotalTrafficOut,
@@ -83,16 +91,69 @@ func apiServerInfo(w http.ResponseWriter, r *http.Request, _ httprouter.Params)
w.Write(buf) 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. // Get proxy info.
type ProxyStatsInfo struct { type ProxyStatsInfo struct {
Name string `json:"name"` Name string `json:"name"`
Conf config.ProxyConf `json:"conf"` Conf interface{} `json:"conf"`
TodayTrafficIn int64 `json:"today_traffic_in"` TodayTrafficIn int64 `json:"today_traffic_in"`
TodayTrafficOut int64 `json:"today_traffic_out"` TodayTrafficOut int64 `json:"today_traffic_out"`
CurConns int64 `json:"cur_conns"` CurConns int64 `json:"cur_conns"`
LastStartTime string `json:"last_start_time"` LastStartTime string `json:"last_start_time"`
LastCloseTime string `json:"last_close_time"` LastCloseTime string `json:"last_close_time"`
Status string `json:"status"` Status string `json:"status"`
} }
type GetProxyInfoResp struct { type GetProxyInfoResp struct {
@@ -100,72 +161,27 @@ type GetProxyInfoResp struct {
Proxies []*ProxyStatsInfo `json:"proxies"` Proxies []*ProxyStatsInfo `json:"proxies"`
} }
// api/proxy/tcp // api/proxy/:type
func apiProxyTcp(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { func apiProxyByType(w http.ResponseWriter, r *http.Request) {
var ( var (
buf []byte buf []byte
res GetProxyInfoResp res GetProxyInfoResp
) )
defer func() { params := mux.Vars(r)
log.Info("Http response [/api/proxy/tcp]: code [%d]", res.Code) proxyType := params["type"]
}()
log.Info("Http request: [/api/proxy/tcp]")
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) buf, _ = json.Marshal(&res)
w.Write(buf) 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) { func getProxyStatsByType(proxyType string) (proxyInfos []*ProxyStatsInfo) {
@@ -174,7 +190,16 @@ func getProxyStatsByType(proxyType string) (proxyInfos []*ProxyStatsInfo) {
for _, ps := range proxyStats { for _, ps := range proxyStats {
proxyInfo := &ProxyStatsInfo{} proxyInfo := &ProxyStatsInfo{}
if pxy, ok := ServerService.pxyManager.GetByName(ps.Name); ok { 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 proxyInfo.Status = consts.Online
} else { } else {
proxyInfo.Status = consts.Offline proxyInfo.Status = consts.Offline
@@ -194,87 +219,32 @@ func getProxyStatsByType(proxyType string) (proxyInfos []*ProxyStatsInfo) {
type GetProxyStatsResp struct { type GetProxyStatsResp struct {
GeneralResponse GeneralResponse
Name string `json:"name"` Name string `json:"name"`
Conf config.ProxyConf `json:"conf"` Conf interface{} `json:"conf"`
TodayTrafficIn int64 `json:"today_traffic_in"` TodayTrafficIn int64 `json:"today_traffic_in"`
TodayTrafficOut int64 `json:"today_traffic_out"` TodayTrafficOut int64 `json:"today_traffic_out"`
CurConns int64 `json:"cur_conns"` CurConns int64 `json:"cur_conns"`
LastStartTime string `json:"last_start_time"` LastStartTime string `json:"last_start_time"`
LastCloseTime string `json:"last_close_time"` LastCloseTime string `json:"last_close_time"`
Status string `json:"status"` Status string `json:"status"`
} }
// api/proxy/tcp/:name // api/proxy/:type/:name
func apiProxyTcpByName(w http.ResponseWriter, r *http.Request, params httprouter.Params) { func apiProxyByTypeAndName(w http.ResponseWriter, r *http.Request) {
var ( var (
buf []byte buf []byte
res GetProxyStatsResp res GetProxyStatsResp
) )
name := params.ByName("name") params := mux.Vars(r)
proxyType := params["type"]
name := params["name"]
defer func() { defer func() {
log.Info("Http response [/api/proxy/tcp/:name]: code [%d]", res.Code) log.Info("Http response [%s]: code [%d]", r.URL.Path, res.Code)
}() }()
log.Info("Http request: [/api/proxy/tcp/:name]") log.Info("Http request: [%s]", r.URL.Path)
res = getProxyStatsByTypeAndName(consts.TcpProxy, name) res = getProxyStatsByTypeAndName(proxyType, name)
buf, _ = json.Marshal(&res)
w.Write(buf)
}
// api/proxy/udp/:name
func apiProxyUdpByName(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
var (
buf []byte
res GetProxyStatsResp
)
name := params.ByName("name")
defer func() {
log.Info("Http response [/api/proxy/udp/:name]: code [%d]", res.Code)
}()
log.Info("Http request: [/api/proxy/udp/:name]")
res = getProxyStatsByTypeAndName(consts.UdpProxy, name)
buf, _ = json.Marshal(&res)
w.Write(buf)
}
// api/proxy/http/:name
func apiProxyHttpByName(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
var (
buf []byte
res GetProxyStatsResp
)
name := params.ByName("name")
defer func() {
log.Info("Http response [/api/proxy/http/:name]: code [%d]", res.Code)
}()
log.Info("Http request: [/api/proxy/http/:name]")
res = getProxyStatsByTypeAndName(consts.HttpProxy, name)
buf, _ = json.Marshal(&res)
w.Write(buf)
}
// api/proxy/https/:name
func apiProxyHttpsByName(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
var (
buf []byte
res GetProxyStatsResp
)
name := params.ByName("name")
defer func() {
log.Info("Http response [/api/proxy/https/:name]: code [%d]", res.Code)
}()
log.Info("Http request: [/api/proxy/https/:name]")
res = getProxyStatsByTypeAndName(consts.HttpsProxy, name)
buf, _ = json.Marshal(&res) buf, _ = json.Marshal(&res)
w.Write(buf) w.Write(buf)
@@ -288,7 +258,20 @@ func getProxyStatsByTypeAndName(proxyType string, proxyName string) (proxyInfo G
proxyInfo.Msg = "no proxy info found" proxyInfo.Msg = "no proxy info found"
} else { } else {
if pxy, ok := ServerService.pxyManager.GetByName(proxyName); ok { if pxy, ok := ServerService.pxyManager.GetByName(proxyName); 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)
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 proxyInfo.Status = consts.Online
} else { } else {
proxyInfo.Status = consts.Offline proxyInfo.Status = consts.Offline
@@ -303,7 +286,7 @@ func getProxyStatsByTypeAndName(proxyType string, proxyName string) (proxyInfo G
return return
} }
// api/proxy/traffic/:name // api/traffic/:name
type GetProxyTrafficResp struct { type GetProxyTrafficResp struct {
GeneralResponse GeneralResponse
@@ -312,17 +295,18 @@ type GetProxyTrafficResp struct {
TrafficOut []int64 `json:"traffic_out"` 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 ( var (
buf []byte buf []byte
res GetProxyTrafficResp res GetProxyTrafficResp
) )
name := params.ByName("name") params := mux.Vars(r)
name := params["name"]
defer func() { 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 res.Name = name
proxyTrafficInfo := StatsGetProxyTraffic(name) proxyTrafficInfo := StatsGetProxyTraffic(name)

View File

@@ -1,4 +1,4 @@
// Copyright © 2017 NAME HERE <EMAIL ADDRESS> // Copyright 2018 fatedier, fatedier@gmail.com
// //
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.
@@ -12,10 +12,15 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
package main package group
import "github.com/spf13/testproject/cmd" import (
"errors"
)
func main() { var (
cmd.Execute() ErrGroupAuthFailed = errors.New("group auth failed")
} ErrGroupParamsInvalid = errors.New("group params invalid")
ErrListenerClosed = errors.New("group listener closed")
ErrGroupDifferentPort = errors.New("group should have same remote port")
)

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

@@ -0,0 +1,204 @@
// 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 {
err = ErrGroupParamsInvalid
return
}
if tg.port != port {
err = ErrGroupDifferentPort
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" "io"
"sync" "sync"
frpIo "github.com/fatedier/frp/utils/io"
frpNet "github.com/fatedier/frp/utils/net" frpNet "github.com/fatedier/frp/utils/net"
"github.com/fatedier/frp/utils/util" "github.com/fatedier/frp/utils/util"
frpIo "github.com/fatedier/golib/io"
) )
type ControlManager struct { type ControlManager struct {

View File

@@ -8,10 +8,11 @@ import (
"time" "time"
"github.com/fatedier/frp/models/msg" "github.com/fatedier/frp/models/msg"
"github.com/fatedier/frp/utils/errors"
"github.com/fatedier/frp/utils/log" "github.com/fatedier/frp/utils/log"
"github.com/fatedier/frp/utils/pool"
"github.com/fatedier/frp/utils/util" "github.com/fatedier/frp/utils/util"
"github.com/fatedier/golib/errors"
"github.com/fatedier/golib/pool"
) )
// Timeout seconds. // Timeout seconds.
@@ -105,10 +106,21 @@ func (nc *NatHoleController) HandleVisitor(m *msg.NatHoleVisitor, raddr *net.UDP
} }
nc.mu.Lock() nc.mu.Lock()
clientCfg, ok := nc.clientCfgs[m.ProxyName] clientCfg, ok := nc.clientCfgs[m.ProxyName]
if !ok || m.SignKey != util.GetAuthKey(clientCfg.Sk, m.Timestamp) { if !ok {
nc.mu.Unlock() 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 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.sessions[sid] = session
nc.mu.Unlock() nc.mu.Unlock()
log.Trace("handle visitor message, sid [%s]", sid) 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. // Wait client connections.
select { select {
case <-session.NotifyCh: case <-session.NotifyCh:
resp := nc.GenNatHoleResponse(raddr, session) resp := nc.GenNatHoleResponse(session, "")
log.Trace("send nat hole response to visitor") log.Trace("send nat hole response to visitor")
nc.listener.WriteToUDP(resp, raddr) nc.listener.WriteToUDP(resp, raddr)
case <-time.After(time.Duration(NatHoleTimeout) * time.Second): 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.ClientAddr = raddr
session.NotifyCh <- struct{}{} session.NotifyCh <- struct{}{}
resp := nc.GenNatHoleResponse(raddr, session) resp := nc.GenNatHoleResponse(session, "")
log.Trace("send nat hole response to client") log.Trace("send nat hole response to client")
nc.listener.WriteToUDP(resp, raddr) 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{ m := &msg.NatHoleResp{
Sid: session.Sid, Sid: sid,
VisitorAddr: session.VisitorAddr.String(), VisitorAddr: visitorAddr,
ClientAddr: session.ClientAddr.String(), ClientAddr: clientAddr,
Error: errInfo,
} }
b := bytes.NewBuffer(nil) b := bytes.NewBuffer(nil)
err := msg.WriteMsg(b, m) err := msg.WriteMsg(b, m)

View File

@@ -1,4 +1,4 @@
package server package ports
import ( import (
"errors" "errors"

View File

@@ -27,12 +27,13 @@ import (
"github.com/fatedier/frp/models/config" "github.com/fatedier/frp/models/config"
"github.com/fatedier/frp/models/msg" "github.com/fatedier/frp/models/msg"
"github.com/fatedier/frp/models/proto/udp" "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" "github.com/fatedier/frp/utils/log"
frpNet "github.com/fatedier/frp/utils/net" frpNet "github.com/fatedier/frp/utils/net"
"github.com/fatedier/frp/utils/util" "github.com/fatedier/frp/utils/util"
"github.com/fatedier/frp/utils/vhost" "github.com/fatedier/frp/utils/vhost"
"github.com/fatedier/golib/errors"
frpIo "github.com/fatedier/golib/io"
) )
type Proxy interface { type Proxy interface {
@@ -180,27 +181,44 @@ type TcpProxy struct {
} }
func (pxy *TcpProxy) Run() (remoteAddr string, err error) { func (pxy *TcpProxy) Run() (remoteAddr string, err error) {
pxy.realPort, err = pxy.ctl.svr.tcpPortManager.Acquire(pxy.name, pxy.cfg.RemotePort) if pxy.cfg.Group != "" {
if err != nil { l, realPort, errRet := pxy.ctl.svr.tcpGroupCtl.Listen(pxy.name, pxy.cfg.Group, pxy.cfg.GroupKey, g.GlbServerCfg.ProxyBindAddr, pxy.cfg.RemotePort)
return if errRet != nil {
} err = errRet
defer func() { return
if err != nil {
pxy.ctl.svr.tcpPortManager.Release(pxy.realPort)
} }
}() defer func() {
if err != nil {
remoteAddr = fmt.Sprintf(":%d", pxy.realPort) l.Close()
pxy.cfg.RemotePort = pxy.realPort }
listener, errRet := frpNet.ListenTcp(g.GlbServerCfg.ProxyBindAddr, pxy.realPort) }()
if errRet != nil { pxy.realPort = realPort
err = errRet listener := frpNet.WrapLogListener(l)
return 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) pxy.startListenHandler(pxy, HandleUserTcpConnection)
return return
} }
@@ -211,7 +229,9 @@ func (pxy *TcpProxy) GetConf() config.ProxyConf {
func (pxy *TcpProxy) Close() { func (pxy *TcpProxy) Close() {
pxy.BaseProxy.Close() pxy.BaseProxy.Close()
pxy.ctl.svr.tcpPortManager.Release(pxy.realPort) if pxy.cfg.Group == "" {
pxy.ctl.svr.tcpPortManager.Release(pxy.realPort)
}
} }
type HttpProxy struct { type HttpProxy struct {
@@ -224,6 +244,7 @@ type HttpProxy struct {
func (pxy *HttpProxy) Run() (remoteAddr string, err error) { func (pxy *HttpProxy) Run() (remoteAddr string, err error) {
routeConfig := vhost.VhostRouteConfig{ routeConfig := vhost.VhostRouteConfig{
RewriteHost: pxy.cfg.HostHeaderRewrite, RewriteHost: pxy.cfg.HostHeaderRewrite,
Headers: pxy.cfg.Headers,
Username: pxy.cfg.HttpUser, Username: pxy.cfg.HttpUser,
Password: pxy.cfg.HttpPwd, Password: pxy.cfg.HttpPwd,
CreateConnFn: pxy.GetRealConn, CreateConnFn: pxy.GetRealConn,

View File

@@ -15,7 +15,9 @@
package server package server
import ( import (
"bytes"
"fmt" "fmt"
"io/ioutil"
"net" "net"
"net/http" "net/http"
"time" "time"
@@ -23,12 +25,15 @@ import (
"github.com/fatedier/frp/assets" "github.com/fatedier/frp/assets"
"github.com/fatedier/frp/g" "github.com/fatedier/frp/g"
"github.com/fatedier/frp/models/msg" "github.com/fatedier/frp/models/msg"
"github.com/fatedier/frp/server/group"
"github.com/fatedier/frp/server/ports"
"github.com/fatedier/frp/utils/log" "github.com/fatedier/frp/utils/log"
frpNet "github.com/fatedier/frp/utils/net" frpNet "github.com/fatedier/frp/utils/net"
"github.com/fatedier/frp/utils/util" "github.com/fatedier/frp/utils/util"
"github.com/fatedier/frp/utils/version" "github.com/fatedier/frp/utils/version"
"github.com/fatedier/frp/utils/vhost" "github.com/fatedier/frp/utils/vhost"
"github.com/fatedier/golib/net/mux"
fmux "github.com/hashicorp/yamux" fmux "github.com/hashicorp/yamux"
) )
@@ -38,35 +43,44 @@ const (
var ServerService *Service var ServerService *Service
// Server service. // Server service
type Service struct { 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 listener frpNet.Listener
// Accept connections using kcp. // Accept connections using kcp
kcpListener frpNet.Listener 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 VhostHttpsMuxer *vhost.HttpsMuxer
httpReverseProxy *vhost.HttpReverseProxy httpReverseProxy *vhost.HttpReverseProxy
// Manage all controllers. // Manage all controllers
ctlManager *ControlManager ctlManager *ControlManager
// Manage all proxies. // Manage all proxies
pxyManager *ProxyManager pxyManager *ProxyManager
// Manage all visitor listeners. // Manage all visitor listeners
visitorManager *VisitorManager visitorManager *VisitorManager
// Manage all tcp ports. // Manage all tcp ports
tcpPortManager *PortManager tcpPortManager *ports.PortManager
// Manage all udp ports. // Manage all udp ports
udpPortManager *PortManager udpPortManager *ports.PortManager
// Controller for nat hole connections. // Tcp Group Controller
tcpGroupCtl *group.TcpGroupCtl
// Controller for nat hole connections
natHoleController *NatHoleController natHoleController *NatHoleController
} }
@@ -76,9 +90,10 @@ func NewService() (svr *Service, err error) {
ctlManager: NewControlManager(), ctlManager: NewControlManager(),
pxyManager: NewProxyManager(), pxyManager: NewProxyManager(),
visitorManager: NewVisitorManager(), visitorManager: NewVisitorManager(),
tcpPortManager: NewPortManager("tcp", cfg.ProxyBindAddr, cfg.AllowPorts), tcpPortManager: ports.NewPortManager("tcp", cfg.ProxyBindAddr, cfg.AllowPorts),
udpPortManager: NewPortManager("udp", cfg.ProxyBindAddr, cfg.AllowPorts), udpPortManager: ports.NewPortManager("udp", cfg.ProxyBindAddr, cfg.AllowPorts),
} }
svr.tcpGroupCtl = group.NewTcpGroupCtl(svr.tcpPortManager)
// Init assets. // Init assets.
err = assets.Load(cfg.AssetsDir) err = assets.Load(cfg.AssetsDir)
@@ -87,12 +102,31 @@ func NewService() (svr *Service, err error) {
return 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. // 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 { if err != nil {
err = fmt.Errorf("Create server listener error, %v", err) err = fmt.Errorf("Create server listener error, %v", err)
return 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) log.Info("frps tcp listen on %s:%d", cfg.BindAddr, cfg.BindPort)
// Listen for accepting connections from client using kcp protocol. // Listen for accepting connections from client using kcp protocol.
@@ -105,9 +139,18 @@ func NewService() (svr *Service, err error) {
log.Info("frps kcp listen on udp %s:%d", cfg.BindAddr, cfg.KcpBindPort) 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. // Create http vhost muxer.
if cfg.VhostHttpPort > 0 { if cfg.VhostHttpPort > 0 {
rp := vhost.NewHttpReverseProxy() rp := vhost.NewHttpReverseProxy(vhost.HttpReverseProxyOptions{
ResponseHeaderTimeoutS: cfg.VhostHttpTimeout,
})
svr.httpReverseProxy = rp svr.httpReverseProxy = rp
address := fmt.Sprintf("%s:%d", cfg.ProxyBindAddr, cfg.VhostHttpPort) address := fmt.Sprintf("%s:%d", cfg.ProxyBindAddr, cfg.VhostHttpPort)
@@ -116,10 +159,14 @@ func NewService() (svr *Service, err error) {
Handler: rp, Handler: rp,
} }
var l net.Listener var l net.Listener
l, err = net.Listen("tcp", address) if httpMuxOn {
if err != nil { l = svr.muxer.ListenHttp(1)
err = fmt.Errorf("Create vhost http listener error, %v", err) } else {
return l, err = net.Listen("tcp", address)
if err != nil {
err = fmt.Errorf("Create vhost http listener error, %v", err)
return
}
} }
go server.Serve(l) go server.Serve(l)
log.Info("http service listen on %s:%d", cfg.ProxyBindAddr, cfg.VhostHttpPort) log.Info("http service listen on %s:%d", cfg.ProxyBindAddr, cfg.VhostHttpPort)
@@ -127,13 +174,18 @@ func NewService() (svr *Service, err error) {
// Create https vhost muxer. // Create https vhost muxer.
if cfg.VhostHttpsPort > 0 { if cfg.VhostHttpsPort > 0 {
var l frpNet.Listener var l net.Listener
l, err = frpNet.ListenTcp(cfg.ProxyBindAddr, cfg.VhostHttpsPort) if httpsMuxOn {
if err != nil { l = svr.muxer.ListenHttps(1)
err = fmt.Errorf("Create vhost https listener error, %v", err) } else {
return 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 { if err != nil {
err = fmt.Errorf("Create vhost httpsMuxer error, %v", err) err = fmt.Errorf("Create vhost httpsMuxer error, %v", err)
return return
@@ -163,6 +215,7 @@ func NewService() (svr *Service, err error) {
} }
log.Info("Dashboard listen on %s:%d", cfg.DashboardAddr, cfg.DashboardPort) log.Info("Dashboard listen on %s:%d", cfg.DashboardAddr, cfg.DashboardPort)
} }
return return
} }
@@ -173,8 +226,10 @@ func (svr *Service) Run() {
if g.GlbServerCfg.KcpBindPort > 0 { if g.GlbServerCfg.KcpBindPort > 0 {
go svr.HandleListener(svr.kcpListener) 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) { func (svr *Service) HandleListener(l frpNet.Listener) {
@@ -234,7 +289,9 @@ func (svr *Service) HandleListener(l frpNet.Listener) {
} }
if g.GlbServerCfg.TcpMux { if g.GlbServerCfg.TcpMux {
session, err := fmux.Server(frpConn, nil) fmuxCfg := fmux.DefaultConfig()
fmuxCfg.LogOutput = ioutil.Discard
session, err := fmux.Server(frpConn, fmuxCfg)
if err != nil { if err != nil {
log.Warn("Failed to create mux connection: %v", err) log.Warn("Failed to create mux connection: %v", err)
frpConn.Close() frpConn.Close()
@@ -244,7 +301,7 @@ func (svr *Service) HandleListener(l frpNet.Listener) {
for { for {
stream, err := session.AcceptStream() stream, err := session.AcceptStream()
if err != nil { if err != nil {
log.Warn("Accept new mux stream error: %v", err) log.Debug("Accept new mux stream error: %v", err)
session.Close() session.Close()
return return
} }

View File

@@ -1,7 +1,7 @@
[common] [common]
server_addr = 127.0.0.1 server_addr = 127.0.0.1
server_port = 10700 server_port = 10700
log_file = ./frpc.log log_file = console
# debug, info, warn, error # debug, info, warn, error
log_level = debug log_level = debug
token = 123456 token = 123456
@@ -23,6 +23,22 @@ remote_port = 10901
use_encryption = true use_encryption = true
use_compression = true use_compression = true
[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] [udp_normal]
type = udp type = udp
local_ip = 127.0.0.1 local_ip = 127.0.0.1
@@ -103,6 +119,14 @@ use_compression = true
http_user = test http_user = test
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] [subhost01]
type = http type = http
local_ip = 127.0.0.1 local_ip = 127.0.0.1

View File

@@ -1,7 +1,7 @@
[common] [common]
server_addr = 0.0.0.0 server_addr = 0.0.0.0
server_port = 10700 server_port = 10700
log_file = ./frpc_visitor.log log_file = console
# debug, info, warn, error # debug, info, warn, error
log_level = debug log_level = debug
token = 123456 token = 123456

View File

@@ -2,7 +2,7 @@
bind_addr = 0.0.0.0 bind_addr = 0.0.0.0
bind_port = 10700 bind_port = 10700
vhost_http_port = 10804 vhost_http_port = 10804
log_file = ./frps.log log_file = console
log_level = debug log_level = debug
token = 123456 token = 123456
allow_ports = 10000-20000,20002,30000-50000 allow_ports = 10000-20000,20002,30000-50000

85
tests/ci/cmd_test.go Normal file
View File

@@ -0,0 +1,85 @@
package ci
import (
"testing"
"time"
"github.com/fatedier/frp/tests/consts"
"github.com/fatedier/frp/tests/util"
"github.com/stretchr/testify/assert"
)
func TestCmdTcp(t *testing.T) {
assert := assert.New(t)
var err error
s := util.NewProcess(consts.FRPS_BIN_PATH, []string{"-t", "123", "-p", "20000"})
err = s.Start()
if assert.NoError(err) {
defer s.Stop()
}
time.Sleep(100 * time.Millisecond)
c := util.NewProcess(consts.FRPC_BIN_PATH, []string{"tcp", "-s", "127.0.0.1:20000", "-t", "123", "-u", "test",
"-l", "10701", "-r", "20801", "-n", "tcp_test"})
err = c.Start()
if assert.NoError(err) {
defer c.Stop()
}
time.Sleep(250 * time.Millisecond)
res, err := util.SendTcpMsg("127.0.0.1:20801", consts.TEST_TCP_ECHO_STR)
assert.NoError(err)
assert.Equal(consts.TEST_TCP_ECHO_STR, res)
}
func TestCmdUdp(t *testing.T) {
assert := assert.New(t)
var err error
s := util.NewProcess(consts.FRPS_BIN_PATH, []string{"-t", "123", "-p", "20000"})
err = s.Start()
if assert.NoError(err) {
defer s.Stop()
}
time.Sleep(100 * time.Millisecond)
c := util.NewProcess(consts.FRPC_BIN_PATH, []string{"udp", "-s", "127.0.0.1:20000", "-t", "123", "-u", "test",
"-l", "10702", "-r", "20802", "-n", "udp_test"})
err = c.Start()
if assert.NoError(err) {
defer c.Stop()
}
time.Sleep(250 * time.Millisecond)
res, err := util.SendUdpMsg("127.0.0.1:20802", consts.TEST_UDP_ECHO_STR)
assert.NoError(err)
assert.Equal(consts.TEST_UDP_ECHO_STR, res)
}
func TestCmdHttp(t *testing.T) {
assert := assert.New(t)
var err error
s := util.NewProcess(consts.FRPS_BIN_PATH, []string{"-t", "123", "-p", "20000", "--vhost_http_port", "20001"})
err = s.Start()
if assert.NoError(err) {
defer s.Stop()
}
time.Sleep(100 * time.Millisecond)
c := util.NewProcess(consts.FRPC_BIN_PATH, []string{"http", "-s", "127.0.0.1:20000", "-t", "123", "-u", "test",
"-n", "udp_test", "-l", "10704", "--custom_domain", "127.0.0.1"})
err = c.Start()
if assert.NoError(err) {
defer c.Stop()
}
time.Sleep(250 * time.Millisecond)
code, body, _, err := util.SendHttpMsg("GET", "http://127.0.0.1:20001", "", nil, "")
if assert.NoError(err) {
assert.Equal(200, code)
assert.Equal(consts.TEST_HTTP_NORMAL_STR, body)
}
}

View File

@@ -0,0 +1,247 @@
package health
import (
"net/http"
"os"
"strings"
"sync"
"testing"
"time"
"github.com/fatedier/frp/tests/config"
"github.com/fatedier/frp/tests/consts"
"github.com/fatedier/frp/tests/mock"
"github.com/fatedier/frp/tests/util"
"github.com/stretchr/testify/assert"
)
const FRPS_CONF = `
[common]
bind_addr = 0.0.0.0
bind_port = 14000
vhost_http_port = 14000
log_file = console
log_level = debug
token = 123456
`
const FRPC_CONF = `
[common]
server_addr = 127.0.0.1
server_port = 14000
log_file = console
log_level = debug
token = 123456
[tcp1]
type = tcp
local_port = 15001
remote_port = 15000
group = test
group_key = 123
health_check_type = tcp
health_check_interval_s = 1
[tcp2]
type = tcp
local_port = 15002
remote_port = 15000
group = test
group_key = 123
health_check_type = tcp
health_check_interval_s = 1
[http1]
type = http
local_port = 15003
custom_domains = test1.com
health_check_type = http
health_check_interval_s = 1
health_check_url = /health
[http2]
type = http
local_port = 15004
custom_domains = test2.com
health_check_type = http
health_check_interval_s = 1
health_check_url = /health
`
func TestHealthCheck(t *testing.T) {
assert := assert.New(t)
// ****** start backgroud services ******
echoSvc1 := mock.NewEchoServer(15001, 1, "echo1")
err := echoSvc1.Start()
if assert.NoError(err) {
defer echoSvc1.Stop()
}
echoSvc2 := mock.NewEchoServer(15002, 1, "echo2")
err = echoSvc2.Start()
if assert.NoError(err) {
defer echoSvc2.Stop()
}
var healthMu sync.RWMutex
svc1Health := true
svc2Health := true
httpSvc1 := mock.NewHttpServer(15003, func(w http.ResponseWriter, r *http.Request) {
if strings.Contains(r.URL.Path, "health") {
healthMu.RLock()
defer healthMu.RUnlock()
if svc1Health {
w.WriteHeader(200)
} else {
w.WriteHeader(500)
}
} else {
w.Write([]byte("http1"))
}
})
err = httpSvc1.Start()
if assert.NoError(err) {
defer httpSvc1.Stop()
}
httpSvc2 := mock.NewHttpServer(15004, func(w http.ResponseWriter, r *http.Request) {
if strings.Contains(r.URL.Path, "health") {
healthMu.RLock()
defer healthMu.RUnlock()
if svc2Health {
w.WriteHeader(200)
} else {
w.WriteHeader(500)
}
} else {
w.Write([]byte("http2"))
}
})
err = httpSvc2.Start()
if assert.NoError(err) {
defer httpSvc2.Stop()
}
time.Sleep(200 * time.Millisecond)
// ****** start frps and frpc ******
frpsCfgPath, err := config.GenerateConfigFile(consts.FRPS_NORMAL_CONFIG, FRPS_CONF)
if assert.NoError(err) {
defer os.Remove(frpsCfgPath)
}
frpcCfgPath, err := config.GenerateConfigFile(consts.FRPC_NORMAL_CONFIG, FRPC_CONF)
if assert.NoError(err) {
defer os.Remove(frpcCfgPath)
}
frpsProcess := util.NewProcess(consts.FRPS_SUB_BIN_PATH, []string{"-c", frpsCfgPath})
err = frpsProcess.Start()
if assert.NoError(err) {
defer frpsProcess.Stop()
}
time.Sleep(100 * time.Millisecond)
frpcProcess := util.NewProcess(consts.FRPC_SUB_BIN_PATH, []string{"-c", frpcCfgPath})
err = frpcProcess.Start()
if assert.NoError(err) {
defer frpcProcess.Stop()
}
time.Sleep(1000 * time.Millisecond)
// ****** healcheck type tcp ******
// echo1 and echo2 is ok
result := make([]string, 0)
res, err := util.SendTcpMsg("127.0.0.1:15000", "echo")
assert.NoError(err)
result = append(result, res)
res, err = util.SendTcpMsg("127.0.0.1:15000", "echo")
assert.NoError(err)
result = append(result, res)
assert.Contains(result, "echo1")
assert.Contains(result, "echo2")
// close echo2 server, echo1 is work
echoSvc2.Stop()
time.Sleep(1200 * time.Millisecond)
result = make([]string, 0)
res, err = util.SendTcpMsg("127.0.0.1:15000", "echo")
assert.NoError(err)
result = append(result, res)
res, err = util.SendTcpMsg("127.0.0.1:15000", "echo")
assert.NoError(err)
result = append(result, res)
assert.NotContains(result, "echo2")
// resume echo2 server, all services are ok
echoSvc2 = mock.NewEchoServer(15002, 1, "echo2")
err = echoSvc2.Start()
if assert.NoError(err) {
defer echoSvc2.Stop()
}
time.Sleep(1200 * time.Millisecond)
result = make([]string, 0)
res, err = util.SendTcpMsg("127.0.0.1:15000", "echo")
assert.NoError(err)
result = append(result, res)
res, err = util.SendTcpMsg("127.0.0.1:15000", "echo")
assert.NoError(err)
result = append(result, res)
assert.Contains(result, "echo1")
assert.Contains(result, "echo2")
// ****** healcheck type http ******
// http1 and http2 is ok
code, body, _, err := util.SendHttpMsg("GET", "http://127.0.0.1:14000/xxx", "test1.com", nil, "")
assert.NoError(err)
assert.Equal(200, code)
assert.Equal("http1", body)
code, body, _, err = util.SendHttpMsg("GET", "http://127.0.0.1:14000/xxx", "test2.com", nil, "")
assert.NoError(err)
assert.Equal(200, code)
assert.Equal("http2", body)
// http2 health check error
healthMu.Lock()
svc2Health = false
healthMu.Unlock()
time.Sleep(1200 * time.Millisecond)
code, body, _, err = util.SendHttpMsg("GET", "http://127.0.0.1:14000/xxx", "test1.com", nil, "")
assert.NoError(err)
assert.Equal(200, code)
assert.Equal("http1", body)
code, _, _, err = util.SendHttpMsg("GET", "http://127.0.0.1:14000/xxx", "test2.com", nil, "")
assert.NoError(err)
assert.Equal(404, code)
// resume http2 service, http1 and http2 are ok
healthMu.Lock()
svc2Health = true
healthMu.Unlock()
time.Sleep(1200 * time.Millisecond)
code, body, _, err = util.SendHttpMsg("GET", "http://127.0.0.1:14000/xxx", "test1.com", nil, "")
assert.NoError(err)
assert.Equal(200, code)
assert.Equal("http1", body)
code, body, _, err = util.SendHttpMsg("GET", "http://127.0.0.1:14000/xxx", "test2.com", nil, "")
assert.NoError(err)
assert.Equal(200, code)
assert.Equal("http2", body)
}

327
tests/ci/normal_test.go Normal file
View File

@@ -0,0 +1,327 @@
package ci
import (
"fmt"
"net/http"
"net/url"
"os"
"strings"
"testing"
"time"
"github.com/gorilla/websocket"
"github.com/stretchr/testify/assert"
"github.com/fatedier/frp/client/proxy"
"github.com/fatedier/frp/server/ports"
"github.com/fatedier/frp/tests/consts"
"github.com/fatedier/frp/tests/mock"
"github.com/fatedier/frp/tests/util"
gnet "github.com/fatedier/golib/net"
)
func TestMain(m *testing.M) {
var err error
tcpEcho1 := mock.NewEchoServer(consts.TEST_TCP_PORT, 1, "")
tcpEcho2 := mock.NewEchoServer(consts.TEST_TCP2_PORT, 2, "")
if err = tcpEcho1.Start(); err != nil {
panic(err)
}
if err = tcpEcho2.Start(); err != nil {
panic(err)
}
go mock.StartUdpEchoServer(consts.TEST_UDP_PORT)
go mock.StartUnixDomainServer(consts.TEST_UNIX_DOMAIN_ADDR)
go mock.StartHttpServer(consts.TEST_HTTP_PORT)
p1 := util.NewProcess(consts.FRPS_BIN_PATH, []string{"-c", "./auto_test_frps.ini"})
if err = p1.Start(); err != nil {
panic(err)
}
time.Sleep(200 * time.Millisecond)
p2 := util.NewProcess(consts.FRPC_BIN_PATH, []string{"-c", "./auto_test_frpc.ini"})
if err = p2.Start(); err != nil {
panic(err)
}
p3 := util.NewProcess(consts.FRPC_BIN_PATH, []string{"-c", "./auto_test_frpc_visitor.ini"})
if err = p3.Start(); err != nil {
panic(err)
}
time.Sleep(500 * time.Millisecond)
exitCode := m.Run()
p1.Stop()
p2.Stop()
p3.Stop()
os.Exit(exitCode)
}
func TestTcp(t *testing.T) {
assert := assert.New(t)
// Normal
addr := fmt.Sprintf("127.0.0.1:%d", consts.TEST_TCP_FRP_PORT)
res, err := util.SendTcpMsg(addr, consts.TEST_TCP_ECHO_STR)
assert.NoError(err)
assert.Equal(consts.TEST_TCP_ECHO_STR, res)
// Encrytion and compression
addr = fmt.Sprintf("127.0.0.1:%d", consts.TEST_TCP_EC_FRP_PORT)
res, err = util.SendTcpMsg(addr, consts.TEST_TCP_ECHO_STR)
assert.NoError(err)
assert.Equal(consts.TEST_TCP_ECHO_STR, res)
}
func TestUdp(t *testing.T) {
assert := assert.New(t)
// Normal
addr := fmt.Sprintf("127.0.0.1:%d", consts.TEST_UDP_FRP_PORT)
res, err := util.SendUdpMsg(addr, consts.TEST_UDP_ECHO_STR)
assert.NoError(err)
assert.Equal(consts.TEST_UDP_ECHO_STR, res)
// Encrytion and compression
addr = fmt.Sprintf("127.0.0.1:%d", consts.TEST_UDP_EC_FRP_PORT)
res, err = util.SendUdpMsg(addr, consts.TEST_UDP_ECHO_STR)
assert.NoError(err)
assert.Equal(consts.TEST_UDP_ECHO_STR, res)
}
func TestUnixDomain(t *testing.T) {
assert := assert.New(t)
// Normal
addr := fmt.Sprintf("127.0.0.1:%d", consts.TEST_UNIX_DOMAIN_FRP_PORT)
res, err := util.SendTcpMsg(addr, consts.TEST_UNIX_DOMAIN_STR)
if assert.NoError(err) {
assert.Equal(consts.TEST_UNIX_DOMAIN_STR, res)
}
}
func TestStcp(t *testing.T) {
assert := assert.New(t)
// Normal
addr := fmt.Sprintf("127.0.0.1:%d", consts.TEST_STCP_FRP_PORT)
res, err := util.SendTcpMsg(addr, consts.TEST_STCP_ECHO_STR)
if assert.NoError(err) {
assert.Equal(consts.TEST_STCP_ECHO_STR, res)
}
// Encrytion and compression
addr = fmt.Sprintf("127.0.0.1:%d", consts.TEST_STCP_EC_FRP_PORT)
res, err = util.SendTcpMsg(addr, consts.TEST_STCP_ECHO_STR)
if assert.NoError(err) {
assert.Equal(consts.TEST_STCP_ECHO_STR, res)
}
}
func TestHttp(t *testing.T) {
assert := assert.New(t)
// web01
code, body, _, err := util.SendHttpMsg("GET", fmt.Sprintf("http://127.0.0.1:%d", consts.TEST_HTTP_FRP_PORT), "", nil, "")
if assert.NoError(err) {
assert.Equal(200, code)
assert.Equal(consts.TEST_HTTP_NORMAL_STR, body)
}
// web02
code, body, _, err = util.SendHttpMsg("GET", fmt.Sprintf("http://127.0.0.1:%d", consts.TEST_HTTP_FRP_PORT), "test2.frp.com", nil, "")
if assert.NoError(err) {
assert.Equal(200, code)
assert.Equal(consts.TEST_HTTP_NORMAL_STR, body)
}
// error host header
code, body, _, err = util.SendHttpMsg("GET", fmt.Sprintf("http://127.0.0.1:%d", consts.TEST_HTTP_FRP_PORT), "errorhost.frp.com", nil, "")
if assert.NoError(err) {
assert.Equal(404, code)
}
// web03
code, body, _, err = util.SendHttpMsg("GET", fmt.Sprintf("http://127.0.0.1:%d", consts.TEST_HTTP_FRP_PORT), "test3.frp.com", nil, "")
if assert.NoError(err) {
assert.Equal(200, code)
assert.Equal(consts.TEST_HTTP_NORMAL_STR, body)
}
code, body, _, err = util.SendHttpMsg("GET", fmt.Sprintf("http://127.0.0.1:%d/foo", consts.TEST_HTTP_FRP_PORT), "test3.frp.com", nil, "")
if assert.NoError(err) {
assert.Equal(200, code)
assert.Equal(consts.TEST_HTTP_FOO_STR, body)
}
// web04
code, body, _, err = util.SendHttpMsg("GET", fmt.Sprintf("http://127.0.0.1:%d/bar", consts.TEST_HTTP_FRP_PORT), "test3.frp.com", nil, "")
if assert.NoError(err) {
assert.Equal(200, code)
assert.Equal(consts.TEST_HTTP_BAR_STR, body)
}
// web05
code, body, _, err = util.SendHttpMsg("GET", fmt.Sprintf("http://127.0.0.1:%d", consts.TEST_HTTP_FRP_PORT), "test5.frp.com", nil, "")
if assert.NoError(err) {
assert.Equal(401, code)
}
headers := make(map[string]string)
headers["Authorization"] = util.BasicAuth("test", "test")
code, body, _, err = util.SendHttpMsg("GET", fmt.Sprintf("http://127.0.0.1:%d", consts.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 = util.SendHttpMsg("GET", fmt.Sprintf("http://127.0.0.1:%d", consts.TEST_HTTP_FRP_PORT), "test6.frp.com", nil, "")
if assert.NoError(err) {
assert.Equal(200, code)
assert.Equal(consts.TEST_HTTP_NORMAL_STR, body)
assert.Equal("true", header.Get("X-Header-Set"))
}
// subhost01
code, body, _, err = util.SendHttpMsg("GET", fmt.Sprintf("http://127.0.0.1:%d", consts.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 = util.SendHttpMsg("GET", fmt.Sprintf("http://127.0.0.1:%d", consts.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", consts.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(consts.TEST_HTTP_NORMAL_STR))
assert.NoError(err)
_, msg, err := c.ReadMessage()
assert.NoError(err)
assert.Equal(consts.TEST_HTTP_NORMAL_STR, string(msg))
}
func TestAllowPorts(t *testing.T) {
assert := assert.New(t)
// Port not allowed
status, err := util.GetProxyStatus(consts.ADMIN_ADDR, consts.ADMIN_USER, consts.ADMIN_PWD, consts.ProxyTcpPortNotAllowed)
if assert.NoError(err) {
assert.Equal(proxy.ProxyStatusStartErr, status.Status)
assert.True(strings.Contains(status.Err, ports.ErrPortNotAllowed.Error()))
}
status, err = util.GetProxyStatus(consts.ADMIN_ADDR, consts.ADMIN_USER, consts.ADMIN_PWD, consts.ProxyUdpPortNotAllowed)
if assert.NoError(err) {
assert.Equal(proxy.ProxyStatusStartErr, status.Status)
assert.True(strings.Contains(status.Err, ports.ErrPortNotAllowed.Error()))
}
status, err = util.GetProxyStatus(consts.ADMIN_ADDR, consts.ADMIN_USER, consts.ADMIN_PWD, consts.ProxyTcpPortUnavailable)
if assert.NoError(err) {
assert.Equal(proxy.ProxyStatusStartErr, status.Status)
assert.True(strings.Contains(status.Err, ports.ErrPortUnAvailable.Error()))
}
// Port normal
status, err = util.GetProxyStatus(consts.ADMIN_ADDR, consts.ADMIN_USER, consts.ADMIN_PWD, consts.ProxyTcpPortNormal)
if assert.NoError(err) {
assert.Equal(proxy.ProxyStatusRunning, status.Status)
}
status, err = util.GetProxyStatus(consts.ADMIN_ADDR, consts.ADMIN_USER, consts.ADMIN_PWD, consts.ProxyUdpPortNormal)
if assert.NoError(err) {
assert.Equal(proxy.ProxyStatusRunning, status.Status)
}
}
func TestRandomPort(t *testing.T) {
assert := assert.New(t)
// tcp
status, err := util.GetProxyStatus(consts.ADMIN_ADDR, consts.ADMIN_USER, consts.ADMIN_PWD, consts.ProxyTcpRandomPort)
if assert.NoError(err) {
addr := status.RemoteAddr
res, err := util.SendTcpMsg(addr, consts.TEST_TCP_ECHO_STR)
assert.NoError(err)
assert.Equal(consts.TEST_TCP_ECHO_STR, res)
}
// udp
status, err = util.GetProxyStatus(consts.ADMIN_ADDR, consts.ADMIN_USER, consts.ADMIN_PWD, consts.ProxyUdpRandomPort)
if assert.NoError(err) {
addr := status.RemoteAddr
res, err := util.SendUdpMsg(addr, consts.TEST_UDP_ECHO_STR)
assert.NoError(err)
assert.Equal(consts.TEST_UDP_ECHO_STR, res)
}
}
func TestPluginHttpProxy(t *testing.T) {
assert := assert.New(t)
status, err := util.GetProxyStatus(consts.ADMIN_ADDR, consts.ADMIN_USER, consts.ADMIN_PWD, consts.ProxyHttpProxy)
if assert.NoError(err) {
assert.Equal(proxy.ProxyStatusRunning, status.Status)
// http proxy
addr := status.RemoteAddr
code, body, _, err := util.SendHttpMsg("GET", fmt.Sprintf("http://127.0.0.1:%d", consts.TEST_HTTP_FRP_PORT),
"", nil, "http://"+addr)
if assert.NoError(err) {
assert.Equal(200, code)
assert.Equal(consts.TEST_HTTP_NORMAL_STR, body)
}
// connect method
conn, err := gnet.DialTcpByProxy("http://"+addr, fmt.Sprintf("127.0.0.1:%d", consts.TEST_TCP_FRP_PORT))
if assert.NoError(err) {
res, err := util.SendTcpMsgByConn(conn, consts.TEST_TCP_ECHO_STR)
assert.NoError(err)
assert.Equal(consts.TEST_TCP_ECHO_STR, res)
}
}
}
func TestRangePortsMapping(t *testing.T) {
assert := assert.New(t)
for i := 0; i < 3; i++ {
name := fmt.Sprintf("%s_%d", consts.ProxyRangeTcpPrefix, i)
status, err := util.GetProxyStatus(consts.ADMIN_ADDR, consts.ADMIN_USER, consts.ADMIN_PWD, name)
if assert.NoError(err) {
assert.Equal(proxy.ProxyStatusRunning, status.Status)
}
}
}
func TestGroup(t *testing.T) {
assert := assert.New(t)
var (
p1 int
p2 int
)
addr := fmt.Sprintf("127.0.0.1:%d", consts.TEST_TCP2_FRP_PORT)
for i := 0; i < 6; i++ {
res, err := util.SendTcpMsg(addr, consts.TEST_TCP_ECHO_STR)
assert.NoError(err)
switch res {
case consts.TEST_TCP_ECHO_STR:
p1++
case consts.TEST_TCP_ECHO_STR + consts.TEST_TCP_ECHO_STR:
p2++
}
}
assert.True(p1 > 0 && p2 > 0, "group proxies load balancing")
}

115
tests/ci/reconnect_test.go Normal file
View File

@@ -0,0 +1,115 @@
package ci
import (
"os"
"testing"
"time"
"github.com/fatedier/frp/tests/config"
"github.com/fatedier/frp/tests/consts"
"github.com/fatedier/frp/tests/util"
"github.com/stretchr/testify/assert"
)
const FRPS_RECONNECT_CONF = `
[common]
bind_addr = 0.0.0.0
bind_port = 20000
log_file = console
log_level = debug
token = 123456
`
const FRPC_RECONNECT_CONF = `
[common]
server_addr = 127.0.0.1
server_port = 20000
log_file = console
log_level = debug
token = 123456
admin_port = 21000
admin_user = abc
admin_pwd = abc
[tcp]
type = tcp
local_port = 10701
remote_port = 20801
`
func TestReconnect(t *testing.T) {
assert := assert.New(t)
frpsCfgPath, err := config.GenerateConfigFile(consts.FRPS_NORMAL_CONFIG, FRPS_RECONNECT_CONF)
if assert.NoError(err) {
defer os.Remove(frpsCfgPath)
}
frpcCfgPath, err := config.GenerateConfigFile(consts.FRPC_NORMAL_CONFIG, FRPC_RECONNECT_CONF)
if assert.NoError(err) {
defer os.Remove(frpcCfgPath)
}
frpsProcess := util.NewProcess(consts.FRPS_BIN_PATH, []string{"-c", frpsCfgPath})
err = frpsProcess.Start()
if assert.NoError(err) {
defer frpsProcess.Stop()
}
time.Sleep(100 * time.Millisecond)
frpcProcess := util.NewProcess(consts.FRPC_BIN_PATH, []string{"-c", frpcCfgPath})
err = frpcProcess.Start()
if assert.NoError(err) {
defer frpcProcess.Stop()
}
time.Sleep(250 * time.Millisecond)
// test tcp
res, err := util.SendTcpMsg("127.0.0.1:20801", consts.TEST_TCP_ECHO_STR)
assert.NoError(err)
assert.Equal(consts.TEST_TCP_ECHO_STR, res)
// stop frpc
frpcProcess.Stop()
time.Sleep(100 * time.Millisecond)
// test tcp, expect failed
_, err = util.SendTcpMsg("127.0.0.1:20801", consts.TEST_TCP_ECHO_STR)
assert.Error(err)
// restart frpc
newFrpcProcess := util.NewProcess(consts.FRPC_BIN_PATH, []string{"-c", frpcCfgPath})
err = newFrpcProcess.Start()
if assert.NoError(err) {
defer newFrpcProcess.Stop()
}
time.Sleep(250 * time.Millisecond)
// test tcp
res, err = util.SendTcpMsg("127.0.0.1:20801", consts.TEST_TCP_ECHO_STR)
assert.NoError(err)
assert.Equal(consts.TEST_TCP_ECHO_STR, res)
// stop frps
frpsProcess.Stop()
time.Sleep(100 * time.Millisecond)
// test tcp, expect failed
_, err = util.SendTcpMsg("127.0.0.1:20801", consts.TEST_TCP_ECHO_STR)
assert.Error(err)
// restart frps
newFrpsProcess := util.NewProcess(consts.FRPS_BIN_PATH, []string{"-c", frpsCfgPath})
err = newFrpsProcess.Start()
if assert.NoError(err) {
defer newFrpsProcess.Stop()
}
time.Sleep(2 * time.Second)
// test tcp
res, err = util.SendTcpMsg("127.0.0.1:20801", consts.TEST_TCP_ECHO_STR)
assert.NoError(err)
assert.Equal(consts.TEST_TCP_ECHO_STR, res)
}

150
tests/ci/reload_test.go Normal file
View File

@@ -0,0 +1,150 @@
package ci
import (
"os"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/fatedier/frp/tests/config"
"github.com/fatedier/frp/tests/consts"
"github.com/fatedier/frp/tests/util"
)
const FRPS_RELOAD_CONF = `
[common]
bind_addr = 0.0.0.0
bind_port = 20000
log_file = console
# debug, info, warn, error
log_level = debug
token = 123456
`
const FRPC_RELOAD_CONF_1 = `
[common]
server_addr = 127.0.0.1
server_port = 20000
log_file = console
# debug, info, warn, error
log_level = debug
token = 123456
admin_port = 21000
admin_user = abc
admin_pwd = abc
[tcp]
type = tcp
local_port = 10701
remote_port = 20801
# change remote port
[tcp2]
type = tcp
local_port = 10701
remote_port = 20802
# delete
[tcp3]
type = tcp
local_port = 10701
remote_port = 20803
`
const FRPC_RELOAD_CONF_2 = `
[common]
server_addr = 127.0.0.1
server_port = 20000
log_file = console
# debug, info, warn, error
log_level = debug
token = 123456
admin_port = 21000
admin_user = abc
admin_pwd = abc
[tcp]
type = tcp
local_port = 10701
remote_port = 20801
[tcp2]
type = tcp
local_port = 10701
remote_port = 20902
`
func TestReload(t *testing.T) {
assert := assert.New(t)
frpsCfgPath, err := config.GenerateConfigFile(consts.FRPS_NORMAL_CONFIG, FRPS_RELOAD_CONF)
if assert.NoError(err) {
defer os.Remove(frpsCfgPath)
}
frpcCfgPath, err := config.GenerateConfigFile(consts.FRPC_NORMAL_CONFIG, FRPC_RELOAD_CONF_1)
if assert.NoError(err) {
rmFile1 := frpcCfgPath
defer os.Remove(rmFile1)
}
frpsProcess := util.NewProcess(consts.FRPS_BIN_PATH, []string{"-c", frpsCfgPath})
err = frpsProcess.Start()
if assert.NoError(err) {
defer frpsProcess.Stop()
}
time.Sleep(100 * time.Millisecond)
frpcProcess := util.NewProcess(consts.FRPC_BIN_PATH, []string{"-c", frpcCfgPath})
err = frpcProcess.Start()
if assert.NoError(err) {
defer frpcProcess.Stop()
}
time.Sleep(250 * time.Millisecond)
// test tcp1
res, err := util.SendTcpMsg("127.0.0.1:20801", consts.TEST_TCP_ECHO_STR)
assert.NoError(err)
assert.Equal(consts.TEST_TCP_ECHO_STR, res)
// test tcp2
res, err = util.SendTcpMsg("127.0.0.1:20802", consts.TEST_TCP_ECHO_STR)
assert.NoError(err)
assert.Equal(consts.TEST_TCP_ECHO_STR, res)
// test tcp3
res, err = util.SendTcpMsg("127.0.0.1:20803", consts.TEST_TCP_ECHO_STR)
assert.NoError(err)
assert.Equal(consts.TEST_TCP_ECHO_STR, res)
// reload frpc config
frpcCfgPath, err = config.GenerateConfigFile(consts.FRPC_NORMAL_CONFIG, FRPC_RELOAD_CONF_2)
if assert.NoError(err) {
rmFile2 := frpcCfgPath
defer os.Remove(rmFile2)
}
err = util.ReloadConf("127.0.0.1:21000", "abc", "abc")
assert.NoError(err)
time.Sleep(time.Second)
// test tcp1
res, err = util.SendTcpMsg("127.0.0.1:20801", consts.TEST_TCP_ECHO_STR)
assert.NoError(err)
assert.Equal(consts.TEST_TCP_ECHO_STR, res)
// test origin tcp2, expect failed
res, err = util.SendTcpMsg("127.0.0.1:20802", consts.TEST_TCP_ECHO_STR)
assert.Error(err)
// test new origin tcp2 with different port
res, err = util.SendTcpMsg("127.0.0.1:20902", consts.TEST_TCP_ECHO_STR)
assert.NoError(err)
assert.Equal(consts.TEST_TCP_ECHO_STR, res)
// test tcp3, expect failed
res, err = util.SendTcpMsg("127.0.0.1:20803", consts.TEST_TCP_ECHO_STR)
assert.Error(err)
}

View File

@@ -1,20 +0,0 @@
#!/bin/bash
pid=`ps aux|grep './../bin/frps -c ./conf/auto_test_frps.ini'|grep -v grep|awk {'print $2'}`
if [ -n "${pid}" ]; then
kill ${pid}
fi
pid=`ps aux|grep './../bin/frpc -c ./conf/auto_test_frpc.ini'|grep -v grep|awk {'print $2'}`
if [ -n "${pid}" ]; then
kill ${pid}
fi
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

13
tests/config/config.go Normal file
View File

@@ -0,0 +1,13 @@
package config
import (
"io/ioutil"
"os"
"path/filepath"
)
func GenerateConfigFile(path string, content string) (realPath string, err error) {
realPath = filepath.Join(os.TempDir(), path)
err = ioutil.WriteFile(realPath, []byte(content), 0666)
return realPath, err
}

71
tests/consts/consts.go Normal file
View File

@@ -0,0 +1,71 @@
package consts
import "path/filepath"
var (
FRPS_BIN_PATH = "../../bin/frps"
FRPC_BIN_PATH = "../../bin/frpc"
FRPS_SUB_BIN_PATH = "../../../bin/frps"
FRPC_SUB_BIN_PATH = "../../../bin/frpc"
FRPS_NORMAL_CONFIG = "./auto_test_frps.ini"
FRPC_NORMAL_CONFIG = "./auto_test_frpc.ini"
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() {
if path, err := filepath.Abs(FRPS_BIN_PATH); err != nil {
panic(err)
} else {
FRPS_BIN_PATH = path
}
if path, err := filepath.Abs(FRPC_BIN_PATH); err != nil {
panic(err)
} else {
FRPC_BIN_PATH = path
}
}

View File

@@ -1,87 +0,0 @@
package tests
import (
"fmt"
"io"
"net"
"os"
"syscall"
frpNet "github.com/fatedier/frp/utils/net"
)
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
}
for {
c, err := l.Accept()
if err != nil {
fmt.Printf("echo server accept error: %v\n", err)
return
}
go echoWorker(c)
}
}
func StartUdpEchoServer() {
l, err := frpNet.ListenUDP("127.0.0.1", TEST_UDP_PORT)
if err != nil {
fmt.Printf("udp echo server listen error: %v\n", err)
return
}
for {
c, err := l.Accept()
if err != nil {
fmt.Printf("udp echo server accept error: %v\n", err)
return
}
go echoWorker(c)
}
}
func StartUnixDomainServer() {
unixPath := TEST_UNIX_DOMAIN_ADDR
os.Remove(unixPath)
syscall.Umask(0)
l, err := net.Listen("unix", unixPath)
if err != nil {
fmt.Printf("unix domain server listen error: %v\n", err)
return
}
for {
c, err := l.Accept()
if err != nil {
fmt.Printf("unix domain server accept error: %v\n", err)
return
}
go echoWorker(c)
}
}
func echoWorker(c net.Conn) {
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
}
}
c.Write(buf[:n])
}
}

View File

@@ -1,301 +0,0 @@
package tests
import (
"fmt"
"net/url"
"strings"
"testing"
"time"
"github.com/gorilla/websocket"
"github.com/stretchr/testify/assert"
"github.com/fatedier/frp/client"
"github.com/fatedier/frp/server"
"github.com/fatedier/frp/utils/net"
)
var (
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_TCP_FRP_PORT int = 10801
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 StartTcpEchoServer()
go StartUdpEchoServer()
go StartUnixDomainServer()
go StartHttpServer()
time.Sleep(500 * time.Millisecond)
}
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)
// 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)
}
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)
// 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 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)
}
// 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)
}
header := make(map[string]string)
header["Authorization"] = basicAuth("test", "test")
code, body, err = sendHttpMsg("GET", fmt.Sprintf("http://127.0.0.1:%d", TEST_HTTP_FRP_PORT), "test5.frp.com", header, "")
if assert.NoError(err) {
assert.Equal(401, code)
}
// 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, server.ErrPortNotAllowed.Error()))
}
status, err = getProxyStatus(ProxyUdpPortNotAllowed)
if assert.NoError(err) {
assert.Equal(client.ProxyStatusStartErr, status.Status)
assert.True(strings.Contains(status.Err, server.ErrPortNotAllowed.Error()))
}
status, err = getProxyStatus(ProxyTcpPortUnavailable)
if assert.NoError(err) {
assert.Equal(client.ProxyStatusStartErr, status.Status)
assert.True(strings.Contains(status.Err, server.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)
}
// connect method
conn, err := net.ConnectTcpServerByHttpProxy("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)
}
}
}
func TestRangePortsMapping(t *testing.T) {
assert := assert.New(t)
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)
}
}
}

120
tests/mock/echo_server.go Normal file
View File

@@ -0,0 +1,120 @@
package mock
import (
"fmt"
"io"
"net"
"os"
"syscall"
frpNet "github.com/fatedier/frp/utils/net"
)
type EchoServer struct {
l frpNet.Listener
port int
repeatedNum int
specifyStr string
}
func NewEchoServer(port int, repeatedNum int, specifyStr string) *EchoServer {
if repeatedNum <= 0 {
repeatedNum = 1
}
return &EchoServer{
port: port,
repeatedNum: repeatedNum,
specifyStr: specifyStr,
}
}
func (es *EchoServer) Start() error {
l, err := frpNet.ListenTcp("127.0.0.1", es.port)
if err != nil {
fmt.Printf("echo server listen error: %v\n", err)
return err
}
es.l = l
go func() {
for {
c, err := l.Accept()
if err != nil {
return
}
go echoWorker(c, es.repeatedNum, es.specifyStr)
}
}()
return nil
}
func (es *EchoServer) Stop() {
es.l.Close()
}
func StartUdpEchoServer(port int) {
l, err := frpNet.ListenUDP("127.0.0.1", port)
if err != nil {
fmt.Printf("udp echo server listen error: %v\n", err)
return
}
for {
c, err := l.Accept()
if err != nil {
fmt.Printf("udp echo server accept error: %v\n", err)
return
}
go echoWorker(c, 1, "")
}
}
func StartUnixDomainServer(unixPath string) {
os.Remove(unixPath)
syscall.Umask(0)
l, err := net.Listen("unix", unixPath)
if err != nil {
fmt.Printf("unix domain server listen error: %v\n", err)
return
}
for {
c, err := l.Accept()
if err != nil {
fmt.Printf("unix domain server accept error: %v\n", err)
return
}
go echoWorker(c, 1, "")
}
}
func echoWorker(c net.Conn, repeatedNum int, specifyStr string) {
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
}
}
if specifyStr != "" {
c.Write([]byte(specifyStr))
} else {
var w []byte
for i := 0; i < repeatedNum; i++ {
w = append(w, buf[:n]...)
}
c.Write(w)
}
}
}

View File

@@ -1,21 +1,54 @@
package tests package mock
import ( import (
"fmt" "fmt"
"log" "log"
"net"
"net/http" "net/http"
"regexp" "regexp"
"strings" "strings"
"github.com/fatedier/frp/tests/consts"
"github.com/gorilla/websocket" "github.com/gorilla/websocket"
) )
type HttpServer struct {
l net.Listener
port int
handler http.HandlerFunc
}
func NewHttpServer(port int, handler http.HandlerFunc) *HttpServer {
return &HttpServer{
port: port,
handler: handler,
}
}
func (hs *HttpServer) Start() error {
l, err := net.Listen("tcp", fmt.Sprintf("127.0.0.1:%d", hs.port))
if err != nil {
fmt.Printf("http server listen error: %v\n", err)
return err
}
hs.l = l
go http.Serve(l, http.HandlerFunc(hs.handler))
return nil
}
func (hs *HttpServer) Stop() {
hs.l.Close()
}
var upgrader = websocket.Upgrader{} var upgrader = websocket.Upgrader{}
func StartHttpServer() { func StartHttpServer(port int) {
http.HandleFunc("/", handleHttp) http.HandleFunc("/", handleHttp)
http.HandleFunc("/ws", handleWebSocket) http.HandleFunc("/ws", handleWebSocket)
http.ListenAndServe(fmt.Sprintf("0.0.0.0:%d", TEST_HTTP_PORT), nil) http.ListenAndServe(fmt.Sprintf("0.0.0.0:%d", port), nil)
} }
func handleWebSocket(w http.ResponseWriter, r *http.Request) { func handleWebSocket(w http.ResponseWriter, r *http.Request) {
@@ -39,6 +72,10 @@ func handleWebSocket(w http.ResponseWriter, r *http.Request) {
} }
func handleHttp(w http.ResponseWriter, r *http.Request) { 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)) match, err := regexp.Match(`.*\.sub\.com`, []byte(r.Host))
if err != nil { if err != nil {
w.WriteHeader(500) w.WriteHeader(500)
@@ -52,17 +89,17 @@ func handleHttp(w http.ResponseWriter, r *http.Request) {
} }
if strings.Contains(r.Host, "127.0.0.1") || strings.Contains(r.Host, "test2.frp.com") || 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, "test5.frp.com") || strings.Contains(r.Host, "test6.frp.com") {
w.WriteHeader(200) w.WriteHeader(200)
w.Write([]byte(TEST_HTTP_NORMAL_STR)) w.Write([]byte(consts.TEST_HTTP_NORMAL_STR))
} else if strings.Contains(r.Host, "test3.frp.com") { } else if strings.Contains(r.Host, "test3.frp.com") {
w.WriteHeader(200) w.WriteHeader(200)
if strings.Contains(r.URL.Path, "foo") { if strings.Contains(r.URL.Path, "foo") {
w.Write([]byte(TEST_HTTP_FOO_STR)) w.Write([]byte(consts.TEST_HTTP_FOO_STR))
} else if strings.Contains(r.URL.Path, "bar") { } else if strings.Contains(r.URL.Path, "bar") {
w.Write([]byte(TEST_HTTP_BAR_STR)) w.Write([]byte(consts.TEST_HTTP_BAR_STR))
} else { } else {
w.Write([]byte(TEST_HTTP_NORMAL_STR)) w.Write([]byte(consts.TEST_HTTP_NORMAL_STR))
} }
} else { } else {
w.WriteHeader(404) w.WriteHeader(404)

View File

@@ -1,9 +0,0 @@
#!/bin/bash
./../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

47
tests/util/process.go Normal file
View File

@@ -0,0 +1,47 @@
package util
import (
"bytes"
"context"
"os/exec"
)
type Process struct {
cmd *exec.Cmd
cancel context.CancelFunc
errorOutput *bytes.Buffer
beforeStopHandler func()
}
func NewProcess(path string, params []string) *Process {
ctx, cancel := context.WithCancel(context.Background())
cmd := exec.CommandContext(ctx, path, params...)
p := &Process{
cmd: cmd,
cancel: cancel,
}
p.errorOutput = bytes.NewBufferString("")
cmd.Stderr = p.errorOutput
return p
}
func (p *Process) Start() error {
return p.cmd.Start()
}
func (p *Process) Stop() error {
if p.beforeStopHandler != nil {
p.beforeStopHandler()
}
p.cancel()
return p.cmd.Wait()
}
func (p *Process) ErrorOutput() string {
return p.errorOutput.String()
}
func (p *Process) SetBeforeStopHandler(fn func()) {
p.beforeStopHandler = fn
}

View File

@@ -1,10 +1,11 @@
package tests package util
import ( import (
"encoding/base64" "encoding/base64"
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
"io"
"io/ioutil" "io/ioutil"
"net" "net"
"net/http" "net/http"
@@ -16,13 +17,13 @@ import (
frpNet "github.com/fatedier/frp/utils/net" frpNet "github.com/fatedier/frp/utils/net"
) )
func getProxyStatus(name string) (status *client.ProxyStatusResp, err error) { func GetProxyStatus(statusAddr string, user string, passwd string, name string) (status *client.ProxyStatusResp, err error) {
req, err := http.NewRequest("GET", "http://"+ADMIN_ADDR+"/api/status", nil) req, err := http.NewRequest("GET", "http://"+statusAddr+"/api/status", nil)
if err != nil { if err != nil {
return status, err return status, err
} }
authStr := "Basic " + base64.StdEncoding.EncodeToString([]byte(ADMIN_USER+":"+ADMIN_PWD)) authStr := "Basic " + base64.StdEncoding.EncodeToString([]byte(user+":"+passwd))
req.Header.Add("Authorization", authStr) req.Header.Add("Authorization", authStr)
resp, err := http.DefaultClient.Do(req) resp, err := http.DefaultClient.Do(req)
if err != nil { if err != nil {
@@ -75,17 +76,38 @@ func getProxyStatus(name string) (status *client.ProxyStatusResp, err error) {
return status, errors.New("no proxy status found") return status, errors.New("no proxy status found")
} }
func sendTcpMsg(addr string, msg string) (res string, err error) { func ReloadConf(reloadAddr string, user string, passwd string) error {
req, err := http.NewRequest("GET", "http://"+reloadAddr+"/api/reload", nil)
if err != nil {
return err
}
authStr := "Basic " + base64.StdEncoding.EncodeToString([]byte(user+":"+passwd))
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()
io.Copy(ioutil.Discard, resp.Body)
}
return nil
}
func SendTcpMsg(addr string, msg string) (res string, err error) {
c, err := frpNet.ConnectTcpServer(addr) c, err := frpNet.ConnectTcpServer(addr)
if err != nil { if err != nil {
err = fmt.Errorf("connect to tcp server error: %v", err) err = fmt.Errorf("connect to tcp server error: %v", err)
return return
} }
defer c.Close() defer c.Close()
return sendTcpMsgByConn(c, msg) return SendTcpMsgByConn(c, msg)
} }
func sendTcpMsgByConn(c net.Conn, msg string) (res string, err error) { func SendTcpMsgByConn(c net.Conn, msg string) (res string, err error) {
timer := time.Now().Add(5 * time.Second) timer := time.Now().Add(5 * time.Second)
c.SetDeadline(timer) c.SetDeadline(timer)
c.Write([]byte(msg)) c.Write([]byte(msg))
@@ -99,7 +121,7 @@ func sendTcpMsgByConn(c net.Conn, msg string) (res string, err error) {
return string(buf[:n]), nil return string(buf[:n]), nil
} }
func sendUdpMsg(addr string, msg string) (res string, err error) { func SendUdpMsg(addr string, msg string) (res string, err error) {
udpAddr, errRet := net.ResolveUDPAddr("udp", addr) udpAddr, errRet := net.ResolveUDPAddr("udp", addr)
if errRet != nil { if errRet != nil {
err = fmt.Errorf("resolve udp addr error: %v", err) err = fmt.Errorf("resolve udp addr error: %v", err)
@@ -126,7 +148,7 @@ func sendUdpMsg(addr string, msg string) (res string, err error) {
return string(buf[:n]), nil return string(buf[:n]), nil
} }
func sendHttpMsg(method, urlStr string, host string, header map[string]string, proxy string) (code int, body string, err error) { 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) req, errRet := http.NewRequest(method, urlStr, nil)
if errRet != nil { if errRet != nil {
err = errRet err = errRet
@@ -136,7 +158,7 @@ func sendHttpMsg(method, urlStr string, host string, header map[string]string, p
if host != "" { if host != "" {
req.Host = host req.Host = host
} }
for k, v := range header { for k, v := range headers {
req.Header.Set(k, v) req.Header.Set(k, v)
} }
@@ -167,6 +189,7 @@ func sendHttpMsg(method, urlStr string, host string, header map[string]string, p
return return
} }
code = resp.StatusCode code = resp.StatusCode
header = resp.Header
buf, errRet := ioutil.ReadAll(resp.Body) buf, errRet := ioutil.ReadAll(resp.Body)
if errRet != nil { if errRet != nil {
err = errRet err = errRet
@@ -176,7 +199,7 @@ func sendHttpMsg(method, urlStr string, host string, header map[string]string, p
return return
} }
func basicAuth(username, passwd string) string { func BasicAuth(username, passwd string) string {
auth := username + ":" + passwd auth := username + ":" + passwd
return "Basic " + base64.StdEncoding.EncodeToString([]byte(auth)) 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,16 @@
package net package net
import ( import (
"bytes"
"errors" "errors"
"fmt" "fmt"
"io" "io"
"net" "net"
"sync"
"sync/atomic" "sync/atomic"
"time" "time"
"github.com/fatedier/frp/utils/log" "github.com/fatedier/frp/utils/log"
gnet "github.com/fatedier/golib/net"
kcp "github.com/fatedier/kcp-go" kcp "github.com/fatedier/kcp-go"
) )
@@ -97,85 +96,36 @@ 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")} return &net.OpError{Op: "set", Net: "wrap", Source: nil, Addr: nil, Err: errors.New("deadline not supported")}
} }
func ConnectServer(protocol string, addr string) (c Conn, err error) { type CloseNotifyConn struct {
switch protocol { net.Conn
case "tcp": log.Logger
return ConnectTcpServer(addr)
case "kcp": // 1 means closed
kcpConn, errRet := kcp.DialWithOptions(addr, nil, 10, 3) closeFlag int32
if errRet != nil {
err = errRet closeFn func()
return }
// 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()
} }
kcpConn.SetStreamMode(true)
kcpConn.SetWriteDelay(true)
kcpConn.SetNoDelay(1, 20, 2, 1)
kcpConn.SetWindowSize(128, 512)
kcpConn.SetMtu(1350)
kcpConn.SetACKNoDelay(false)
kcpConn.SetReadBuffer(4194304)
kcpConn.SetWriteBuffer(4194304)
c = WrapConn(kcpConn)
return
default:
return nil, fmt.Errorf("unsupport protocol: %s", protocol)
}
}
func ConnectServerByHttpProxy(httpProxy string, protocol string, addr string) (c Conn, err error) {
switch protocol {
case "tcp":
return ConnectTcpServerByHttpProxy(httpProxy, addr)
case "kcp":
// http proxy is not supported for kcp
return ConnectServer(protocol, addr)
default:
return nil, fmt.Errorf("unsupport protocol: %s", protocol)
}
}
type SharedConn struct {
Conn
sync.Mutex
buf *bytes.Buffer
}
// the bytes you read in io.Reader, will be reserved in SharedConn
func NewShareConn(conn Conn) (*SharedConn, io.Reader) {
sc := &SharedConn{
Conn: conn,
buf: bytes.NewBuffer(make([]byte, 0, 1024)),
}
return sc, io.TeeReader(conn, sc.buf)
}
func (sc *SharedConn) Read(p []byte) (n int, err error) {
sc.Lock()
if sc.buf == nil {
sc.Unlock()
return sc.Conn.Read(p)
}
sc.Unlock()
n, err = sc.buf.Read(p)
if err == io.EOF {
sc.Lock()
sc.buf = nil
sc.Unlock()
var n2 int
n2, err = sc.Conn.Read(p[n:])
n += n2
} }
return return
} }
func (sc *SharedConn) WriteBuff(buffer []byte) (err error) {
sc.buf.Reset()
_, err = sc.buf.Write(buffer)
return err
}
type StatsConn struct { type StatsConn struct {
Conn Conn
@@ -214,3 +164,46 @@ func (statsConn *StatsConn) Close() (err error) {
} }
return return
} }
func ConnectServer(protocol string, addr string) (c Conn, err error) {
switch protocol {
case "tcp":
return ConnectTcpServer(addr)
case "kcp":
kcpConn, errRet := kcp.DialWithOptions(addr, nil, 10, 3)
if errRet != nil {
err = errRet
return
}
kcpConn.SetStreamMode(true)
kcpConn.SetWriteDelay(true)
kcpConn.SetNoDelay(1, 20, 2, 1)
kcpConn.SetWindowSize(128, 512)
kcpConn.SetMtu(1350)
kcpConn.SetACKNoDelay(false)
kcpConn.SetReadBuffer(4194304)
kcpConn.SetWriteBuffer(4194304)
c = WrapConn(kcpConn)
return
default:
return nil, fmt.Errorf("unsupport protocol: %s", protocol)
}
}
func ConnectServerByProxy(proxyUrl string, protocol string, addr string) (c Conn, err error) {
switch protocol {
case "tcp":
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)
}
}

View File

@@ -19,8 +19,6 @@ import (
"io" "io"
"net/http" "net/http"
"strings" "strings"
"github.com/julienschmidt/httprouter"
) )
type HttpAuthWraper struct { 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 { func HttpBasicAuth(h http.HandlerFunc, user, passwd string) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
reqUser, reqPasswd, hasAuth := r.BasicAuth() 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 { type HttpGzipWraper struct {
h http.Handler h http.Handler
} }

View File

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

View File

@@ -15,12 +15,8 @@
package net package net
import ( import (
"bufio"
"encoding/base64"
"fmt" "fmt"
"net" "net"
"net/http"
"net/url"
"github.com/fatedier/frp/utils/log" "github.com/fatedier/frp/utils/log"
) )
@@ -93,7 +89,7 @@ type TcpConn struct {
log.Logger log.Logger
} }
func NewTcpConn(conn *net.TCPConn) (c *TcpConn) { func NewTcpConn(conn net.Conn) (c *TcpConn) {
c = &TcpConn{ c = &TcpConn{
Conn: conn, Conn: conn,
Logger: log.NewPrefixLogger(""), Logger: log.NewPrefixLogger(""),
@@ -113,54 +109,3 @@ func ConnectTcpServer(addr string) (c Conn, err error) {
c = NewTcpConn(conn) c = NewTcpConn(conn)
return 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" "time"
"github.com/fatedier/frp/utils/log" "github.com/fatedier/frp/utils/log"
"github.com/fatedier/frp/utils/pool"
"github.com/fatedier/golib/pool"
) )
type UdpPacket struct { type UdpPacket struct {

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.WaitDone()
}

View File

@@ -19,7 +19,7 @@ import (
"strings" "strings"
) )
var version string = "0.18.0" var version string = "0.22.0"
func Full() string { func Full() string {
return version 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 // add every case there if server will not accept client's protocol and return false
func Compat(client string) (ok bool, msg string) { func Compat(client string) (ok bool, msg string) {
if LessThan(client, "0.10.0") { if LessThan(client, "0.18.0") {
return false, "Please upgrade your frpc version to at least 0.10.0" return false, "Please upgrade your frpc version to at least 0.18.0"
} }
return true, "" return true, ""
} }

View File

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

View File

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

View File

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

View File

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

View File

@@ -17,7 +17,7 @@ import (
"sync" "sync"
"time" "time"
frpIo "github.com/fatedier/frp/utils/io" frpIo "github.com/fatedier/golib/io"
) )
// onExitFlushLoop is a callback set by tests to detect the state of the // onExitFlushLoop is a callback set by tests to detect the state of the

View File

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

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