mirror of
https://github.com/fatedier/frp.git
synced 2026-04-09 18:49:17 +08:00
Compare commits
140 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e62d9a5242 | ||
|
|
94212ac8b8 | ||
|
|
e9e86fccf0 | ||
|
|
58745992ef | ||
|
|
234d634bfe | ||
|
|
fdc6902a90 | ||
|
|
d8d587fd93 | ||
|
|
92791260a7 | ||
|
|
4dfd851c46 | ||
|
|
bc4df74b5e | ||
|
|
666f122a72 | ||
|
|
f999c8a87e | ||
|
|
90a32ab75d | ||
|
|
0713fd28da | ||
|
|
f5b33e6de8 | ||
|
|
fc6043bb4d | ||
|
|
bc46e3330a | ||
|
|
5fc7b3ceb5 | ||
|
|
6277af4790 | ||
|
|
00bd0a8af4 | ||
|
|
a415573e45 | ||
|
|
e68012858e | ||
|
|
ca8a5b753c | ||
|
|
d1f4ac0f2d | ||
|
|
ff357882ac | ||
|
|
934ac2b836 | ||
|
|
1ad50d5982 | ||
|
|
388b016842 | ||
|
|
134a46c00b | ||
|
|
50796643fb | ||
|
|
b1838b1d5e | ||
|
|
757b3613fe | ||
|
|
ae08811636 | ||
|
|
b657c0fe09 | ||
|
|
84df71047c | ||
|
|
abc6d720d0 | ||
|
|
80154639e3 | ||
|
|
f2117d8331 | ||
|
|
261be6a7b7 | ||
|
|
b53a2c1ed9 | ||
|
|
ee0df07a3c | ||
|
|
4e363eca2b | ||
|
|
4277405c0e | ||
|
|
6a99f0caf7 | ||
|
|
394af08561 | ||
|
|
6451583e60 | ||
|
|
30cb0a3ab0 | ||
|
|
5680a88267 | ||
|
|
6b089858db | ||
|
|
b3ed863021 | ||
|
|
5796c27ed5 | ||
|
|
310e8dd768 | ||
|
|
0b40ac2dbc | ||
|
|
f22c8e0882 | ||
|
|
a388bb2c95 | ||
|
|
e611c44dea | ||
|
|
8e36e2bb67 | ||
|
|
541ad8d899 | ||
|
|
17cc0735d1 | ||
|
|
fd336a5503 | ||
|
|
802d1c1861 | ||
|
|
65fe0a1179 | ||
|
|
2d24879fa3 | ||
|
|
75383a95b3 | ||
|
|
95444ea46b | ||
|
|
9f9c01b520 | ||
|
|
285d1eba0d | ||
|
|
0dfd3a421c | ||
|
|
6a1f15b25e | ||
|
|
9f47c324b7 | ||
|
|
f0df6084af | ||
|
|
879ca47590 | ||
|
|
6a7efc81c9 | ||
|
|
12c5c553c3 | ||
|
|
988e9b1de3 | ||
|
|
db6bbc5187 | ||
|
|
c67b4e7b94 | ||
|
|
b7a73d3469 | ||
|
|
7f9d88c10a | ||
|
|
79237d2b94 | ||
|
|
9c4ec56491 | ||
|
|
74a8752570 | ||
|
|
a8ab4c5003 | ||
|
|
9cee263c91 | ||
|
|
c6bf6f59e6 | ||
|
|
4b7aef2196 | ||
|
|
f6d0046b5a | ||
|
|
84363266d2 | ||
|
|
9ac8f2a047 | ||
|
|
b2b55533b8 | ||
|
|
a4cfab689a | ||
|
|
c7df39074c | ||
|
|
fdcdccb0c2 | ||
|
|
e945c1667a | ||
|
|
87a4de4370 | ||
|
|
e1e2913b77 | ||
|
|
9be24db410 | ||
|
|
6b61cb3742 | ||
|
|
90b7f2080f | ||
|
|
d1f1c72a55 | ||
|
|
1925847ef8 | ||
|
|
8b216b0ca9 | ||
|
|
dbfeea99f3 | ||
|
|
5e64bbfa7c | ||
|
|
e691a40260 | ||
|
|
d812488767 | ||
|
|
3c03690ab7 | ||
|
|
3df27b9c04 | ||
|
|
ba45d29b7c | ||
|
|
3cf83f57a8 | ||
|
|
03e4318d79 | ||
|
|
178d134f46 | ||
|
|
cbf9c731a0 | ||
|
|
de4bfcc43c | ||
|
|
9737978f28 | ||
|
|
5bc7fe2cea | ||
|
|
65d8fe37c5 | ||
|
|
1723d7b651 | ||
|
|
2481dfab64 | ||
|
|
95a881a7d3 | ||
|
|
fe403ab328 | ||
|
|
66555dbb00 | ||
|
|
7f9ea48405 | ||
|
|
96d7e2da6f | ||
|
|
d879b8208b | ||
|
|
3585e456d4 | ||
|
|
1de8c3fc87 | ||
|
|
bbab3fe9ca | ||
|
|
48990da22e | ||
|
|
5543fc2a9a | ||
|
|
c41de6fd28 | ||
|
|
8c8fd9790e | ||
|
|
5a7ef3be74 | ||
|
|
d9b5e0bde0 | ||
|
|
05ca72dbf0 | ||
|
|
ef6f8bbf6c | ||
|
|
70ac7d3d11 | ||
|
|
385c4d3dd5 | ||
|
|
5e1983f7ed | ||
|
|
516cdbddb0 |
2
.github/ISSUE_TEMPLATE
vendored
2
.github/ISSUE_TEMPLATE
vendored
@@ -1,5 +1,7 @@
|
|||||||
Issue is only used for submiting bug report and documents typo. If there are same issues or answers can be found in documents, we will close it directly.
|
Issue is only used for submiting bug report and documents typo. If there are same issues or answers can be found in documents, we will close it directly.
|
||||||
(为了节约时间,提高处理问题的效率,不按照格式填写的 issue 将会直接关闭。)
|
(为了节约时间,提高处理问题的效率,不按照格式填写的 issue 将会直接关闭。)
|
||||||
|
(请不要在 issue 评论中出现无意义的 **加1**,**我也是** 等内容,将会被直接删除。)
|
||||||
|
(由于个人精力有限,和系统环境,网络环境等相关的求助问题请转至其他论坛或社交平台。)
|
||||||
|
|
||||||
Use the commands below to provide key information from your environment:
|
Use the commands below to provide key information from your environment:
|
||||||
You do NOT have to include this information if this is a FEATURE REQUEST
|
You do NOT have to include this information if this is a FEATURE REQUEST
|
||||||
|
|||||||
@@ -2,8 +2,7 @@ sudo: false
|
|||||||
language: go
|
language: go
|
||||||
|
|
||||||
go:
|
go:
|
||||||
- 1.10.x
|
- 1.12.x
|
||||||
- 1.11.x
|
|
||||||
|
|
||||||
install:
|
install:
|
||||||
- make
|
- make
|
||||||
|
|||||||
251
Gopkg.lock
generated
251
Gopkg.lock
generated
@@ -1,251 +0,0 @@
|
|||||||
# 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
78
Gopkg.toml
@@ -1,78 +0,0 @@
|
|||||||
# 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
|
|
||||||
12
Makefile
12
Makefile
@@ -6,11 +6,12 @@ build: frps frpc
|
|||||||
|
|
||||||
# compile assets into binary file
|
# compile assets into binary file
|
||||||
file:
|
file:
|
||||||
rm -rf ./assets/static/*
|
rm -rf ./assets/frps/static/*
|
||||||
cp -rf ./web/frps/dist/* ./assets/static
|
rm -rf ./assets/frpc/static/*
|
||||||
go get -d github.com/rakyll/statik
|
cp -rf ./web/frps/dist/* ./assets/frps/static
|
||||||
go install github.com/rakyll/statik
|
cp -rf ./web/frpc/dist/* ./assets/frpc/static
|
||||||
rm -rf ./assets/statik
|
rm -rf ./assets/frps/statik
|
||||||
|
rm -rf ./assets/frpc/statik
|
||||||
go generate ./assets/...
|
go generate ./assets/...
|
||||||
|
|
||||||
fmt:
|
fmt:
|
||||||
@@ -18,7 +19,6 @@ fmt:
|
|||||||
|
|
||||||
frps:
|
frps:
|
||||||
go build -o bin/frps ./cmd/frps
|
go build -o bin/frps ./cmd/frps
|
||||||
@cp -rf ./assets/static ./bin
|
|
||||||
|
|
||||||
frpc:
|
frpc:
|
||||||
go build -o bin/frpc ./cmd/frpc
|
go build -o bin/frpc ./cmd/frpc
|
||||||
|
|||||||
95
README.md
95
README.md
@@ -22,14 +22,17 @@ Now it also try to support p2p connect.
|
|||||||
* [Forward DNS query request](#forward-dns-query-request)
|
* [Forward DNS query request](#forward-dns-query-request)
|
||||||
* [Forward unix domain socket](#forward-unix-domain-socket)
|
* [Forward unix domain socket](#forward-unix-domain-socket)
|
||||||
* [Expose a simple http file server](#expose-a-simple-http-file-server)
|
* [Expose a simple http file server](#expose-a-simple-http-file-server)
|
||||||
|
* [Enable HTTPS for local HTTP service](#enable-https-for-local-http-service)
|
||||||
* [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)
|
||||||
* [Features](#features)
|
* [Features](#features)
|
||||||
* [Configuration File](#configuration-file)
|
* [Configuration File](#configuration-file)
|
||||||
* [Configuration file template](#configuration-file-template)
|
* [Configuration file template](#configuration-file-template)
|
||||||
* [Dashboard](#dashboard)
|
* [Dashboard](#dashboard)
|
||||||
|
* [Admin UI](#admin-ui)
|
||||||
* [Authentication](#authentication)
|
* [Authentication](#authentication)
|
||||||
* [Encryption and Compression](#encryption-and-compression)
|
* [Encryption and Compression](#encryption-and-compression)
|
||||||
|
* [TLS](#tls)
|
||||||
* [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)
|
||||||
@@ -42,6 +45,8 @@ Now it also try to support p2p connect.
|
|||||||
* [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)
|
* [Set Headers In HTTP Request](#set-headers-in-http-request)
|
||||||
* [Get Real IP](#get-real-ip)
|
* [Get Real IP](#get-real-ip)
|
||||||
|
* [HTTP X-Forwarded-For](#http-x-forwarded-for)
|
||||||
|
* [Proxy Protocol](#proxy-protocol)
|
||||||
* [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)
|
||||||
* [URL routing](#url-routing)
|
* [URL routing](#url-routing)
|
||||||
@@ -61,7 +66,7 @@ Now it also try to support p2p connect.
|
|||||||
|
|
||||||
frp is under development and you can try it with latest release version. Master branch for releasing stable version when dev branch for developing.
|
frp is under development and you can try it with latest release version. Master branch for releasing stable version when dev branch for developing.
|
||||||
|
|
||||||
**We may change any protocol and can't promise backward compatible. Please check the release log when upgrading.**
|
**We may change any protocol and can't promise backward compatibility. Please check the release log when upgrading.**
|
||||||
|
|
||||||
## Architecture
|
## Architecture
|
||||||
|
|
||||||
@@ -241,11 +246,35 @@ Configure frps same as above.
|
|||||||
|
|
||||||
2. Visit `http://x.x.x.x:6000/static/` by your browser, set correct user and password, so you can see files in `/tmp/file`.
|
2. Visit `http://x.x.x.x:6000/static/` by your browser, set correct user and password, so you can see files in `/tmp/file`.
|
||||||
|
|
||||||
|
### Enable HTTPS for local HTTP service
|
||||||
|
|
||||||
|
1. Start frpc with configurations:
|
||||||
|
|
||||||
|
```ini
|
||||||
|
# frpc.ini
|
||||||
|
[common]
|
||||||
|
server_addr = x.x.x.x
|
||||||
|
server_port = 7000
|
||||||
|
|
||||||
|
[test_htts2http]
|
||||||
|
type = https
|
||||||
|
custom_domains = test.yourdomain.com
|
||||||
|
|
||||||
|
plugin = https2http
|
||||||
|
plugin_local_addr = 127.0.0.1:80
|
||||||
|
plugin_crt_path = ./server.crt
|
||||||
|
plugin_key_path = ./server.key
|
||||||
|
plugin_host_header_rewrite = 127.0.0.1
|
||||||
|
plugin_header_X-From-Where = frp
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Visit `https://test.yourdomain.com`.
|
||||||
|
|
||||||
### Expose your service in security
|
### Expose your service in security
|
||||||
|
|
||||||
For some services, if expose them to the public network directly will be a security risk.
|
For some services, if expose them to the public network directly will be a security risk.
|
||||||
|
|
||||||
**stcp(secret tcp)** help you create a proxy avoiding any one can access it.
|
**stcp(secret tcp)** helps you create a proxy avoiding any one can access it.
|
||||||
|
|
||||||
Configure frps same as above.
|
Configure frps same as above.
|
||||||
|
|
||||||
@@ -389,6 +418,22 @@ Then visit `http://[server_addr]:7500` to see dashboard, default username and pa
|
|||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
### Admin UI
|
||||||
|
|
||||||
|
Admin UI help you check and manage frpc's configure.
|
||||||
|
|
||||||
|
Configure a address for admin UI to enable this feature:
|
||||||
|
|
||||||
|
```ini
|
||||||
|
[common]
|
||||||
|
admin_addr = 127.0.0.1
|
||||||
|
admin_port = 7400
|
||||||
|
admin_user = admin
|
||||||
|
admin_pwd = admin
|
||||||
|
```
|
||||||
|
|
||||||
|
Then visit `http://127.0.0.1:7400` to see admin UI, default username and password are both `admin`.
|
||||||
|
|
||||||
### Authentication
|
### Authentication
|
||||||
|
|
||||||
`token` in frps.ini and frpc.ini should be same.
|
`token` in frps.ini and frpc.ini should be same.
|
||||||
@@ -407,6 +452,14 @@ use_encryption = true
|
|||||||
use_compression = true
|
use_compression = true
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### TLS
|
||||||
|
|
||||||
|
frp support TLS protocol between frpc and frps since v0.25.0.
|
||||||
|
|
||||||
|
Config `tls_enable = true` in `common` section to frpc.ini to enable this feature.
|
||||||
|
|
||||||
|
For port multiplexing, frp send a first byte 0x17 to dial a TLS connection.
|
||||||
|
|
||||||
### Hot-Reload frpc configuration
|
### Hot-Reload frpc configuration
|
||||||
|
|
||||||
First you need to set admin port in frpc's configure file to let it provide HTTP API for more features.
|
First you need to set admin port in frpc's configure file to let it provide HTTP API for more features.
|
||||||
@@ -458,8 +511,6 @@ tcp_mux = false
|
|||||||
|
|
||||||
### Support KCP Protocol
|
### Support KCP Protocol
|
||||||
|
|
||||||
frp support kcp protocol since v0.12.0.
|
|
||||||
|
|
||||||
KCP is a fast and reliable protocol that can achieve the transmission effect of a reduction of the average latency by 30% to 40% and reduction of the maximum delay by a factor of three, at the cost of 10% to 20% more bandwidth wasted than TCP.
|
KCP is a fast and reliable protocol that can achieve the transmission effect of a reduction of the average latency by 30% to 40% and reduction of the maximum delay by a factor of three, at the cost of 10% to 20% more bandwidth wasted than TCP.
|
||||||
|
|
||||||
Using kcp in frp:
|
Using kcp in frp:
|
||||||
@@ -510,7 +561,8 @@ This feature is fit for a large number of short connections.
|
|||||||
### Load balancing
|
### Load balancing
|
||||||
|
|
||||||
Load balancing is supported by `group`.
|
Load balancing is supported by `group`.
|
||||||
This feature is available only for type `tcp` now.
|
|
||||||
|
This feature is available only for type `tcp` and `http` now.
|
||||||
|
|
||||||
```ini
|
```ini
|
||||||
# frpc.ini
|
# frpc.ini
|
||||||
@@ -533,6 +585,10 @@ group_key = 123
|
|||||||
|
|
||||||
Proxies in same group will accept connections from port 80 randomly.
|
Proxies in same group will accept connections from port 80 randomly.
|
||||||
|
|
||||||
|
For `tcp` type, `remote_port` in one group shoud be same.
|
||||||
|
|
||||||
|
For `http` type, `custom_domains, subdomain, locations` shoud be same.
|
||||||
|
|
||||||
### Health Check
|
### Health Check
|
||||||
|
|
||||||
Health check feature can help you achieve high availability with load balancing.
|
Health check feature can help you achieve high availability with load balancing.
|
||||||
@@ -592,7 +648,7 @@ 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.
|
The `Host` request header will be rewritten to `Host: dev.yourdomain.com` before it reach your local http server.
|
||||||
|
|
||||||
### Set Headers In HTTP Request
|
### Set Headers In HTTP Request
|
||||||
|
|
||||||
@@ -613,9 +669,32 @@ In this example, it will set header `X-From-Where: frp` to http request.
|
|||||||
|
|
||||||
### Get Real IP
|
### Get Real IP
|
||||||
|
|
||||||
|
#### HTTP X-Forwarded-For
|
||||||
|
|
||||||
Features for http proxy only.
|
Features for http proxy only.
|
||||||
|
|
||||||
You can get user's real IP from http request header `X-Forwarded-For` and `X-Real-IP`.
|
You can get user's real IP from HTTP request header `X-Forwarded-For` and `X-Real-IP`.
|
||||||
|
|
||||||
|
#### Proxy Protocol
|
||||||
|
|
||||||
|
frp support Proxy Protocol to send user's real IP to local service. It support all types without UDP.
|
||||||
|
|
||||||
|
Here is an example for https service:
|
||||||
|
|
||||||
|
```ini
|
||||||
|
# frpc.ini
|
||||||
|
[web]
|
||||||
|
type = https
|
||||||
|
local_port = 443
|
||||||
|
custom_domains = test.yourdomain.com
|
||||||
|
|
||||||
|
# now v1 and v2 is supported
|
||||||
|
proxy_protocol_version = v2
|
||||||
|
```
|
||||||
|
|
||||||
|
You can enable Proxy Protocol support in nginx to parse user's real IP to http header `X-Real-IP`.
|
||||||
|
|
||||||
|
Then you can get it from HTTP request header in your local service.
|
||||||
|
|
||||||
### Password protecting your web service
|
### Password protecting your web service
|
||||||
|
|
||||||
@@ -736,8 +815,6 @@ plugin_http_passwd = abc
|
|||||||
## Development Plan
|
## Development Plan
|
||||||
|
|
||||||
* Log http request information in frps.
|
* Log http request information in frps.
|
||||||
* Direct reverse proxy, like haproxy.
|
|
||||||
* kubernetes ingress support.
|
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
|
|||||||
104
README_zh.md
104
README_zh.md
@@ -16,16 +16,19 @@ frp 是一个可用于内网穿透的高性能的反向代理应用,支持 tcp
|
|||||||
* [通过 ssh 访问公司内网机器](#通过-ssh-访问公司内网机器)
|
* [通过 ssh 访问公司内网机器](#通过-ssh-访问公司内网机器)
|
||||||
* [通过自定义域名访问部署于内网的 web 服务](#通过自定义域名访问部署于内网的-web-服务)
|
* [通过自定义域名访问部署于内网的 web 服务](#通过自定义域名访问部署于内网的-web-服务)
|
||||||
* [转发 DNS 查询请求](#转发-dns-查询请求)
|
* [转发 DNS 查询请求](#转发-dns-查询请求)
|
||||||
* [转发 Unix域套接字](#转发-unix域套接字)
|
* [转发 Unix 域套接字](#转发-unix-域套接字)
|
||||||
* [对外提供简单的文件访问服务](#对外提供简单的文件访问服务)
|
* [对外提供简单的文件访问服务](#对外提供简单的文件访问服务)
|
||||||
|
* [为本地 HTTP 服务启用 HTTPS](#为本地-http-服务启用-https)
|
||||||
* [安全地暴露内网服务](#安全地暴露内网服务)
|
* [安全地暴露内网服务](#安全地暴露内网服务)
|
||||||
* [点对点内网穿透](#点对点内网穿透)
|
* [点对点内网穿透](#点对点内网穿透)
|
||||||
* [功能说明](#功能说明)
|
* [功能说明](#功能说明)
|
||||||
* [配置文件](#配置文件)
|
* [配置文件](#配置文件)
|
||||||
* [配置文件模版渲染](#配置文件模版渲染)
|
* [配置文件模版渲染](#配置文件模版渲染)
|
||||||
* [Dashboard](#dashboard)
|
* [Dashboard](#dashboard)
|
||||||
|
* [Admin UI](#admin-ui)
|
||||||
* [身份验证](#身份验证)
|
* [身份验证](#身份验证)
|
||||||
* [加密与压缩](#加密与压缩)
|
* [加密与压缩](#加密与压缩)
|
||||||
|
* [TLS](#tls)
|
||||||
* [客户端热加载配置文件](#客户端热加载配置文件)
|
* [客户端热加载配置文件](#客户端热加载配置文件)
|
||||||
* [客户端查看代理状态](#客户端查看代理状态)
|
* [客户端查看代理状态](#客户端查看代理状态)
|
||||||
* [端口白名单](#端口白名单)
|
* [端口白名单](#端口白名单)
|
||||||
@@ -38,6 +41,8 @@ frp 是一个可用于内网穿透的高性能的反向代理应用,支持 tcp
|
|||||||
* [修改 Host Header](#修改-host-header)
|
* [修改 Host Header](#修改-host-header)
|
||||||
* [设置 HTTP 请求的 header](#设置-http-请求的-header)
|
* [设置 HTTP 请求的 header](#设置-http-请求的-header)
|
||||||
* [获取用户真实 IP](#获取用户真实-ip)
|
* [获取用户真实 IP](#获取用户真实-ip)
|
||||||
|
* [HTTP X-Forwarded-For](#http-x-forwarded-for)
|
||||||
|
* [Proxy Protocol](#proxy-protocol)
|
||||||
* [通过密码保护你的 web 服务](#通过密码保护你的-web-服务)
|
* [通过密码保护你的 web 服务](#通过密码保护你的-web-服务)
|
||||||
* [自定义二级域名](#自定义二级域名)
|
* [自定义二级域名](#自定义二级域名)
|
||||||
* [URL 路由](#url-路由)
|
* [URL 路由](#url-路由)
|
||||||
@@ -47,6 +52,7 @@ frp 是一个可用于内网穿透的高性能的反向代理应用,支持 tcp
|
|||||||
* [开发计划](#开发计划)
|
* [开发计划](#开发计划)
|
||||||
* [为 frp 做贡献](#为-frp-做贡献)
|
* [为 frp 做贡献](#为-frp-做贡献)
|
||||||
* [捐助](#捐助)
|
* [捐助](#捐助)
|
||||||
|
* [知识星球](#知识星球)
|
||||||
* [支付宝扫码捐赠](#支付宝扫码捐赠)
|
* [支付宝扫码捐赠](#支付宝扫码捐赠)
|
||||||
* [微信支付捐赠](#微信支付捐赠)
|
* [微信支付捐赠](#微信支付捐赠)
|
||||||
* [Paypal 捐赠](#paypal-捐赠)
|
* [Paypal 捐赠](#paypal-捐赠)
|
||||||
@@ -123,7 +129,7 @@ master 分支用于发布稳定版本,dev 分支用于开发,您可以尝试
|
|||||||
vhost_http_port = 8080
|
vhost_http_port = 8080
|
||||||
```
|
```
|
||||||
|
|
||||||
2. 启动 frps;
|
2. 启动 frps:
|
||||||
|
|
||||||
`./frps -c ./frps.ini`
|
`./frps -c ./frps.ini`
|
||||||
|
|
||||||
@@ -188,7 +194,7 @@ DNS 查询请求通常使用 UDP 协议,frp 支持对内网 UDP 服务的穿
|
|||||||
|
|
||||||
`dig @x.x.x.x -p 6000 www.google.com`
|
`dig @x.x.x.x -p 6000 www.google.com`
|
||||||
|
|
||||||
### 转发 Unix域套接字
|
### 转发 Unix 域套接字
|
||||||
|
|
||||||
通过 tcp 端口访问内网的 unix域套接字(例如和 docker daemon 通信)。
|
通过 tcp 端口访问内网的 unix域套接字(例如和 docker daemon 通信)。
|
||||||
|
|
||||||
@@ -241,6 +247,34 @@ frps 的部署步骤同上。
|
|||||||
|
|
||||||
2. 通过浏览器访问 `http://x.x.x.x:6000/static/` 来查看位于 `/tmp/file` 目录下的文件,会要求输入已设置好的用户名和密码。
|
2. 通过浏览器访问 `http://x.x.x.x:6000/static/` 来查看位于 `/tmp/file` 目录下的文件,会要求输入已设置好的用户名和密码。
|
||||||
|
|
||||||
|
### 为本地 HTTP 服务启用 HTTPS
|
||||||
|
|
||||||
|
通过 `https2http` 插件可以让本地 HTTP 服务转换成 HTTPS 服务对外提供。
|
||||||
|
|
||||||
|
1. 启用 frpc,启用 `https2http` 插件,配置如下:
|
||||||
|
|
||||||
|
```ini
|
||||||
|
# frpc.ini
|
||||||
|
[common]
|
||||||
|
server_addr = x.x.x.x
|
||||||
|
server_port = 7000
|
||||||
|
|
||||||
|
[test_htts2http]
|
||||||
|
type = https
|
||||||
|
custom_domains = test.yourdomain.com
|
||||||
|
|
||||||
|
plugin = https2http
|
||||||
|
plugin_local_addr = 127.0.0.1:80
|
||||||
|
|
||||||
|
# HTTPS 证书相关的配置
|
||||||
|
plugin_crt_path = ./server.crt
|
||||||
|
plugin_key_path = ./server.key
|
||||||
|
plugin_host_header_rewrite = 127.0.0.1
|
||||||
|
plugin_header_X-From-Where = frp
|
||||||
|
```
|
||||||
|
|
||||||
|
2. 通过浏览器访问 `https://test.yourdomain.com` 即可。
|
||||||
|
|
||||||
### 安全地暴露内网服务
|
### 安全地暴露内网服务
|
||||||
|
|
||||||
对于某些服务来说如果直接暴露于公网上将会存在安全隐患。
|
对于某些服务来说如果直接暴露于公网上将会存在安全隐患。
|
||||||
@@ -404,6 +438,24 @@ dashboard_pwd = admin
|
|||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
### Admin UI
|
||||||
|
|
||||||
|
Admin UI 可以帮助用户通过浏览器来查询和管理客户端的 proxy 状态和配置。
|
||||||
|
|
||||||
|
需要在 frpc.ini 中指定 admin 服务使用的端口,即可开启此功能:
|
||||||
|
|
||||||
|
```ini
|
||||||
|
[common]
|
||||||
|
admin_addr = 127.0.0.1
|
||||||
|
admin_port = 7400
|
||||||
|
admin_user = admin
|
||||||
|
admin_pwd = admin
|
||||||
|
```
|
||||||
|
|
||||||
|
打开浏览器通过 `http://127.0.0.1:7400` 访问 Admin UI,用户名密码默认为 `admin`。
|
||||||
|
|
||||||
|
如果想要在外网环境访问 Admin UI,将 7400 端口映射出去即可,但需要重视安全风险。
|
||||||
|
|
||||||
### 身份验证
|
### 身份验证
|
||||||
|
|
||||||
服务端和客户端的 common 配置中的 `token` 参数一致则身份验证通过。
|
服务端和客户端的 common 配置中的 `token` 参数一致则身份验证通过。
|
||||||
@@ -426,6 +478,14 @@ use_compression = true
|
|||||||
|
|
||||||
如果传输的报文长度较长,通过设置 `use_compression = true` 对传输内容进行压缩,可以有效减小 frpc 与 frps 之间的网络流量,加快流量转发速度,但是会额外消耗一些 cpu 资源。
|
如果传输的报文长度较长,通过设置 `use_compression = true` 对传输内容进行压缩,可以有效减小 frpc 与 frps 之间的网络流量,加快流量转发速度,但是会额外消耗一些 cpu 资源。
|
||||||
|
|
||||||
|
#### TLS
|
||||||
|
|
||||||
|
从 v0.25.0 版本开始 frpc 和 frps 之间支持通过 TLS 协议加密传输。通过在 `frpc.ini` 的 `common` 中配置 `tls_enable = true` 来启用此功能,安全性更高。
|
||||||
|
|
||||||
|
为了端口复用,frp 建立 TLS 连接的第一个字节为 0x17。
|
||||||
|
|
||||||
|
**注意: 启用此功能后除 xtcp 外,不需要再设置 use_encryption。**
|
||||||
|
|
||||||
### 客户端热加载配置文件
|
### 客户端热加载配置文件
|
||||||
|
|
||||||
当修改了 frpc 中的代理配置,可以通过 `frpc reload` 命令来动态加载配置文件,通常会在 10 秒内完成代理的更新。
|
当修改了 frpc 中的代理配置,可以通过 `frpc reload` 命令来动态加载配置文件,通常会在 10 秒内完成代理的更新。
|
||||||
@@ -485,7 +545,7 @@ tcp_mux = false
|
|||||||
|
|
||||||
### 底层通信可选 kcp 协议
|
### 底层通信可选 kcp 协议
|
||||||
|
|
||||||
从 v0.12.0 版本开始,底层通信协议支持选择 kcp 协议,在弱网环境下传输效率提升明显,但是会有一些额外的流量消耗。
|
底层通信协议支持选择 kcp 协议,在弱网环境下传输效率提升明显,但是会有一些额外的流量消耗。
|
||||||
|
|
||||||
开启 kcp 协议支持:
|
开启 kcp 协议支持:
|
||||||
|
|
||||||
@@ -537,7 +597,8 @@ tcp_mux = false
|
|||||||
### 负载均衡
|
### 负载均衡
|
||||||
|
|
||||||
可以将多个相同类型的 proxy 加入到同一个 group 中,从而实现负载均衡的功能。
|
可以将多个相同类型的 proxy 加入到同一个 group 中,从而实现负载均衡的功能。
|
||||||
目前只支持 tcp 类型的 proxy。
|
|
||||||
|
目前只支持 TCP 和 HTTP 类型的 proxy。
|
||||||
|
|
||||||
```ini
|
```ini
|
||||||
# frpc.ini
|
# frpc.ini
|
||||||
@@ -558,7 +619,9 @@ group_key = 123
|
|||||||
|
|
||||||
用户连接 frps 服务器的 80 端口,frps 会将接收到的用户连接随机分发给其中一个存活的 proxy。这样可以在一台 frpc 机器挂掉后仍然有其他节点能够提供服务。
|
用户连接 frps 服务器的 80 端口,frps 会将接收到的用户连接随机分发给其中一个存活的 proxy。这样可以在一台 frpc 机器挂掉后仍然有其他节点能够提供服务。
|
||||||
|
|
||||||
要求 `group_key` 相同,做权限验证,且 `remote_port` 相同。
|
TCP 类型代理要求 `group_key` 相同,做权限验证,且 `remote_port` 相同。
|
||||||
|
|
||||||
|
HTTP 类型代理要求 `group_key, custom_domains 或 subdomain 和 locations` 相同。
|
||||||
|
|
||||||
### 健康检查
|
### 健康检查
|
||||||
|
|
||||||
@@ -639,7 +702,34 @@ header_X-From-Where = frp
|
|||||||
|
|
||||||
### 获取用户真实 IP
|
### 获取用户真实 IP
|
||||||
|
|
||||||
目前只有 **http** 类型的代理支持这一功能,可以通过用户请求的 header 中的 `X-Forwarded-For` 和 `X-Real-IP` 来获取用户真实 IP。
|
#### HTTP X-Forwarded-For
|
||||||
|
|
||||||
|
目前只有 **http** 类型的代理支持这一功能,可以通过用户请求的 header 中的 `X-Forwarded-For` 来获取用户真实 IP,默认启用。
|
||||||
|
|
||||||
|
#### Proxy Protocol
|
||||||
|
|
||||||
|
frp 支持通过 **Proxy Protocol** 协议来传递经过 frp 代理的请求的真实 IP,此功能支持所有以 TCP 为底层协议的类型,不支持 UDP。
|
||||||
|
|
||||||
|
**Proxy Protocol** 功能启用后,frpc 在和本地服务建立连接后,会先发送一段 **Proxy Protocol** 的协议内容给本地服务,本地服务通过解析这一内容可以获得访问用户的真实 IP。所以不仅仅是 HTTP 服务,任何的 TCP 服务,只要支持这一协议,都可以获得用户的真实 IP 地址。
|
||||||
|
|
||||||
|
需要注意的是,在代理配置中如果要启用此功能,需要本地的服务能够支持 **Proxy Protocol** 这一协议,目前 nginx 和 haproxy 都能够很好的支持。
|
||||||
|
|
||||||
|
这里以 https 类型为例:
|
||||||
|
|
||||||
|
```ini
|
||||||
|
# frpc.ini
|
||||||
|
[web]
|
||||||
|
type = https
|
||||||
|
local_port = 443
|
||||||
|
custom_domains = test.yourdomain.com
|
||||||
|
|
||||||
|
# 目前支持 v1 和 v2 两个版本的 proxy protocol 协议。
|
||||||
|
proxy_protocol_version = v2
|
||||||
|
```
|
||||||
|
|
||||||
|
只需要在代理配置中增加一行 `proxy_protocol_version = v2` 即可开启此功能。
|
||||||
|
|
||||||
|
本地的 https 服务可以通过在 nginx 的配置中启用 **Proxy Protocol** 的解析并将结果设置在 `X-Real-IP` 这个 Header 中就可以在自己的 Web 服务中通过 `X-Real-IP` 获取到用户的真实 IP。
|
||||||
|
|
||||||
### 通过密码保护你的 web 服务
|
### 通过密码保护你的 web 服务
|
||||||
|
|
||||||
|
|||||||
@@ -14,8 +14,10 @@
|
|||||||
|
|
||||||
package assets
|
package assets
|
||||||
|
|
||||||
//go:generate statik -src=./static
|
//go:generate statik -src=./frps/static -dest=./frps
|
||||||
//go:generate go fmt statik/statik.go
|
//go:generate statik -src=./frpc/static -dest=./frpc
|
||||||
|
//go:generate go fmt ./frps/statik/statik.go
|
||||||
|
//go:generate go fmt ./frpc/statik/statik.go
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
@@ -24,8 +26,6 @@ import (
|
|||||||
"path"
|
"path"
|
||||||
|
|
||||||
"github.com/rakyll/statik/fs"
|
"github.com/rakyll/statik/fs"
|
||||||
|
|
||||||
_ "github.com/fatedier/frp/assets/statik"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 9.4 KiB After Width: | Height: | Size: 9.4 KiB |
1
assets/frpc/static/index.html
Normal file
1
assets/frpc/static/index.html
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<!doctype html> <html lang=en> <head> <meta charset=utf-8> <title>frp client admin UI</title> <link rel="shortcut icon" href="favicon.ico"></head> <body> <div id=app></div> <script type="text/javascript" src="manifest.js?d2cd6337d30c7b22e836"></script><script type="text/javascript" src="vendor.js?edb271e1d9c81f857840"></script></body> </html>
|
||||||
1
assets/frpc/static/manifest.js
Normal file
1
assets/frpc/static/manifest.js
Normal file
@@ -0,0 +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,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:"edb271e1d9c81f857840"}[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}}([]);
|
||||||
1
assets/frpc/static/vendor.js
Normal file
1
assets/frpc/static/vendor.js
Normal file
File diff suppressed because one or more lines are too long
10
assets/frpc/statik/statik.go
Normal file
10
assets/frpc/statik/statik.go
Normal file
File diff suppressed because one or more lines are too long
BIN
assets/frps/static/6f0a76321d30f3c8120915e57f7bd77e.ttf
Normal file
BIN
assets/frps/static/6f0a76321d30f3c8120915e57f7bd77e.ttf
Normal file
Binary file not shown.
BIN
assets/frps/static/favicon.ico
Normal file
BIN
assets/frps/static/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 9.4 KiB |
10
assets/frps/statik/statik.go
Normal file
10
assets/frps/statik/statik.go
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -20,7 +20,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/fatedier/frp/g"
|
"github.com/fatedier/frp/assets"
|
||||||
frpNet "github.com/fatedier/frp/utils/net"
|
frpNet "github.com/fatedier/frp/utils/net"
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
@@ -35,12 +35,21 @@ func (svr *Service) RunAdminServer(addr string, port int) (err error) {
|
|||||||
// url router
|
// url router
|
||||||
router := mux.NewRouter()
|
router := mux.NewRouter()
|
||||||
|
|
||||||
user, passwd := g.GlbClientCfg.AdminUser, g.GlbClientCfg.AdminPwd
|
user, passwd := svr.cfg.AdminUser, svr.cfg.AdminPwd
|
||||||
router.Use(frpNet.NewHttpAuthMiddleware(user, passwd).Middleware)
|
router.Use(frpNet.NewHttpAuthMiddleware(user, passwd).Middleware)
|
||||||
|
|
||||||
// api, see dashboard_api.go
|
// api, see dashboard_api.go
|
||||||
router.HandleFunc("/api/reload", svr.apiReload).Methods("GET")
|
router.HandleFunc("/api/reload", svr.apiReload).Methods("GET")
|
||||||
router.HandleFunc("/api/status", svr.apiStatus).Methods("GET")
|
router.HandleFunc("/api/status", svr.apiStatus).Methods("GET")
|
||||||
|
router.HandleFunc("/api/config", svr.apiGetConfig).Methods("GET")
|
||||||
|
router.HandleFunc("/api/config", svr.apiPutConfig).Methods("PUT")
|
||||||
|
|
||||||
|
// view
|
||||||
|
router.Handle("/favicon.ico", http.FileServer(assets.FileSystem)).Methods("GET")
|
||||||
|
router.PathPrefix("/static/").Handler(frpNet.MakeHttpGzipHandler(http.StripPrefix("/static/", http.FileServer(assets.FileSystem)))).Methods("GET")
|
||||||
|
router.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
http.Redirect(w, r, "/static/", http.StatusMovedPermanently)
|
||||||
|
})
|
||||||
|
|
||||||
address := fmt.Sprintf("%s:%d", addr, port)
|
address := fmt.Sprintf("%s:%d", addr, port)
|
||||||
server := &http.Server{
|
server := &http.Server{
|
||||||
|
|||||||
@@ -17,68 +17,64 @@ package client
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/fatedier/frp/client/proxy"
|
"github.com/fatedier/frp/client/proxy"
|
||||||
"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"
|
||||||
)
|
)
|
||||||
|
|
||||||
type GeneralResponse struct {
|
type GeneralResponse struct {
|
||||||
Code int64 `json:"code"`
|
Code int
|
||||||
Msg string `json:"msg"`
|
Msg string
|
||||||
}
|
}
|
||||||
|
|
||||||
// api/reload
|
// GET api/reload
|
||||||
type ReloadResp struct {
|
|
||||||
GeneralResponse
|
|
||||||
}
|
|
||||||
|
|
||||||
func (svr *Service) apiReload(w http.ResponseWriter, r *http.Request) {
|
func (svr *Service) apiReload(w http.ResponseWriter, r *http.Request) {
|
||||||
var (
|
res := GeneralResponse{Code: 200}
|
||||||
buf []byte
|
|
||||||
res ReloadResp
|
log.Info("Http request [/api/reload]")
|
||||||
)
|
|
||||||
defer func() {
|
defer func() {
|
||||||
log.Info("Http response [/api/reload]: code [%d]", res.Code)
|
log.Info("Http response [/api/reload], code [%d]", res.Code)
|
||||||
buf, _ = json.Marshal(&res)
|
w.WriteHeader(res.Code)
|
||||||
w.Write(buf)
|
if len(res.Msg) > 0 {
|
||||||
|
w.Write([]byte(res.Msg))
|
||||||
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
log.Info("Http request: [/api/reload]")
|
content, err := config.GetRenderedConfFromFile(svr.cfgFile)
|
||||||
|
|
||||||
content, err := config.GetRenderedConfFromFile(g.GlbClientCfg.CfgFile)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
res.Code = 1
|
res.Code = 400
|
||||||
res.Msg = err.Error()
|
res.Msg = err.Error()
|
||||||
log.Error("reload frpc config file error: %v", err)
|
log.Warn("reload frpc config file error: %s", res.Msg)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
newCommonCfg, err := config.UnmarshalClientConfFromIni(nil, content)
|
newCommonCfg, err := config.UnmarshalClientConfFromIni(content)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
res.Code = 2
|
res.Code = 400
|
||||||
res.Msg = err.Error()
|
res.Msg = err.Error()
|
||||||
log.Error("reload frpc common section error: %v", err)
|
log.Warn("reload frpc common section error: %s", res.Msg)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
pxyCfgs, visitorCfgs, err := config.LoadAllConfFromIni(g.GlbClientCfg.User, content, newCommonCfg.Start)
|
pxyCfgs, visitorCfgs, err := config.LoadAllConfFromIni(svr.cfg.User, content, newCommonCfg.Start)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
res.Code = 3
|
res.Code = 400
|
||||||
res.Msg = err.Error()
|
res.Msg = err.Error()
|
||||||
log.Error("reload frpc proxy config error: %v", err)
|
log.Warn("reload frpc proxy config error: %s", res.Msg)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err = svr.ctl.ReloadConf(pxyCfgs, visitorCfgs)
|
err = svr.ReloadConf(pxyCfgs, visitorCfgs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
res.Code = 4
|
res.Code = 500
|
||||||
res.Msg = err.Error()
|
res.Msg = err.Error()
|
||||||
log.Error("reload frpc proxy config error: %v", err)
|
log.Warn("reload frpc proxy config error: %s", res.Msg)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
log.Info("success reload conf")
|
log.Info("success reload conf")
|
||||||
@@ -110,7 +106,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 *proxy.ProxyStatus) ProxyStatusResp {
|
func NewProxyStatusResp(status *proxy.ProxyStatus, serverAddr string) ProxyStatusResp {
|
||||||
psr := ProxyStatusResp{
|
psr := ProxyStatusResp{
|
||||||
Name: status.Name,
|
Name: status.Name,
|
||||||
Type: status.Type,
|
Type: status.Type,
|
||||||
@@ -124,18 +120,18 @@ func NewProxyStatusResp(status *proxy.ProxyStatus) ProxyStatusResp {
|
|||||||
}
|
}
|
||||||
psr.Plugin = cfg.Plugin
|
psr.Plugin = cfg.Plugin
|
||||||
if status.Err != "" {
|
if status.Err != "" {
|
||||||
psr.RemoteAddr = fmt.Sprintf("%s:%d", g.GlbClientCfg.ServerAddr, cfg.RemotePort)
|
psr.RemoteAddr = fmt.Sprintf("%s:%d", serverAddr, cfg.RemotePort)
|
||||||
} else {
|
} else {
|
||||||
psr.RemoteAddr = g.GlbClientCfg.ServerAddr + status.RemoteAddr
|
psr.RemoteAddr = serverAddr + status.RemoteAddr
|
||||||
}
|
}
|
||||||
case *config.UdpProxyConf:
|
case *config.UdpProxyConf:
|
||||||
if cfg.LocalPort != 0 {
|
if cfg.LocalPort != 0 {
|
||||||
psr.LocalAddr = fmt.Sprintf("%s:%d", cfg.LocalIp, cfg.LocalPort)
|
psr.LocalAddr = fmt.Sprintf("%s:%d", cfg.LocalIp, cfg.LocalPort)
|
||||||
}
|
}
|
||||||
if status.Err != "" {
|
if status.Err != "" {
|
||||||
psr.RemoteAddr = fmt.Sprintf("%s:%d", g.GlbClientCfg.ServerAddr, cfg.RemotePort)
|
psr.RemoteAddr = fmt.Sprintf("%s:%d", serverAddr, cfg.RemotePort)
|
||||||
} else {
|
} else {
|
||||||
psr.RemoteAddr = g.GlbClientCfg.ServerAddr + status.RemoteAddr
|
psr.RemoteAddr = serverAddr + status.RemoteAddr
|
||||||
}
|
}
|
||||||
case *config.HttpProxyConf:
|
case *config.HttpProxyConf:
|
||||||
if cfg.LocalPort != 0 {
|
if cfg.LocalPort != 0 {
|
||||||
@@ -163,7 +159,7 @@ func NewProxyStatusResp(status *proxy.ProxyStatus) ProxyStatusResp {
|
|||||||
return psr
|
return psr
|
||||||
}
|
}
|
||||||
|
|
||||||
// api/status
|
// GET api/status
|
||||||
func (svr *Service) apiStatus(w http.ResponseWriter, r *http.Request) {
|
func (svr *Service) apiStatus(w http.ResponseWriter, r *http.Request) {
|
||||||
var (
|
var (
|
||||||
buf []byte
|
buf []byte
|
||||||
@@ -175,29 +171,29 @@ func (svr *Service) apiStatus(w http.ResponseWriter, r *http.Request) {
|
|||||||
res.Https = make([]ProxyStatusResp, 0)
|
res.Https = make([]ProxyStatusResp, 0)
|
||||||
res.Stcp = make([]ProxyStatusResp, 0)
|
res.Stcp = make([]ProxyStatusResp, 0)
|
||||||
res.Xtcp = make([]ProxyStatusResp, 0)
|
res.Xtcp = make([]ProxyStatusResp, 0)
|
||||||
|
|
||||||
|
log.Info("Http request [/api/status]")
|
||||||
defer func() {
|
defer func() {
|
||||||
log.Info("Http response [/api/status]")
|
log.Info("Http response [/api/status]")
|
||||||
buf, _ = json.Marshal(&res)
|
buf, _ = json.Marshal(&res)
|
||||||
w.Write(buf)
|
w.Write(buf)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
log.Info("Http request: [/api/status]")
|
|
||||||
|
|
||||||
ps := svr.ctl.pm.GetAllProxyStatus()
|
ps := svr.ctl.pm.GetAllProxyStatus()
|
||||||
for _, status := range ps {
|
for _, status := range ps {
|
||||||
switch status.Type {
|
switch status.Type {
|
||||||
case "tcp":
|
case "tcp":
|
||||||
res.Tcp = append(res.Tcp, NewProxyStatusResp(status))
|
res.Tcp = append(res.Tcp, NewProxyStatusResp(status, svr.cfg.ServerAddr))
|
||||||
case "udp":
|
case "udp":
|
||||||
res.Udp = append(res.Udp, NewProxyStatusResp(status))
|
res.Udp = append(res.Udp, NewProxyStatusResp(status, svr.cfg.ServerAddr))
|
||||||
case "http":
|
case "http":
|
||||||
res.Http = append(res.Http, NewProxyStatusResp(status))
|
res.Http = append(res.Http, NewProxyStatusResp(status, svr.cfg.ServerAddr))
|
||||||
case "https":
|
case "https":
|
||||||
res.Https = append(res.Https, NewProxyStatusResp(status))
|
res.Https = append(res.Https, NewProxyStatusResp(status, svr.cfg.ServerAddr))
|
||||||
case "stcp":
|
case "stcp":
|
||||||
res.Stcp = append(res.Stcp, NewProxyStatusResp(status))
|
res.Stcp = append(res.Stcp, NewProxyStatusResp(status, svr.cfg.ServerAddr))
|
||||||
case "xtcp":
|
case "xtcp":
|
||||||
res.Xtcp = append(res.Xtcp, NewProxyStatusResp(status))
|
res.Xtcp = append(res.Xtcp, NewProxyStatusResp(status, svr.cfg.ServerAddr))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
sort.Sort(ByProxyStatusResp(res.Tcp))
|
sort.Sort(ByProxyStatusResp(res.Tcp))
|
||||||
@@ -208,3 +204,122 @@ func (svr *Service) apiStatus(w http.ResponseWriter, r *http.Request) {
|
|||||||
sort.Sort(ByProxyStatusResp(res.Xtcp))
|
sort.Sort(ByProxyStatusResp(res.Xtcp))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GET api/config
|
||||||
|
func (svr *Service) apiGetConfig(w http.ResponseWriter, r *http.Request) {
|
||||||
|
res := GeneralResponse{Code: 200}
|
||||||
|
|
||||||
|
log.Info("Http get request [/api/config]")
|
||||||
|
defer func() {
|
||||||
|
log.Info("Http get response [/api/config], code [%d]", res.Code)
|
||||||
|
w.WriteHeader(res.Code)
|
||||||
|
if len(res.Msg) > 0 {
|
||||||
|
w.Write([]byte(res.Msg))
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
if svr.cfgFile == "" {
|
||||||
|
res.Code = 400
|
||||||
|
res.Msg = "frpc has no config file path"
|
||||||
|
log.Warn("%s", res.Msg)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
content, err := config.GetRenderedConfFromFile(svr.cfgFile)
|
||||||
|
if err != nil {
|
||||||
|
res.Code = 400
|
||||||
|
res.Msg = err.Error()
|
||||||
|
log.Warn("load frpc config file error: %s", res.Msg)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
rows := strings.Split(content, "\n")
|
||||||
|
newRows := make([]string, 0, len(rows))
|
||||||
|
for _, row := range rows {
|
||||||
|
row = strings.TrimSpace(row)
|
||||||
|
if strings.HasPrefix(row, "token") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
newRows = append(newRows, row)
|
||||||
|
}
|
||||||
|
res.Msg = strings.Join(newRows, "\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
// PUT api/config
|
||||||
|
func (svr *Service) apiPutConfig(w http.ResponseWriter, r *http.Request) {
|
||||||
|
res := GeneralResponse{Code: 200}
|
||||||
|
|
||||||
|
log.Info("Http put request [/api/config]")
|
||||||
|
defer func() {
|
||||||
|
log.Info("Http put response [/api/config], code [%d]", res.Code)
|
||||||
|
w.WriteHeader(res.Code)
|
||||||
|
if len(res.Msg) > 0 {
|
||||||
|
w.Write([]byte(res.Msg))
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// get new config content
|
||||||
|
body, err := ioutil.ReadAll(r.Body)
|
||||||
|
if err != nil {
|
||||||
|
res.Code = 400
|
||||||
|
res.Msg = fmt.Sprintf("read request body error: %v", err)
|
||||||
|
log.Warn("%s", res.Msg)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(body) == 0 {
|
||||||
|
res.Code = 400
|
||||||
|
res.Msg = "body can't be empty"
|
||||||
|
log.Warn("%s", res.Msg)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// get token from origin content
|
||||||
|
token := ""
|
||||||
|
b, err := ioutil.ReadFile(svr.cfgFile)
|
||||||
|
if err != nil {
|
||||||
|
res.Code = 400
|
||||||
|
res.Msg = err.Error()
|
||||||
|
log.Warn("load frpc config file error: %s", res.Msg)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
content := string(b)
|
||||||
|
|
||||||
|
for _, row := range strings.Split(content, "\n") {
|
||||||
|
row = strings.TrimSpace(row)
|
||||||
|
if strings.HasPrefix(row, "token") {
|
||||||
|
token = row
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tmpRows := make([]string, 0)
|
||||||
|
for _, row := range strings.Split(string(body), "\n") {
|
||||||
|
row = strings.TrimSpace(row)
|
||||||
|
if strings.HasPrefix(row, "token") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
tmpRows = append(tmpRows, row)
|
||||||
|
}
|
||||||
|
|
||||||
|
newRows := make([]string, 0)
|
||||||
|
if token != "" {
|
||||||
|
for _, row := range tmpRows {
|
||||||
|
newRows = append(newRows, row)
|
||||||
|
if strings.HasPrefix(row, "[common]") {
|
||||||
|
newRows = append(newRows, token)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
newRows = tmpRows
|
||||||
|
}
|
||||||
|
content = strings.Join(newRows, "\n")
|
||||||
|
|
||||||
|
err = ioutil.WriteFile(svr.cfgFile, []byte(content), 0644)
|
||||||
|
if err != nil {
|
||||||
|
res.Code = 500
|
||||||
|
res.Msg = fmt.Sprintf("write content to frpc config file error: %v", err)
|
||||||
|
log.Warn("%s", res.Msg)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
package client
|
package client
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/tls"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"runtime/debug"
|
"runtime/debug"
|
||||||
@@ -22,7 +23,6 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/fatedier/frp/client/proxy"
|
"github.com/fatedier/frp/client/proxy"
|
||||||
"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/log"
|
"github.com/fatedier/frp/utils/log"
|
||||||
@@ -64,16 +64,24 @@ type Control struct {
|
|||||||
// last time got the Pong message
|
// last time got the Pong message
|
||||||
lastPong time.Time
|
lastPong time.Time
|
||||||
|
|
||||||
|
// The client configuration
|
||||||
|
clientCfg config.ClientCommonConf
|
||||||
|
|
||||||
readerShutdown *shutdown.Shutdown
|
readerShutdown *shutdown.Shutdown
|
||||||
writerShutdown *shutdown.Shutdown
|
writerShutdown *shutdown.Shutdown
|
||||||
msgHandlerShutdown *shutdown.Shutdown
|
msgHandlerShutdown *shutdown.Shutdown
|
||||||
|
|
||||||
|
// The UDP port that the server is listening on
|
||||||
|
serverUDPPort int
|
||||||
|
|
||||||
mu sync.RWMutex
|
mu sync.RWMutex
|
||||||
|
|
||||||
log.Logger
|
log.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewControl(runId string, conn frpNet.Conn, session *fmux.Session, pxyCfgs map[string]config.ProxyConf, visitorCfgs map[string]config.VisitorConf) *Control {
|
func NewControl(runId string, conn frpNet.Conn, session *fmux.Session, clientCfg config.ClientCommonConf,
|
||||||
|
pxyCfgs map[string]config.ProxyConf, visitorCfgs map[string]config.VisitorConf, serverUDPPort int) *Control {
|
||||||
|
|
||||||
ctl := &Control{
|
ctl := &Control{
|
||||||
runId: runId,
|
runId: runId,
|
||||||
conn: conn,
|
conn: conn,
|
||||||
@@ -83,12 +91,14 @@ func NewControl(runId string, conn frpNet.Conn, session *fmux.Session, pxyCfgs m
|
|||||||
readCh: make(chan msg.Message, 100),
|
readCh: make(chan msg.Message, 100),
|
||||||
closedCh: make(chan struct{}),
|
closedCh: make(chan struct{}),
|
||||||
closedDoneCh: make(chan struct{}),
|
closedDoneCh: make(chan struct{}),
|
||||||
|
clientCfg: clientCfg,
|
||||||
readerShutdown: shutdown.New(),
|
readerShutdown: shutdown.New(),
|
||||||
writerShutdown: shutdown.New(),
|
writerShutdown: shutdown.New(),
|
||||||
msgHandlerShutdown: shutdown.New(),
|
msgHandlerShutdown: shutdown.New(),
|
||||||
|
serverUDPPort: serverUDPPort,
|
||||||
Logger: log.NewPrefixLogger(""),
|
Logger: log.NewPrefixLogger(""),
|
||||||
}
|
}
|
||||||
ctl.pm = proxy.NewProxyManager(ctl.sendCh, runId)
|
ctl.pm = proxy.NewProxyManager(ctl.sendCh, runId, clientCfg, serverUDPPort)
|
||||||
|
|
||||||
ctl.vm = NewVisitorManager(ctl)
|
ctl.vm = NewVisitorManager(ctl)
|
||||||
ctl.vm.Reload(visitorCfgs)
|
ctl.vm.Reload(visitorCfgs)
|
||||||
@@ -130,7 +140,7 @@ func (ctl *Control) HandleReqWorkConn(inMsg *msg.ReqWorkConn) {
|
|||||||
workConn.AddLogPrefix(startMsg.ProxyName)
|
workConn.AddLogPrefix(startMsg.ProxyName)
|
||||||
|
|
||||||
// dispatch this work connection to related proxy
|
// dispatch this work connection to related proxy
|
||||||
ctl.pm.HandleWorkConn(startMsg.ProxyName, workConn)
|
ctl.pm.HandleWorkConn(startMsg.ProxyName, workConn, &startMsg)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ctl *Control) HandleNewProxyResp(inMsg *msg.NewProxyResp) {
|
func (ctl *Control) HandleNewProxyResp(inMsg *msg.NewProxyResp) {
|
||||||
@@ -147,6 +157,9 @@ func (ctl *Control) HandleNewProxyResp(inMsg *msg.NewProxyResp) {
|
|||||||
func (ctl *Control) Close() error {
|
func (ctl *Control) Close() error {
|
||||||
ctl.pm.Close()
|
ctl.pm.Close()
|
||||||
ctl.conn.Close()
|
ctl.conn.Close()
|
||||||
|
if ctl.session != nil {
|
||||||
|
ctl.session.Close()
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -157,7 +170,7 @@ func (ctl *Control) ClosedDoneCh() <-chan struct{} {
|
|||||||
|
|
||||||
// connectServer return a new connection to frps
|
// 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 ctl.clientCfg.TcpMux {
|
||||||
stream, errRet := ctl.session.OpenStream()
|
stream, errRet := ctl.session.OpenStream()
|
||||||
if errRet != nil {
|
if errRet != nil {
|
||||||
err = errRet
|
err = errRet
|
||||||
@@ -166,8 +179,14 @@ func (ctl *Control) connectServer() (conn frpNet.Conn, err error) {
|
|||||||
}
|
}
|
||||||
conn = frpNet.WrapConn(stream)
|
conn = frpNet.WrapConn(stream)
|
||||||
} else {
|
} else {
|
||||||
conn, err = frpNet.ConnectServerByProxy(g.GlbClientCfg.HttpProxy, g.GlbClientCfg.Protocol,
|
var tlsConfig *tls.Config
|
||||||
fmt.Sprintf("%s:%d", g.GlbClientCfg.ServerAddr, g.GlbClientCfg.ServerPort))
|
if ctl.clientCfg.TLSEnable {
|
||||||
|
tlsConfig = &tls.Config{
|
||||||
|
InsecureSkipVerify: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
conn, err = frpNet.ConnectServerByProxyWithTLS(ctl.clientCfg.HttpProxy, ctl.clientCfg.Protocol,
|
||||||
|
fmt.Sprintf("%s:%d", ctl.clientCfg.ServerAddr, ctl.clientCfg.ServerPort), tlsConfig)
|
||||||
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)
|
||||||
return
|
return
|
||||||
@@ -187,7 +206,7 @@ func (ctl *Control) reader() {
|
|||||||
defer ctl.readerShutdown.Done()
|
defer ctl.readerShutdown.Done()
|
||||||
defer close(ctl.closedCh)
|
defer close(ctl.closedCh)
|
||||||
|
|
||||||
encReader := crypto.NewReader(ctl.conn, []byte(g.GlbClientCfg.Token))
|
encReader := crypto.NewReader(ctl.conn, []byte(ctl.clientCfg.Token))
|
||||||
for {
|
for {
|
||||||
if m, err := msg.ReadMsg(encReader); err != nil {
|
if m, err := msg.ReadMsg(encReader); err != nil {
|
||||||
if err == io.EOF {
|
if err == io.EOF {
|
||||||
@@ -195,6 +214,7 @@ func (ctl *Control) reader() {
|
|||||||
return
|
return
|
||||||
} else {
|
} else {
|
||||||
ctl.Warn("read error: %v", err)
|
ctl.Warn("read error: %v", err)
|
||||||
|
ctl.conn.Close()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -206,7 +226,7 @@ func (ctl *Control) reader() {
|
|||||||
// writer writes messages got from sendCh to frps
|
// writer writes messages got from sendCh to frps
|
||||||
func (ctl *Control) writer() {
|
func (ctl *Control) writer() {
|
||||||
defer ctl.writerShutdown.Done()
|
defer ctl.writerShutdown.Done()
|
||||||
encWriter, err := crypto.NewWriter(ctl.conn, []byte(g.GlbClientCfg.Token))
|
encWriter, err := crypto.NewWriter(ctl.conn, []byte(ctl.clientCfg.Token))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctl.conn.Error("crypto new writer error: %v", err)
|
ctl.conn.Error("crypto new writer error: %v", err)
|
||||||
ctl.conn.Close()
|
ctl.conn.Close()
|
||||||
@@ -235,7 +255,7 @@ func (ctl *Control) msgHandler() {
|
|||||||
}()
|
}()
|
||||||
defer ctl.msgHandlerShutdown.Done()
|
defer ctl.msgHandlerShutdown.Done()
|
||||||
|
|
||||||
hbSend := time.NewTicker(time.Duration(g.GlbClientCfg.HeartBeatInterval) * time.Second)
|
hbSend := time.NewTicker(time.Duration(ctl.clientCfg.HeartBeatInterval) * time.Second)
|
||||||
defer hbSend.Stop()
|
defer hbSend.Stop()
|
||||||
hbCheck := time.NewTicker(time.Second)
|
hbCheck := time.NewTicker(time.Second)
|
||||||
defer hbCheck.Stop()
|
defer hbCheck.Stop()
|
||||||
@@ -249,7 +269,7 @@ func (ctl *Control) msgHandler() {
|
|||||||
ctl.Debug("send heartbeat to server")
|
ctl.Debug("send heartbeat to server")
|
||||||
ctl.sendCh <- &msg.Ping{}
|
ctl.sendCh <- &msg.Ping{}
|
||||||
case <-hbCheck.C:
|
case <-hbCheck.C:
|
||||||
if time.Since(ctl.lastPong) > time.Duration(g.GlbClientCfg.HeartBeatTimeout)*time.Second {
|
if time.Since(ctl.lastPong) > time.Duration(ctl.clientCfg.HeartBeatTimeout)*time.Second {
|
||||||
ctl.Warn("heartbeat timeout")
|
ctl.Warn("heartbeat timeout")
|
||||||
// let reader() stop
|
// let reader() stop
|
||||||
ctl.conn.Close()
|
ctl.conn.Close()
|
||||||
@@ -293,6 +313,9 @@ func (ctl *Control) worker() {
|
|||||||
ctl.vm.Close()
|
ctl.vm.Close()
|
||||||
|
|
||||||
close(ctl.closedDoneCh)
|
close(ctl.closedDoneCh)
|
||||||
|
if ctl.session != nil {
|
||||||
|
ctl.session.Close()
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,8 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
@@ -94,12 +96,12 @@ func (monitor *HealthCheckMonitor) Stop() {
|
|||||||
|
|
||||||
func (monitor *HealthCheckMonitor) checkWorker() {
|
func (monitor *HealthCheckMonitor) checkWorker() {
|
||||||
for {
|
for {
|
||||||
ctx, cancel := context.WithDeadline(monitor.ctx, time.Now().Add(monitor.timeout))
|
doCtx, cancel := context.WithDeadline(monitor.ctx, time.Now().Add(monitor.timeout))
|
||||||
err := monitor.doCheck(ctx)
|
err := monitor.doCheck(doCtx)
|
||||||
|
|
||||||
// check if this monitor has been closed
|
// check if this monitor has been closed
|
||||||
select {
|
select {
|
||||||
case <-ctx.Done():
|
case <-monitor.ctx.Done():
|
||||||
cancel()
|
cancel()
|
||||||
return
|
return
|
||||||
default:
|
default:
|
||||||
@@ -170,6 +172,8 @@ func (monitor *HealthCheckMonitor) doHttpCheck(ctx context.Context) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
io.Copy(ioutil.Discard, resp.Body)
|
||||||
|
|
||||||
if resp.StatusCode/100 != 2 {
|
if resp.StatusCode/100 != 2 {
|
||||||
return fmt.Errorf("do http health check, StatusCode is [%d] not 2xx", resp.StatusCode)
|
return fmt.Errorf("do http health check, StatusCode is [%d] not 2xx", resp.StatusCode)
|
||||||
|
|||||||
@@ -18,11 +18,13 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
"net"
|
"net"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"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/models/plugin"
|
"github.com/fatedier/frp/models/plugin"
|
||||||
@@ -33,6 +35,8 @@ import (
|
|||||||
"github.com/fatedier/golib/errors"
|
"github.com/fatedier/golib/errors"
|
||||||
frpIo "github.com/fatedier/golib/io"
|
frpIo "github.com/fatedier/golib/io"
|
||||||
"github.com/fatedier/golib/pool"
|
"github.com/fatedier/golib/pool"
|
||||||
|
fmux "github.com/hashicorp/yamux"
|
||||||
|
pp "github.com/pires/go-proxyproto"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Proxy defines how to handle work connections for different proxy type.
|
// Proxy defines how to handle work connections for different proxy type.
|
||||||
@@ -40,45 +44,47 @@ type Proxy interface {
|
|||||||
Run() error
|
Run() error
|
||||||
|
|
||||||
// InWorkConn accept work connections registered to server.
|
// InWorkConn accept work connections registered to server.
|
||||||
InWorkConn(conn frpNet.Conn)
|
InWorkConn(frpNet.Conn, *msg.StartWorkConn)
|
||||||
|
|
||||||
Close()
|
Close()
|
||||||
log.Logger
|
log.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewProxy(pxyConf config.ProxyConf) (pxy Proxy) {
|
func NewProxy(pxyConf config.ProxyConf, clientCfg config.ClientCommonConf, serverUDPPort int) (pxy Proxy) {
|
||||||
baseProxy := BaseProxy{
|
baseProxy := BaseProxy{
|
||||||
Logger: log.NewPrefixLogger(pxyConf.GetBaseInfo().ProxyName),
|
Logger: log.NewPrefixLogger(pxyConf.GetBaseInfo().ProxyName),
|
||||||
|
clientCfg: clientCfg,
|
||||||
|
serverUDPPort: serverUDPPort,
|
||||||
}
|
}
|
||||||
switch cfg := pxyConf.(type) {
|
switch cfg := pxyConf.(type) {
|
||||||
case *config.TcpProxyConf:
|
case *config.TcpProxyConf:
|
||||||
pxy = &TcpProxy{
|
pxy = &TcpProxy{
|
||||||
BaseProxy: baseProxy,
|
BaseProxy: &baseProxy,
|
||||||
cfg: cfg,
|
cfg: cfg,
|
||||||
}
|
}
|
||||||
case *config.UdpProxyConf:
|
case *config.UdpProxyConf:
|
||||||
pxy = &UdpProxy{
|
pxy = &UdpProxy{
|
||||||
BaseProxy: baseProxy,
|
BaseProxy: &baseProxy,
|
||||||
cfg: cfg,
|
cfg: cfg,
|
||||||
}
|
}
|
||||||
case *config.HttpProxyConf:
|
case *config.HttpProxyConf:
|
||||||
pxy = &HttpProxy{
|
pxy = &HttpProxy{
|
||||||
BaseProxy: baseProxy,
|
BaseProxy: &baseProxy,
|
||||||
cfg: cfg,
|
cfg: cfg,
|
||||||
}
|
}
|
||||||
case *config.HttpsProxyConf:
|
case *config.HttpsProxyConf:
|
||||||
pxy = &HttpsProxy{
|
pxy = &HttpsProxy{
|
||||||
BaseProxy: baseProxy,
|
BaseProxy: &baseProxy,
|
||||||
cfg: cfg,
|
cfg: cfg,
|
||||||
}
|
}
|
||||||
case *config.StcpProxyConf:
|
case *config.StcpProxyConf:
|
||||||
pxy = &StcpProxy{
|
pxy = &StcpProxy{
|
||||||
BaseProxy: baseProxy,
|
BaseProxy: &baseProxy,
|
||||||
cfg: cfg,
|
cfg: cfg,
|
||||||
}
|
}
|
||||||
case *config.XtcpProxyConf:
|
case *config.XtcpProxyConf:
|
||||||
pxy = &XtcpProxy{
|
pxy = &XtcpProxy{
|
||||||
BaseProxy: baseProxy,
|
BaseProxy: &baseProxy,
|
||||||
cfg: cfg,
|
cfg: cfg,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -86,14 +92,16 @@ func NewProxy(pxyConf config.ProxyConf) (pxy Proxy) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type BaseProxy struct {
|
type BaseProxy struct {
|
||||||
closed bool
|
closed bool
|
||||||
mu sync.RWMutex
|
mu sync.RWMutex
|
||||||
|
clientCfg config.ClientCommonConf
|
||||||
|
serverUDPPort int
|
||||||
log.Logger
|
log.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
// TCP
|
// TCP
|
||||||
type TcpProxy struct {
|
type TcpProxy struct {
|
||||||
BaseProxy
|
*BaseProxy
|
||||||
|
|
||||||
cfg *config.TcpProxyConf
|
cfg *config.TcpProxyConf
|
||||||
proxyPlugin plugin.Plugin
|
proxyPlugin plugin.Plugin
|
||||||
@@ -115,14 +123,14 @@ func (pxy *TcpProxy) Close() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pxy *TcpProxy) InWorkConn(conn frpNet.Conn) {
|
func (pxy *TcpProxy) InWorkConn(conn frpNet.Conn, m *msg.StartWorkConn) {
|
||||||
HandleTcpWorkConnection(&pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, conn,
|
HandleTcpWorkConnection(&pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, conn,
|
||||||
[]byte(g.GlbClientCfg.Token))
|
[]byte(pxy.clientCfg.Token), m)
|
||||||
}
|
}
|
||||||
|
|
||||||
// HTTP
|
// HTTP
|
||||||
type HttpProxy struct {
|
type HttpProxy struct {
|
||||||
BaseProxy
|
*BaseProxy
|
||||||
|
|
||||||
cfg *config.HttpProxyConf
|
cfg *config.HttpProxyConf
|
||||||
proxyPlugin plugin.Plugin
|
proxyPlugin plugin.Plugin
|
||||||
@@ -144,14 +152,14 @@ func (pxy *HttpProxy) Close() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pxy *HttpProxy) InWorkConn(conn frpNet.Conn) {
|
func (pxy *HttpProxy) InWorkConn(conn frpNet.Conn, m *msg.StartWorkConn) {
|
||||||
HandleTcpWorkConnection(&pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, conn,
|
HandleTcpWorkConnection(&pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, conn,
|
||||||
[]byte(g.GlbClientCfg.Token))
|
[]byte(pxy.clientCfg.Token), m)
|
||||||
}
|
}
|
||||||
|
|
||||||
// HTTPS
|
// HTTPS
|
||||||
type HttpsProxy struct {
|
type HttpsProxy struct {
|
||||||
BaseProxy
|
*BaseProxy
|
||||||
|
|
||||||
cfg *config.HttpsProxyConf
|
cfg *config.HttpsProxyConf
|
||||||
proxyPlugin plugin.Plugin
|
proxyPlugin plugin.Plugin
|
||||||
@@ -173,14 +181,14 @@ func (pxy *HttpsProxy) Close() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pxy *HttpsProxy) InWorkConn(conn frpNet.Conn) {
|
func (pxy *HttpsProxy) InWorkConn(conn frpNet.Conn, m *msg.StartWorkConn) {
|
||||||
HandleTcpWorkConnection(&pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, conn,
|
HandleTcpWorkConnection(&pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, conn,
|
||||||
[]byte(g.GlbClientCfg.Token))
|
[]byte(pxy.clientCfg.Token), m)
|
||||||
}
|
}
|
||||||
|
|
||||||
// STCP
|
// STCP
|
||||||
type StcpProxy struct {
|
type StcpProxy struct {
|
||||||
BaseProxy
|
*BaseProxy
|
||||||
|
|
||||||
cfg *config.StcpProxyConf
|
cfg *config.StcpProxyConf
|
||||||
proxyPlugin plugin.Plugin
|
proxyPlugin plugin.Plugin
|
||||||
@@ -202,14 +210,14 @@ func (pxy *StcpProxy) Close() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pxy *StcpProxy) InWorkConn(conn frpNet.Conn) {
|
func (pxy *StcpProxy) InWorkConn(conn frpNet.Conn, m *msg.StartWorkConn) {
|
||||||
HandleTcpWorkConnection(&pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, conn,
|
HandleTcpWorkConnection(&pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, conn,
|
||||||
[]byte(g.GlbClientCfg.Token))
|
[]byte(pxy.clientCfg.Token), m)
|
||||||
}
|
}
|
||||||
|
|
||||||
// XTCP
|
// XTCP
|
||||||
type XtcpProxy struct {
|
type XtcpProxy struct {
|
||||||
BaseProxy
|
*BaseProxy
|
||||||
|
|
||||||
cfg *config.XtcpProxyConf
|
cfg *config.XtcpProxyConf
|
||||||
proxyPlugin plugin.Plugin
|
proxyPlugin plugin.Plugin
|
||||||
@@ -231,7 +239,7 @@ func (pxy *XtcpProxy) Close() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pxy *XtcpProxy) InWorkConn(conn frpNet.Conn) {
|
func (pxy *XtcpProxy) InWorkConn(conn frpNet.Conn, m *msg.StartWorkConn) {
|
||||||
defer conn.Close()
|
defer conn.Close()
|
||||||
var natHoleSidMsg msg.NatHoleSid
|
var natHoleSidMsg msg.NatHoleSid
|
||||||
err := msg.ReadMsgInto(conn, &natHoleSidMsg)
|
err := msg.ReadMsgInto(conn, &natHoleSidMsg)
|
||||||
@@ -245,7 +253,7 @@ func (pxy *XtcpProxy) InWorkConn(conn frpNet.Conn) {
|
|||||||
Sid: natHoleSidMsg.Sid,
|
Sid: natHoleSidMsg.Sid,
|
||||||
}
|
}
|
||||||
raddr, _ := net.ResolveUDPAddr("udp",
|
raddr, _ := net.ResolveUDPAddr("udp",
|
||||||
fmt.Sprintf("%s:%d", g.GlbClientCfg.ServerAddr, g.GlbClientCfg.ServerUdpPort))
|
fmt.Sprintf("%s:%d", pxy.clientCfg.ServerAddr, pxy.serverUDPPort))
|
||||||
clientConn, err := net.DialUDP("udp", nil, raddr)
|
clientConn, err := net.DialUDP("udp", nil, raddr)
|
||||||
defer clientConn.Close()
|
defer clientConn.Close()
|
||||||
|
|
||||||
@@ -278,37 +286,102 @@ func (pxy *XtcpProxy) InWorkConn(conn frpNet.Conn) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
pxy.Trace("get natHoleRespMsg, sid [%s], client address [%s]", natHoleRespMsg.Sid, natHoleRespMsg.ClientAddr)
|
pxy.Trace("get natHoleRespMsg, sid [%s], client address [%s] visitor address [%s]", natHoleRespMsg.Sid, natHoleRespMsg.ClientAddr, natHoleRespMsg.VisitorAddr)
|
||||||
|
|
||||||
// Send sid to visitor udp address.
|
// Send detect message
|
||||||
time.Sleep(time.Second)
|
array := strings.Split(natHoleRespMsg.VisitorAddr, ":")
|
||||||
|
if len(array) <= 1 {
|
||||||
|
pxy.Error("get NatHoleResp visitor address error: %v", natHoleRespMsg.VisitorAddr)
|
||||||
|
}
|
||||||
laddr, _ := net.ResolveUDPAddr("udp", clientConn.LocalAddr().String())
|
laddr, _ := net.ResolveUDPAddr("udp", clientConn.LocalAddr().String())
|
||||||
daddr, err := net.ResolveUDPAddr("udp", natHoleRespMsg.VisitorAddr)
|
/*
|
||||||
|
for i := 1000; i < 65000; i++ {
|
||||||
|
pxy.sendDetectMsg(array[0], int64(i), laddr, "a")
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
port, err := strconv.ParseInt(array[1], 10, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
pxy.Error("resolve visitor udp address error: %v", err)
|
pxy.Error("get natHoleResp visitor address error: %v", natHoleRespMsg.VisitorAddr)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
pxy.sendDetectMsg(array[0], int(port), laddr, []byte(natHoleRespMsg.Sid))
|
||||||
|
pxy.Trace("send all detect msg done")
|
||||||
|
|
||||||
lConn, err := net.DialUDP("udp", laddr, daddr)
|
msg.WriteMsg(conn, &msg.NatHoleClientDetectOK{})
|
||||||
|
|
||||||
|
// Listen for clientConn's address and wait for visitor connection
|
||||||
|
lConn, err := net.ListenUDP("udp", laddr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
pxy.Error("dial visitor udp address error: %v", err)
|
pxy.Error("listen on visitorConn's local adress error: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
lConn.Write([]byte(natHoleRespMsg.Sid))
|
defer lConn.Close()
|
||||||
|
|
||||||
kcpConn, err := frpNet.NewKcpConnFromUdp(lConn, true, natHoleRespMsg.VisitorAddr)
|
lConn.SetReadDeadline(time.Now().Add(8 * time.Second))
|
||||||
|
sidBuf := pool.GetBuf(1024)
|
||||||
|
var uAddr *net.UDPAddr
|
||||||
|
n, uAddr, err = lConn.ReadFromUDP(sidBuf)
|
||||||
|
if err != nil {
|
||||||
|
pxy.Warn("get sid from visitor error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
lConn.SetReadDeadline(time.Time{})
|
||||||
|
if string(sidBuf[:n]) != natHoleRespMsg.Sid {
|
||||||
|
pxy.Warn("incorrect sid from visitor")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
pool.PutBuf(sidBuf)
|
||||||
|
pxy.Info("nat hole connection make success, sid [%s]", natHoleRespMsg.Sid)
|
||||||
|
|
||||||
|
lConn.WriteToUDP(sidBuf[:n], uAddr)
|
||||||
|
|
||||||
|
kcpConn, err := frpNet.NewKcpConnFromUdp(lConn, false, natHoleRespMsg.VisitorAddr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
pxy.Error("create kcp connection from udp connection error: %v", err)
|
pxy.Error("create kcp connection from udp connection error: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fmuxCfg := fmux.DefaultConfig()
|
||||||
|
fmuxCfg.KeepAliveInterval = 5 * time.Second
|
||||||
|
fmuxCfg.LogOutput = ioutil.Discard
|
||||||
|
sess, err := fmux.Server(kcpConn, fmuxCfg)
|
||||||
|
if err != nil {
|
||||||
|
pxy.Error("create yamux server from kcp connection error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer sess.Close()
|
||||||
|
muxConn, err := sess.Accept()
|
||||||
|
if err != nil {
|
||||||
|
pxy.Error("accept for yamux connection error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
HandleTcpWorkConnection(&pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf,
|
HandleTcpWorkConnection(&pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf,
|
||||||
frpNet.WrapConn(kcpConn), []byte(pxy.cfg.Sk))
|
frpNet.WrapConn(muxConn), []byte(pxy.cfg.Sk), m)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pxy *XtcpProxy) sendDetectMsg(addr string, port int, laddr *net.UDPAddr, content []byte) (err error) {
|
||||||
|
daddr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", addr, port))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
tConn, err := net.DialUDP("udp", laddr, daddr)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
//uConn := ipv4.NewConn(tConn)
|
||||||
|
//uConn.SetTTL(3)
|
||||||
|
|
||||||
|
tConn.Write(content)
|
||||||
|
tConn.Close()
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// UDP
|
// UDP
|
||||||
type UdpProxy struct {
|
type UdpProxy struct {
|
||||||
BaseProxy
|
*BaseProxy
|
||||||
|
|
||||||
cfg *config.UdpProxyConf
|
cfg *config.UdpProxyConf
|
||||||
|
|
||||||
@@ -346,7 +419,7 @@ func (pxy *UdpProxy) Close() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pxy *UdpProxy) InWorkConn(conn frpNet.Conn) {
|
func (pxy *UdpProxy) InWorkConn(conn frpNet.Conn, m *msg.StartWorkConn) {
|
||||||
pxy.Info("incoming a new work connection for udp proxy, %s", conn.RemoteAddr().String())
|
pxy.Info("incoming a new work connection for udp proxy, %s", conn.RemoteAddr().String())
|
||||||
// close resources releated with old workConn
|
// close resources releated with old workConn
|
||||||
pxy.Close()
|
pxy.Close()
|
||||||
@@ -413,7 +486,7 @@ func (pxy *UdpProxy) InWorkConn(conn frpNet.Conn) {
|
|||||||
|
|
||||||
// Common handler for tcp work connections.
|
// Common handler for tcp work connections.
|
||||||
func HandleTcpWorkConnection(localInfo *config.LocalSvrConf, proxyPlugin plugin.Plugin,
|
func HandleTcpWorkConnection(localInfo *config.LocalSvrConf, proxyPlugin plugin.Plugin,
|
||||||
baseInfo *config.BaseProxyConf, workConn frpNet.Conn, encKey []byte) {
|
baseInfo *config.BaseProxyConf, workConn frpNet.Conn, encKey []byte, m *msg.StartWorkConn) {
|
||||||
|
|
||||||
var (
|
var (
|
||||||
remote io.ReadWriteCloser
|
remote io.ReadWriteCloser
|
||||||
@@ -433,10 +506,43 @@ func HandleTcpWorkConnection(localInfo *config.LocalSvrConf, proxyPlugin plugin.
|
|||||||
remote = frpIo.WithCompression(remote)
|
remote = frpIo.WithCompression(remote)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// check if we need to send proxy protocol info
|
||||||
|
var extraInfo []byte
|
||||||
|
if baseInfo.ProxyProtocolVersion != "" {
|
||||||
|
if m.SrcAddr != "" && m.SrcPort != 0 {
|
||||||
|
if m.DstAddr == "" {
|
||||||
|
m.DstAddr = "127.0.0.1"
|
||||||
|
}
|
||||||
|
h := &pp.Header{
|
||||||
|
Command: pp.PROXY,
|
||||||
|
SourceAddress: net.ParseIP(m.SrcAddr),
|
||||||
|
SourcePort: m.SrcPort,
|
||||||
|
DestinationAddress: net.ParseIP(m.DstAddr),
|
||||||
|
DestinationPort: m.DstPort,
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.Contains(m.SrcAddr, ".") {
|
||||||
|
h.TransportProtocol = pp.TCPv4
|
||||||
|
} else {
|
||||||
|
h.TransportProtocol = pp.TCPv6
|
||||||
|
}
|
||||||
|
|
||||||
|
if baseInfo.ProxyProtocolVersion == "v1" {
|
||||||
|
h.Version = 1
|
||||||
|
} else if baseInfo.ProxyProtocolVersion == "v2" {
|
||||||
|
h.Version = 2
|
||||||
|
}
|
||||||
|
|
||||||
|
buf := bytes.NewBuffer(nil)
|
||||||
|
h.WriteTo(buf)
|
||||||
|
extraInfo = buf.Bytes()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if proxyPlugin != nil {
|
if proxyPlugin != nil {
|
||||||
// if plugin is set, let plugin handle connections first
|
// if plugin is set, let plugin handle connections first
|
||||||
workConn.Debug("handle by plugin: %s", proxyPlugin.Name())
|
workConn.Debug("handle by plugin: %s", proxyPlugin.Name())
|
||||||
proxyPlugin.Handle(remote, workConn)
|
proxyPlugin.Handle(remote, workConn, extraInfo)
|
||||||
workConn.Debug("handle by plugin finished")
|
workConn.Debug("handle by plugin finished")
|
||||||
return
|
return
|
||||||
} else {
|
} else {
|
||||||
@@ -449,6 +555,11 @@ func HandleTcpWorkConnection(localInfo *config.LocalSvrConf, proxyPlugin plugin.
|
|||||||
|
|
||||||
workConn.Debug("join connections, localConn(l[%s] r[%s]) workConn(l[%s] r[%s])", localConn.LocalAddr().String(),
|
workConn.Debug("join connections, localConn(l[%s] r[%s]) workConn(l[%s] r[%s])", localConn.LocalAddr().String(),
|
||||||
localConn.RemoteAddr().String(), workConn.LocalAddr().String(), workConn.RemoteAddr().String())
|
localConn.RemoteAddr().String(), workConn.LocalAddr().String(), workConn.RemoteAddr().String())
|
||||||
|
|
||||||
|
if len(extraInfo) > 0 {
|
||||||
|
localConn.Write(extraInfo)
|
||||||
|
}
|
||||||
|
|
||||||
frpIo.Join(localConn, remote)
|
frpIo.Join(localConn, remote)
|
||||||
workConn.Debug("join connections closed")
|
workConn.Debug("join connections closed")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,17 +20,24 @@ type ProxyManager struct {
|
|||||||
closed bool
|
closed bool
|
||||||
mu sync.RWMutex
|
mu sync.RWMutex
|
||||||
|
|
||||||
|
clientCfg config.ClientCommonConf
|
||||||
|
|
||||||
|
// The UDP port that the server is listening on
|
||||||
|
serverUDPPort int
|
||||||
|
|
||||||
logPrefix string
|
logPrefix string
|
||||||
log.Logger
|
log.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewProxyManager(msgSendCh chan (msg.Message), logPrefix string) *ProxyManager {
|
func NewProxyManager(msgSendCh chan (msg.Message), logPrefix string, clientCfg config.ClientCommonConf, serverUDPPort int) *ProxyManager {
|
||||||
return &ProxyManager{
|
return &ProxyManager{
|
||||||
proxies: make(map[string]*ProxyWrapper),
|
proxies: make(map[string]*ProxyWrapper),
|
||||||
sendCh: msgSendCh,
|
sendCh: msgSendCh,
|
||||||
closed: false,
|
closed: false,
|
||||||
logPrefix: logPrefix,
|
clientCfg: clientCfg,
|
||||||
Logger: log.NewPrefixLogger(logPrefix),
|
serverUDPPort: serverUDPPort,
|
||||||
|
logPrefix: logPrefix,
|
||||||
|
Logger: log.NewPrefixLogger(logPrefix),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -58,12 +65,12 @@ func (pm *ProxyManager) Close() {
|
|||||||
pm.proxies = make(map[string]*ProxyWrapper)
|
pm.proxies = make(map[string]*ProxyWrapper)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pm *ProxyManager) HandleWorkConn(name string, workConn frpNet.Conn) {
|
func (pm *ProxyManager) HandleWorkConn(name string, workConn frpNet.Conn, m *msg.StartWorkConn) {
|
||||||
pm.mu.RLock()
|
pm.mu.RLock()
|
||||||
pw, ok := pm.proxies[name]
|
pw, ok := pm.proxies[name]
|
||||||
pm.mu.RUnlock()
|
pm.mu.RUnlock()
|
||||||
if ok {
|
if ok {
|
||||||
pw.InWorkConn(workConn)
|
pw.InWorkConn(workConn, m)
|
||||||
} else {
|
} else {
|
||||||
workConn.Close()
|
workConn.Close()
|
||||||
}
|
}
|
||||||
@@ -126,7 +133,7 @@ func (pm *ProxyManager) Reload(pxyCfgs map[string]config.ProxyConf) {
|
|||||||
addPxyNames := make([]string, 0)
|
addPxyNames := make([]string, 0)
|
||||||
for name, cfg := range pxyCfgs {
|
for name, cfg := range pxyCfgs {
|
||||||
if _, ok := pm.proxies[name]; !ok {
|
if _, ok := pm.proxies[name]; !ok {
|
||||||
pxy := NewProxyWrapper(cfg, pm.HandleEvent, pm.logPrefix)
|
pxy := NewProxyWrapper(cfg, pm.clientCfg, pm.HandleEvent, pm.logPrefix, pm.serverUDPPort)
|
||||||
pm.proxies[name] = pxy
|
pm.proxies[name] = pxy
|
||||||
addPxyNames = append(addPxyNames, name)
|
addPxyNames = append(addPxyNames, name)
|
||||||
|
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ type ProxyWrapper struct {
|
|||||||
log.Logger
|
log.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewProxyWrapper(cfg config.ProxyConf, eventHandler event.EventHandler, logPrefix string) *ProxyWrapper {
|
func NewProxyWrapper(cfg config.ProxyConf, clientCfg config.ClientCommonConf, eventHandler event.EventHandler, logPrefix string, serverUDPPort int) *ProxyWrapper {
|
||||||
baseInfo := cfg.GetBaseInfo()
|
baseInfo := cfg.GetBaseInfo()
|
||||||
pw := &ProxyWrapper{
|
pw := &ProxyWrapper{
|
||||||
ProxyStatus: ProxyStatus{
|
ProxyStatus: ProxyStatus{
|
||||||
@@ -90,7 +90,7 @@ func NewProxyWrapper(cfg config.ProxyConf, eventHandler event.EventHandler, logP
|
|||||||
pw.Trace("enable health check monitor")
|
pw.Trace("enable health check monitor")
|
||||||
}
|
}
|
||||||
|
|
||||||
pw.pxy = NewProxy(pw.Cfg)
|
pw.pxy = NewProxy(pw.Cfg, clientCfg, serverUDPPort)
|
||||||
return pw
|
return pw
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -217,13 +217,13 @@ func (pw *ProxyWrapper) statusFailedCallback() {
|
|||||||
pw.Info("health check failed")
|
pw.Info("health check failed")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pw *ProxyWrapper) InWorkConn(workConn frpNet.Conn) {
|
func (pw *ProxyWrapper) InWorkConn(workConn frpNet.Conn, m *msg.StartWorkConn) {
|
||||||
pw.mu.RLock()
|
pw.mu.RLock()
|
||||||
pxy := pw.pxy
|
pxy := pw.pxy
|
||||||
pw.mu.RUnlock()
|
pw.mu.RUnlock()
|
||||||
if pxy != nil {
|
if pxy != nil {
|
||||||
workConn.Debug("start a new work connection, localAddr: %s remoteAddr: %s", workConn.LocalAddr().String(), workConn.RemoteAddr().String())
|
workConn.Debug("start a new work connection, localAddr: %s remoteAddr: %s", workConn.LocalAddr().String(), workConn.RemoteAddr().String())
|
||||||
go pxy.InWorkConn(workConn)
|
go pxy.InWorkConn(workConn, m)
|
||||||
} else {
|
} else {
|
||||||
workConn.Close()
|
workConn.Close()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
package client
|
package client
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/tls"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"runtime"
|
"runtime"
|
||||||
@@ -22,7 +23,7 @@ import (
|
|||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/fatedier/frp/g"
|
"github.com/fatedier/frp/assets"
|
||||||
"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/log"
|
"github.com/fatedier/frp/utils/log"
|
||||||
@@ -33,6 +34,7 @@ import (
|
|||||||
fmux "github.com/hashicorp/yamux"
|
fmux "github.com/hashicorp/yamux"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Service is a client service.
|
||||||
type Service struct {
|
type Service struct {
|
||||||
// uniq id got from frps, attach it in loginMsg
|
// uniq id got from frps, attach it in loginMsg
|
||||||
runId string
|
runId string
|
||||||
@@ -41,16 +43,27 @@ type Service struct {
|
|||||||
ctl *Control
|
ctl *Control
|
||||||
ctlMu sync.RWMutex
|
ctlMu sync.RWMutex
|
||||||
|
|
||||||
|
cfg config.ClientCommonConf
|
||||||
pxyCfgs map[string]config.ProxyConf
|
pxyCfgs map[string]config.ProxyConf
|
||||||
visitorCfgs map[string]config.VisitorConf
|
visitorCfgs map[string]config.VisitorConf
|
||||||
cfgMu sync.RWMutex
|
cfgMu sync.RWMutex
|
||||||
|
|
||||||
|
// The configuration file used to initialize this client, or an empty
|
||||||
|
// string if no configuration file was used.
|
||||||
|
cfgFile string
|
||||||
|
|
||||||
|
// This is configured by the login response from frps
|
||||||
|
serverUDPPort int
|
||||||
|
|
||||||
exit uint32 // 0 means not exit
|
exit uint32 // 0 means not exit
|
||||||
closedCh chan int
|
closedCh chan int
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewService(pxyCfgs map[string]config.ProxyConf, visitorCfgs map[string]config.VisitorConf) (svr *Service) {
|
// NewService creates a new client service with the given configuration.
|
||||||
|
func NewService(cfg config.ClientCommonConf, pxyCfgs map[string]config.ProxyConf, visitorCfgs map[string]config.VisitorConf, cfgFile string) (svr *Service, err error) {
|
||||||
svr = &Service{
|
svr = &Service{
|
||||||
|
cfg: cfg,
|
||||||
|
cfgFile: cfgFile,
|
||||||
pxyCfgs: pxyCfgs,
|
pxyCfgs: pxyCfgs,
|
||||||
visitorCfgs: visitorCfgs,
|
visitorCfgs: visitorCfgs,
|
||||||
exit: 0,
|
exit: 0,
|
||||||
@@ -74,14 +87,14 @@ func (svr *Service) Run() error {
|
|||||||
|
|
||||||
// if login_fail_exit is true, just exit this program
|
// if login_fail_exit is true, just exit this program
|
||||||
// otherwise sleep a while and try again to connect to server
|
// otherwise sleep a while and try again to connect to server
|
||||||
if g.GlbClientCfg.LoginFailExit {
|
if svr.cfg.LoginFailExit {
|
||||||
return err
|
return err
|
||||||
} else {
|
} else {
|
||||||
time.Sleep(10 * time.Second)
|
time.Sleep(10 * time.Second)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// login success
|
// login success
|
||||||
ctl := NewControl(svr.runId, conn, session, svr.pxyCfgs, svr.visitorCfgs)
|
ctl := NewControl(svr.runId, conn, session, svr.cfg, svr.pxyCfgs, svr.visitorCfgs, svr.serverUDPPort)
|
||||||
ctl.Run()
|
ctl.Run()
|
||||||
svr.ctlMu.Lock()
|
svr.ctlMu.Lock()
|
||||||
svr.ctl = ctl
|
svr.ctl = ctl
|
||||||
@@ -92,12 +105,18 @@ func (svr *Service) Run() error {
|
|||||||
|
|
||||||
go svr.keepControllerWorking()
|
go svr.keepControllerWorking()
|
||||||
|
|
||||||
if g.GlbClientCfg.AdminPort != 0 {
|
if svr.cfg.AdminPort != 0 {
|
||||||
err := svr.RunAdminServer(g.GlbClientCfg.AdminAddr, g.GlbClientCfg.AdminPort)
|
// Init admin server assets
|
||||||
|
err := assets.Load(svr.cfg.AssetsDir)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Load assets error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = svr.RunAdminServer(svr.cfg.AdminAddr, svr.cfg.AdminPort)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warn("run admin server error: %v", err)
|
log.Warn("run admin server error: %v", err)
|
||||||
}
|
}
|
||||||
log.Info("admin server listen on %s:%d", g.GlbClientCfg.AdminAddr, g.GlbClientCfg.AdminPort)
|
log.Info("admin server listen on %s:%d", svr.cfg.AdminAddr, svr.cfg.AdminPort)
|
||||||
}
|
}
|
||||||
|
|
||||||
<-svr.closedCh
|
<-svr.closedCh
|
||||||
@@ -129,7 +148,7 @@ func (svr *Service) keepControllerWorking() {
|
|||||||
// reconnect success, init delayTime
|
// reconnect success, init delayTime
|
||||||
delayTime = time.Second
|
delayTime = time.Second
|
||||||
|
|
||||||
ctl := NewControl(svr.runId, conn, session, svr.pxyCfgs, svr.visitorCfgs)
|
ctl := NewControl(svr.runId, conn, session, svr.cfg, svr.pxyCfgs, svr.visitorCfgs, svr.serverUDPPort)
|
||||||
ctl.Run()
|
ctl.Run()
|
||||||
svr.ctlMu.Lock()
|
svr.ctlMu.Lock()
|
||||||
svr.ctl = ctl
|
svr.ctl = ctl
|
||||||
@@ -143,8 +162,14 @@ func (svr *Service) keepControllerWorking() {
|
|||||||
// conn: control connection
|
// conn: control connection
|
||||||
// session: if it's not nil, using tcp mux
|
// session: if it's not nil, using tcp mux
|
||||||
func (svr *Service) login() (conn frpNet.Conn, session *fmux.Session, err error) {
|
func (svr *Service) login() (conn frpNet.Conn, session *fmux.Session, err error) {
|
||||||
conn, err = frpNet.ConnectServerByProxy(g.GlbClientCfg.HttpProxy, g.GlbClientCfg.Protocol,
|
var tlsConfig *tls.Config
|
||||||
fmt.Sprintf("%s:%d", g.GlbClientCfg.ServerAddr, g.GlbClientCfg.ServerPort))
|
if svr.cfg.TLSEnable {
|
||||||
|
tlsConfig = &tls.Config{
|
||||||
|
InsecureSkipVerify: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
conn, err = frpNet.ConnectServerByProxyWithTLS(svr.cfg.HttpProxy, svr.cfg.Protocol,
|
||||||
|
fmt.Sprintf("%s:%d", svr.cfg.ServerAddr, svr.cfg.ServerPort), tlsConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -152,10 +177,13 @@ func (svr *Service) login() (conn frpNet.Conn, session *fmux.Session, err error)
|
|||||||
defer func() {
|
defer func() {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
conn.Close()
|
conn.Close()
|
||||||
|
if session != nil {
|
||||||
|
session.Close()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
if g.GlbClientCfg.TcpMux {
|
if svr.cfg.TcpMux {
|
||||||
fmuxCfg := fmux.DefaultConfig()
|
fmuxCfg := fmux.DefaultConfig()
|
||||||
fmuxCfg.KeepAliveInterval = 20 * time.Second
|
fmuxCfg.KeepAliveInterval = 20 * time.Second
|
||||||
fmuxCfg.LogOutput = ioutil.Discard
|
fmuxCfg.LogOutput = ioutil.Discard
|
||||||
@@ -176,10 +204,10 @@ func (svr *Service) login() (conn frpNet.Conn, session *fmux.Session, err error)
|
|||||||
loginMsg := &msg.Login{
|
loginMsg := &msg.Login{
|
||||||
Arch: runtime.GOARCH,
|
Arch: runtime.GOARCH,
|
||||||
Os: runtime.GOOS,
|
Os: runtime.GOOS,
|
||||||
PoolCount: g.GlbClientCfg.PoolCount,
|
PoolCount: svr.cfg.PoolCount,
|
||||||
User: g.GlbClientCfg.User,
|
User: svr.cfg.User,
|
||||||
Version: version.Full(),
|
Version: version.Full(),
|
||||||
PrivilegeKey: util.GetAuthKey(g.GlbClientCfg.Token, now),
|
PrivilegeKey: util.GetAuthKey(svr.cfg.Token, now),
|
||||||
Timestamp: now,
|
Timestamp: now,
|
||||||
RunId: svr.runId,
|
RunId: svr.runId,
|
||||||
}
|
}
|
||||||
@@ -202,7 +230,7 @@ func (svr *Service) login() (conn frpNet.Conn, session *fmux.Session, err error)
|
|||||||
}
|
}
|
||||||
|
|
||||||
svr.runId = loginRespMsg.RunId
|
svr.runId = loginRespMsg.RunId
|
||||||
g.GlbClientCfg.ServerUdpPort = loginRespMsg.ServerUdpPort
|
svr.serverUDPPort = loginRespMsg.ServerUdpPort
|
||||||
log.Info("login to server success, get run id [%s], server udp port [%d]", loginRespMsg.RunId, loginRespMsg.ServerUdpPort)
|
log.Info("login to server success, get run id [%s], server udp port [%d]", loginRespMsg.RunId, loginRespMsg.ServerUdpPort)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,15 +18,11 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
"net"
|
"net"
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"golang.org/x/net/ipv4"
|
|
||||||
|
|
||||||
"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/log"
|
"github.com/fatedier/frp/utils/log"
|
||||||
@@ -35,6 +31,7 @@ import (
|
|||||||
|
|
||||||
frpIo "github.com/fatedier/golib/io"
|
frpIo "github.com/fatedier/golib/io"
|
||||||
"github.com/fatedier/golib/pool"
|
"github.com/fatedier/golib/pool"
|
||||||
|
fmux "github.com/hashicorp/yamux"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Visitor is used for forward traffics from local port tot remote service.
|
// Visitor is used for forward traffics from local port tot remote service.
|
||||||
@@ -52,12 +49,12 @@ func NewVisitor(ctl *Control, cfg config.VisitorConf) (visitor Visitor) {
|
|||||||
switch cfg := cfg.(type) {
|
switch cfg := cfg.(type) {
|
||||||
case *config.StcpVisitorConf:
|
case *config.StcpVisitorConf:
|
||||||
visitor = &StcpVisitor{
|
visitor = &StcpVisitor{
|
||||||
BaseVisitor: baseVisitor,
|
BaseVisitor: &baseVisitor,
|
||||||
cfg: cfg,
|
cfg: cfg,
|
||||||
}
|
}
|
||||||
case *config.XtcpVisitorConf:
|
case *config.XtcpVisitorConf:
|
||||||
visitor = &XtcpVisitor{
|
visitor = &XtcpVisitor{
|
||||||
BaseVisitor: baseVisitor,
|
BaseVisitor: &baseVisitor,
|
||||||
cfg: cfg,
|
cfg: cfg,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -73,7 +70,7 @@ type BaseVisitor struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type StcpVisitor struct {
|
type StcpVisitor struct {
|
||||||
BaseVisitor
|
*BaseVisitor
|
||||||
|
|
||||||
cfg *config.StcpVisitorConf
|
cfg *config.StcpVisitorConf
|
||||||
}
|
}
|
||||||
@@ -160,7 +157,7 @@ func (sv *StcpVisitor) handleConn(userConn frpNet.Conn) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type XtcpVisitor struct {
|
type XtcpVisitor struct {
|
||||||
BaseVisitor
|
*BaseVisitor
|
||||||
|
|
||||||
cfg *config.XtcpVisitorConf
|
cfg *config.XtcpVisitorConf
|
||||||
}
|
}
|
||||||
@@ -195,13 +192,13 @@ func (sv *XtcpVisitor) handleConn(userConn frpNet.Conn) {
|
|||||||
defer userConn.Close()
|
defer userConn.Close()
|
||||||
|
|
||||||
sv.Debug("get a new xtcp user connection")
|
sv.Debug("get a new xtcp user connection")
|
||||||
if g.GlbClientCfg.ServerUdpPort == 0 {
|
if sv.ctl.serverUDPPort == 0 {
|
||||||
sv.Error("xtcp is not supported by server")
|
sv.Error("xtcp is not supported by server")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
raddr, err := net.ResolveUDPAddr("udp",
|
raddr, err := net.ResolveUDPAddr("udp",
|
||||||
fmt.Sprintf("%s:%d", g.GlbClientCfg.ServerAddr, g.GlbClientCfg.ServerUdpPort))
|
fmt.Sprintf("%s:%d", sv.ctl.clientCfg.ServerAddr, sv.ctl.serverUDPPort))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
sv.Error("resolve server UDP addr error")
|
sv.Error("resolve server UDP addr error")
|
||||||
return
|
return
|
||||||
@@ -249,40 +246,31 @@ func (sv *XtcpVisitor) handleConn(userConn frpNet.Conn) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
sv.Trace("get natHoleRespMsg, sid [%s], client address [%s]", natHoleRespMsg.Sid, natHoleRespMsg.ClientAddr)
|
sv.Trace("get natHoleRespMsg, sid [%s], client address [%s], visitor address [%s]", natHoleRespMsg.Sid, natHoleRespMsg.ClientAddr, natHoleRespMsg.VisitorAddr)
|
||||||
|
|
||||||
// Close visitorConn, so we can use it's local address.
|
// Close visitorConn, so we can use it's local address.
|
||||||
visitorConn.Close()
|
visitorConn.Close()
|
||||||
|
|
||||||
// Send detect message.
|
// send sid message to client
|
||||||
array := strings.Split(natHoleRespMsg.ClientAddr, ":")
|
|
||||||
if len(array) <= 1 {
|
|
||||||
sv.Error("get natHoleResp client address error: %s", natHoleRespMsg.ClientAddr)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
laddr, _ := net.ResolveUDPAddr("udp", visitorConn.LocalAddr().String())
|
laddr, _ := net.ResolveUDPAddr("udp", visitorConn.LocalAddr().String())
|
||||||
/*
|
daddr, err := net.ResolveUDPAddr("udp", natHoleRespMsg.ClientAddr)
|
||||||
for i := 1000; i < 65000; i++ {
|
|
||||||
sv.sendDetectMsg(array[0], int64(i), laddr, "a")
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
port, err := strconv.ParseInt(array[1], 10, 64)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
sv.Error("get natHoleResp client address error: %s", natHoleRespMsg.ClientAddr)
|
sv.Error("resolve client udp address error: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
sv.sendDetectMsg(array[0], int(port), laddr, []byte(natHoleRespMsg.Sid))
|
lConn, err := net.DialUDP("udp", laddr, daddr)
|
||||||
sv.Trace("send all detect msg done")
|
if err != nil {
|
||||||
|
sv.Error("dial client udp address error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer lConn.Close()
|
||||||
|
|
||||||
// Listen for visitorConn's address and wait for client connection.
|
lConn.Write([]byte(natHoleRespMsg.Sid))
|
||||||
lConn, err := net.ListenUDP("udp", laddr)
|
|
||||||
if err != nil {
|
// read ack sid from client
|
||||||
sv.Error("listen on visitorConn's local adress error: %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
lConn.SetReadDeadline(time.Now().Add(5 * time.Second))
|
|
||||||
sidBuf := pool.GetBuf(1024)
|
sidBuf := pool.GetBuf(1024)
|
||||||
n, _, err = lConn.ReadFromUDP(sidBuf)
|
lConn.SetReadDeadline(time.Now().Add(8 * time.Second))
|
||||||
|
n, err = lConn.Read(sidBuf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
sv.Warn("get sid from client error: %v", err)
|
sv.Warn("get sid from client error: %v", err)
|
||||||
return
|
return
|
||||||
@@ -292,11 +280,13 @@ func (sv *XtcpVisitor) handleConn(userConn frpNet.Conn) {
|
|||||||
sv.Warn("incorrect sid from client")
|
sv.Warn("incorrect sid from client")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
sv.Info("nat hole connection make success, sid [%s]", string(sidBuf[:n]))
|
|
||||||
pool.PutBuf(sidBuf)
|
pool.PutBuf(sidBuf)
|
||||||
|
|
||||||
|
sv.Info("nat hole connection make success, sid [%s]", natHoleRespMsg.Sid)
|
||||||
|
|
||||||
|
// wrap kcp connection
|
||||||
var remote io.ReadWriteCloser
|
var remote io.ReadWriteCloser
|
||||||
remote, err = frpNet.NewKcpConnFromUdp(lConn, false, natHoleRespMsg.ClientAddr)
|
remote, err = frpNet.NewKcpConnFromUdp(lConn, true, natHoleRespMsg.ClientAddr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
sv.Error("create kcp connection from udp connection error: %v", err)
|
sv.Error("create kcp connection from udp connection error: %v", err)
|
||||||
return
|
return
|
||||||
@@ -314,25 +304,21 @@ func (sv *XtcpVisitor) handleConn(userConn frpNet.Conn) {
|
|||||||
remote = frpIo.WithCompression(remote)
|
remote = frpIo.WithCompression(remote)
|
||||||
}
|
}
|
||||||
|
|
||||||
frpIo.Join(userConn, remote)
|
fmuxCfg := fmux.DefaultConfig()
|
||||||
|
fmuxCfg.KeepAliveInterval = 5 * time.Second
|
||||||
|
fmuxCfg.LogOutput = ioutil.Discard
|
||||||
|
sess, err := fmux.Client(remote, fmuxCfg)
|
||||||
|
if err != nil {
|
||||||
|
sv.Error("create yamux session error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer sess.Close()
|
||||||
|
muxConn, err := sess.Open()
|
||||||
|
if err != nil {
|
||||||
|
sv.Error("open yamux stream error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
frpIo.Join(userConn, muxConn)
|
||||||
sv.Debug("join connections closed")
|
sv.Debug("join connections closed")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sv *XtcpVisitor) sendDetectMsg(addr string, port int, laddr *net.UDPAddr, content []byte) (err error) {
|
|
||||||
daddr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", addr, port))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
tConn, err := net.DialUDP("udp", laddr, daddr)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
uConn := ipv4.NewConn(tConn)
|
|
||||||
uConn.SetTTL(3)
|
|
||||||
|
|
||||||
tConn.Write(content)
|
|
||||||
tConn.Close()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -12,9 +12,13 @@
|
|||||||
// 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 // "github.com/fatedier/frp/cmd/frpc"
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"math/rand"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
_ "github.com/fatedier/frp/assets/frpc/statik"
|
||||||
"github.com/fatedier/frp/cmd/frpc/sub"
|
"github.com/fatedier/frp/cmd/frpc/sub"
|
||||||
|
|
||||||
"github.com/fatedier/golib/crypto"
|
"github.com/fatedier/golib/crypto"
|
||||||
@@ -22,6 +26,7 @@ import (
|
|||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
crypto.DefaultSalt = "frp"
|
crypto.DefaultSalt = "frp"
|
||||||
|
rand.Seed(time.Now().UnixNano())
|
||||||
|
|
||||||
sub.Execute()
|
sub.Execute()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,11 +28,12 @@ import (
|
|||||||
func init() {
|
func init() {
|
||||||
httpCmd.PersistentFlags().StringVarP(&serverAddr, "server_addr", "s", "127.0.0.1:7000", "frp server's address")
|
httpCmd.PersistentFlags().StringVarP(&serverAddr, "server_addr", "s", "127.0.0.1:7000", "frp server's address")
|
||||||
httpCmd.PersistentFlags().StringVarP(&user, "user", "u", "", "user")
|
httpCmd.PersistentFlags().StringVarP(&user, "user", "u", "", "user")
|
||||||
httpCmd.PersistentFlags().StringVarP(&protocol, "protocol", "p", "tcp", "tcp or kcp")
|
httpCmd.PersistentFlags().StringVarP(&protocol, "protocol", "p", "tcp", "tcp or kcp or websocket")
|
||||||
httpCmd.PersistentFlags().StringVarP(&token, "token", "t", "", "auth token")
|
httpCmd.PersistentFlags().StringVarP(&token, "token", "t", "", "auth token")
|
||||||
httpCmd.PersistentFlags().StringVarP(&logLevel, "log_level", "", "info", "log level")
|
httpCmd.PersistentFlags().StringVarP(&logLevel, "log_level", "", "info", "log level")
|
||||||
httpCmd.PersistentFlags().StringVarP(&logFile, "log_file", "", "console", "console or file path")
|
httpCmd.PersistentFlags().StringVarP(&logFile, "log_file", "", "console", "console or file path")
|
||||||
httpCmd.PersistentFlags().IntVarP(&logMaxDays, "log_max_days", "", 3, "log file reversed days")
|
httpCmd.PersistentFlags().IntVarP(&logMaxDays, "log_max_days", "", 3, "log file reversed days")
|
||||||
|
httpCmd.PersistentFlags().BoolVarP(&disableLogColor, "disable_log_color", "", false, "disable log color in console")
|
||||||
|
|
||||||
httpCmd.PersistentFlags().StringVarP(&proxyName, "proxy_name", "n", "", "proxy name")
|
httpCmd.PersistentFlags().StringVarP(&proxyName, "proxy_name", "n", "", "proxy name")
|
||||||
httpCmd.PersistentFlags().StringVarP(&localIp, "local_ip", "i", "127.0.0.1", "local ip")
|
httpCmd.PersistentFlags().StringVarP(&localIp, "local_ip", "i", "127.0.0.1", "local ip")
|
||||||
@@ -53,7 +54,7 @@ var httpCmd = &cobra.Command{
|
|||||||
Use: "http",
|
Use: "http",
|
||||||
Short: "Run frpc with a single http proxy",
|
Short: "Run frpc with a single http proxy",
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
err := parseClientCommonCfg(CfgFileTypeCmd, "")
|
clientCfg, err := parseClientCommonCfg(CfgFileTypeCmd, "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
@@ -86,7 +87,7 @@ var httpCmd = &cobra.Command{
|
|||||||
proxyConfs := map[string]config.ProxyConf{
|
proxyConfs := map[string]config.ProxyConf{
|
||||||
cfg.ProxyName: cfg,
|
cfg.ProxyName: cfg,
|
||||||
}
|
}
|
||||||
err = startService(proxyConfs, nil)
|
err = startService(clientCfg, proxyConfs, nil, "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
|
|||||||
@@ -28,11 +28,12 @@ import (
|
|||||||
func init() {
|
func init() {
|
||||||
httpsCmd.PersistentFlags().StringVarP(&serverAddr, "server_addr", "s", "127.0.0.1:7000", "frp server's address")
|
httpsCmd.PersistentFlags().StringVarP(&serverAddr, "server_addr", "s", "127.0.0.1:7000", "frp server's address")
|
||||||
httpsCmd.PersistentFlags().StringVarP(&user, "user", "u", "", "user")
|
httpsCmd.PersistentFlags().StringVarP(&user, "user", "u", "", "user")
|
||||||
httpsCmd.PersistentFlags().StringVarP(&protocol, "protocol", "p", "tcp", "tcp or kcp")
|
httpsCmd.PersistentFlags().StringVarP(&protocol, "protocol", "p", "tcp", "tcp or kcp or websocket")
|
||||||
httpsCmd.PersistentFlags().StringVarP(&token, "token", "t", "", "auth token")
|
httpsCmd.PersistentFlags().StringVarP(&token, "token", "t", "", "auth token")
|
||||||
httpsCmd.PersistentFlags().StringVarP(&logLevel, "log_level", "", "info", "log level")
|
httpsCmd.PersistentFlags().StringVarP(&logLevel, "log_level", "", "info", "log level")
|
||||||
httpsCmd.PersistentFlags().StringVarP(&logFile, "log_file", "", "console", "console or file path")
|
httpsCmd.PersistentFlags().StringVarP(&logFile, "log_file", "", "console", "console or file path")
|
||||||
httpsCmd.PersistentFlags().IntVarP(&logMaxDays, "log_max_days", "", 3, "log file reversed days")
|
httpsCmd.PersistentFlags().IntVarP(&logMaxDays, "log_max_days", "", 3, "log file reversed days")
|
||||||
|
httpsCmd.PersistentFlags().BoolVarP(&disableLogColor, "disable_log_color", "", false, "disable log color in console")
|
||||||
|
|
||||||
httpsCmd.PersistentFlags().StringVarP(&proxyName, "proxy_name", "n", "", "proxy name")
|
httpsCmd.PersistentFlags().StringVarP(&proxyName, "proxy_name", "n", "", "proxy name")
|
||||||
httpsCmd.PersistentFlags().StringVarP(&localIp, "local_ip", "i", "127.0.0.1", "local ip")
|
httpsCmd.PersistentFlags().StringVarP(&localIp, "local_ip", "i", "127.0.0.1", "local ip")
|
||||||
@@ -49,7 +50,7 @@ var httpsCmd = &cobra.Command{
|
|||||||
Use: "https",
|
Use: "https",
|
||||||
Short: "Run frpc with a single https proxy",
|
Short: "Run frpc with a single https proxy",
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
err := parseClientCommonCfg(CfgFileTypeCmd, "")
|
clientCfg, err := parseClientCommonCfg(CfgFileTypeCmd, "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
@@ -78,7 +79,7 @@ var httpsCmd = &cobra.Command{
|
|||||||
proxyConfs := map[string]config.ProxyConf{
|
proxyConfs := map[string]config.ProxyConf{
|
||||||
cfg.ProxyName: cfg,
|
cfg.ProxyName: cfg,
|
||||||
}
|
}
|
||||||
err = startService(proxyConfs, nil)
|
err = startService(clientCfg, proxyConfs, nil, "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ package sub
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
@@ -25,8 +24,6 @@ import (
|
|||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
"github.com/fatedier/frp/client"
|
|
||||||
"github.com/fatedier/frp/g"
|
|
||||||
"github.com/fatedier/frp/models/config"
|
"github.com/fatedier/frp/models/config"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -44,13 +41,13 @@ var reloadCmd = &cobra.Command{
|
|||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = parseClientCommonCfg(CfgFileTypeIni, iniContent)
|
clientCfg, err := parseClientCommonCfg(CfgFileTypeIni, iniContent)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = reload()
|
err = reload(clientCfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("frpc reload error: %v\n", err)
|
fmt.Printf("frpc reload error: %v\n", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
@@ -60,40 +57,34 @@ var reloadCmd = &cobra.Command{
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
func reload() error {
|
func reload(clientCfg config.ClientCommonConf) error {
|
||||||
if g.GlbClientCfg.AdminPort == 0 {
|
if clientCfg.AdminPort == 0 {
|
||||||
return fmt.Errorf("admin_port shoud be set if you want to use reload feature")
|
return fmt.Errorf("admin_port shoud be set if you want to use reload feature")
|
||||||
}
|
}
|
||||||
|
|
||||||
req, err := http.NewRequest("GET", "http://"+
|
req, err := http.NewRequest("GET", "http://"+
|
||||||
g.GlbClientCfg.AdminAddr+":"+fmt.Sprintf("%d", g.GlbClientCfg.AdminPort)+"/api/reload", nil)
|
clientCfg.AdminAddr+":"+fmt.Sprintf("%d", clientCfg.AdminPort)+"/api/reload", nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
authStr := "Basic " + base64.StdEncoding.EncodeToString([]byte(g.GlbClientCfg.AdminUser+":"+
|
authStr := "Basic " + base64.StdEncoding.EncodeToString([]byte(clientCfg.AdminUser+":"+
|
||||||
g.GlbClientCfg.AdminPwd))
|
clientCfg.AdminPwd))
|
||||||
|
|
||||||
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 {
|
||||||
return err
|
return err
|
||||||
} else {
|
|
||||||
if resp.StatusCode != 200 {
|
|
||||||
return fmt.Errorf("admin api status code [%d]", resp.StatusCode)
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
body, err := ioutil.ReadAll(resp.Body)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
res := &client.GeneralResponse{}
|
|
||||||
err = json.Unmarshal(body, &res)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("unmarshal http response error: %s", strings.TrimSpace(string(body)))
|
|
||||||
} else if res.Code != 0 {
|
|
||||||
return fmt.Errorf(res.Msg)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return nil
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
if resp.StatusCode == 200 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
body, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return fmt.Errorf("code [%d], %s", resp.StatusCode, strings.TrimSpace(string(body)))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,7 +28,6 @@ import (
|
|||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
"github.com/fatedier/frp/client"
|
"github.com/fatedier/frp/client"
|
||||||
"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"
|
||||||
"github.com/fatedier/frp/utils/version"
|
"github.com/fatedier/frp/utils/version"
|
||||||
@@ -43,13 +42,14 @@ var (
|
|||||||
cfgFile string
|
cfgFile string
|
||||||
showVersion bool
|
showVersion bool
|
||||||
|
|
||||||
serverAddr string
|
serverAddr string
|
||||||
user string
|
user string
|
||||||
protocol string
|
protocol string
|
||||||
token string
|
token string
|
||||||
logLevel string
|
logLevel string
|
||||||
logFile string
|
logFile string
|
||||||
logMaxDays int
|
logMaxDays int
|
||||||
|
disableLogColor bool
|
||||||
|
|
||||||
proxyName string
|
proxyName string
|
||||||
localIp string
|
localIp string
|
||||||
@@ -73,7 +73,7 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
rootCmd.PersistentFlags().StringVarP(&cfgFile, "", "c", "./frpc.ini", "config file of frpc")
|
rootCmd.PersistentFlags().StringVarP(&cfgFile, "config", "c", "./frpc.ini", "config file of frpc")
|
||||||
rootCmd.PersistentFlags().BoolVarP(&showVersion, "version", "v", false, "version of frpc")
|
rootCmd.PersistentFlags().BoolVarP(&showVersion, "version", "v", false, "version of frpc")
|
||||||
|
|
||||||
kcpDoneCh = make(chan struct{})
|
kcpDoneCh = make(chan struct{})
|
||||||
@@ -113,59 +113,62 @@ func handleSignal(svr *client.Service) {
|
|||||||
close(kcpDoneCh)
|
close(kcpDoneCh)
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseClientCommonCfg(fileType int, content string) (err error) {
|
func parseClientCommonCfg(fileType int, content string) (cfg config.ClientCommonConf, err error) {
|
||||||
if fileType == CfgFileTypeIni {
|
if fileType == CfgFileTypeIni {
|
||||||
err = parseClientCommonCfgFromIni(content)
|
cfg, err = parseClientCommonCfgFromIni(content)
|
||||||
} else if fileType == CfgFileTypeCmd {
|
} else if fileType == CfgFileTypeCmd {
|
||||||
err = parseClientCommonCfgFromCmd()
|
cfg, err = parseClientCommonCfgFromCmd()
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err = g.GlbClientCfg.ClientCommonConf.Check()
|
err = cfg.Check()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseClientCommonCfgFromIni(content string) (err error) {
|
func parseClientCommonCfgFromIni(content string) (config.ClientCommonConf, error) {
|
||||||
cfg, err := config.UnmarshalClientConfFromIni(&g.GlbClientCfg.ClientCommonConf, content)
|
cfg, err := config.UnmarshalClientConfFromIni(content)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return config.ClientCommonConf{}, err
|
||||||
}
|
}
|
||||||
g.GlbClientCfg.ClientCommonConf = *cfg
|
return cfg, err
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseClientCommonCfgFromCmd() (err error) {
|
func parseClientCommonCfgFromCmd() (cfg config.ClientCommonConf, err error) {
|
||||||
|
cfg = config.GetDefaultClientConf()
|
||||||
|
|
||||||
strs := strings.Split(serverAddr, ":")
|
strs := strings.Split(serverAddr, ":")
|
||||||
if len(strs) < 2 {
|
if len(strs) < 2 {
|
||||||
err = fmt.Errorf("invalid server_addr")
|
err = fmt.Errorf("invalid server_addr")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if strs[0] != "" {
|
if strs[0] != "" {
|
||||||
g.GlbClientCfg.ServerAddr = strs[0]
|
cfg.ServerAddr = strs[0]
|
||||||
}
|
}
|
||||||
g.GlbClientCfg.ServerPort, err = strconv.Atoi(strs[1])
|
cfg.ServerPort, err = strconv.Atoi(strs[1])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = fmt.Errorf("invalid server_addr")
|
err = fmt.Errorf("invalid server_addr")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
g.GlbClientCfg.User = user
|
cfg.User = user
|
||||||
g.GlbClientCfg.Protocol = protocol
|
cfg.Protocol = protocol
|
||||||
g.GlbClientCfg.Token = token
|
cfg.Token = token
|
||||||
g.GlbClientCfg.LogLevel = logLevel
|
cfg.LogLevel = logLevel
|
||||||
g.GlbClientCfg.LogFile = logFile
|
cfg.LogFile = logFile
|
||||||
g.GlbClientCfg.LogMaxDays = int64(logMaxDays)
|
cfg.LogMaxDays = int64(logMaxDays)
|
||||||
if logFile == "console" {
|
if logFile == "console" {
|
||||||
g.GlbClientCfg.LogWay = "console"
|
cfg.LogWay = "console"
|
||||||
} else {
|
} else {
|
||||||
g.GlbClientCfg.LogWay = "file"
|
cfg.LogWay = "file"
|
||||||
}
|
}
|
||||||
return nil
|
cfg.DisableLogColor = disableLogColor
|
||||||
|
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func runClient(cfgFilePath string) (err error) {
|
func runClient(cfgFilePath string) (err error) {
|
||||||
@@ -174,26 +177,27 @@ func runClient(cfgFilePath string) (err error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
g.GlbClientCfg.CfgFile = cfgFilePath
|
|
||||||
|
|
||||||
err = parseClientCommonCfg(CfgFileTypeIni, content)
|
cfg, err := parseClientCommonCfg(CfgFileTypeIni, content)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
pxyCfgs, visitorCfgs, err := config.LoadAllConfFromIni(g.GlbClientCfg.User, content, g.GlbClientCfg.Start)
|
pxyCfgs, visitorCfgs, err := config.LoadAllConfFromIni(cfg.User, content, cfg.Start)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = startService(pxyCfgs, visitorCfgs)
|
err = startService(cfg, pxyCfgs, visitorCfgs, cfgFilePath)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func startService(pxyCfgs map[string]config.ProxyConf, visitorCfgs map[string]config.VisitorConf) (err error) {
|
func startService(cfg config.ClientCommonConf, pxyCfgs map[string]config.ProxyConf, visitorCfgs map[string]config.VisitorConf, cfgFile string) (err error) {
|
||||||
log.InitLog(g.GlbClientCfg.LogWay, g.GlbClientCfg.LogFile, g.GlbClientCfg.LogLevel, g.GlbClientCfg.LogMaxDays)
|
log.InitLog(cfg.LogWay, cfg.LogFile, cfg.LogLevel,
|
||||||
if g.GlbClientCfg.DnsServer != "" {
|
cfg.LogMaxDays, cfg.DisableLogColor)
|
||||||
s := g.GlbClientCfg.DnsServer
|
|
||||||
|
if cfg.DnsServer != "" {
|
||||||
|
s := cfg.DnsServer
|
||||||
if !strings.Contains(s, ":") {
|
if !strings.Contains(s, ":") {
|
||||||
s += ":53"
|
s += ":53"
|
||||||
}
|
}
|
||||||
@@ -205,15 +209,19 @@ func startService(pxyCfgs map[string]config.ProxyConf, visitorCfgs map[string]co
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
svr := client.NewService(pxyCfgs, visitorCfgs)
|
svr, errRet := client.NewService(cfg, pxyCfgs, visitorCfgs, cfgFile)
|
||||||
|
if errRet != nil {
|
||||||
|
err = errRet
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// Capture the exit signal if we use kcp.
|
// Capture the exit signal if we use kcp.
|
||||||
if g.GlbClientCfg.Protocol == "kcp" {
|
if cfg.Protocol == "kcp" {
|
||||||
go handleSignal(svr)
|
go handleSignal(svr)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = svr.Run()
|
err = svr.Run()
|
||||||
if g.GlbClientCfg.Protocol == "kcp" {
|
if cfg.Protocol == "kcp" {
|
||||||
<-kcpDoneCh
|
<-kcpDoneCh
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -27,7 +27,6 @@ import (
|
|||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
"github.com/fatedier/frp/client"
|
"github.com/fatedier/frp/client"
|
||||||
"github.com/fatedier/frp/g"
|
|
||||||
"github.com/fatedier/frp/models/config"
|
"github.com/fatedier/frp/models/config"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -45,13 +44,13 @@ var statusCmd = &cobra.Command{
|
|||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = parseClientCommonCfg(CfgFileTypeIni, iniContent)
|
clientCfg, err := parseClientCommonCfg(CfgFileTypeIni, iniContent)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = status()
|
err = status(clientCfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("frpc get status error: %v\n", err)
|
fmt.Printf("frpc get status error: %v\n", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
@@ -60,94 +59,96 @@ var statusCmd = &cobra.Command{
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
func status() error {
|
func status(clientCfg config.ClientCommonConf) error {
|
||||||
if g.GlbClientCfg.AdminPort == 0 {
|
if clientCfg.AdminPort == 0 {
|
||||||
return fmt.Errorf("admin_port shoud be set if you want to get proxy status")
|
return fmt.Errorf("admin_port shoud be set if you want to get proxy status")
|
||||||
}
|
}
|
||||||
|
|
||||||
req, err := http.NewRequest("GET", "http://"+
|
req, err := http.NewRequest("GET", "http://"+
|
||||||
g.GlbClientCfg.AdminAddr+":"+fmt.Sprintf("%d", g.GlbClientCfg.AdminPort)+"/api/status", nil)
|
clientCfg.AdminAddr+":"+fmt.Sprintf("%d", clientCfg.AdminPort)+"/api/status", nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
authStr := "Basic " + base64.StdEncoding.EncodeToString([]byte(g.GlbClientCfg.AdminUser+":"+
|
authStr := "Basic " + base64.StdEncoding.EncodeToString([]byte(clientCfg.AdminUser+":"+
|
||||||
g.GlbClientCfg.AdminPwd))
|
clientCfg.AdminPwd))
|
||||||
|
|
||||||
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 {
|
||||||
return err
|
return err
|
||||||
} else {
|
|
||||||
if resp.StatusCode != 200 {
|
|
||||||
return fmt.Errorf("admin api status code [%d]", resp.StatusCode)
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
body, err := ioutil.ReadAll(resp.Body)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
res := &client.StatusResp{}
|
|
||||||
err = json.Unmarshal(body, &res)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("unmarshal http response error: %s", strings.TrimSpace(string(body)))
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Println("Proxy Status...")
|
|
||||||
if len(res.Tcp) > 0 {
|
|
||||||
fmt.Printf("TCP")
|
|
||||||
tbl := table.New("Name", "Status", "LocalAddr", "Plugin", "RemoteAddr", "Error")
|
|
||||||
for _, ps := range res.Tcp {
|
|
||||||
tbl.AddRow(ps.Name, ps.Status, ps.LocalAddr, ps.Plugin, ps.RemoteAddr, ps.Err)
|
|
||||||
}
|
|
||||||
tbl.Print()
|
|
||||||
fmt.Println("")
|
|
||||||
}
|
|
||||||
if len(res.Udp) > 0 {
|
|
||||||
fmt.Printf("UDP")
|
|
||||||
tbl := table.New("Name", "Status", "LocalAddr", "Plugin", "RemoteAddr", "Error")
|
|
||||||
for _, ps := range res.Udp {
|
|
||||||
tbl.AddRow(ps.Name, ps.Status, ps.LocalAddr, ps.Plugin, ps.RemoteAddr, ps.Err)
|
|
||||||
}
|
|
||||||
tbl.Print()
|
|
||||||
fmt.Println("")
|
|
||||||
}
|
|
||||||
if len(res.Http) > 0 {
|
|
||||||
fmt.Printf("HTTP")
|
|
||||||
tbl := table.New("Name", "Status", "LocalAddr", "Plugin", "RemoteAddr", "Error")
|
|
||||||
for _, ps := range res.Http {
|
|
||||||
tbl.AddRow(ps.Name, ps.Status, ps.LocalAddr, ps.Plugin, ps.RemoteAddr, ps.Err)
|
|
||||||
}
|
|
||||||
tbl.Print()
|
|
||||||
fmt.Println("")
|
|
||||||
}
|
|
||||||
if len(res.Https) > 0 {
|
|
||||||
fmt.Printf("HTTPS")
|
|
||||||
tbl := table.New("Name", "Status", "LocalAddr", "Plugin", "RemoteAddr", "Error")
|
|
||||||
for _, ps := range res.Https {
|
|
||||||
tbl.AddRow(ps.Name, ps.Status, ps.LocalAddr, ps.Plugin, ps.RemoteAddr, ps.Err)
|
|
||||||
}
|
|
||||||
tbl.Print()
|
|
||||||
fmt.Println("")
|
|
||||||
}
|
|
||||||
if len(res.Stcp) > 0 {
|
|
||||||
fmt.Printf("STCP")
|
|
||||||
tbl := table.New("Name", "Status", "LocalAddr", "Plugin", "RemoteAddr", "Error")
|
|
||||||
for _, ps := range res.Stcp {
|
|
||||||
tbl.AddRow(ps.Name, ps.Status, ps.LocalAddr, ps.Plugin, ps.RemoteAddr, ps.Err)
|
|
||||||
}
|
|
||||||
tbl.Print()
|
|
||||||
fmt.Println("")
|
|
||||||
}
|
|
||||||
if len(res.Xtcp) > 0 {
|
|
||||||
fmt.Printf("XTCP")
|
|
||||||
tbl := table.New("Name", "Status", "LocalAddr", "Plugin", "RemoteAddr", "Error")
|
|
||||||
for _, ps := range res.Xtcp {
|
|
||||||
tbl.AddRow(ps.Name, ps.Status, ps.LocalAddr, ps.Plugin, ps.RemoteAddr, ps.Err)
|
|
||||||
}
|
|
||||||
tbl.Print()
|
|
||||||
fmt.Println("")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
if resp.StatusCode != 200 {
|
||||||
|
return fmt.Errorf("admin api status code [%d]", resp.StatusCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
body, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
res := &client.StatusResp{}
|
||||||
|
err = json.Unmarshal(body, &res)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unmarshal http response error: %s", strings.TrimSpace(string(body)))
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println("Proxy Status...")
|
||||||
|
if len(res.Tcp) > 0 {
|
||||||
|
fmt.Printf("TCP")
|
||||||
|
tbl := table.New("Name", "Status", "LocalAddr", "Plugin", "RemoteAddr", "Error")
|
||||||
|
for _, ps := range res.Tcp {
|
||||||
|
tbl.AddRow(ps.Name, ps.Status, ps.LocalAddr, ps.Plugin, ps.RemoteAddr, ps.Err)
|
||||||
|
}
|
||||||
|
tbl.Print()
|
||||||
|
fmt.Println("")
|
||||||
|
}
|
||||||
|
if len(res.Udp) > 0 {
|
||||||
|
fmt.Printf("UDP")
|
||||||
|
tbl := table.New("Name", "Status", "LocalAddr", "Plugin", "RemoteAddr", "Error")
|
||||||
|
for _, ps := range res.Udp {
|
||||||
|
tbl.AddRow(ps.Name, ps.Status, ps.LocalAddr, ps.Plugin, ps.RemoteAddr, ps.Err)
|
||||||
|
}
|
||||||
|
tbl.Print()
|
||||||
|
fmt.Println("")
|
||||||
|
}
|
||||||
|
if len(res.Http) > 0 {
|
||||||
|
fmt.Printf("HTTP")
|
||||||
|
tbl := table.New("Name", "Status", "LocalAddr", "Plugin", "RemoteAddr", "Error")
|
||||||
|
for _, ps := range res.Http {
|
||||||
|
tbl.AddRow(ps.Name, ps.Status, ps.LocalAddr, ps.Plugin, ps.RemoteAddr, ps.Err)
|
||||||
|
}
|
||||||
|
tbl.Print()
|
||||||
|
fmt.Println("")
|
||||||
|
}
|
||||||
|
if len(res.Https) > 0 {
|
||||||
|
fmt.Printf("HTTPS")
|
||||||
|
tbl := table.New("Name", "Status", "LocalAddr", "Plugin", "RemoteAddr", "Error")
|
||||||
|
for _, ps := range res.Https {
|
||||||
|
tbl.AddRow(ps.Name, ps.Status, ps.LocalAddr, ps.Plugin, ps.RemoteAddr, ps.Err)
|
||||||
|
}
|
||||||
|
tbl.Print()
|
||||||
|
fmt.Println("")
|
||||||
|
}
|
||||||
|
if len(res.Stcp) > 0 {
|
||||||
|
fmt.Printf("STCP")
|
||||||
|
tbl := table.New("Name", "Status", "LocalAddr", "Plugin", "RemoteAddr", "Error")
|
||||||
|
for _, ps := range res.Stcp {
|
||||||
|
tbl.AddRow(ps.Name, ps.Status, ps.LocalAddr, ps.Plugin, ps.RemoteAddr, ps.Err)
|
||||||
|
}
|
||||||
|
tbl.Print()
|
||||||
|
fmt.Println("")
|
||||||
|
}
|
||||||
|
if len(res.Xtcp) > 0 {
|
||||||
|
fmt.Printf("XTCP")
|
||||||
|
tbl := table.New("Name", "Status", "LocalAddr", "Plugin", "RemoteAddr", "Error")
|
||||||
|
for _, ps := range res.Xtcp {
|
||||||
|
tbl.AddRow(ps.Name, ps.Status, ps.LocalAddr, ps.Plugin, ps.RemoteAddr, ps.Err)
|
||||||
|
}
|
||||||
|
tbl.Print()
|
||||||
|
fmt.Println("")
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,11 +27,12 @@ import (
|
|||||||
func init() {
|
func init() {
|
||||||
stcpCmd.PersistentFlags().StringVarP(&serverAddr, "server_addr", "s", "127.0.0.1:7000", "frp server's address")
|
stcpCmd.PersistentFlags().StringVarP(&serverAddr, "server_addr", "s", "127.0.0.1:7000", "frp server's address")
|
||||||
stcpCmd.PersistentFlags().StringVarP(&user, "user", "u", "", "user")
|
stcpCmd.PersistentFlags().StringVarP(&user, "user", "u", "", "user")
|
||||||
stcpCmd.PersistentFlags().StringVarP(&protocol, "protocol", "p", "tcp", "tcp or kcp")
|
stcpCmd.PersistentFlags().StringVarP(&protocol, "protocol", "p", "tcp", "tcp or kcp or websocket")
|
||||||
stcpCmd.PersistentFlags().StringVarP(&token, "token", "t", "", "auth token")
|
stcpCmd.PersistentFlags().StringVarP(&token, "token", "t", "", "auth token")
|
||||||
stcpCmd.PersistentFlags().StringVarP(&logLevel, "log_level", "", "info", "log level")
|
stcpCmd.PersistentFlags().StringVarP(&logLevel, "log_level", "", "info", "log level")
|
||||||
stcpCmd.PersistentFlags().StringVarP(&logFile, "log_file", "", "console", "console or file path")
|
stcpCmd.PersistentFlags().StringVarP(&logFile, "log_file", "", "console", "console or file path")
|
||||||
stcpCmd.PersistentFlags().IntVarP(&logMaxDays, "log_max_days", "", 3, "log file reversed days")
|
stcpCmd.PersistentFlags().IntVarP(&logMaxDays, "log_max_days", "", 3, "log file reversed days")
|
||||||
|
stcpCmd.PersistentFlags().BoolVarP(&disableLogColor, "disable_log_color", "", false, "disable log color in console")
|
||||||
|
|
||||||
stcpCmd.PersistentFlags().StringVarP(&proxyName, "proxy_name", "n", "", "proxy name")
|
stcpCmd.PersistentFlags().StringVarP(&proxyName, "proxy_name", "n", "", "proxy name")
|
||||||
stcpCmd.PersistentFlags().StringVarP(&role, "role", "", "server", "role")
|
stcpCmd.PersistentFlags().StringVarP(&role, "role", "", "server", "role")
|
||||||
@@ -51,7 +52,7 @@ var stcpCmd = &cobra.Command{
|
|||||||
Use: "stcp",
|
Use: "stcp",
|
||||||
Short: "Run frpc with a single stcp proxy",
|
Short: "Run frpc with a single stcp proxy",
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
err := parseClientCommonCfg(CfgFileTypeCmd, "")
|
clientCfg, err := parseClientCommonCfg(CfgFileTypeCmd, "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
@@ -103,7 +104,7 @@ var stcpCmd = &cobra.Command{
|
|||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = startService(proxyConfs, visitorConfs)
|
err = startService(clientCfg, proxyConfs, visitorConfs, "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ func init() {
|
|||||||
tcpCmd.PersistentFlags().StringVarP(&logLevel, "log_level", "", "info", "log level")
|
tcpCmd.PersistentFlags().StringVarP(&logLevel, "log_level", "", "info", "log level")
|
||||||
tcpCmd.PersistentFlags().StringVarP(&logFile, "log_file", "", "console", "console or file path")
|
tcpCmd.PersistentFlags().StringVarP(&logFile, "log_file", "", "console", "console or file path")
|
||||||
tcpCmd.PersistentFlags().IntVarP(&logMaxDays, "log_max_days", "", 3, "log file reversed days")
|
tcpCmd.PersistentFlags().IntVarP(&logMaxDays, "log_max_days", "", 3, "log file reversed days")
|
||||||
|
tcpCmd.PersistentFlags().BoolVarP(&disableLogColor, "disable_log_color", "", false, "disable log color in console")
|
||||||
|
|
||||||
tcpCmd.PersistentFlags().StringVarP(&proxyName, "proxy_name", "n", "", "proxy name")
|
tcpCmd.PersistentFlags().StringVarP(&proxyName, "proxy_name", "n", "", "proxy name")
|
||||||
tcpCmd.PersistentFlags().StringVarP(&localIp, "local_ip", "i", "127.0.0.1", "local ip")
|
tcpCmd.PersistentFlags().StringVarP(&localIp, "local_ip", "i", "127.0.0.1", "local ip")
|
||||||
@@ -47,7 +48,7 @@ var tcpCmd = &cobra.Command{
|
|||||||
Use: "tcp",
|
Use: "tcp",
|
||||||
Short: "Run frpc with a single tcp proxy",
|
Short: "Run frpc with a single tcp proxy",
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
err := parseClientCommonCfg(CfgFileTypeCmd, "")
|
clientCfg, err := parseClientCommonCfg(CfgFileTypeCmd, "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
@@ -75,7 +76,7 @@ var tcpCmd = &cobra.Command{
|
|||||||
proxyConfs := map[string]config.ProxyConf{
|
proxyConfs := map[string]config.ProxyConf{
|
||||||
cfg.ProxyName: cfg,
|
cfg.ProxyName: cfg,
|
||||||
}
|
}
|
||||||
err = startService(proxyConfs, nil)
|
err = startService(clientCfg, proxyConfs, nil, "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
|
|||||||
@@ -27,11 +27,12 @@ import (
|
|||||||
func init() {
|
func init() {
|
||||||
udpCmd.PersistentFlags().StringVarP(&serverAddr, "server_addr", "s", "127.0.0.1:7000", "frp server's address")
|
udpCmd.PersistentFlags().StringVarP(&serverAddr, "server_addr", "s", "127.0.0.1:7000", "frp server's address")
|
||||||
udpCmd.PersistentFlags().StringVarP(&user, "user", "u", "", "user")
|
udpCmd.PersistentFlags().StringVarP(&user, "user", "u", "", "user")
|
||||||
udpCmd.PersistentFlags().StringVarP(&protocol, "protocol", "p", "tcp", "tcp or kcp")
|
udpCmd.PersistentFlags().StringVarP(&protocol, "protocol", "p", "tcp", "tcp or kcp or websocket")
|
||||||
udpCmd.PersistentFlags().StringVarP(&token, "token", "t", "", "auth token")
|
udpCmd.PersistentFlags().StringVarP(&token, "token", "t", "", "auth token")
|
||||||
udpCmd.PersistentFlags().StringVarP(&logLevel, "log_level", "", "info", "log level")
|
udpCmd.PersistentFlags().StringVarP(&logLevel, "log_level", "", "info", "log level")
|
||||||
udpCmd.PersistentFlags().StringVarP(&logFile, "log_file", "", "console", "console or file path")
|
udpCmd.PersistentFlags().StringVarP(&logFile, "log_file", "", "console", "console or file path")
|
||||||
udpCmd.PersistentFlags().IntVarP(&logMaxDays, "log_max_days", "", 3, "log file reversed days")
|
udpCmd.PersistentFlags().IntVarP(&logMaxDays, "log_max_days", "", 3, "log file reversed days")
|
||||||
|
udpCmd.PersistentFlags().BoolVarP(&disableLogColor, "disable_log_color", "", false, "disable log color in console")
|
||||||
|
|
||||||
udpCmd.PersistentFlags().StringVarP(&proxyName, "proxy_name", "n", "", "proxy name")
|
udpCmd.PersistentFlags().StringVarP(&proxyName, "proxy_name", "n", "", "proxy name")
|
||||||
udpCmd.PersistentFlags().StringVarP(&localIp, "local_ip", "i", "127.0.0.1", "local ip")
|
udpCmd.PersistentFlags().StringVarP(&localIp, "local_ip", "i", "127.0.0.1", "local ip")
|
||||||
@@ -47,7 +48,7 @@ var udpCmd = &cobra.Command{
|
|||||||
Use: "udp",
|
Use: "udp",
|
||||||
Short: "Run frpc with a single udp proxy",
|
Short: "Run frpc with a single udp proxy",
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
err := parseClientCommonCfg(CfgFileTypeCmd, "")
|
clientCfg, err := parseClientCommonCfg(CfgFileTypeCmd, "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
@@ -75,7 +76,7 @@ var udpCmd = &cobra.Command{
|
|||||||
proxyConfs := map[string]config.ProxyConf{
|
proxyConfs := map[string]config.ProxyConf{
|
||||||
cfg.ProxyName: cfg,
|
cfg.ProxyName: cfg,
|
||||||
}
|
}
|
||||||
err = startService(proxyConfs, nil)
|
err = startService(clientCfg, proxyConfs, nil, "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
|
|||||||
@@ -27,11 +27,12 @@ import (
|
|||||||
func init() {
|
func init() {
|
||||||
xtcpCmd.PersistentFlags().StringVarP(&serverAddr, "server_addr", "s", "127.0.0.1:7000", "frp server's address")
|
xtcpCmd.PersistentFlags().StringVarP(&serverAddr, "server_addr", "s", "127.0.0.1:7000", "frp server's address")
|
||||||
xtcpCmd.PersistentFlags().StringVarP(&user, "user", "u", "", "user")
|
xtcpCmd.PersistentFlags().StringVarP(&user, "user", "u", "", "user")
|
||||||
xtcpCmd.PersistentFlags().StringVarP(&protocol, "protocol", "p", "tcp", "tcp or kcp")
|
xtcpCmd.PersistentFlags().StringVarP(&protocol, "protocol", "p", "tcp", "tcp or kcp or websocket")
|
||||||
xtcpCmd.PersistentFlags().StringVarP(&token, "token", "t", "", "auth token")
|
xtcpCmd.PersistentFlags().StringVarP(&token, "token", "t", "", "auth token")
|
||||||
xtcpCmd.PersistentFlags().StringVarP(&logLevel, "log_level", "", "info", "log level")
|
xtcpCmd.PersistentFlags().StringVarP(&logLevel, "log_level", "", "info", "log level")
|
||||||
xtcpCmd.PersistentFlags().StringVarP(&logFile, "log_file", "", "console", "console or file path")
|
xtcpCmd.PersistentFlags().StringVarP(&logFile, "log_file", "", "console", "console or file path")
|
||||||
xtcpCmd.PersistentFlags().IntVarP(&logMaxDays, "log_max_days", "", 3, "log file reversed days")
|
xtcpCmd.PersistentFlags().IntVarP(&logMaxDays, "log_max_days", "", 3, "log file reversed days")
|
||||||
|
xtcpCmd.PersistentFlags().BoolVarP(&disableLogColor, "disable_log_color", "", false, "disable log color in console")
|
||||||
|
|
||||||
xtcpCmd.PersistentFlags().StringVarP(&proxyName, "proxy_name", "n", "", "proxy name")
|
xtcpCmd.PersistentFlags().StringVarP(&proxyName, "proxy_name", "n", "", "proxy name")
|
||||||
xtcpCmd.PersistentFlags().StringVarP(&role, "role", "", "server", "role")
|
xtcpCmd.PersistentFlags().StringVarP(&role, "role", "", "server", "role")
|
||||||
@@ -51,7 +52,7 @@ var xtcpCmd = &cobra.Command{
|
|||||||
Use: "xtcp",
|
Use: "xtcp",
|
||||||
Short: "Run frpc with a single xtcp proxy",
|
Short: "Run frpc with a single xtcp proxy",
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
err := parseClientCommonCfg(CfgFileTypeCmd, "")
|
clientCfg, err := parseClientCommonCfg(CfgFileTypeCmd, "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
@@ -68,7 +69,7 @@ var xtcpCmd = &cobra.Command{
|
|||||||
if role == "server" {
|
if role == "server" {
|
||||||
cfg := &config.XtcpProxyConf{}
|
cfg := &config.XtcpProxyConf{}
|
||||||
cfg.ProxyName = prefix + proxyName
|
cfg.ProxyName = prefix + proxyName
|
||||||
cfg.ProxyType = consts.StcpProxy
|
cfg.ProxyType = consts.XtcpProxy
|
||||||
cfg.UseEncryption = useEncryption
|
cfg.UseEncryption = useEncryption
|
||||||
cfg.UseCompression = useCompression
|
cfg.UseCompression = useCompression
|
||||||
cfg.Role = role
|
cfg.Role = role
|
||||||
@@ -84,7 +85,7 @@ var xtcpCmd = &cobra.Command{
|
|||||||
} else if role == "visitor" {
|
} else if role == "visitor" {
|
||||||
cfg := &config.XtcpVisitorConf{}
|
cfg := &config.XtcpVisitorConf{}
|
||||||
cfg.ProxyName = prefix + proxyName
|
cfg.ProxyName = prefix + proxyName
|
||||||
cfg.ProxyType = consts.StcpProxy
|
cfg.ProxyType = consts.XtcpProxy
|
||||||
cfg.UseEncryption = useEncryption
|
cfg.UseEncryption = useEncryption
|
||||||
cfg.UseCompression = useCompression
|
cfg.UseCompression = useCompression
|
||||||
cfg.Role = role
|
cfg.Role = role
|
||||||
@@ -103,7 +104,7 @@ var xtcpCmd = &cobra.Command{
|
|||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = startService(proxyConfs, visitorConfs)
|
err = startService(clientCfg, proxyConfs, visitorConfs, "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
|
|||||||
@@ -12,14 +12,20 @@
|
|||||||
// 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 // "github.com/fatedier/frp/cmd/frps"
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"math/rand"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/fatedier/golib/crypto"
|
"github.com/fatedier/golib/crypto"
|
||||||
|
|
||||||
|
_ "github.com/fatedier/frp/assets/frps/statik"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
crypto.DefaultSalt = "frp"
|
crypto.DefaultSalt = "frp"
|
||||||
|
rand.Seed(time.Now().UnixNano())
|
||||||
|
|
||||||
Execute()
|
Execute()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ import (
|
|||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
"github.com/fatedier/frp/g"
|
|
||||||
"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"
|
||||||
@@ -53,6 +52,7 @@ var (
|
|||||||
logFile string
|
logFile string
|
||||||
logLevel string
|
logLevel string
|
||||||
logMaxDays int64
|
logMaxDays int64
|
||||||
|
disableLogColor bool
|
||||||
token string
|
token string
|
||||||
subDomainHost string
|
subDomainHost string
|
||||||
tcpMux bool
|
tcpMux bool
|
||||||
@@ -62,7 +62,7 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
rootCmd.PersistentFlags().StringVarP(&cfgFile, "", "c", "", "config file of frps")
|
rootCmd.PersistentFlags().StringVarP(&cfgFile, "config", "c", "", "config file of frps")
|
||||||
rootCmd.PersistentFlags().BoolVarP(&showVersion, "version", "v", false, "version of frpc")
|
rootCmd.PersistentFlags().BoolVarP(&showVersion, "version", "v", false, "version of frpc")
|
||||||
|
|
||||||
rootCmd.PersistentFlags().StringVarP(&bindAddr, "bind_addr", "", "0.0.0.0", "bind address")
|
rootCmd.PersistentFlags().StringVarP(&bindAddr, "bind_addr", "", "0.0.0.0", "bind address")
|
||||||
@@ -79,7 +79,9 @@ func init() {
|
|||||||
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(&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().BoolVarP(&disableLogColor, "disable_log_color", "", false, "disable log color in console")
|
||||||
|
|
||||||
rootCmd.PersistentFlags().StringVarP(&token, "token", "t", "", "auth token")
|
rootCmd.PersistentFlags().StringVarP(&token, "token", "t", "", "auth token")
|
||||||
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().StringVarP(&allowPorts, "allow_ports", "", "", "allow ports")
|
||||||
@@ -95,6 +97,7 @@ var rootCmd = &cobra.Command{
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var cfg config.ServerCommonConf
|
||||||
var err error
|
var err error
|
||||||
if cfgFile != "" {
|
if cfgFile != "" {
|
||||||
var content string
|
var content string
|
||||||
@@ -102,16 +105,15 @@ var rootCmd = &cobra.Command{
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
g.GlbServerCfg.CfgFile = cfgFile
|
cfg, err = parseServerCommonCfg(CfgFileTypeIni, content)
|
||||||
err = parseServerCommonCfg(CfgFileTypeIni, content)
|
|
||||||
} else {
|
} else {
|
||||||
err = parseServerCommonCfg(CfgFileTypeCmd, "")
|
cfg, err = parseServerCommonCfg(CfgFileTypeCmd, "")
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = runServer()
|
err = runServer(cfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
@@ -126,52 +128,51 @@ func Execute() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseServerCommonCfg(fileType int, content string) (err error) {
|
func parseServerCommonCfg(fileType int, content string) (cfg config.ServerCommonConf, err error) {
|
||||||
if fileType == CfgFileTypeIni {
|
if fileType == CfgFileTypeIni {
|
||||||
err = parseServerCommonCfgFromIni(content)
|
cfg, err = parseServerCommonCfgFromIni(content)
|
||||||
} else if fileType == CfgFileTypeCmd {
|
} else if fileType == CfgFileTypeCmd {
|
||||||
err = parseServerCommonCfgFromCmd()
|
cfg, err = parseServerCommonCfgFromCmd()
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err = g.GlbServerCfg.ServerCommonConf.Check()
|
err = cfg.Check()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
config.InitServerCfg(&g.GlbServerCfg.ServerCommonConf)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseServerCommonCfgFromIni(content string) (err error) {
|
func parseServerCommonCfgFromIni(content string) (config.ServerCommonConf, error) {
|
||||||
cfg, err := config.UnmarshalServerConfFromIni(&g.GlbServerCfg.ServerCommonConf, content)
|
cfg, err := config.UnmarshalServerConfFromIni(content)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return config.ServerCommonConf{}, err
|
||||||
}
|
}
|
||||||
g.GlbServerCfg.ServerCommonConf = *cfg
|
return cfg, nil
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseServerCommonCfgFromCmd() (err error) {
|
func parseServerCommonCfgFromCmd() (cfg config.ServerCommonConf, err error) {
|
||||||
g.GlbServerCfg.BindAddr = bindAddr
|
cfg = config.GetDefaultServerConf()
|
||||||
g.GlbServerCfg.BindPort = bindPort
|
|
||||||
g.GlbServerCfg.BindUdpPort = bindUdpPort
|
cfg.BindAddr = bindAddr
|
||||||
g.GlbServerCfg.KcpBindPort = kcpBindPort
|
cfg.BindPort = bindPort
|
||||||
g.GlbServerCfg.ProxyBindAddr = proxyBindAddr
|
cfg.BindUdpPort = bindUdpPort
|
||||||
g.GlbServerCfg.VhostHttpPort = vhostHttpPort
|
cfg.KcpBindPort = kcpBindPort
|
||||||
g.GlbServerCfg.VhostHttpsPort = vhostHttpsPort
|
cfg.ProxyBindAddr = proxyBindAddr
|
||||||
g.GlbServerCfg.VhostHttpTimeout = vhostHttpTimeout
|
cfg.VhostHttpPort = vhostHttpPort
|
||||||
g.GlbServerCfg.DashboardAddr = dashboardAddr
|
cfg.VhostHttpsPort = vhostHttpsPort
|
||||||
g.GlbServerCfg.DashboardPort = dashboardPort
|
cfg.VhostHttpTimeout = vhostHttpTimeout
|
||||||
g.GlbServerCfg.DashboardUser = dashboardUser
|
cfg.DashboardAddr = dashboardAddr
|
||||||
g.GlbServerCfg.DashboardPwd = dashboardPwd
|
cfg.DashboardPort = dashboardPort
|
||||||
g.GlbServerCfg.LogFile = logFile
|
cfg.DashboardUser = dashboardUser
|
||||||
g.GlbServerCfg.LogLevel = logLevel
|
cfg.DashboardPwd = dashboardPwd
|
||||||
g.GlbServerCfg.LogMaxDays = logMaxDays
|
cfg.LogFile = logFile
|
||||||
g.GlbServerCfg.Token = token
|
cfg.LogLevel = logLevel
|
||||||
g.GlbServerCfg.SubDomainHost = subDomainHost
|
cfg.LogMaxDays = logMaxDays
|
||||||
|
cfg.Token = token
|
||||||
|
cfg.SubDomainHost = subDomainHost
|
||||||
if len(allowPorts) > 0 {
|
if len(allowPorts) > 0 {
|
||||||
// e.g. 1000-2000,2001,2002,3000-4000
|
// e.g. 1000-2000,2001,2002,3000-4000
|
||||||
ports, errRet := util.ParseRangeNumbers(allowPorts)
|
ports, errRet := util.ParseRangeNumbers(allowPorts)
|
||||||
@@ -181,28 +182,27 @@ func parseServerCommonCfgFromCmd() (err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, port := range ports {
|
for _, port := range ports {
|
||||||
g.GlbServerCfg.AllowPorts[int(port)] = struct{}{}
|
cfg.AllowPorts[int(port)] = struct{}{}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
g.GlbServerCfg.MaxPortsPerClient = maxPortsPerClient
|
cfg.MaxPortsPerClient = maxPortsPerClient
|
||||||
|
|
||||||
if logFile == "console" {
|
if logFile == "console" {
|
||||||
g.GlbClientCfg.LogWay = "console"
|
cfg.LogWay = "console"
|
||||||
} else {
|
} else {
|
||||||
g.GlbClientCfg.LogWay = "file"
|
cfg.LogWay = "file"
|
||||||
}
|
}
|
||||||
|
cfg.DisableLogColor = disableLogColor
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func runServer() (err error) {
|
func runServer(cfg config.ServerCommonConf) (err error) {
|
||||||
log.InitLog(g.GlbServerCfg.LogWay, g.GlbServerCfg.LogFile, g.GlbServerCfg.LogLevel,
|
log.InitLog(cfg.LogWay, cfg.LogFile, cfg.LogLevel, cfg.LogMaxDays, cfg.DisableLogColor)
|
||||||
g.GlbServerCfg.LogMaxDays)
|
svr, err := server.NewService(cfg)
|
||||||
svr, err := server.NewService()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
log.Info("Start frps success")
|
log.Info("Start frps success")
|
||||||
server.ServerService = svr
|
|
||||||
svr.Run()
|
svr.Run()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,9 @@ log_level = info
|
|||||||
|
|
||||||
log_max_days = 3
|
log_max_days = 3
|
||||||
|
|
||||||
|
# disable log colors when log_file is console, default is false
|
||||||
|
disable_log_color = false
|
||||||
|
|
||||||
# for authentication
|
# for authentication
|
||||||
token = 12345678
|
token = 12345678
|
||||||
|
|
||||||
@@ -26,6 +29,8 @@ admin_addr = 127.0.0.1
|
|||||||
admin_port = 7400
|
admin_port = 7400
|
||||||
admin_user = admin
|
admin_user = admin
|
||||||
admin_pwd = admin
|
admin_pwd = admin
|
||||||
|
# Admin assets directory. By default, these assets are bundled with frpc.
|
||||||
|
# assets_dir = ./static
|
||||||
|
|
||||||
# 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
|
||||||
@@ -44,10 +49,13 @@ login_fail_exit = true
|
|||||||
# now it supports tcp and kcp and websocket, default is tcp
|
# now it supports tcp and kcp and websocket, default is tcp
|
||||||
protocol = tcp
|
protocol = tcp
|
||||||
|
|
||||||
|
# if tls_enable is true, frpc will connect frps by tls
|
||||||
|
tls_enable = true
|
||||||
|
|
||||||
# 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 seperated by ','
|
||||||
# default is empty, means all proxies
|
# default is empty, means all proxies
|
||||||
# start = ssh,dns
|
# start = ssh,dns
|
||||||
|
|
||||||
@@ -151,6 +159,9 @@ use_encryption = false
|
|||||||
use_compression = false
|
use_compression = false
|
||||||
subdomain = web01
|
subdomain = web01
|
||||||
custom_domains = web02.yourdomain.com
|
custom_domains = web02.yourdomain.com
|
||||||
|
# if not empty, frpc will use proxy protocol to transfer connection info to your local service
|
||||||
|
# v1 or v2 or empty
|
||||||
|
proxy_protocol_version = v2
|
||||||
|
|
||||||
[plugin_unix_domain_socket]
|
[plugin_unix_domain_socket]
|
||||||
type = tcp
|
type = tcp
|
||||||
@@ -184,6 +195,16 @@ plugin_strip_prefix = static
|
|||||||
plugin_http_user = abc
|
plugin_http_user = abc
|
||||||
plugin_http_passwd = abc
|
plugin_http_passwd = abc
|
||||||
|
|
||||||
|
[plugin_https2http]
|
||||||
|
type = https
|
||||||
|
custom_domains = test.yourdomain.com
|
||||||
|
plugin = https2http
|
||||||
|
plugin_local_addr = 127.0.0.1:80
|
||||||
|
plugin_crt_path = ./server.crt
|
||||||
|
plugin_key_path = ./server.key
|
||||||
|
plugin_host_header_rewrite = 127.0.0.1
|
||||||
|
plugin_header_X-From-Where = frp
|
||||||
|
|
||||||
[secret_tcp]
|
[secret_tcp]
|
||||||
# If the type is secret tcp, remote_port is useless
|
# If the type is secret tcp, remote_port is useless
|
||||||
# Who want to connect local port should deploy another frpc with stcp proxy and role is visitor
|
# Who want to connect local port should deploy another frpc with stcp proxy and role is visitor
|
||||||
|
|||||||
@@ -43,6 +43,9 @@ log_level = info
|
|||||||
|
|
||||||
log_max_days = 3
|
log_max_days = 3
|
||||||
|
|
||||||
|
# disable log colors when log_file is console, default is false
|
||||||
|
disable_log_color = false
|
||||||
|
|
||||||
# auth token
|
# auth token
|
||||||
token = 12345678
|
token = 12345678
|
||||||
|
|
||||||
@@ -65,3 +68,6 @@ subdomain_host = frps.com
|
|||||||
|
|
||||||
# if tcp stream multiplexing is used, default is true
|
# if tcp stream multiplexing is used, default is true
|
||||||
tcp_mux = true
|
tcp_mux = true
|
||||||
|
|
||||||
|
# custom 404 page for HTTP requests
|
||||||
|
# custom_404_page = /path/to/404.html
|
||||||
|
|||||||
14
conf/systemd/frpc.service
Normal file
14
conf/systemd/frpc.service
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=Frp Client Service
|
||||||
|
After=network.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
User=nobody
|
||||||
|
Restart=on-failure
|
||||||
|
RestartSec=5s
|
||||||
|
ExecStart=/usr/bin/frpc -c /etc/frp/frpc.ini
|
||||||
|
ExecReload=/usr/bin/frpc reload -c /etc/frp/frpc.ini
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
14
conf/systemd/frpc@.service
Normal file
14
conf/systemd/frpc@.service
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=Frp Client Service
|
||||||
|
After=network.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=idle
|
||||||
|
User=nobody
|
||||||
|
Restart=on-failure
|
||||||
|
RestartSec=5s
|
||||||
|
ExecStart=/usr/bin/frpc -c /etc/frp/%i.ini
|
||||||
|
ExecReload=/usr/bin/frpc reload -c /etc/frp/%i.ini
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
13
conf/systemd/frps.service
Normal file
13
conf/systemd/frps.service
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=Frp Server Service
|
||||||
|
After=network.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
User=nobody
|
||||||
|
Restart=on-failure
|
||||||
|
RestartSec=5s
|
||||||
|
ExecStart=/usr/bin/frps -c /etc/frp/frps.ini
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
13
conf/systemd/frps@.service
Normal file
13
conf/systemd/frps@.service
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=Frp Server Service
|
||||||
|
After=network.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
User=nobody
|
||||||
|
Restart=on-failure
|
||||||
|
RestartSec=5s
|
||||||
|
ExecStart=/usr/bin/frps -c /etc/frp/%i.ini
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
32
g/g.go
32
g/g.go
@@ -1,32 +0,0 @@
|
|||||||
package g
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/fatedier/frp/models/config"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
GlbClientCfg *ClientCfg
|
|
||||||
GlbServerCfg *ServerCfg
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
GlbClientCfg = &ClientCfg{
|
|
||||||
ClientCommonConf: *config.GetDefaultClientConf(),
|
|
||||||
}
|
|
||||||
GlbServerCfg = &ServerCfg{
|
|
||||||
ServerCommonConf: *config.GetDefaultServerConf(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type ClientCfg struct {
|
|
||||||
config.ClientCommonConf
|
|
||||||
|
|
||||||
CfgFile string
|
|
||||||
ServerUdpPort int // this is configured by login response from frps
|
|
||||||
}
|
|
||||||
|
|
||||||
type ServerCfg struct {
|
|
||||||
config.ServerCommonConf
|
|
||||||
|
|
||||||
CfgFile string
|
|
||||||
}
|
|
||||||
32
go.mod
Normal file
32
go.mod
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
module github.com/fatedier/frp
|
||||||
|
|
||||||
|
go 1.12
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5
|
||||||
|
github.com/fatedier/beego v0.0.0-20171024143340-6c6a4f5bd5eb
|
||||||
|
github.com/fatedier/golib v0.0.0-20181107124048-ff8cd814b049
|
||||||
|
github.com/fatedier/kcp-go v2.0.4-0.20190803094908-fe8645b0a904+incompatible
|
||||||
|
github.com/golang/snappy v0.0.0-20170215233205-553a64147049 // indirect
|
||||||
|
github.com/gorilla/mux v1.7.3
|
||||||
|
github.com/gorilla/websocket v1.4.0
|
||||||
|
github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d
|
||||||
|
github.com/inconshreveable/mousetrap v1.0.0 // indirect
|
||||||
|
github.com/klauspost/cpuid v1.2.0 // indirect
|
||||||
|
github.com/klauspost/reedsolomon v1.9.1 // indirect
|
||||||
|
github.com/mattn/go-runewidth v0.0.4 // indirect
|
||||||
|
github.com/pires/go-proxyproto v0.0.0-20190111085350-4d51b51e3bfc
|
||||||
|
github.com/pkg/errors v0.8.0 // indirect
|
||||||
|
github.com/rakyll/statik v0.1.1
|
||||||
|
github.com/rodaine/table v1.0.0
|
||||||
|
github.com/spf13/cobra v0.0.3
|
||||||
|
github.com/spf13/pflag v1.0.1 // indirect
|
||||||
|
github.com/stretchr/testify v1.3.0
|
||||||
|
github.com/templexxx/cpufeat v0.0.0-20170927014610-3794dfbfb047 // indirect
|
||||||
|
github.com/templexxx/xor v0.0.0-20170926022130-0af8e873c554 // indirect
|
||||||
|
github.com/tjfoc/gmsm v0.0.0-20171124023159-98aa888b79d8 // indirect
|
||||||
|
github.com/vaughan0/go-ini v0.0.0-20130923145212-a98ad7ee00ec
|
||||||
|
github.com/xtaci/lossyconn v0.0.0-20190602105132-8df528c0c9ae // indirect
|
||||||
|
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80
|
||||||
|
golang.org/x/text v0.3.2 // indirect
|
||||||
|
)
|
||||||
63
go.sum
Normal file
63
go.sum
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
|
||||||
|
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
|
||||||
|
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
|
||||||
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/fatedier/beego v0.0.0-20171024143340-6c6a4f5bd5eb h1:wCrNShQidLmvVWn/0PikGmpdP0vtQmnvyRg3ZBEhczw=
|
||||||
|
github.com/fatedier/beego v0.0.0-20171024143340-6c6a4f5bd5eb/go.mod h1:wx3gB6dbIfBRcucp94PI9Bt3I0F2c/MyNEWuhzpWiwk=
|
||||||
|
github.com/fatedier/golib v0.0.0-20181107124048-ff8cd814b049 h1:teH578mf2ii42NHhIp3PhgvjU5bv+NFMq9fSQR8NaG8=
|
||||||
|
github.com/fatedier/golib v0.0.0-20181107124048-ff8cd814b049/go.mod h1:DqIrnl0rp3Zybg9zbJmozTy1n8fYJoX+QoAj9slIkKM=
|
||||||
|
github.com/fatedier/kcp-go v2.0.4-0.20190803094908-fe8645b0a904+incompatible h1:ssXat9YXFvigNge/IkkZvFMn8yeYKFX+uI6wn2mLJ74=
|
||||||
|
github.com/fatedier/kcp-go v2.0.4-0.20190803094908-fe8645b0a904+incompatible/go.mod h1:YpCOaxj7vvMThhIQ9AfTOPW2sfztQR5WDfs7AflSy4s=
|
||||||
|
github.com/golang/snappy v0.0.0-20170215233205-553a64147049 h1:K9KHZbXKpGydfDN0aZrsoHpLJlZsBrGMFWbgLDGnPZk=
|
||||||
|
github.com/golang/snappy v0.0.0-20170215233205-553a64147049/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||||
|
github.com/gorilla/mux v1.7.3 h1:gnP5JzjVOuiZD07fKKToCAOjS0yOpj/qPETTXCCS6hw=
|
||||||
|
github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
||||||
|
github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q=
|
||||||
|
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
|
||||||
|
github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d h1:kJCB4vdITiW1eC1vq2e6IsrXKrZit1bv/TDYFGMp4BQ=
|
||||||
|
github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM=
|
||||||
|
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
|
||||||
|
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||||
|
github.com/klauspost/cpuid v1.2.0 h1:NMpwD2G9JSFOE1/TJjGSo5zG7Yb2bTe7eq1jH+irmeE=
|
||||||
|
github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
|
||||||
|
github.com/klauspost/reedsolomon v1.9.1 h1:kYrT1MlR4JH6PqOpC+okdb9CDTcwEC/BqpzK4WFyXL8=
|
||||||
|
github.com/klauspost/reedsolomon v1.9.1/go.mod h1:CwCi+NUr9pqSVktrkN+Ondf06rkhYZ/pcNv7fu+8Un4=
|
||||||
|
github.com/mattn/go-runewidth v0.0.4 h1:2BvfKmzob6Bmd4YsL0zygOqfdFnK7GR4QL06Do4/p7Y=
|
||||||
|
github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
||||||
|
github.com/pires/go-proxyproto v0.0.0-20190111085350-4d51b51e3bfc h1:lNOt1SMsgHXTdpuGw+RpnJtzUcCb/oRKZP65pBy9pr8=
|
||||||
|
github.com/pires/go-proxyproto v0.0.0-20190111085350-4d51b51e3bfc/go.mod h1:6/gX3+E/IYGa0wMORlSMla999awQFdbaeQCHjSMKIzY=
|
||||||
|
github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw=
|
||||||
|
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/rakyll/statik v0.1.1 h1:fCLHsIMajHqD5RKigbFXpvX3dN7c80Pm12+NCrI3kvg=
|
||||||
|
github.com/rakyll/statik v0.1.1/go.mod h1:OEi9wJV/fMUAGx1eNjq75DKDsJVuEv1U0oYdX6GX8Zs=
|
||||||
|
github.com/rodaine/table v1.0.0 h1:UaCJG5Axc/cNXVGXqnCrffm1KxP0OfYLe1HuJLf5sFY=
|
||||||
|
github.com/rodaine/table v1.0.0/go.mod h1:YAUzwPOji0DUJNEvggdxyQcUAl4g3hDRcFlyjnnR51I=
|
||||||
|
github.com/spf13/cobra v0.0.3 h1:ZlrZ4XsMRm04Fr5pSFxBgfND2EBVa1nLpiy1stUsX/8=
|
||||||
|
github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
|
||||||
|
github.com/spf13/pflag v1.0.1 h1:aCvUg6QPl3ibpQUxyLkrEkCHtPqYJL4x9AuhqVqFis4=
|
||||||
|
github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||||
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
|
||||||
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
|
github.com/templexxx/cpufeat v0.0.0-20170927014610-3794dfbfb047 h1:K+jtWCOuZgCra7eXZ/VWn2FbJmrA/D058mTXhh2rq+8=
|
||||||
|
github.com/templexxx/cpufeat v0.0.0-20170927014610-3794dfbfb047/go.mod h1:wM7WEvslTq+iOEAMDLSzhVuOt5BRZ05WirO+b09GHQU=
|
||||||
|
github.com/templexxx/xor v0.0.0-20170926022130-0af8e873c554 h1:pexgSe+JCFuxG+uoMZLO+ce8KHtdHGhst4cs6rw3gmk=
|
||||||
|
github.com/templexxx/xor v0.0.0-20170926022130-0af8e873c554/go.mod h1:5XA7W9S6mni3h5uvOC75dA3m9CCCaS83lltmc0ukdi4=
|
||||||
|
github.com/tjfoc/gmsm v0.0.0-20171124023159-98aa888b79d8 h1:6CNSDqI1wiE+JqyOy5Qt/yo/DoNI2/QmmOZeiCid2Nw=
|
||||||
|
github.com/tjfoc/gmsm v0.0.0-20171124023159-98aa888b79d8/go.mod h1:XxO4hdhhrzAd+G4CjDqaOkd0hUzmtPR/d3EiBBMn/wc=
|
||||||
|
github.com/vaughan0/go-ini v0.0.0-20130923145212-a98ad7ee00ec h1:DGmKwyZwEB8dI7tbLt/I/gQuP559o/0FrAkHKlQM/Ks=
|
||||||
|
github.com/vaughan0/go-ini v0.0.0-20130923145212-a98ad7ee00ec/go.mod h1:owBmyHYMLkxyrugmfwE/DLJyW8Ro9mkphwuVErQ0iUw=
|
||||||
|
github.com/xtaci/lossyconn v0.0.0-20190602105132-8df528c0c9ae h1:J0GxkO96kL4WF+AIT3M4mfUVinOCPgf2uUWYFUzN0sM=
|
||||||
|
github.com/xtaci/lossyconn v0.0.0-20190602105132-8df528c0c9ae/go.mod h1:gXtu8J62kEgmN++bm9BVICuT/e8yiLI2KFobd/TRFsE=
|
||||||
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M=
|
||||||
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
|
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80 h1:Ao/3l156eZf2AW5wK8a7/smtodRU+gha3+BeqJ69lRk=
|
||||||
|
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU=
|
||||||
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
|
||||||
|
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||||
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
@@ -23,33 +23,103 @@ import (
|
|||||||
ini "github.com/vaughan0/go-ini"
|
ini "github.com/vaughan0/go-ini"
|
||||||
)
|
)
|
||||||
|
|
||||||
// client common config
|
// ClientCommonConf contains information for a client service. It is
|
||||||
|
// recommended to use GetDefaultClientConf instead of creating this object
|
||||||
|
// directly, so that all unspecified fields have reasonable default values.
|
||||||
type ClientCommonConf struct {
|
type ClientCommonConf struct {
|
||||||
ServerAddr string `json:"server_addr"`
|
// ServerAddr specifies the address of the server to connect to. By
|
||||||
ServerPort int `json:"server_port"`
|
// default, this value is "0.0.0.0".
|
||||||
HttpProxy string `json:"http_proxy"`
|
ServerAddr string `json:"server_addr"`
|
||||||
LogFile string `json:"log_file"`
|
// ServerPort specifies the port to connect to the server on. By default,
|
||||||
LogWay string `json:"log_way"`
|
// this value is 7000.
|
||||||
LogLevel string `json:"log_level"`
|
ServerPort int `json:"server_port"`
|
||||||
LogMaxDays int64 `json:"log_max_days"`
|
// HttpProxy specifies a proxy address to connect to the server through. If
|
||||||
Token string `json:"token"`
|
// this value is "", the server will be connected to directly. By default,
|
||||||
AdminAddr string `json:"admin_addr"`
|
// this value is read from the "http_proxy" environment variable.
|
||||||
AdminPort int `json:"admin_port"`
|
HttpProxy string `json:"http_proxy"`
|
||||||
AdminUser string `json:"admin_user"`
|
// LogFile specifies a file where logs will be written to. This value will
|
||||||
AdminPwd string `json:"admin_pwd"`
|
// only be used if LogWay is set appropriately. By default, this value is
|
||||||
PoolCount int `json:"pool_count"`
|
// "console".
|
||||||
TcpMux bool `json:"tcp_mux"`
|
LogFile string `json:"log_file"`
|
||||||
User string `json:"user"`
|
// LogWay specifies the way logging is managed. Valid values are "console"
|
||||||
DnsServer string `json:"dns_server"`
|
// or "file". If "console" is used, logs will be printed to stdout. If
|
||||||
LoginFailExit bool `json:"login_fail_exit"`
|
// "file" is used, logs will be printed to LogFile. By default, this value
|
||||||
Start map[string]struct{} `json:"start"`
|
// is "console".
|
||||||
Protocol string `json:"protocol"`
|
LogWay string `json:"log_way"`
|
||||||
HeartBeatInterval int64 `json:"heartbeat_interval"`
|
// LogLevel specifies the minimum log level. Valid values are "trace",
|
||||||
HeartBeatTimeout int64 `json:"heartbeat_timeout"`
|
// "debug", "info", "warn", and "error". By default, this value is "info".
|
||||||
|
LogLevel string `json:"log_level"`
|
||||||
|
// LogMaxDays specifies the maximum number of days to store log information
|
||||||
|
// before deletion. This is only used if LogWay == "file". By default, this
|
||||||
|
// value is 0.
|
||||||
|
LogMaxDays int64 `json:"log_max_days"`
|
||||||
|
// DisableLogColor disables log colors when LogWay == "console" when set to
|
||||||
|
// true. By default, this value is false.
|
||||||
|
DisableLogColor bool `json:"disable_log_color"`
|
||||||
|
// Token specifies the authorization token used to create keys to be sent
|
||||||
|
// to the server. The server must have a matching token for authorization
|
||||||
|
// to succeed. By default, this value is "".
|
||||||
|
Token string `json:"token"`
|
||||||
|
// AdminAddr specifies the address that the admin server binds to. By
|
||||||
|
// default, this value is "127.0.0.1".
|
||||||
|
AdminAddr string `json:"admin_addr"`
|
||||||
|
// AdminPort specifies the port for the admin server to listen on. If this
|
||||||
|
// value is 0, the admin server will not be started. By default, this value
|
||||||
|
// is 0.
|
||||||
|
AdminPort int `json:"admin_port"`
|
||||||
|
// AdminUser specifies the username that the admin server will use for
|
||||||
|
// login. By default, this value is "admin".
|
||||||
|
AdminUser string `json:"admin_user"`
|
||||||
|
// AdminPwd specifies the password that the admin server will use for
|
||||||
|
// login. By default, this value is "admin".
|
||||||
|
AdminPwd string `json:"admin_pwd"`
|
||||||
|
// AssetsDir specifies the local directory that the admin server will load
|
||||||
|
// resources from. If this value is "", assets will be loaded from the
|
||||||
|
// bundled executable using statik. By default, this value is "".
|
||||||
|
AssetsDir string `json:"assets_dir"`
|
||||||
|
// PoolCount specifies the number of connections the client will make to
|
||||||
|
// the server in advance. By default, this value is 0.
|
||||||
|
PoolCount int `json:"pool_count"`
|
||||||
|
// TcpMux toggles TCP stream multiplexing. This allows multiple requests
|
||||||
|
// from a client to share a single TCP connection. If this value is true,
|
||||||
|
// the server must have TCP multiplexing enabled as well. By default, this
|
||||||
|
// value is true.
|
||||||
|
TcpMux bool `json:"tcp_mux"`
|
||||||
|
// User specifies a prefix for proxy names to distinguish them from other
|
||||||
|
// clients. If this value is not "", proxy names will automatically be
|
||||||
|
// changed to "{user}.{proxy_name}". By default, this value is "".
|
||||||
|
User string `json:"user"`
|
||||||
|
// DnsServer specifies a DNS server address for FRPC to use. If this value
|
||||||
|
// is "", the default DNS will be used. By default, this value is "".
|
||||||
|
DnsServer string `json:"dns_server"`
|
||||||
|
// LoginFailExit controls whether or not the client should exit after a
|
||||||
|
// failed login attempt. If false, the client will retry until a login
|
||||||
|
// attempt succeeds. By default, this value is true.
|
||||||
|
LoginFailExit bool `json:"login_fail_exit"`
|
||||||
|
// Start specifies a set of enabled proxies by name. If this set is empty,
|
||||||
|
// all supplied proxies are enabled. By default, this value is an empty
|
||||||
|
// set.
|
||||||
|
Start map[string]struct{} `json:"start"`
|
||||||
|
// Protocol specifies the protocol to use when interacting with the server.
|
||||||
|
// Valid values are "tcp", "kcp", and "websocket". By default, this value
|
||||||
|
// is "tcp".
|
||||||
|
Protocol string `json:"protocol"`
|
||||||
|
// TLSEnable specifies whether or not TLS should be used when communicating
|
||||||
|
// with the server.
|
||||||
|
TLSEnable bool `json:"tls_enable"`
|
||||||
|
// HeartBeatInterval specifies at what interval heartbeats are sent to the
|
||||||
|
// server, in seconds. It is not recommended to change this value. By
|
||||||
|
// default, this value is 30.
|
||||||
|
HeartBeatInterval int64 `json:"heartbeat_interval"`
|
||||||
|
// HeartBeatTimeout specifies the maximum allowed heartbeat response delay
|
||||||
|
// before the connection is terminated, in seconds. It is not recommended
|
||||||
|
// to change this value. By default, this value is 90.
|
||||||
|
HeartBeatTimeout int64 `json:"heartbeat_timeout"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetDefaultClientConf() *ClientCommonConf {
|
// GetDefaultClientConf returns a client configuration with default values.
|
||||||
return &ClientCommonConf{
|
func GetDefaultClientConf() ClientCommonConf {
|
||||||
|
return ClientCommonConf{
|
||||||
ServerAddr: "0.0.0.0",
|
ServerAddr: "0.0.0.0",
|
||||||
ServerPort: 7000,
|
ServerPort: 7000,
|
||||||
HttpProxy: os.Getenv("http_proxy"),
|
HttpProxy: os.Getenv("http_proxy"),
|
||||||
@@ -57,11 +127,13 @@ func GetDefaultClientConf() *ClientCommonConf {
|
|||||||
LogWay: "console",
|
LogWay: "console",
|
||||||
LogLevel: "info",
|
LogLevel: "info",
|
||||||
LogMaxDays: 3,
|
LogMaxDays: 3,
|
||||||
|
DisableLogColor: false,
|
||||||
Token: "",
|
Token: "",
|
||||||
AdminAddr: "127.0.0.1",
|
AdminAddr: "127.0.0.1",
|
||||||
AdminPort: 0,
|
AdminPort: 0,
|
||||||
AdminUser: "",
|
AdminUser: "",
|
||||||
AdminPwd: "",
|
AdminPwd: "",
|
||||||
|
AssetsDir: "",
|
||||||
PoolCount: 1,
|
PoolCount: 1,
|
||||||
TcpMux: true,
|
TcpMux: true,
|
||||||
User: "",
|
User: "",
|
||||||
@@ -69,21 +141,18 @@ func GetDefaultClientConf() *ClientCommonConf {
|
|||||||
LoginFailExit: true,
|
LoginFailExit: true,
|
||||||
Start: make(map[string]struct{}),
|
Start: make(map[string]struct{}),
|
||||||
Protocol: "tcp",
|
Protocol: "tcp",
|
||||||
|
TLSEnable: false,
|
||||||
HeartBeatInterval: 30,
|
HeartBeatInterval: 30,
|
||||||
HeartBeatTimeout: 90,
|
HeartBeatTimeout: 90,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func UnmarshalClientConfFromIni(defaultCfg *ClientCommonConf, content string) (cfg *ClientCommonConf, err error) {
|
func UnmarshalClientConfFromIni(content string) (cfg ClientCommonConf, err error) {
|
||||||
cfg = defaultCfg
|
cfg = GetDefaultClientConf()
|
||||||
if cfg == nil {
|
|
||||||
cfg = GetDefaultClientConf()
|
|
||||||
}
|
|
||||||
|
|
||||||
conf, err := ini.Load(strings.NewReader(content))
|
conf, err := ini.Load(strings.NewReader(content))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = fmt.Errorf("parse ini conf file error: %v", err)
|
return ClientCommonConf{}, fmt.Errorf("parse ini conf file error: %v", err)
|
||||||
return nil, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -104,6 +173,10 @@ func UnmarshalClientConfFromIni(defaultCfg *ClientCommonConf, content string) (c
|
|||||||
cfg.ServerPort = int(v)
|
cfg.ServerPort = int(v)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if tmpStr, ok = conf.Get("common", "disable_log_color"); ok && tmpStr == "true" {
|
||||||
|
cfg.DisableLogColor = true
|
||||||
|
}
|
||||||
|
|
||||||
if tmpStr, ok = conf.Get("common", "http_proxy"); ok {
|
if tmpStr, ok = conf.Get("common", "http_proxy"); ok {
|
||||||
cfg.HttpProxy = tmpStr
|
cfg.HttpProxy = tmpStr
|
||||||
}
|
}
|
||||||
@@ -152,6 +225,10 @@ func UnmarshalClientConfFromIni(defaultCfg *ClientCommonConf, content string) (c
|
|||||||
cfg.AdminPwd = tmpStr
|
cfg.AdminPwd = tmpStr
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if tmpStr, ok = conf.Get("common", "assets_dir"); ok {
|
||||||
|
cfg.AssetsDir = tmpStr
|
||||||
|
}
|
||||||
|
|
||||||
if tmpStr, ok = conf.Get("common", "pool_count"); ok {
|
if tmpStr, ok = conf.Get("common", "pool_count"); ok {
|
||||||
if v, err = strconv.ParseInt(tmpStr, 10, 64); err == nil {
|
if v, err = strconv.ParseInt(tmpStr, 10, 64); err == nil {
|
||||||
cfg.PoolCount = int(v)
|
cfg.PoolCount = int(v)
|
||||||
@@ -194,6 +271,12 @@ func UnmarshalClientConfFromIni(defaultCfg *ClientCommonConf, content string) (c
|
|||||||
cfg.Protocol = tmpStr
|
cfg.Protocol = tmpStr
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if tmpStr, ok = conf.Get("common", "tls_enable"); ok && tmpStr == "true" {
|
||||||
|
cfg.TLSEnable = true
|
||||||
|
} else {
|
||||||
|
cfg.TLSEnable = false
|
||||||
|
}
|
||||||
|
|
||||||
if tmpStr, ok = conf.Get("common", "heartbeat_timeout"); ok {
|
if tmpStr, ok = conf.Get("common", "heartbeat_timeout"); ok {
|
||||||
if v, err = strconv.ParseInt(tmpStr, 10, 64); err != nil {
|
if v, err = strconv.ParseInt(tmpStr, 10, 64); err != nil {
|
||||||
err = fmt.Errorf("Parse conf error: invalid heartbeat_timeout")
|
err = fmt.Errorf("Parse conf error: invalid heartbeat_timeout")
|
||||||
|
|||||||
@@ -58,11 +58,11 @@ type ProxyConf interface {
|
|||||||
UnmarshalFromIni(prefix string, name string, conf ini.Section) error
|
UnmarshalFromIni(prefix string, name string, conf ini.Section) error
|
||||||
MarshalToMsg(pMsg *msg.NewProxy)
|
MarshalToMsg(pMsg *msg.NewProxy)
|
||||||
CheckForCli() error
|
CheckForCli() error
|
||||||
CheckForSvr() error
|
CheckForSvr(serverCfg ServerCommonConf) error
|
||||||
Compare(conf ProxyConf) bool
|
Compare(conf ProxyConf) bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewProxyConfFromMsg(pMsg *msg.NewProxy) (cfg ProxyConf, err error) {
|
func NewProxyConfFromMsg(pMsg *msg.NewProxy, serverCfg ServerCommonConf) (cfg ProxyConf, err error) {
|
||||||
if pMsg.ProxyType == "" {
|
if pMsg.ProxyType == "" {
|
||||||
pMsg.ProxyType = consts.TcpProxy
|
pMsg.ProxyType = consts.TcpProxy
|
||||||
}
|
}
|
||||||
@@ -73,7 +73,7 @@ func NewProxyConfFromMsg(pMsg *msg.NewProxy) (cfg ProxyConf, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
cfg.UnmarshalFromMsg(pMsg)
|
cfg.UnmarshalFromMsg(pMsg)
|
||||||
err = cfg.CheckForSvr()
|
err = cfg.CheckForSvr(serverCfg)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -97,18 +97,36 @@ func NewProxyConfFromIni(prefix string, name string, section ini.Section) (cfg P
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// BaseProxy info
|
// BaseProxyConf provides configuration info that is common to all proxy types.
|
||||||
type BaseProxyConf struct {
|
type BaseProxyConf struct {
|
||||||
|
// ProxyName is the name of this proxy.
|
||||||
ProxyName string `json:"proxy_name"`
|
ProxyName string `json:"proxy_name"`
|
||||||
|
// ProxyType specifies the type of this proxy. Valid values include "tcp",
|
||||||
|
// "udp", "http", "https", "stcp", and "xtcp". By default, this value is
|
||||||
|
// "tcp".
|
||||||
ProxyType string `json:"proxy_type"`
|
ProxyType string `json:"proxy_type"`
|
||||||
|
|
||||||
UseEncryption bool `json:"use_encryption"`
|
// UseEncryption controls whether or not communication with the server will
|
||||||
UseCompression bool `json:"use_compression"`
|
// be encrypted. Encryption is done using the tokens supplied in the server
|
||||||
Group string `json:"group"`
|
// and client configuration. By default, this value is false.
|
||||||
GroupKey string `json:"group_key"`
|
UseEncryption bool `json:"use_encryption"`
|
||||||
|
// UseCompression controls whether or not communication with the server
|
||||||
|
// will be compressed. By default, this value is false.
|
||||||
|
UseCompression bool `json:"use_compression"`
|
||||||
|
// Group specifies which group the proxy is a part of. The server will use
|
||||||
|
// this information to load balance proxies in the same group. If the value
|
||||||
|
// is "", this proxy will not be in a group. By default, this value is "".
|
||||||
|
Group string `json:"group"`
|
||||||
|
// GroupKey specifies a group key, which should be the same among proxies
|
||||||
|
// of the same group. By default, this value is "".
|
||||||
|
GroupKey string `json:"group_key"`
|
||||||
|
|
||||||
|
// ProxyProtocolVersion specifies which protocol version to use. Valid
|
||||||
|
// values include "v1", "v2", and "". If the value is "", a protocol
|
||||||
|
// version will be automatically selected. By default, this value is "".
|
||||||
|
ProxyProtocolVersion string `json:"proxy_protocol_version"`
|
||||||
LocalSvrConf
|
LocalSvrConf
|
||||||
HealthCheckConf // only used for client
|
HealthCheckConf
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cfg *BaseProxyConf) GetBaseInfo() *BaseProxyConf {
|
func (cfg *BaseProxyConf) GetBaseInfo() *BaseProxyConf {
|
||||||
@@ -121,7 +139,8 @@ func (cfg *BaseProxyConf) compare(cmp *BaseProxyConf) bool {
|
|||||||
cfg.UseEncryption != cmp.UseEncryption ||
|
cfg.UseEncryption != cmp.UseEncryption ||
|
||||||
cfg.UseCompression != cmp.UseCompression ||
|
cfg.UseCompression != cmp.UseCompression ||
|
||||||
cfg.Group != cmp.Group ||
|
cfg.Group != cmp.Group ||
|
||||||
cfg.GroupKey != cmp.GroupKey {
|
cfg.GroupKey != cmp.GroupKey ||
|
||||||
|
cfg.ProxyProtocolVersion != cmp.ProxyProtocolVersion {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if !cfg.LocalSvrConf.compare(&cmp.LocalSvrConf) {
|
if !cfg.LocalSvrConf.compare(&cmp.LocalSvrConf) {
|
||||||
@@ -162,6 +181,7 @@ func (cfg *BaseProxyConf) UnmarshalFromIni(prefix string, name string, section i
|
|||||||
|
|
||||||
cfg.Group = section["group"]
|
cfg.Group = section["group"]
|
||||||
cfg.GroupKey = section["group_key"]
|
cfg.GroupKey = section["group_key"]
|
||||||
|
cfg.ProxyProtocolVersion = section["proxy_protocol_version"]
|
||||||
|
|
||||||
if err := cfg.LocalSvrConf.UnmarshalFromIni(prefix, name, section); err != nil {
|
if err := cfg.LocalSvrConf.UnmarshalFromIni(prefix, name, section); err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -194,6 +214,12 @@ func (cfg *BaseProxyConf) MarshalToMsg(pMsg *msg.NewProxy) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (cfg *BaseProxyConf) checkForCli() (err error) {
|
func (cfg *BaseProxyConf) checkForCli() (err error) {
|
||||||
|
if cfg.ProxyProtocolVersion != "" {
|
||||||
|
if cfg.ProxyProtocolVersion != "v1" && cfg.ProxyProtocolVersion != "v2" {
|
||||||
|
return fmt.Errorf("no support proxy protocol version: %s", cfg.ProxyProtocolVersion)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if err = cfg.LocalSvrConf.checkForCli(); err != nil {
|
if err = cfg.LocalSvrConf.checkForCli(); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -298,21 +324,21 @@ func (cfg *DomainConf) checkForCli() (err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cfg *DomainConf) checkForSvr() (err error) {
|
func (cfg *DomainConf) checkForSvr(serverCfg ServerCommonConf) (err error) {
|
||||||
if err = cfg.check(); err != nil {
|
if err = cfg.check(); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, domain := range cfg.CustomDomains {
|
for _, domain := range cfg.CustomDomains {
|
||||||
if subDomainHost != "" && len(strings.Split(subDomainHost, ".")) < len(strings.Split(domain, ".")) {
|
if serverCfg.SubDomainHost != "" && len(strings.Split(serverCfg.SubDomainHost, ".")) < len(strings.Split(domain, ".")) {
|
||||||
if strings.Contains(domain, subDomainHost) {
|
if strings.Contains(domain, serverCfg.SubDomainHost) {
|
||||||
return fmt.Errorf("custom domain [%s] should not belong to subdomain_host [%s]", domain, subDomainHost)
|
return fmt.Errorf("custom domain [%s] should not belong to subdomain_host [%s]", domain, serverCfg.SubDomainHost)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if cfg.SubDomain != "" {
|
if cfg.SubDomain != "" {
|
||||||
if subDomainHost == "" {
|
if serverCfg.SubDomainHost == "" {
|
||||||
return fmt.Errorf("subdomain is not supported because this feature is not enabled in remote 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, "*") {
|
||||||
@@ -322,12 +348,20 @@ func (cfg *DomainConf) checkForSvr() (err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Local service info
|
// LocalSvrConf configures what location the client will proxy to, or what
|
||||||
|
// plugin will be used.
|
||||||
type LocalSvrConf struct {
|
type LocalSvrConf struct {
|
||||||
LocalIp string `json:"local_ip"`
|
// LocalIp specifies the IP address or host name to proxy to.
|
||||||
LocalPort int `json:"local_port"`
|
LocalIp string `json:"local_ip"`
|
||||||
|
// LocalPort specifies the port to proxy to.
|
||||||
|
LocalPort int `json:"local_port"`
|
||||||
|
|
||||||
Plugin string `json:"plugin"`
|
// Plugin specifies what plugin should be used for proxying. If this value
|
||||||
|
// is set, the LocalIp and LocalPort values will be ignored. By default,
|
||||||
|
// this value is "".
|
||||||
|
Plugin string `json:"plugin"`
|
||||||
|
// PluginParams specify parameters to be passed to the plugin, if one is
|
||||||
|
// being used. By default, this value is an empty map.
|
||||||
PluginParams map[string]string `json:"plugin_params"`
|
PluginParams map[string]string `json:"plugin_params"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -389,15 +423,35 @@ func (cfg *LocalSvrConf) checkForCli() (err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Health check info
|
// HealthCheckConf configures health checking. This can be useful for load
|
||||||
|
// balancing purposes to detect and remove proxies to failing services.
|
||||||
type HealthCheckConf struct {
|
type HealthCheckConf struct {
|
||||||
HealthCheckType string `json:"health_check_type"` // tcp | http
|
// HealthCheckType specifies what protocol to use for health checking.
|
||||||
HealthCheckTimeoutS int `json:"health_check_timeout_s"`
|
// Valid values include "tcp", "http", and "". If this value is "", health
|
||||||
HealthCheckMaxFailed int `json:"health_check_max_failed"`
|
// checking will not be performed. By default, this value is "".
|
||||||
HealthCheckIntervalS int `json:"health_check_interval_s"`
|
//
|
||||||
HealthCheckUrl string `json:"health_check_url"`
|
// If the type is "tcp", a connection will be attempted to the target
|
||||||
|
// server. If a connection cannot be established, the health check fails.
|
||||||
// local_ip + local_port
|
//
|
||||||
|
// If the type is "http", a GET request will be made to the endpoint
|
||||||
|
// specified by HealthCheckUrl. If the response is not a 200, the health
|
||||||
|
// check fails.
|
||||||
|
HealthCheckType string `json:"health_check_type"` // tcp | http
|
||||||
|
// HealthCheckTimeoutS specifies the number of seconds to wait for a health
|
||||||
|
// check attempt to connect. If the timeout is reached, this counts as a
|
||||||
|
// health check failure. By default, this value is 3.
|
||||||
|
HealthCheckTimeoutS int `json:"health_check_timeout_s"`
|
||||||
|
// HealthCheckMaxFailed specifies the number of allowed failures before the
|
||||||
|
// proxy is stopped. By default, this value is 1.
|
||||||
|
HealthCheckMaxFailed int `json:"health_check_max_failed"`
|
||||||
|
// HealthCheckIntervalS specifies the time in seconds between health
|
||||||
|
// checks. By default, this value is 10.
|
||||||
|
HealthCheckIntervalS int `json:"health_check_interval_s"`
|
||||||
|
// HealthCheckUrl specifies the address to send health checks to if the
|
||||||
|
// health check type is "http".
|
||||||
|
HealthCheckUrl string `json:"health_check_url"`
|
||||||
|
// HealthCheckAddr specifies the address to connect to if the health check
|
||||||
|
// type is "tcp".
|
||||||
HealthCheckAddr string `json:"-"`
|
HealthCheckAddr string `json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -494,7 +548,7 @@ func (cfg *TcpProxyConf) CheckForCli() (err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cfg *TcpProxyConf) CheckForSvr() error { return nil }
|
func (cfg *TcpProxyConf) CheckForSvr(serverCfg ServerCommonConf) error { return nil }
|
||||||
|
|
||||||
// UDP
|
// UDP
|
||||||
type UdpProxyConf struct {
|
type UdpProxyConf struct {
|
||||||
@@ -542,7 +596,7 @@ func (cfg *UdpProxyConf) CheckForCli() (err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cfg *UdpProxyConf) CheckForSvr() error { return nil }
|
func (cfg *UdpProxyConf) CheckForSvr(serverCfg ServerCommonConf) error { return nil }
|
||||||
|
|
||||||
// HTTP
|
// HTTP
|
||||||
type HttpProxyConf struct {
|
type HttpProxyConf struct {
|
||||||
@@ -647,11 +701,11 @@ func (cfg *HttpProxyConf) CheckForCli() (err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cfg *HttpProxyConf) CheckForSvr() (err error) {
|
func (cfg *HttpProxyConf) CheckForSvr(serverCfg ServerCommonConf) (err error) {
|
||||||
if vhostHttpPort == 0 {
|
if serverCfg.VhostHttpPort == 0 {
|
||||||
return 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(serverCfg); err != nil {
|
||||||
err = fmt.Errorf("proxy [%s] domain conf check error: %v", cfg.ProxyName, err)
|
err = fmt.Errorf("proxy [%s] domain conf check error: %v", cfg.ProxyName, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -707,11 +761,11 @@ func (cfg *HttpsProxyConf) CheckForCli() (err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cfg *HttpsProxyConf) CheckForSvr() (err error) {
|
func (cfg *HttpsProxyConf) CheckForSvr(serverCfg ServerCommonConf) (err error) {
|
||||||
if vhostHttpsPort == 0 {
|
if serverCfg.VhostHttpsPort == 0 {
|
||||||
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(serverCfg); err != nil {
|
||||||
err = fmt.Errorf("proxy [%s] domain conf check error: %v", cfg.ProxyName, err)
|
err = fmt.Errorf("proxy [%s] domain conf check error: %v", cfg.ProxyName, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -780,7 +834,7 @@ func (cfg *StcpProxyConf) CheckForCli() (err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cfg *StcpProxyConf) CheckForSvr() (err error) {
|
func (cfg *StcpProxyConf) CheckForSvr(serverCfg ServerCommonConf) (err error) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -847,7 +901,7 @@ func (cfg *XtcpProxyConf) CheckForCli() (err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cfg *XtcpProxyConf) CheckForSvr() (err error) {
|
func (cfg *XtcpProxyConf) CheckForSvr(serverCfg ServerCommonConf) (err error) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -24,61 +24,122 @@ import (
|
|||||||
"github.com/fatedier/frp/utils/util"
|
"github.com/fatedier/frp/utils/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
// ServerCommonConf contains information for a server service. It is
|
||||||
// server global configure used for generate proxy conf used in frps
|
// recommended to use GetDefaultServerConf instead of creating this object
|
||||||
proxyBindAddr string
|
// directly, so that all unspecified fields have reasonable default values.
|
||||||
subDomainHost string
|
|
||||||
vhostHttpPort int
|
|
||||||
vhostHttpsPort int
|
|
||||||
)
|
|
||||||
|
|
||||||
func InitServerCfg(cfg *ServerCommonConf) {
|
|
||||||
proxyBindAddr = cfg.ProxyBindAddr
|
|
||||||
subDomainHost = cfg.SubDomainHost
|
|
||||||
vhostHttpPort = cfg.VhostHttpPort
|
|
||||||
vhostHttpsPort = cfg.VhostHttpsPort
|
|
||||||
}
|
|
||||||
|
|
||||||
// common config
|
|
||||||
type ServerCommonConf struct {
|
type ServerCommonConf struct {
|
||||||
BindAddr string `json:"bind_addr"`
|
// BindAddr specifies the address that the server binds to. By default,
|
||||||
BindPort int `json:"bind_port"`
|
// this value is "0.0.0.0".
|
||||||
BindUdpPort int `json:"bind_udp_port"`
|
BindAddr string `json:"bind_addr"`
|
||||||
KcpBindPort int `json:"kcp_bind_port"`
|
// BindPort specifies the port that the server listens on. By default, this
|
||||||
|
// value is 7000.
|
||||||
|
BindPort int `json:"bind_port"`
|
||||||
|
// BindUdpPort specifies the UDP port that the server listens on. If this
|
||||||
|
// value is 0, the server will not listen for UDP connections. By default,
|
||||||
|
// this value is 0
|
||||||
|
BindUdpPort int `json:"bind_udp_port"`
|
||||||
|
// BindKcpPort specifies the KCP port that the server listens on. If this
|
||||||
|
// value is 0, the server will not listen for KCP connections. By default,
|
||||||
|
// this value is 0.
|
||||||
|
KcpBindPort int `json:"kcp_bind_port"`
|
||||||
|
// ProxyBindAddr specifies the address that the proxy binds to. This value
|
||||||
|
// may be the same as BindAddr. By default, this value is "0.0.0.0".
|
||||||
ProxyBindAddr string `json:"proxy_bind_addr"`
|
ProxyBindAddr string `json:"proxy_bind_addr"`
|
||||||
|
|
||||||
// If VhostHttpPort equals 0, don't listen a public port for http protocol.
|
// VhostHttpPort specifies the port that the server listens for HTTP Vhost
|
||||||
|
// requests. If this value is 0, the server will not listen for HTTP
|
||||||
|
// requests. By default, this value is 0.
|
||||||
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
|
// VhostHttpsPort specifies the port that the server listens for HTTPS
|
||||||
VhostHttpsPort int `json:"vhost_http_port"`
|
// Vhost requests. If this value is 0, the server will not listen for HTTPS
|
||||||
|
// requests. By default, this value is 0.
|
||||||
|
VhostHttpsPort int `json:"vhost_https_port"`
|
||||||
|
|
||||||
|
// VhostHttpTimeout specifies the response header timeout for the Vhost
|
||||||
|
// HTTP server, in seconds. By default, this value is 60.
|
||||||
VhostHttpTimeout int64 `json:"vhost_http_timeout"`
|
VhostHttpTimeout int64 `json:"vhost_http_timeout"`
|
||||||
|
|
||||||
|
// DashboardAddr specifies the address that the dashboard binds to. By
|
||||||
|
// default, this value is "0.0.0.0".
|
||||||
DashboardAddr string `json:"dashboard_addr"`
|
DashboardAddr string `json:"dashboard_addr"`
|
||||||
|
|
||||||
// if DashboardPort equals 0, dashboard is not available
|
// DashboardPort specifies the port that the dashboard listens on. If this
|
||||||
DashboardPort int `json:"dashboard_port"`
|
// value is 0, the dashboard will not be started. By default, this value is
|
||||||
|
// 0.
|
||||||
|
DashboardPort int `json:"dashboard_port"`
|
||||||
|
// DashboardUser specifies the username that the dashboard will use for
|
||||||
|
// login. By default, this value is "admin".
|
||||||
DashboardUser string `json:"dashboard_user"`
|
DashboardUser string `json:"dashboard_user"`
|
||||||
DashboardPwd string `json:"dashboard_pwd"`
|
// DashboardUser specifies the password that the dashboard will use for
|
||||||
AssetsDir string `json:"asserts_dir"`
|
// login. By default, this value is "admin".
|
||||||
LogFile string `json:"log_file"`
|
DashboardPwd string `json:"dashboard_pwd"`
|
||||||
LogWay string `json:"log_way"` // console or file
|
// AssetsDir specifies the local directory that the dashboard will load
|
||||||
LogLevel string `json:"log_level"`
|
// resources from. If this value is "", assets will be loaded from the
|
||||||
LogMaxDays int64 `json:"log_max_days"`
|
// bundled executable using statik. By default, this value is "".
|
||||||
Token string `json:"token"`
|
AssetsDir string `json:"asserts_dir"`
|
||||||
|
// LogFile specifies a file where logs will be written to. This value will
|
||||||
|
// only be used if LogWay is set appropriately. By default, this value is
|
||||||
|
// "console".
|
||||||
|
LogFile string `json:"log_file"`
|
||||||
|
// LogWay specifies the way logging is managed. Valid values are "console"
|
||||||
|
// or "file". If "console" is used, logs will be printed to stdout. If
|
||||||
|
// "file" is used, logs will be printed to LogFile. By default, this value
|
||||||
|
// is "console".
|
||||||
|
LogWay string `json:"log_way"`
|
||||||
|
// LogLevel specifies the minimum log level. Valid values are "trace",
|
||||||
|
// "debug", "info", "warn", and "error". By default, this value is "info".
|
||||||
|
LogLevel string `json:"log_level"`
|
||||||
|
// LogMaxDays specifies the maximum number of days to store log information
|
||||||
|
// before deletion. This is only used if LogWay == "file". By default, this
|
||||||
|
// value is 0.
|
||||||
|
LogMaxDays int64 `json:"log_max_days"`
|
||||||
|
// DisableLogColor disables log colors when LogWay == "console" when set to
|
||||||
|
// true. By default, this value is false.
|
||||||
|
DisableLogColor bool `json:"disable_log_color"`
|
||||||
|
// Token specifies the authorization token used to authenticate keys
|
||||||
|
// received from clients. Clients must have a matching token to be
|
||||||
|
// authorized to use the server. By default, this value is "".
|
||||||
|
Token string `json:"token"`
|
||||||
|
// SubDomainHost specifies the domain that will be attached to sub-domains
|
||||||
|
// requested by the client when using Vhost proxying. For example, if this
|
||||||
|
// value is set to "frps.com" and the client requested the subdomain
|
||||||
|
// "test", the resulting URL would be "test.frps.com". By default, this
|
||||||
|
// value is "".
|
||||||
SubDomainHost string `json:"subdomain_host"`
|
SubDomainHost string `json:"subdomain_host"`
|
||||||
TcpMux bool `json:"tcp_mux"`
|
// TcpMux toggles TCP stream multiplexing. This allows multiple requests
|
||||||
|
// from a client to share a single TCP connection. By default, this value
|
||||||
|
// is true.
|
||||||
|
TcpMux bool `json:"tcp_mux"`
|
||||||
|
// Custom404Page specifies a path to a custom 404 page to display. If this
|
||||||
|
// value is "", a default page will be displayed. By default, this value is
|
||||||
|
// "".
|
||||||
|
Custom404Page string `json:"custom_404_page"`
|
||||||
|
|
||||||
AllowPorts map[int]struct{}
|
// AllowPorts specifies a set of ports that clients are able to proxy to.
|
||||||
MaxPoolCount int64 `json:"max_pool_count"`
|
// If the length of this value is 0, all ports are allowed. By default,
|
||||||
|
// this value is an empty set.
|
||||||
|
AllowPorts map[int]struct{}
|
||||||
|
// MaxPoolCount specifies the maximum pool size for each proxy. By default,
|
||||||
|
// this value is 5.
|
||||||
|
MaxPoolCount int64 `json:"max_pool_count"`
|
||||||
|
// MaxPortsPerClient specifies the maximum number of ports a single client
|
||||||
|
// may proxy to. If this value is 0, no limit will be applied. By default,
|
||||||
|
// this value is 0.
|
||||||
MaxPortsPerClient int64 `json:"max_ports_per_client"`
|
MaxPortsPerClient int64 `json:"max_ports_per_client"`
|
||||||
HeartBeatTimeout int64 `json:"heart_beat_timeout"`
|
// HeartBeatTimeout specifies the maximum time to wait for a heartbeat
|
||||||
UserConnTimeout int64 `json:"user_conn_timeout"`
|
// before terminating the connection. It is not recommended to change this
|
||||||
|
// value. By default, this value is 90.
|
||||||
|
HeartBeatTimeout int64 `json:"heart_beat_timeout"`
|
||||||
|
// UserConnTimeout specifies the maximum time to wait for a work
|
||||||
|
// connection. By default, this value is 10.
|
||||||
|
UserConnTimeout int64 `json:"user_conn_timeout"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetDefaultServerConf() *ServerCommonConf {
|
// GetDefaultServerConf returns a server configuration with reasonable
|
||||||
return &ServerCommonConf{
|
// defaults.
|
||||||
|
func GetDefaultServerConf() ServerCommonConf {
|
||||||
|
return ServerCommonConf{
|
||||||
BindAddr: "0.0.0.0",
|
BindAddr: "0.0.0.0",
|
||||||
BindPort: 7000,
|
BindPort: 7000,
|
||||||
BindUdpPort: 0,
|
BindUdpPort: 0,
|
||||||
@@ -96,6 +157,7 @@ func GetDefaultServerConf() *ServerCommonConf {
|
|||||||
LogWay: "console",
|
LogWay: "console",
|
||||||
LogLevel: "info",
|
LogLevel: "info",
|
||||||
LogMaxDays: 3,
|
LogMaxDays: 3,
|
||||||
|
DisableLogColor: false,
|
||||||
Token: "",
|
Token: "",
|
||||||
SubDomainHost: "",
|
SubDomainHost: "",
|
||||||
TcpMux: true,
|
TcpMux: true,
|
||||||
@@ -104,19 +166,19 @@ func GetDefaultServerConf() *ServerCommonConf {
|
|||||||
MaxPortsPerClient: 0,
|
MaxPortsPerClient: 0,
|
||||||
HeartBeatTimeout: 90,
|
HeartBeatTimeout: 90,
|
||||||
UserConnTimeout: 10,
|
UserConnTimeout: 10,
|
||||||
|
Custom404Page: "",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func UnmarshalServerConfFromIni(defaultCfg *ServerCommonConf, content string) (cfg *ServerCommonConf, err error) {
|
// UnmarshalServerConfFromIni parses the contents of a server configuration ini
|
||||||
cfg = defaultCfg
|
// file and returns the resulting server configuration.
|
||||||
if cfg == nil {
|
func UnmarshalServerConfFromIni(content string) (cfg ServerCommonConf, err error) {
|
||||||
cfg = GetDefaultServerConf()
|
cfg = GetDefaultServerConf()
|
||||||
}
|
|
||||||
|
|
||||||
conf, err := ini.Load(strings.NewReader(content))
|
conf, err := ini.Load(strings.NewReader(content))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = fmt.Errorf("parse ini conf file error: %v", err)
|
err = fmt.Errorf("parse ini conf file error: %v", err)
|
||||||
return nil, err
|
return ServerCommonConf{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -242,6 +304,10 @@ func UnmarshalServerConfFromIni(defaultCfg *ServerCommonConf, content string) (c
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if tmpStr, ok = conf.Get("common", "disable_log_color"); ok && tmpStr == "true" {
|
||||||
|
cfg.DisableLogColor = true
|
||||||
|
}
|
||||||
|
|
||||||
cfg.Token, _ = conf.Get("common", "token")
|
cfg.Token, _ = conf.Get("common", "token")
|
||||||
|
|
||||||
if allowPortsStr, ok := conf.Get("common", "allow_ports"); ok {
|
if allowPortsStr, ok := conf.Get("common", "allow_ports"); ok {
|
||||||
@@ -293,6 +359,10 @@ func UnmarshalServerConfFromIni(defaultCfg *ServerCommonConf, content string) (c
|
|||||||
cfg.TcpMux = true
|
cfg.TcpMux = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if tmpStr, ok = conf.Get("common", "custom_404_page"); ok {
|
||||||
|
cfg.Custom404Page = tmpStr
|
||||||
|
}
|
||||||
|
|
||||||
if tmpStr, ok = conf.Get("common", "heartbeat_timeout"); ok {
|
if tmpStr, ok = conf.Get("common", "heartbeat_timeout"); ok {
|
||||||
v, errRet := strconv.ParseInt(tmpStr, 10, 64)
|
v, errRet := strconv.ParseInt(tmpStr, 10, 64)
|
||||||
if errRet != nil {
|
if errRet != nil {
|
||||||
|
|||||||
@@ -17,44 +17,46 @@ package msg
|
|||||||
import "net"
|
import "net"
|
||||||
|
|
||||||
const (
|
const (
|
||||||
TypeLogin = 'o'
|
TypeLogin = 'o'
|
||||||
TypeLoginResp = '1'
|
TypeLoginResp = '1'
|
||||||
TypeNewProxy = 'p'
|
TypeNewProxy = 'p'
|
||||||
TypeNewProxyResp = '2'
|
TypeNewProxyResp = '2'
|
||||||
TypeCloseProxy = 'c'
|
TypeCloseProxy = 'c'
|
||||||
TypeNewWorkConn = 'w'
|
TypeNewWorkConn = 'w'
|
||||||
TypeReqWorkConn = 'r'
|
TypeReqWorkConn = 'r'
|
||||||
TypeStartWorkConn = 's'
|
TypeStartWorkConn = 's'
|
||||||
TypeNewVisitorConn = 'v'
|
TypeNewVisitorConn = 'v'
|
||||||
TypeNewVisitorConnResp = '3'
|
TypeNewVisitorConnResp = '3'
|
||||||
TypePing = 'h'
|
TypePing = 'h'
|
||||||
TypePong = '4'
|
TypePong = '4'
|
||||||
TypeUdpPacket = 'u'
|
TypeUdpPacket = 'u'
|
||||||
TypeNatHoleVisitor = 'i'
|
TypeNatHoleVisitor = 'i'
|
||||||
TypeNatHoleClient = 'n'
|
TypeNatHoleClient = 'n'
|
||||||
TypeNatHoleResp = 'm'
|
TypeNatHoleResp = 'm'
|
||||||
TypeNatHoleSid = '5'
|
TypeNatHoleClientDetectOK = 'd'
|
||||||
|
TypeNatHoleSid = '5'
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
msgTypeMap = map[byte]interface{}{
|
msgTypeMap = map[byte]interface{}{
|
||||||
TypeLogin: Login{},
|
TypeLogin: Login{},
|
||||||
TypeLoginResp: LoginResp{},
|
TypeLoginResp: LoginResp{},
|
||||||
TypeNewProxy: NewProxy{},
|
TypeNewProxy: NewProxy{},
|
||||||
TypeNewProxyResp: NewProxyResp{},
|
TypeNewProxyResp: NewProxyResp{},
|
||||||
TypeCloseProxy: CloseProxy{},
|
TypeCloseProxy: CloseProxy{},
|
||||||
TypeNewWorkConn: NewWorkConn{},
|
TypeNewWorkConn: NewWorkConn{},
|
||||||
TypeReqWorkConn: ReqWorkConn{},
|
TypeReqWorkConn: ReqWorkConn{},
|
||||||
TypeStartWorkConn: StartWorkConn{},
|
TypeStartWorkConn: StartWorkConn{},
|
||||||
TypeNewVisitorConn: NewVisitorConn{},
|
TypeNewVisitorConn: NewVisitorConn{},
|
||||||
TypeNewVisitorConnResp: NewVisitorConnResp{},
|
TypeNewVisitorConnResp: NewVisitorConnResp{},
|
||||||
TypePing: Ping{},
|
TypePing: Ping{},
|
||||||
TypePong: Pong{},
|
TypePong: Pong{},
|
||||||
TypeUdpPacket: UdpPacket{},
|
TypeUdpPacket: UdpPacket{},
|
||||||
TypeNatHoleVisitor: NatHoleVisitor{},
|
TypeNatHoleVisitor: NatHoleVisitor{},
|
||||||
TypeNatHoleClient: NatHoleClient{},
|
TypeNatHoleClient: NatHoleClient{},
|
||||||
TypeNatHoleResp: NatHoleResp{},
|
TypeNatHoleResp: NatHoleResp{},
|
||||||
TypeNatHoleSid: NatHoleSid{},
|
TypeNatHoleClientDetectOK: NatHoleClientDetectOK{},
|
||||||
|
TypeNatHoleSid: NatHoleSid{},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -124,6 +126,10 @@ type ReqWorkConn struct {
|
|||||||
|
|
||||||
type StartWorkConn struct {
|
type StartWorkConn struct {
|
||||||
ProxyName string `json:"proxy_name"`
|
ProxyName string `json:"proxy_name"`
|
||||||
|
SrcAddr string `json:"src_addr"`
|
||||||
|
DstAddr string `json:"dst_addr"`
|
||||||
|
SrcPort uint16 `json:"src_port"`
|
||||||
|
DstPort uint16 `json:"dst_port"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type NewVisitorConn struct {
|
type NewVisitorConn struct {
|
||||||
@@ -169,6 +175,9 @@ type NatHoleResp struct {
|
|||||||
Error string `json:"error"`
|
Error string `json:"error"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type NatHoleClientDetectOK struct {
|
||||||
|
}
|
||||||
|
|
||||||
type NatHoleSid struct {
|
type NatHoleSid struct {
|
||||||
Sid string `json:"sid"`
|
Sid string `json:"sid"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,11 @@ import (
|
|||||||
// Timeout seconds.
|
// Timeout seconds.
|
||||||
var NatHoleTimeout int64 = 10
|
var NatHoleTimeout int64 = 10
|
||||||
|
|
||||||
|
type SidRequest struct {
|
||||||
|
Sid string
|
||||||
|
NotifyCh chan struct{}
|
||||||
|
}
|
||||||
|
|
||||||
type NatHoleController struct {
|
type NatHoleController struct {
|
||||||
listener *net.UDPConn
|
listener *net.UDPConn
|
||||||
|
|
||||||
@@ -44,11 +49,11 @@ func NewNatHoleController(udpBindAddr string) (nc *NatHoleController, err error)
|
|||||||
return nc, nil
|
return nc, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (nc *NatHoleController) ListenClient(name string, sk string) (sidCh chan string) {
|
func (nc *NatHoleController) ListenClient(name string, sk string) (sidCh chan *SidRequest) {
|
||||||
clientCfg := &NatHoleClientCfg{
|
clientCfg := &NatHoleClientCfg{
|
||||||
Name: name,
|
Name: name,
|
||||||
Sk: sk,
|
Sk: sk,
|
||||||
SidCh: make(chan string),
|
SidCh: make(chan *SidRequest),
|
||||||
}
|
}
|
||||||
nc.mu.Lock()
|
nc.mu.Lock()
|
||||||
nc.clientCfgs[name] = clientCfg
|
nc.clientCfgs[name] = clientCfg
|
||||||
@@ -132,7 +137,10 @@ func (nc *NatHoleController) HandleVisitor(m *msg.NatHoleVisitor, raddr *net.UDP
|
|||||||
}()
|
}()
|
||||||
|
|
||||||
err := errors.PanicToError(func() {
|
err := errors.PanicToError(func() {
|
||||||
clientCfg.SidCh <- sid
|
clientCfg.SidCh <- &SidRequest{
|
||||||
|
Sid: sid,
|
||||||
|
NotifyCh: session.NotifyCh,
|
||||||
|
}
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
@@ -158,7 +166,6 @@ func (nc *NatHoleController) HandleClient(m *msg.NatHoleClient, raddr *net.UDPAd
|
|||||||
}
|
}
|
||||||
log.Trace("handle client message, sid [%s]", session.Sid)
|
log.Trace("handle client message, sid [%s]", session.Sid)
|
||||||
session.ClientAddr = raddr
|
session.ClientAddr = raddr
|
||||||
session.NotifyCh <- struct{}{}
|
|
||||||
|
|
||||||
resp := nc.GenNatHoleResponse(session, "")
|
resp := nc.GenNatHoleResponse(session, "")
|
||||||
log.Trace("send nat hole response to client")
|
log.Trace("send nat hole response to client")
|
||||||
@@ -201,5 +208,5 @@ type NatHoleSession struct {
|
|||||||
type NatHoleClientCfg struct {
|
type NatHoleClientCfg struct {
|
||||||
Name string
|
Name string
|
||||||
Sk string
|
Sk string
|
||||||
SidCh chan string
|
SidCh chan *SidRequest
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ func (hp *HttpProxy) Name() string {
|
|||||||
return PluginHttpProxy
|
return PluginHttpProxy
|
||||||
}
|
}
|
||||||
|
|
||||||
func (hp *HttpProxy) Handle(conn io.ReadWriteCloser, realConn frpNet.Conn) {
|
func (hp *HttpProxy) Handle(conn io.ReadWriteCloser, realConn frpNet.Conn, extraBufToLocal []byte) {
|
||||||
wrapConn := frpNet.WrapReadWriteCloserToConn(conn, realConn)
|
wrapConn := frpNet.WrapReadWriteCloserToConn(conn, realConn)
|
||||||
|
|
||||||
sc, rd := gnet.NewSharedConn(wrapConn)
|
sc, rd := gnet.NewSharedConn(wrapConn)
|
||||||
|
|||||||
129
models/plugin/https2http.go
Normal file
129
models/plugin/https2http.go
Normal file
@@ -0,0 +1,129 @@
|
|||||||
|
// Copyright 2019 fatedier, fatedier@gmail.com
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package plugin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/tls"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httputil"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
frpNet "github.com/fatedier/frp/utils/net"
|
||||||
|
)
|
||||||
|
|
||||||
|
const PluginHTTPS2HTTP = "https2http"
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
Register(PluginHTTPS2HTTP, NewHTTPS2HTTPPlugin)
|
||||||
|
}
|
||||||
|
|
||||||
|
type HTTPS2HTTPPlugin struct {
|
||||||
|
crtPath string
|
||||||
|
keyPath string
|
||||||
|
hostHeaderRewrite string
|
||||||
|
localAddr string
|
||||||
|
headers map[string]string
|
||||||
|
|
||||||
|
l *Listener
|
||||||
|
s *http.Server
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewHTTPS2HTTPPlugin(params map[string]string) (Plugin, error) {
|
||||||
|
crtPath := params["plugin_crt_path"]
|
||||||
|
keyPath := params["plugin_key_path"]
|
||||||
|
localAddr := params["plugin_local_addr"]
|
||||||
|
hostHeaderRewrite := params["plugin_host_header_rewrite"]
|
||||||
|
headers := make(map[string]string)
|
||||||
|
for k, v := range params {
|
||||||
|
if !strings.HasPrefix(k, "plugin_header_") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if k = strings.TrimPrefix(k, "plugin_header_"); k != "" {
|
||||||
|
headers[k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if crtPath == "" {
|
||||||
|
return nil, fmt.Errorf("plugin_crt_path is required")
|
||||||
|
}
|
||||||
|
if keyPath == "" {
|
||||||
|
return nil, fmt.Errorf("plugin_key_path is required")
|
||||||
|
}
|
||||||
|
if localAddr == "" {
|
||||||
|
return nil, fmt.Errorf("plugin_local_addr is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
listener := NewProxyListener()
|
||||||
|
|
||||||
|
p := &HTTPS2HTTPPlugin{
|
||||||
|
crtPath: crtPath,
|
||||||
|
keyPath: keyPath,
|
||||||
|
localAddr: localAddr,
|
||||||
|
hostHeaderRewrite: hostHeaderRewrite,
|
||||||
|
headers: headers,
|
||||||
|
l: listener,
|
||||||
|
}
|
||||||
|
|
||||||
|
rp := &httputil.ReverseProxy{
|
||||||
|
Director: func(req *http.Request) {
|
||||||
|
req.URL.Scheme = "http"
|
||||||
|
req.URL.Host = p.localAddr
|
||||||
|
if p.hostHeaderRewrite != "" {
|
||||||
|
req.Host = p.hostHeaderRewrite
|
||||||
|
}
|
||||||
|
for k, v := range p.headers {
|
||||||
|
req.Header.Set(k, v)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
p.s = &http.Server{
|
||||||
|
Handler: rp,
|
||||||
|
}
|
||||||
|
|
||||||
|
tlsConfig, err := p.genTLSConfig()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("gen TLS config error: %v", err)
|
||||||
|
}
|
||||||
|
ln := tls.NewListener(listener, tlsConfig)
|
||||||
|
|
||||||
|
go p.s.Serve(ln)
|
||||||
|
return p, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *HTTPS2HTTPPlugin) genTLSConfig() (*tls.Config, error) {
|
||||||
|
cert, err := tls.LoadX509KeyPair(p.crtPath, p.keyPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
config := &tls.Config{Certificates: []tls.Certificate{cert}}
|
||||||
|
return config, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *HTTPS2HTTPPlugin) Handle(conn io.ReadWriteCloser, realConn frpNet.Conn, extraBufToLocal []byte) {
|
||||||
|
wrapConn := frpNet.WrapReadWriteCloserToConn(conn, realConn)
|
||||||
|
p.l.PutConn(wrapConn)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *HTTPS2HTTPPlugin) Name() string {
|
||||||
|
return PluginHTTPS2HTTP
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *HTTPS2HTTPPlugin) Close() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -46,7 +46,7 @@ func Create(name string, params map[string]string) (p Plugin, err error) {
|
|||||||
|
|
||||||
type Plugin interface {
|
type Plugin interface {
|
||||||
Name() string
|
Name() string
|
||||||
Handle(conn io.ReadWriteCloser, realConn frpNet.Conn)
|
Handle(conn io.ReadWriteCloser, realConn frpNet.Conn, extraBufToLocal []byte)
|
||||||
Close() error
|
Close() error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ func NewSocks5Plugin(params map[string]string) (p Plugin, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sp *Socks5Plugin) Handle(conn io.ReadWriteCloser, realConn frpNet.Conn) {
|
func (sp *Socks5Plugin) Handle(conn io.ReadWriteCloser, realConn frpNet.Conn, extraBufToLocal []byte) {
|
||||||
defer conn.Close()
|
defer conn.Close()
|
||||||
wrapConn := frpNet.WrapReadWriteCloserToConn(conn, realConn)
|
wrapConn := frpNet.WrapReadWriteCloserToConn(conn, realConn)
|
||||||
sp.Server.ServeConn(wrapConn)
|
sp.Server.ServeConn(wrapConn)
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ func NewStaticFilePlugin(params map[string]string) (Plugin, error) {
|
|||||||
return sp, nil
|
return sp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sp *StaticFilePlugin) Handle(conn io.ReadWriteCloser, realConn frpNet.Conn) {
|
func (sp *StaticFilePlugin) Handle(conn io.ReadWriteCloser, realConn frpNet.Conn, extraBufToLocal []byte) {
|
||||||
wrapConn := frpNet.WrapReadWriteCloserToConn(conn, realConn)
|
wrapConn := frpNet.WrapReadWriteCloserToConn(conn, realConn)
|
||||||
sp.l.PutConn(wrapConn)
|
sp.l.PutConn(wrapConn)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -53,11 +53,14 @@ func NewUnixDomainSocketPlugin(params map[string]string) (p Plugin, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (uds *UnixDomainSocketPlugin) Handle(conn io.ReadWriteCloser, realConn frpNet.Conn) {
|
func (uds *UnixDomainSocketPlugin) Handle(conn io.ReadWriteCloser, realConn frpNet.Conn, extraBufToLocal []byte) {
|
||||||
localConn, err := net.DialUnix("unix", nil, uds.UnixAddr)
|
localConn, err := net.DialUnix("unix", nil, uds.UnixAddr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if len(extraBufToLocal) > 0 {
|
||||||
|
localConn.Write(extraBufToLocal)
|
||||||
|
}
|
||||||
|
|
||||||
frpIo.Join(localConn, conn)
|
frpIo.Join(localConn, conn)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -67,7 +67,6 @@ func ForwardUserConn(udpConn *net.UDPConn, readCh <-chan *msg.UdpPacket, sendCh
|
|||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func Forwarder(dstAddr *net.UDPAddr, readCh <-chan *msg.UdpPacket, sendCh chan<- msg.Message) {
|
func Forwarder(dstAddr *net.UDPAddr, readCh <-chan *msg.UdpPacket, sendCh chan<- msg.Message) {
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ for os in $os_all; do
|
|||||||
mv ./frps_${os}_${arch} ${frp_path}/frps
|
mv ./frps_${os}_${arch} ${frp_path}/frps
|
||||||
fi
|
fi
|
||||||
cp ./LICENSE ${frp_path}
|
cp ./LICENSE ${frp_path}
|
||||||
cp ./conf/* ${frp_path}
|
cp -rf ./conf/* ${frp_path}
|
||||||
|
|
||||||
# packages
|
# packages
|
||||||
cd ./packages
|
cd ./packages
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"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"
|
frpErr "github.com/fatedier/frp/models/errors"
|
||||||
@@ -62,10 +61,13 @@ func (cm *ControlManager) Add(runId string, ctl *Control) (oldCtl *Control) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cm *ControlManager) Del(runId string) {
|
// we should make sure if it's the same control to prevent delete a new one
|
||||||
|
func (cm *ControlManager) Del(runId string, ctl *Control) {
|
||||||
cm.mu.Lock()
|
cm.mu.Lock()
|
||||||
defer cm.mu.Unlock()
|
defer cm.mu.Unlock()
|
||||||
delete(cm.ctlsByRunId, runId)
|
if c, ok := cm.ctlsByRunId[runId]; ok && c == ctl {
|
||||||
|
delete(cm.ctlsByRunId, runId)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cm *ControlManager) GetById(runId string) (ctl *Control, ok bool) {
|
func (cm *ControlManager) GetById(runId string) (ctl *Control, ok bool) {
|
||||||
@@ -126,11 +128,19 @@ type Control struct {
|
|||||||
allShutdown *shutdown.Shutdown
|
allShutdown *shutdown.Shutdown
|
||||||
|
|
||||||
mu sync.RWMutex
|
mu sync.RWMutex
|
||||||
|
|
||||||
|
// Server configuration information
|
||||||
|
serverCfg config.ServerCommonConf
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewControl(rc *controller.ResourceController, pxyManager *proxy.ProxyManager,
|
func NewControl(rc *controller.ResourceController, pxyManager *proxy.ProxyManager,
|
||||||
statsCollector stats.Collector, ctlConn net.Conn, loginMsg *msg.Login) *Control {
|
statsCollector stats.Collector, ctlConn net.Conn, loginMsg *msg.Login,
|
||||||
|
serverCfg config.ServerCommonConf) *Control {
|
||||||
|
|
||||||
|
poolCount := loginMsg.PoolCount
|
||||||
|
if poolCount > int(serverCfg.MaxPoolCount) {
|
||||||
|
poolCount = int(serverCfg.MaxPoolCount)
|
||||||
|
}
|
||||||
return &Control{
|
return &Control{
|
||||||
rc: rc,
|
rc: rc,
|
||||||
pxyManager: pxyManager,
|
pxyManager: pxyManager,
|
||||||
@@ -139,9 +149,9 @@ func NewControl(rc *controller.ResourceController, pxyManager *proxy.ProxyManage
|
|||||||
loginMsg: loginMsg,
|
loginMsg: loginMsg,
|
||||||
sendCh: make(chan msg.Message, 10),
|
sendCh: make(chan msg.Message, 10),
|
||||||
readCh: make(chan msg.Message, 10),
|
readCh: make(chan msg.Message, 10),
|
||||||
workConnCh: make(chan net.Conn, loginMsg.PoolCount+10),
|
workConnCh: make(chan net.Conn, poolCount+10),
|
||||||
proxies: make(map[string]proxy.Proxy),
|
proxies: make(map[string]proxy.Proxy),
|
||||||
poolCount: loginMsg.PoolCount,
|
poolCount: poolCount,
|
||||||
portsUsedNum: 0,
|
portsUsedNum: 0,
|
||||||
lastPing: time.Now(),
|
lastPing: time.Now(),
|
||||||
runId: loginMsg.RunId,
|
runId: loginMsg.RunId,
|
||||||
@@ -150,6 +160,7 @@ func NewControl(rc *controller.ResourceController, pxyManager *proxy.ProxyManage
|
|||||||
writerShutdown: shutdown.New(),
|
writerShutdown: shutdown.New(),
|
||||||
managerShutdown: shutdown.New(),
|
managerShutdown: shutdown.New(),
|
||||||
allShutdown: shutdown.New(),
|
allShutdown: shutdown.New(),
|
||||||
|
serverCfg: serverCfg,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -158,7 +169,7 @@ func (ctl *Control) Start() {
|
|||||||
loginRespMsg := &msg.LoginResp{
|
loginRespMsg := &msg.LoginResp{
|
||||||
Version: version.Full(),
|
Version: version.Full(),
|
||||||
RunId: ctl.runId,
|
RunId: ctl.runId,
|
||||||
ServerUdpPort: g.GlbServerCfg.BindUdpPort,
|
ServerUdpPort: ctl.serverCfg.BindUdpPort,
|
||||||
Error: "",
|
Error: "",
|
||||||
}
|
}
|
||||||
msg.WriteMsg(ctl.conn, loginRespMsg)
|
msg.WriteMsg(ctl.conn, loginRespMsg)
|
||||||
@@ -229,7 +240,7 @@ func (ctl *Control) GetWorkConn() (workConn net.Conn, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
case <-time.After(time.Duration(g.GlbServerCfg.UserConnTimeout) * time.Second):
|
case <-time.After(time.Duration(ctl.serverCfg.UserConnTimeout) * time.Second):
|
||||||
err = fmt.Errorf("timeout trying to get work connection")
|
err = fmt.Errorf("timeout trying to get work connection")
|
||||||
ctl.conn.Warn("%v", err)
|
ctl.conn.Warn("%v", err)
|
||||||
return
|
return
|
||||||
@@ -260,7 +271,7 @@ func (ctl *Control) writer() {
|
|||||||
defer ctl.allShutdown.Start()
|
defer ctl.allShutdown.Start()
|
||||||
defer ctl.writerShutdown.Done()
|
defer ctl.writerShutdown.Done()
|
||||||
|
|
||||||
encWriter, err := crypto.NewWriter(ctl.conn, []byte(g.GlbServerCfg.Token))
|
encWriter, err := crypto.NewWriter(ctl.conn, []byte(ctl.serverCfg.Token))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctl.conn.Error("crypto new writer error: %v", err)
|
ctl.conn.Error("crypto new writer error: %v", err)
|
||||||
ctl.allShutdown.Start()
|
ctl.allShutdown.Start()
|
||||||
@@ -290,7 +301,7 @@ func (ctl *Control) reader() {
|
|||||||
defer ctl.allShutdown.Start()
|
defer ctl.allShutdown.Start()
|
||||||
defer ctl.readerShutdown.Done()
|
defer ctl.readerShutdown.Done()
|
||||||
|
|
||||||
encReader := crypto.NewReader(ctl.conn, []byte(g.GlbServerCfg.Token))
|
encReader := crypto.NewReader(ctl.conn, []byte(ctl.serverCfg.Token))
|
||||||
for {
|
for {
|
||||||
if m, err := msg.ReadMsg(encReader); err != nil {
|
if m, err := msg.ReadMsg(encReader); err != nil {
|
||||||
if err == io.EOF {
|
if err == io.EOF {
|
||||||
@@ -298,6 +309,7 @@ func (ctl *Control) reader() {
|
|||||||
return
|
return
|
||||||
} else {
|
} else {
|
||||||
ctl.conn.Warn("read error: %v", err)
|
ctl.conn.Warn("read error: %v", err)
|
||||||
|
ctl.conn.Close()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -370,7 +382,7 @@ func (ctl *Control) manager() {
|
|||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
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(ctl.serverCfg.HeartBeatTimeout)*time.Second {
|
||||||
ctl.conn.Warn("heartbeat timeout")
|
ctl.conn.Warn("heartbeat timeout")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -413,22 +425,22 @@ func (ctl *Control) manager() {
|
|||||||
func (ctl *Control) RegisterProxy(pxyMsg *msg.NewProxy) (remoteAddr string, err error) {
|
func (ctl *Control) RegisterProxy(pxyMsg *msg.NewProxy) (remoteAddr string, err error) {
|
||||||
var pxyConf config.ProxyConf
|
var pxyConf config.ProxyConf
|
||||||
// Load configures from NewProxy message and check.
|
// Load configures from NewProxy message and check.
|
||||||
pxyConf, err = config.NewProxyConfFromMsg(pxyMsg)
|
pxyConf, err = config.NewProxyConfFromMsg(pxyMsg, ctl.serverCfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewProxy will return a interface Proxy.
|
// NewProxy will return a interface Proxy.
|
||||||
// In fact it create different proxies by different proxy type, we just call run() here.
|
// In fact it create different proxies by different proxy type, we just call run() here.
|
||||||
pxy, err := proxy.NewProxy(ctl.runId, ctl.rc, ctl.statsCollector, ctl.poolCount, ctl.GetWorkConn, pxyConf)
|
pxy, err := proxy.NewProxy(ctl.runId, ctl.rc, ctl.statsCollector, ctl.poolCount, ctl.GetWorkConn, pxyConf, ctl.serverCfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return remoteAddr, err
|
return remoteAddr, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check ports used number in each client
|
// Check ports used number in each client
|
||||||
if g.GlbServerCfg.MaxPortsPerClient > 0 {
|
if ctl.serverCfg.MaxPortsPerClient > 0 {
|
||||||
ctl.mu.Lock()
|
ctl.mu.Lock()
|
||||||
if ctl.portsUsedNum+pxy.GetUsedPortsNum() > int(g.GlbServerCfg.MaxPortsPerClient) {
|
if ctl.portsUsedNum+pxy.GetUsedPortsNum() > int(ctl.serverCfg.MaxPortsPerClient) {
|
||||||
ctl.mu.Unlock()
|
ctl.mu.Unlock()
|
||||||
err = fmt.Errorf("exceed the max_ports_per_client")
|
err = fmt.Errorf("exceed the max_ports_per_client")
|
||||||
return
|
return
|
||||||
@@ -474,7 +486,7 @@ func (ctl *Control) CloseProxy(closeMsg *msg.CloseProxy) (err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if g.GlbServerCfg.MaxPortsPerClient > 0 {
|
if ctl.serverCfg.MaxPortsPerClient > 0 {
|
||||||
ctl.portsUsedNum = ctl.portsUsedNum - pxy.GetUsedPortsNum()
|
ctl.portsUsedNum = ctl.portsUsedNum - pxy.GetUsedPortsNum()
|
||||||
}
|
}
|
||||||
pxy.Close()
|
pxy.Close()
|
||||||
|
|||||||
@@ -29,6 +29,9 @@ type ResourceController struct {
|
|||||||
// Tcp Group Controller
|
// Tcp Group Controller
|
||||||
TcpGroupCtl *group.TcpGroupCtl
|
TcpGroupCtl *group.TcpGroupCtl
|
||||||
|
|
||||||
|
// HTTP Group Controller
|
||||||
|
HTTPGroupCtl *group.HTTPGroupController
|
||||||
|
|
||||||
// Manage all tcp ports
|
// Manage all tcp ports
|
||||||
TcpPortManager *ports.PortManager
|
TcpPortManager *ports.PortManager
|
||||||
|
|
||||||
@@ -38,7 +41,7 @@ type ResourceController struct {
|
|||||||
// For http proxies, forwarding http requests
|
// For http proxies, forwarding http requests
|
||||||
HttpReverseProxy *vhost.HttpReverseProxy
|
HttpReverseProxy *vhost.HttpReverseProxy
|
||||||
|
|
||||||
// For https proxies, route requests to different clients by hostname and other infomation
|
// For https proxies, route requests to different clients by hostname and other information
|
||||||
VhostHttpsMuxer *vhost.HttpsMuxer
|
VhostHttpsMuxer *vhost.HttpsMuxer
|
||||||
|
|
||||||
// Controller for nat hole connections
|
// Controller for nat hole connections
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/fatedier/frp/assets"
|
"github.com/fatedier/frp/assets"
|
||||||
"github.com/fatedier/frp/g"
|
|
||||||
frpNet "github.com/fatedier/frp/utils/net"
|
frpNet "github.com/fatedier/frp/utils/net"
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
@@ -36,7 +35,7 @@ func (svr *Service) RunDashboardServer(addr string, port int) (err error) {
|
|||||||
// url router
|
// url router
|
||||||
router := mux.NewRouter()
|
router := mux.NewRouter()
|
||||||
|
|
||||||
user, passwd := g.GlbServerCfg.DashboardUser, g.GlbServerCfg.DashboardPwd
|
user, passwd := svr.cfg.DashboardUser, svr.cfg.DashboardPwd
|
||||||
router.Use(frpNet.NewHttpAuthMiddleware(user, passwd).Middleware)
|
router.Use(frpNet.NewHttpAuthMiddleware(user, passwd).Middleware)
|
||||||
|
|
||||||
// api, see dashboard_api.go
|
// api, see dashboard_api.go
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"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"
|
||||||
"github.com/fatedier/frp/utils/log"
|
"github.com/fatedier/frp/utils/log"
|
||||||
@@ -28,13 +27,11 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type GeneralResponse struct {
|
type GeneralResponse struct {
|
||||||
Code int64 `json:"code"`
|
Code int
|
||||||
Msg string `json:"msg"`
|
Msg string
|
||||||
}
|
}
|
||||||
|
|
||||||
type ServerInfoResp struct {
|
type ServerInfoResp struct {
|
||||||
GeneralResponse
|
|
||||||
|
|
||||||
Version string `json:"version"`
|
Version string `json:"version"`
|
||||||
BindPort int `json:"bind_port"`
|
BindPort int `json:"bind_port"`
|
||||||
BindUdpPort int `json:"bind_udp_port"`
|
BindUdpPort int `json:"bind_udp_port"`
|
||||||
@@ -55,28 +52,28 @@ type ServerInfoResp struct {
|
|||||||
|
|
||||||
// api/serverinfo
|
// api/serverinfo
|
||||||
func (svr *Service) ApiServerInfo(w http.ResponseWriter, r *http.Request) {
|
func (svr *Service) ApiServerInfo(w http.ResponseWriter, r *http.Request) {
|
||||||
var (
|
res := GeneralResponse{Code: 200}
|
||||||
buf []byte
|
|
||||||
res ServerInfoResp
|
|
||||||
)
|
|
||||||
defer func() {
|
defer func() {
|
||||||
log.Info("Http response [%s]: code [%d]", r.URL.Path, res.Code)
|
log.Info("Http response [%s]: code [%d]", r.URL.Path, res.Code)
|
||||||
|
w.WriteHeader(res.Code)
|
||||||
|
if len(res.Msg) > 0 {
|
||||||
|
w.Write([]byte(res.Msg))
|
||||||
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
log.Info("Http request: [%s]", r.URL.Path)
|
log.Info("Http request: [%s]", r.URL.Path)
|
||||||
cfg := &g.GlbServerCfg.ServerCommonConf
|
|
||||||
serverStats := svr.statsCollector.GetServer()
|
serverStats := svr.statsCollector.GetServer()
|
||||||
res = ServerInfoResp{
|
svrResp := ServerInfoResp{
|
||||||
Version: version.Full(),
|
Version: version.Full(),
|
||||||
BindPort: cfg.BindPort,
|
BindPort: svr.cfg.BindPort,
|
||||||
BindUdpPort: cfg.BindUdpPort,
|
BindUdpPort: svr.cfg.BindUdpPort,
|
||||||
VhostHttpPort: cfg.VhostHttpPort,
|
VhostHttpPort: svr.cfg.VhostHttpPort,
|
||||||
VhostHttpsPort: cfg.VhostHttpsPort,
|
VhostHttpsPort: svr.cfg.VhostHttpsPort,
|
||||||
KcpBindPort: cfg.KcpBindPort,
|
KcpBindPort: svr.cfg.KcpBindPort,
|
||||||
SubdomainHost: cfg.SubDomainHost,
|
SubdomainHost: svr.cfg.SubDomainHost,
|
||||||
MaxPoolCount: cfg.MaxPoolCount,
|
MaxPoolCount: svr.cfg.MaxPoolCount,
|
||||||
MaxPortsPerClient: cfg.MaxPortsPerClient,
|
MaxPortsPerClient: svr.cfg.MaxPortsPerClient,
|
||||||
HeartBeatTimeout: cfg.HeartBeatTimeout,
|
HeartBeatTimeout: svr.cfg.HeartBeatTimeout,
|
||||||
|
|
||||||
TotalTrafficIn: serverStats.TotalTrafficIn,
|
TotalTrafficIn: serverStats.TotalTrafficIn,
|
||||||
TotalTrafficOut: serverStats.TotalTrafficOut,
|
TotalTrafficOut: serverStats.TotalTrafficOut,
|
||||||
@@ -85,8 +82,8 @@ func (svr *Service) ApiServerInfo(w http.ResponseWriter, r *http.Request) {
|
|||||||
ProxyTypeCounts: serverStats.ProxyTypeCounts,
|
ProxyTypeCounts: serverStats.ProxyTypeCounts,
|
||||||
}
|
}
|
||||||
|
|
||||||
buf, _ = json.Marshal(&res)
|
buf, _ := json.Marshal(&svrResp)
|
||||||
w.Write(buf)
|
res.Msg = string(buf)
|
||||||
}
|
}
|
||||||
|
|
||||||
type BaseOutConf struct {
|
type BaseOutConf struct {
|
||||||
@@ -155,31 +152,29 @@ type ProxyStatsInfo struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type GetProxyInfoResp struct {
|
type GetProxyInfoResp struct {
|
||||||
GeneralResponse
|
|
||||||
Proxies []*ProxyStatsInfo `json:"proxies"`
|
Proxies []*ProxyStatsInfo `json:"proxies"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// api/proxy/:type
|
// api/proxy/:type
|
||||||
func (svr *Service) ApiProxyByType(w http.ResponseWriter, r *http.Request) {
|
func (svr *Service) ApiProxyByType(w http.ResponseWriter, r *http.Request) {
|
||||||
var (
|
res := GeneralResponse{Code: 200}
|
||||||
buf []byte
|
|
||||||
res GetProxyInfoResp
|
|
||||||
)
|
|
||||||
params := mux.Vars(r)
|
params := mux.Vars(r)
|
||||||
proxyType := params["type"]
|
proxyType := params["type"]
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
log.Info("Http response [%s]: code [%d]", r.URL.Path, res.Code)
|
log.Info("Http response [%s]: code [%d]", r.URL.Path, res.Code)
|
||||||
log.Info(r.URL.Path)
|
w.WriteHeader(res.Code)
|
||||||
log.Info(r.URL.RawPath)
|
if len(res.Msg) > 0 {
|
||||||
|
w.Write([]byte(res.Msg))
|
||||||
|
}
|
||||||
}()
|
}()
|
||||||
log.Info("Http request: [%s]", r.URL.Path)
|
log.Info("Http request: [%s]", r.URL.Path)
|
||||||
|
|
||||||
res.Proxies = svr.getProxyStatsByType(proxyType)
|
proxyInfoResp := GetProxyInfoResp{}
|
||||||
|
proxyInfoResp.Proxies = svr.getProxyStatsByType(proxyType)
|
||||||
buf, _ = json.Marshal(&res)
|
|
||||||
w.Write(buf)
|
|
||||||
|
|
||||||
|
buf, _ := json.Marshal(&proxyInfoResp)
|
||||||
|
res.Msg = string(buf)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (svr *Service) getProxyStatsByType(proxyType string) (proxyInfos []*ProxyStatsInfo) {
|
func (svr *Service) getProxyStatsByType(proxyType string) (proxyInfos []*ProxyStatsInfo) {
|
||||||
@@ -215,8 +210,6 @@ func (svr *Service) getProxyStatsByType(proxyType string) (proxyInfos []*ProxySt
|
|||||||
|
|
||||||
// Get proxy info by name.
|
// Get proxy info by name.
|
||||||
type GetProxyStatsResp struct {
|
type GetProxyStatsResp struct {
|
||||||
GeneralResponse
|
|
||||||
|
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Conf interface{} `json:"conf"`
|
Conf interface{} `json:"conf"`
|
||||||
TodayTrafficIn int64 `json:"today_traffic_in"`
|
TodayTrafficIn int64 `json:"today_traffic_in"`
|
||||||
@@ -229,45 +222,50 @@ type GetProxyStatsResp struct {
|
|||||||
|
|
||||||
// api/proxy/:type/:name
|
// api/proxy/:type/:name
|
||||||
func (svr *Service) ApiProxyByTypeAndName(w http.ResponseWriter, r *http.Request) {
|
func (svr *Service) ApiProxyByTypeAndName(w http.ResponseWriter, r *http.Request) {
|
||||||
var (
|
res := GeneralResponse{Code: 200}
|
||||||
buf []byte
|
|
||||||
res GetProxyStatsResp
|
|
||||||
)
|
|
||||||
params := mux.Vars(r)
|
params := mux.Vars(r)
|
||||||
proxyType := params["type"]
|
proxyType := params["type"]
|
||||||
name := params["name"]
|
name := params["name"]
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
log.Info("Http response [%s]: code [%d]", r.URL.Path, res.Code)
|
log.Info("Http response [%s]: code [%d]", r.URL.Path, res.Code)
|
||||||
|
w.WriteHeader(res.Code)
|
||||||
|
if len(res.Msg) > 0 {
|
||||||
|
w.Write([]byte(res.Msg))
|
||||||
|
}
|
||||||
}()
|
}()
|
||||||
log.Info("Http request: [%s]", r.URL.Path)
|
log.Info("Http request: [%s]", r.URL.Path)
|
||||||
|
|
||||||
res = svr.getProxyStatsByTypeAndName(proxyType, name)
|
proxyStatsResp := GetProxyStatsResp{}
|
||||||
|
proxyStatsResp, res.Code, res.Msg = svr.getProxyStatsByTypeAndName(proxyType, name)
|
||||||
|
if res.Code != 200 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
buf, _ = json.Marshal(&res)
|
buf, _ := json.Marshal(&proxyStatsResp)
|
||||||
w.Write(buf)
|
res.Msg = string(buf)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (svr *Service) getProxyStatsByTypeAndName(proxyType string, proxyName string) (proxyInfo GetProxyStatsResp) {
|
func (svr *Service) getProxyStatsByTypeAndName(proxyType string, proxyName string) (proxyInfo GetProxyStatsResp, code int, msg string) {
|
||||||
proxyInfo.Name = proxyName
|
proxyInfo.Name = proxyName
|
||||||
ps := svr.statsCollector.GetProxiesByTypeAndName(proxyType, proxyName)
|
ps := svr.statsCollector.GetProxiesByTypeAndName(proxyType, proxyName)
|
||||||
if ps == nil {
|
if ps == nil {
|
||||||
proxyInfo.Code = 1
|
code = 404
|
||||||
proxyInfo.Msg = "no proxy info found"
|
msg = "no proxy info found"
|
||||||
} else {
|
} else {
|
||||||
if pxy, ok := svr.pxyManager.GetByName(proxyName); ok {
|
if pxy, ok := svr.pxyManager.GetByName(proxyName); ok {
|
||||||
content, err := json.Marshal(pxy.GetConf())
|
content, err := json.Marshal(pxy.GetConf())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warn("marshal proxy [%s] conf info error: %v", ps.Name, err)
|
log.Warn("marshal proxy [%s] conf info error: %v", ps.Name, err)
|
||||||
proxyInfo.Code = 2
|
code = 400
|
||||||
proxyInfo.Msg = "parse conf error"
|
msg = "parse conf error"
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
proxyInfo.Conf = getConfByType(ps.Type)
|
proxyInfo.Conf = getConfByType(ps.Type)
|
||||||
if err = json.Unmarshal(content, &proxyInfo.Conf); err != nil {
|
if err = json.Unmarshal(content, &proxyInfo.Conf); err != nil {
|
||||||
log.Warn("unmarshal proxy [%s] conf info error: %v", ps.Name, err)
|
log.Warn("unmarshal proxy [%s] conf info error: %v", ps.Name, err)
|
||||||
proxyInfo.Code = 2
|
code = 400
|
||||||
proxyInfo.Msg = "parse conf error"
|
msg = "parse conf error"
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
proxyInfo.Status = consts.Online
|
proxyInfo.Status = consts.Online
|
||||||
@@ -279,6 +277,7 @@ func (svr *Service) getProxyStatsByTypeAndName(proxyType string, proxyName strin
|
|||||||
proxyInfo.CurConns = ps.CurConns
|
proxyInfo.CurConns = ps.CurConns
|
||||||
proxyInfo.LastStartTime = ps.LastStartTime
|
proxyInfo.LastStartTime = ps.LastStartTime
|
||||||
proxyInfo.LastCloseTime = ps.LastCloseTime
|
proxyInfo.LastCloseTime = ps.LastCloseTime
|
||||||
|
code = 200
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
@@ -286,36 +285,38 @@ func (svr *Service) getProxyStatsByTypeAndName(proxyType string, proxyName strin
|
|||||||
|
|
||||||
// api/traffic/:name
|
// api/traffic/:name
|
||||||
type GetProxyTrafficResp struct {
|
type GetProxyTrafficResp struct {
|
||||||
GeneralResponse
|
|
||||||
|
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
TrafficIn []int64 `json:"traffic_in"`
|
TrafficIn []int64 `json:"traffic_in"`
|
||||||
TrafficOut []int64 `json:"traffic_out"`
|
TrafficOut []int64 `json:"traffic_out"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (svr *Service) ApiProxyTraffic(w http.ResponseWriter, r *http.Request) {
|
func (svr *Service) ApiProxyTraffic(w http.ResponseWriter, r *http.Request) {
|
||||||
var (
|
res := GeneralResponse{Code: 200}
|
||||||
buf []byte
|
|
||||||
res GetProxyTrafficResp
|
|
||||||
)
|
|
||||||
params := mux.Vars(r)
|
params := mux.Vars(r)
|
||||||
name := params["name"]
|
name := params["name"]
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
log.Info("Http response [%s]: code [%d]", r.URL.Path, res.Code)
|
log.Info("Http response [%s]: code [%d]", r.URL.Path, res.Code)
|
||||||
|
w.WriteHeader(res.Code)
|
||||||
|
if len(res.Msg) > 0 {
|
||||||
|
w.Write([]byte(res.Msg))
|
||||||
|
}
|
||||||
}()
|
}()
|
||||||
log.Info("Http request: [%s]", r.URL.Path)
|
log.Info("Http request: [%s]", r.URL.Path)
|
||||||
|
|
||||||
res.Name = name
|
trafficResp := GetProxyTrafficResp{}
|
||||||
|
trafficResp.Name = name
|
||||||
proxyTrafficInfo := svr.statsCollector.GetProxyTraffic(name)
|
proxyTrafficInfo := svr.statsCollector.GetProxyTraffic(name)
|
||||||
|
|
||||||
if proxyTrafficInfo == nil {
|
if proxyTrafficInfo == nil {
|
||||||
res.Code = 1
|
res.Code = 404
|
||||||
res.Msg = "no proxy info found"
|
res.Msg = "no proxy info found"
|
||||||
|
return
|
||||||
} else {
|
} else {
|
||||||
res.TrafficIn = proxyTrafficInfo.TrafficIn
|
trafficResp.TrafficIn = proxyTrafficInfo.TrafficIn
|
||||||
res.TrafficOut = proxyTrafficInfo.TrafficOut
|
trafficResp.TrafficOut = proxyTrafficInfo.TrafficOut
|
||||||
}
|
}
|
||||||
|
|
||||||
buf, _ = json.Marshal(&res)
|
buf, _ := json.Marshal(&trafficResp)
|
||||||
w.Write(buf)
|
res.Msg = string(buf)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,4 +23,5 @@ var (
|
|||||||
ErrGroupParamsInvalid = errors.New("group params invalid")
|
ErrGroupParamsInvalid = errors.New("group params invalid")
|
||||||
ErrListenerClosed = errors.New("group listener closed")
|
ErrListenerClosed = errors.New("group listener closed")
|
||||||
ErrGroupDifferentPort = errors.New("group should have same remote port")
|
ErrGroupDifferentPort = errors.New("group should have same remote port")
|
||||||
|
ErrProxyRepeated = errors.New("group proxy repeated")
|
||||||
)
|
)
|
||||||
|
|||||||
157
server/group/http.go
Normal file
157
server/group/http.go
Normal file
@@ -0,0 +1,157 @@
|
|||||||
|
package group
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
|
|
||||||
|
frpNet "github.com/fatedier/frp/utils/net"
|
||||||
|
|
||||||
|
"github.com/fatedier/frp/utils/vhost"
|
||||||
|
)
|
||||||
|
|
||||||
|
type HTTPGroupController struct {
|
||||||
|
groups map[string]*HTTPGroup
|
||||||
|
|
||||||
|
vhostRouter *vhost.VhostRouters
|
||||||
|
|
||||||
|
mu sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewHTTPGroupController(vhostRouter *vhost.VhostRouters) *HTTPGroupController {
|
||||||
|
return &HTTPGroupController{
|
||||||
|
groups: make(map[string]*HTTPGroup),
|
||||||
|
vhostRouter: vhostRouter,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctl *HTTPGroupController) Register(proxyName, group, groupKey string,
|
||||||
|
routeConfig vhost.VhostRouteConfig) (err error) {
|
||||||
|
|
||||||
|
indexKey := httpGroupIndex(group, routeConfig.Domain, routeConfig.Location)
|
||||||
|
ctl.mu.Lock()
|
||||||
|
g, ok := ctl.groups[indexKey]
|
||||||
|
if !ok {
|
||||||
|
g = NewHTTPGroup(ctl)
|
||||||
|
ctl.groups[indexKey] = g
|
||||||
|
}
|
||||||
|
ctl.mu.Unlock()
|
||||||
|
|
||||||
|
return g.Register(proxyName, group, groupKey, routeConfig)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctl *HTTPGroupController) UnRegister(proxyName, group, domain, location string) {
|
||||||
|
indexKey := httpGroupIndex(group, domain, location)
|
||||||
|
ctl.mu.Lock()
|
||||||
|
defer ctl.mu.Unlock()
|
||||||
|
g, ok := ctl.groups[indexKey]
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
isEmpty := g.UnRegister(proxyName)
|
||||||
|
if isEmpty {
|
||||||
|
delete(ctl.groups, indexKey)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type HTTPGroup struct {
|
||||||
|
group string
|
||||||
|
groupKey string
|
||||||
|
domain string
|
||||||
|
location string
|
||||||
|
|
||||||
|
createFuncs map[string]vhost.CreateConnFunc
|
||||||
|
pxyNames []string
|
||||||
|
index uint64
|
||||||
|
ctl *HTTPGroupController
|
||||||
|
mu sync.RWMutex
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewHTTPGroup(ctl *HTTPGroupController) *HTTPGroup {
|
||||||
|
return &HTTPGroup{
|
||||||
|
createFuncs: make(map[string]vhost.CreateConnFunc),
|
||||||
|
pxyNames: make([]string, 0),
|
||||||
|
ctl: ctl,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *HTTPGroup) Register(proxyName, group, groupKey string,
|
||||||
|
routeConfig vhost.VhostRouteConfig) (err error) {
|
||||||
|
|
||||||
|
g.mu.Lock()
|
||||||
|
defer g.mu.Unlock()
|
||||||
|
if len(g.createFuncs) == 0 {
|
||||||
|
// the first proxy in this group
|
||||||
|
tmp := routeConfig // copy object
|
||||||
|
tmp.CreateConnFn = g.createConn
|
||||||
|
err = g.ctl.vhostRouter.Add(routeConfig.Domain, routeConfig.Location, &tmp)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
g.group = group
|
||||||
|
g.groupKey = groupKey
|
||||||
|
g.domain = routeConfig.Domain
|
||||||
|
g.location = routeConfig.Location
|
||||||
|
} else {
|
||||||
|
if g.group != group || g.domain != routeConfig.Domain || g.location != routeConfig.Location {
|
||||||
|
err = ErrGroupParamsInvalid
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if g.groupKey != groupKey {
|
||||||
|
err = ErrGroupAuthFailed
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if _, ok := g.createFuncs[proxyName]; ok {
|
||||||
|
err = ErrProxyRepeated
|
||||||
|
return
|
||||||
|
}
|
||||||
|
g.createFuncs[proxyName] = routeConfig.CreateConnFn
|
||||||
|
g.pxyNames = append(g.pxyNames, proxyName)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *HTTPGroup) UnRegister(proxyName string) (isEmpty bool) {
|
||||||
|
g.mu.Lock()
|
||||||
|
defer g.mu.Unlock()
|
||||||
|
delete(g.createFuncs, proxyName)
|
||||||
|
for i, name := range g.pxyNames {
|
||||||
|
if name == proxyName {
|
||||||
|
g.pxyNames = append(g.pxyNames[:i], g.pxyNames[i+1:]...)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(g.createFuncs) == 0 {
|
||||||
|
isEmpty = true
|
||||||
|
g.ctl.vhostRouter.Del(g.domain, g.location)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *HTTPGroup) createConn(remoteAddr string) (frpNet.Conn, error) {
|
||||||
|
var f vhost.CreateConnFunc
|
||||||
|
newIndex := atomic.AddUint64(&g.index, 1)
|
||||||
|
|
||||||
|
g.mu.RLock()
|
||||||
|
group := g.group
|
||||||
|
domain := g.domain
|
||||||
|
location := g.location
|
||||||
|
if len(g.pxyNames) > 0 {
|
||||||
|
name := g.pxyNames[int(newIndex)%len(g.pxyNames)]
|
||||||
|
f, _ = g.createFuncs[name]
|
||||||
|
}
|
||||||
|
g.mu.RUnlock()
|
||||||
|
|
||||||
|
if f == nil {
|
||||||
|
return nil, fmt.Errorf("no CreateConnFunc for http group [%s], domain [%s], location [%s]", group, domain, location)
|
||||||
|
}
|
||||||
|
|
||||||
|
return f(remoteAddr)
|
||||||
|
}
|
||||||
|
|
||||||
|
func httpGroupIndex(group, domain, location string) string {
|
||||||
|
return fmt.Sprintf("%s_%s_%s", group, domain, location)
|
||||||
|
}
|
||||||
@@ -24,46 +24,47 @@ import (
|
|||||||
gerr "github.com/fatedier/golib/errors"
|
gerr "github.com/fatedier/golib/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
type TcpGroupListener struct {
|
// TcpGroupCtl manage all TcpGroups
|
||||||
groupName string
|
type TcpGroupCtl struct {
|
||||||
group *TcpGroup
|
groups map[string]*TcpGroup
|
||||||
|
|
||||||
addr net.Addr
|
// portManager is used to manage port
|
||||||
closeCh chan struct{}
|
portManager *ports.PortManager
|
||||||
|
mu sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
func newTcpGroupListener(name string, group *TcpGroup, addr net.Addr) *TcpGroupListener {
|
// NewTcpGroupCtl return a new TcpGroupCtl
|
||||||
return &TcpGroupListener{
|
func NewTcpGroupCtl(portManager *ports.PortManager) *TcpGroupCtl {
|
||||||
groupName: name,
|
return &TcpGroupCtl{
|
||||||
group: group,
|
groups: make(map[string]*TcpGroup),
|
||||||
addr: addr,
|
portManager: portManager,
|
||||||
closeCh: make(chan struct{}),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ln *TcpGroupListener) Accept() (c net.Conn, err error) {
|
// Listen is the wrapper for TcpGroup's Listen
|
||||||
var ok bool
|
// If there are no group, we will create one here
|
||||||
select {
|
func (tgc *TcpGroupCtl) Listen(proxyName string, group string, groupKey string,
|
||||||
case <-ln.closeCh:
|
addr string, port int) (l net.Listener, realPort int, err error) {
|
||||||
return nil, ErrListenerClosed
|
|
||||||
case c, ok = <-ln.group.Accept():
|
tgc.mu.Lock()
|
||||||
if !ok {
|
tcpGroup, ok := tgc.groups[group]
|
||||||
return nil, ErrListenerClosed
|
if !ok {
|
||||||
}
|
tcpGroup = NewTcpGroup(tgc)
|
||||||
return c, nil
|
tgc.groups[group] = tcpGroup
|
||||||
}
|
}
|
||||||
|
tgc.mu.Unlock()
|
||||||
|
|
||||||
|
return tcpGroup.Listen(proxyName, group, groupKey, addr, port)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ln *TcpGroupListener) Addr() net.Addr {
|
// RemoveGroup remove TcpGroup from controller
|
||||||
return ln.addr
|
func (tgc *TcpGroupCtl) RemoveGroup(group string) {
|
||||||
}
|
tgc.mu.Lock()
|
||||||
|
defer tgc.mu.Unlock()
|
||||||
func (ln *TcpGroupListener) Close() (err error) {
|
delete(tgc.groups, group)
|
||||||
close(ln.closeCh)
|
|
||||||
ln.group.CloseListener(ln)
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TcpGroup route connections to different proxies
|
||||||
type TcpGroup struct {
|
type TcpGroup struct {
|
||||||
group string
|
group string
|
||||||
groupKey string
|
groupKey string
|
||||||
@@ -79,6 +80,7 @@ type TcpGroup struct {
|
|||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewTcpGroup return a new TcpGroup
|
||||||
func NewTcpGroup(ctl *TcpGroupCtl) *TcpGroup {
|
func NewTcpGroup(ctl *TcpGroupCtl) *TcpGroup {
|
||||||
return &TcpGroup{
|
return &TcpGroup{
|
||||||
lns: make([]*TcpGroupListener, 0),
|
lns: make([]*TcpGroupListener, 0),
|
||||||
@@ -87,10 +89,14 @@ func NewTcpGroup(ctl *TcpGroupCtl) *TcpGroup {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Listen will return a new TcpGroupListener
|
||||||
|
// if TcpGroup already has a listener, just add a new TcpGroupListener to the queues
|
||||||
|
// otherwise, listen on the real address
|
||||||
func (tg *TcpGroup) Listen(proxyName string, group string, groupKey string, addr string, port int) (ln *TcpGroupListener, realPort int, err error) {
|
func (tg *TcpGroup) Listen(proxyName string, group string, groupKey string, addr string, port int) (ln *TcpGroupListener, realPort int, err error) {
|
||||||
tg.mu.Lock()
|
tg.mu.Lock()
|
||||||
defer tg.mu.Unlock()
|
defer tg.mu.Unlock()
|
||||||
if len(tg.lns) == 0 {
|
if len(tg.lns) == 0 {
|
||||||
|
// the first listener, listen on the real address
|
||||||
realPort, err = tg.ctl.portManager.Acquire(proxyName, port)
|
realPort, err = tg.ctl.portManager.Acquire(proxyName, port)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
@@ -114,6 +120,7 @@ func (tg *TcpGroup) Listen(proxyName string, group string, groupKey string, addr
|
|||||||
}
|
}
|
||||||
go tg.worker()
|
go tg.worker()
|
||||||
} else {
|
} else {
|
||||||
|
// address and port in the same group must be equal
|
||||||
if tg.group != group || tg.addr != addr {
|
if tg.group != group || tg.addr != addr {
|
||||||
err = ErrGroupParamsInvalid
|
err = ErrGroupParamsInvalid
|
||||||
return
|
return
|
||||||
@@ -133,6 +140,7 @@ func (tg *TcpGroup) Listen(proxyName string, group string, groupKey string, addr
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// worker is called when the real tcp listener has been created
|
||||||
func (tg *TcpGroup) worker() {
|
func (tg *TcpGroup) worker() {
|
||||||
for {
|
for {
|
||||||
c, err := tg.tcpLn.Accept()
|
c, err := tg.tcpLn.Accept()
|
||||||
@@ -152,6 +160,7 @@ func (tg *TcpGroup) Accept() <-chan net.Conn {
|
|||||||
return tg.acceptCh
|
return tg.acceptCh
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CloseListener remove the TcpGroupListener from the TcpGroup
|
||||||
func (tg *TcpGroup) CloseListener(ln *TcpGroupListener) {
|
func (tg *TcpGroup) CloseListener(ln *TcpGroupListener) {
|
||||||
tg.mu.Lock()
|
tg.mu.Lock()
|
||||||
defer tg.mu.Unlock()
|
defer tg.mu.Unlock()
|
||||||
@@ -169,36 +178,47 @@ func (tg *TcpGroup) CloseListener(ln *TcpGroupListener) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type TcpGroupCtl struct {
|
// TcpGroupListener
|
||||||
groups map[string]*TcpGroup
|
type TcpGroupListener struct {
|
||||||
|
groupName string
|
||||||
|
group *TcpGroup
|
||||||
|
|
||||||
portManager *ports.PortManager
|
addr net.Addr
|
||||||
mu sync.Mutex
|
closeCh chan struct{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewTcpGroupCtl(portManager *ports.PortManager) *TcpGroupCtl {
|
func newTcpGroupListener(name string, group *TcpGroup, addr net.Addr) *TcpGroupListener {
|
||||||
return &TcpGroupCtl{
|
return &TcpGroupListener{
|
||||||
groups: make(map[string]*TcpGroup),
|
groupName: name,
|
||||||
portManager: portManager,
|
group: group,
|
||||||
|
addr: addr,
|
||||||
|
closeCh: make(chan struct{}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tgc *TcpGroupCtl) Listen(proxyNanme string, group string, groupKey string,
|
// Accept will accept connections from TcpGroup
|
||||||
addr string, port int) (l net.Listener, realPort int, err error) {
|
func (ln *TcpGroupListener) Accept() (c net.Conn, err error) {
|
||||||
|
var ok bool
|
||||||
tgc.mu.Lock()
|
select {
|
||||||
defer tgc.mu.Unlock()
|
case <-ln.closeCh:
|
||||||
if tcpGroup, ok := tgc.groups[group]; ok {
|
return nil, ErrListenerClosed
|
||||||
return tcpGroup.Listen(proxyNanme, group, groupKey, addr, port)
|
case c, ok = <-ln.group.Accept():
|
||||||
} else {
|
if !ok {
|
||||||
tcpGroup = NewTcpGroup(tgc)
|
return nil, ErrListenerClosed
|
||||||
tgc.groups[group] = tcpGroup
|
}
|
||||||
return tcpGroup.Listen(proxyNanme, group, groupKey, addr, port)
|
return c, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tgc *TcpGroupCtl) RemoveGroup(group string) {
|
func (ln *TcpGroupListener) Addr() net.Addr {
|
||||||
tgc.mu.Lock()
|
return ln.addr
|
||||||
defer tgc.mu.Unlock()
|
}
|
||||||
delete(tgc.groups, group)
|
|
||||||
|
// Close close the listener
|
||||||
|
func (ln *TcpGroupListener) Close() (err error) {
|
||||||
|
close(ln.closeCh)
|
||||||
|
|
||||||
|
// remove self from TcpGroup
|
||||||
|
ln.group.CloseListener(ln)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,9 +16,9 @@ package proxy
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"io"
|
"io"
|
||||||
|
"net"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/fatedier/frp/g"
|
|
||||||
"github.com/fatedier/frp/models/config"
|
"github.com/fatedier/frp/models/config"
|
||||||
"github.com/fatedier/frp/server/stats"
|
"github.com/fatedier/frp/server/stats"
|
||||||
frpNet "github.com/fatedier/frp/utils/net"
|
frpNet "github.com/fatedier/frp/utils/net"
|
||||||
@@ -29,7 +29,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type HttpProxy struct {
|
type HttpProxy struct {
|
||||||
BaseProxy
|
*BaseProxy
|
||||||
cfg *config.HttpProxyConf
|
cfg *config.HttpProxyConf
|
||||||
|
|
||||||
closeFuncs []func()
|
closeFuncs []func()
|
||||||
@@ -49,40 +49,78 @@ func (pxy *HttpProxy) Run() (remoteAddr string, err error) {
|
|||||||
locations = []string{""}
|
locations = []string{""}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
if err != nil {
|
||||||
|
pxy.Close()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
addrs := make([]string, 0)
|
addrs := make([]string, 0)
|
||||||
for _, domain := range pxy.cfg.CustomDomains {
|
for _, domain := range pxy.cfg.CustomDomains {
|
||||||
|
if domain == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
routeConfig.Domain = domain
|
routeConfig.Domain = domain
|
||||||
for _, location := range locations {
|
for _, location := range locations {
|
||||||
routeConfig.Location = location
|
routeConfig.Location = location
|
||||||
err = pxy.rc.HttpReverseProxy.Register(routeConfig)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
tmpDomain := routeConfig.Domain
|
tmpDomain := routeConfig.Domain
|
||||||
tmpLocation := routeConfig.Location
|
tmpLocation := routeConfig.Location
|
||||||
addrs = append(addrs, util.CanonicalAddr(tmpDomain, int(g.GlbServerCfg.VhostHttpPort)))
|
|
||||||
pxy.closeFuncs = append(pxy.closeFuncs, func() {
|
// handle group
|
||||||
pxy.rc.HttpReverseProxy.UnRegister(tmpDomain, tmpLocation)
|
if pxy.cfg.Group != "" {
|
||||||
})
|
err = pxy.rc.HTTPGroupCtl.Register(pxy.name, pxy.cfg.Group, pxy.cfg.GroupKey, routeConfig)
|
||||||
pxy.Info("http proxy listen for host [%s] location [%s]", routeConfig.Domain, routeConfig.Location)
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
pxy.closeFuncs = append(pxy.closeFuncs, func() {
|
||||||
|
pxy.rc.HTTPGroupCtl.UnRegister(pxy.name, pxy.cfg.Group, tmpDomain, tmpLocation)
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
// no group
|
||||||
|
err = pxy.rc.HttpReverseProxy.Register(routeConfig)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
pxy.closeFuncs = append(pxy.closeFuncs, func() {
|
||||||
|
pxy.rc.HttpReverseProxy.UnRegister(tmpDomain, tmpLocation)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
addrs = append(addrs, util.CanonicalAddr(routeConfig.Domain, int(pxy.serverCfg.VhostHttpPort)))
|
||||||
|
pxy.Info("http proxy listen for host [%s] location [%s] group [%s]", routeConfig.Domain, routeConfig.Location, pxy.cfg.Group)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if pxy.cfg.SubDomain != "" {
|
if pxy.cfg.SubDomain != "" {
|
||||||
routeConfig.Domain = pxy.cfg.SubDomain + "." + g.GlbServerCfg.SubDomainHost
|
routeConfig.Domain = pxy.cfg.SubDomain + "." + pxy.serverCfg.SubDomainHost
|
||||||
for _, location := range locations {
|
for _, location := range locations {
|
||||||
routeConfig.Location = location
|
routeConfig.Location = location
|
||||||
err = pxy.rc.HttpReverseProxy.Register(routeConfig)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
tmpDomain := routeConfig.Domain
|
tmpDomain := routeConfig.Domain
|
||||||
tmpLocation := routeConfig.Location
|
tmpLocation := routeConfig.Location
|
||||||
addrs = append(addrs, util.CanonicalAddr(tmpDomain, g.GlbServerCfg.VhostHttpPort))
|
|
||||||
pxy.closeFuncs = append(pxy.closeFuncs, func() {
|
// handle group
|
||||||
pxy.rc.HttpReverseProxy.UnRegister(tmpDomain, tmpLocation)
|
if pxy.cfg.Group != "" {
|
||||||
})
|
err = pxy.rc.HTTPGroupCtl.Register(pxy.name, pxy.cfg.Group, pxy.cfg.GroupKey, routeConfig)
|
||||||
pxy.Info("http proxy listen for host [%s] location [%s]", routeConfig.Domain, routeConfig.Location)
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
pxy.closeFuncs = append(pxy.closeFuncs, func() {
|
||||||
|
pxy.rc.HTTPGroupCtl.UnRegister(pxy.name, pxy.cfg.Group, tmpDomain, tmpLocation)
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
err = pxy.rc.HttpReverseProxy.Register(routeConfig)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
pxy.closeFuncs = append(pxy.closeFuncs, func() {
|
||||||
|
pxy.rc.HttpReverseProxy.UnRegister(tmpDomain, tmpLocation)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
addrs = append(addrs, util.CanonicalAddr(tmpDomain, pxy.serverCfg.VhostHttpPort))
|
||||||
|
|
||||||
|
pxy.Info("http proxy listen for host [%s] location [%s] group [%s]", routeConfig.Domain, routeConfig.Location, pxy.cfg.Group)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
remoteAddr = strings.Join(addrs, ",")
|
remoteAddr = strings.Join(addrs, ",")
|
||||||
@@ -93,8 +131,14 @@ func (pxy *HttpProxy) GetConf() config.ProxyConf {
|
|||||||
return pxy.cfg
|
return pxy.cfg
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pxy *HttpProxy) GetRealConn() (workConn frpNet.Conn, err error) {
|
func (pxy *HttpProxy) GetRealConn(remoteAddr string) (workConn frpNet.Conn, err error) {
|
||||||
tmpConn, errRet := pxy.GetWorkConnFromPool()
|
rAddr, errRet := net.ResolveTCPAddr("tcp", remoteAddr)
|
||||||
|
if errRet != nil {
|
||||||
|
pxy.Warn("resolve TCP addr [%s] error: %v", remoteAddr, errRet)
|
||||||
|
// we do not return error here since remoteAddr is not necessary for proxies without proxy protocol enabled
|
||||||
|
}
|
||||||
|
|
||||||
|
tmpConn, errRet := pxy.GetWorkConnFromPool(rAddr, nil)
|
||||||
if errRet != nil {
|
if errRet != nil {
|
||||||
err = errRet
|
err = errRet
|
||||||
return
|
return
|
||||||
@@ -102,7 +146,7 @@ func (pxy *HttpProxy) GetRealConn() (workConn frpNet.Conn, err error) {
|
|||||||
|
|
||||||
var rwc io.ReadWriteCloser = tmpConn
|
var rwc io.ReadWriteCloser = tmpConn
|
||||||
if pxy.cfg.UseEncryption {
|
if pxy.cfg.UseEncryption {
|
||||||
rwc, err = frpIo.WithEncryption(rwc, []byte(g.GlbServerCfg.Token))
|
rwc, err = frpIo.WithEncryption(rwc, []byte(pxy.serverCfg.Token))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
pxy.Error("create encryption stream error: %v", err)
|
pxy.Error("create encryption stream error: %v", err)
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -17,22 +17,30 @@ package proxy
|
|||||||
import (
|
import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/fatedier/frp/g"
|
|
||||||
"github.com/fatedier/frp/models/config"
|
"github.com/fatedier/frp/models/config"
|
||||||
"github.com/fatedier/frp/utils/util"
|
"github.com/fatedier/frp/utils/util"
|
||||||
"github.com/fatedier/frp/utils/vhost"
|
"github.com/fatedier/frp/utils/vhost"
|
||||||
)
|
)
|
||||||
|
|
||||||
type HttpsProxy struct {
|
type HttpsProxy struct {
|
||||||
BaseProxy
|
*BaseProxy
|
||||||
cfg *config.HttpsProxyConf
|
cfg *config.HttpsProxyConf
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pxy *HttpsProxy) Run() (remoteAddr string, err error) {
|
func (pxy *HttpsProxy) Run() (remoteAddr string, err error) {
|
||||||
routeConfig := &vhost.VhostRouteConfig{}
|
routeConfig := &vhost.VhostRouteConfig{}
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
if err != nil {
|
||||||
|
pxy.Close()
|
||||||
|
}
|
||||||
|
}()
|
||||||
addrs := make([]string, 0)
|
addrs := make([]string, 0)
|
||||||
for _, domain := range pxy.cfg.CustomDomains {
|
for _, domain := range pxy.cfg.CustomDomains {
|
||||||
|
if domain == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
routeConfig.Domain = domain
|
routeConfig.Domain = domain
|
||||||
l, errRet := pxy.rc.VhostHttpsMuxer.Listen(routeConfig)
|
l, errRet := pxy.rc.VhostHttpsMuxer.Listen(routeConfig)
|
||||||
if errRet != nil {
|
if errRet != nil {
|
||||||
@@ -42,11 +50,11 @@ func (pxy *HttpsProxy) Run() (remoteAddr string, err error) {
|
|||||||
l.AddLogPrefix(pxy.name)
|
l.AddLogPrefix(pxy.name)
|
||||||
pxy.Info("https proxy listen for host [%s]", routeConfig.Domain)
|
pxy.Info("https proxy listen for host [%s]", routeConfig.Domain)
|
||||||
pxy.listeners = append(pxy.listeners, l)
|
pxy.listeners = append(pxy.listeners, l)
|
||||||
addrs = append(addrs, util.CanonicalAddr(routeConfig.Domain, g.GlbServerCfg.VhostHttpsPort))
|
addrs = append(addrs, util.CanonicalAddr(routeConfig.Domain, pxy.serverCfg.VhostHttpsPort))
|
||||||
}
|
}
|
||||||
|
|
||||||
if pxy.cfg.SubDomain != "" {
|
if pxy.cfg.SubDomain != "" {
|
||||||
routeConfig.Domain = pxy.cfg.SubDomain + "." + g.GlbServerCfg.SubDomainHost
|
routeConfig.Domain = pxy.cfg.SubDomain + "." + pxy.serverCfg.SubDomainHost
|
||||||
l, errRet := pxy.rc.VhostHttpsMuxer.Listen(routeConfig)
|
l, errRet := pxy.rc.VhostHttpsMuxer.Listen(routeConfig)
|
||||||
if errRet != nil {
|
if errRet != nil {
|
||||||
err = errRet
|
err = errRet
|
||||||
@@ -55,7 +63,7 @@ func (pxy *HttpsProxy) Run() (remoteAddr string, err error) {
|
|||||||
l.AddLogPrefix(pxy.name)
|
l.AddLogPrefix(pxy.name)
|
||||||
pxy.Info("https proxy listen for host [%s]", routeConfig.Domain)
|
pxy.Info("https proxy listen for host [%s]", routeConfig.Domain)
|
||||||
pxy.listeners = append(pxy.listeners, l)
|
pxy.listeners = append(pxy.listeners, l)
|
||||||
addrs = append(addrs, util.CanonicalAddr(routeConfig.Domain, int(g.GlbServerCfg.VhostHttpsPort)))
|
addrs = append(addrs, util.CanonicalAddr(routeConfig.Domain, int(pxy.serverCfg.VhostHttpsPort)))
|
||||||
}
|
}
|
||||||
|
|
||||||
pxy.startListenHandler(pxy, HandleUserTcpConnection)
|
pxy.startListenHandler(pxy, HandleUserTcpConnection)
|
||||||
|
|||||||
@@ -17,9 +17,10 @@ package proxy
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"net"
|
||||||
|
"strconv"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"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/server/controller"
|
"github.com/fatedier/frp/server/controller"
|
||||||
@@ -36,7 +37,7 @@ type Proxy interface {
|
|||||||
Run() (remoteAddr string, err error)
|
Run() (remoteAddr string, err error)
|
||||||
GetName() string
|
GetName() string
|
||||||
GetConf() config.ProxyConf
|
GetConf() config.ProxyConf
|
||||||
GetWorkConnFromPool() (workConn frpNet.Conn, err error)
|
GetWorkConnFromPool(src, dst net.Addr) (workConn frpNet.Conn, err error)
|
||||||
GetUsedPortsNum() int
|
GetUsedPortsNum() int
|
||||||
Close()
|
Close()
|
||||||
log.Logger
|
log.Logger
|
||||||
@@ -50,6 +51,7 @@ type BaseProxy struct {
|
|||||||
usedPortsNum int
|
usedPortsNum int
|
||||||
poolCount int
|
poolCount int
|
||||||
getWorkConnFn GetWorkConnFn
|
getWorkConnFn GetWorkConnFn
|
||||||
|
serverCfg config.ServerCommonConf
|
||||||
|
|
||||||
mu sync.RWMutex
|
mu sync.RWMutex
|
||||||
log.Logger
|
log.Logger
|
||||||
@@ -70,7 +72,9 @@ func (pxy *BaseProxy) Close() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pxy *BaseProxy) GetWorkConnFromPool() (workConn frpNet.Conn, err error) {
|
// GetWorkConnFromPool try to get a new work connections from pool
|
||||||
|
// for quickly response, we immediately send the StartWorkConn message to frpc after take out one from pool
|
||||||
|
func (pxy *BaseProxy) GetWorkConnFromPool(src, dst net.Addr) (workConn frpNet.Conn, err error) {
|
||||||
// try all connections from the pool
|
// try all connections from the pool
|
||||||
for i := 0; i < pxy.poolCount+1; i++ {
|
for i := 0; i < pxy.poolCount+1; i++ {
|
||||||
if workConn, err = pxy.getWorkConnFn(); err != nil {
|
if workConn, err = pxy.getWorkConnFn(); err != nil {
|
||||||
@@ -80,8 +84,29 @@ func (pxy *BaseProxy) GetWorkConnFromPool() (workConn frpNet.Conn, err error) {
|
|||||||
pxy.Info("get a new work connection: [%s]", workConn.RemoteAddr().String())
|
pxy.Info("get a new work connection: [%s]", workConn.RemoteAddr().String())
|
||||||
workConn.AddLogPrefix(pxy.GetName())
|
workConn.AddLogPrefix(pxy.GetName())
|
||||||
|
|
||||||
|
var (
|
||||||
|
srcAddr string
|
||||||
|
dstAddr string
|
||||||
|
srcPortStr string
|
||||||
|
dstPortStr string
|
||||||
|
srcPort int
|
||||||
|
dstPort int
|
||||||
|
)
|
||||||
|
|
||||||
|
if src != nil {
|
||||||
|
srcAddr, srcPortStr, _ = net.SplitHostPort(src.String())
|
||||||
|
srcPort, _ = strconv.Atoi(srcPortStr)
|
||||||
|
}
|
||||||
|
if dst != nil {
|
||||||
|
dstAddr, dstPortStr, _ = net.SplitHostPort(dst.String())
|
||||||
|
dstPort, _ = strconv.Atoi(dstPortStr)
|
||||||
|
}
|
||||||
err := msg.WriteMsg(workConn, &msg.StartWorkConn{
|
err := msg.WriteMsg(workConn, &msg.StartWorkConn{
|
||||||
ProxyName: pxy.GetName(),
|
ProxyName: pxy.GetName(),
|
||||||
|
SrcAddr: srcAddr,
|
||||||
|
SrcPort: uint16(srcPort),
|
||||||
|
DstAddr: dstAddr,
|
||||||
|
DstPort: uint16(dstPort),
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
workConn.Warn("failed to send message to work connection from pool: %v, times: %d", err, i)
|
workConn.Warn("failed to send message to work connection from pool: %v, times: %d", err, i)
|
||||||
@@ -101,7 +126,7 @@ func (pxy *BaseProxy) GetWorkConnFromPool() (workConn frpNet.Conn, err error) {
|
|||||||
// startListenHandler start a goroutine handler for each listener.
|
// startListenHandler start a goroutine handler for each listener.
|
||||||
// p: p will just be passed to handler(Proxy, frpNet.Conn).
|
// p: p will just be passed to handler(Proxy, frpNet.Conn).
|
||||||
// handler: each proxy type can set different handler function to deal with connections accepted from listeners.
|
// handler: each proxy type can set different handler function to deal with connections accepted from listeners.
|
||||||
func (pxy *BaseProxy) startListenHandler(p Proxy, handler func(Proxy, frpNet.Conn, stats.Collector)) {
|
func (pxy *BaseProxy) startListenHandler(p Proxy, handler func(Proxy, frpNet.Conn, stats.Collector, config.ServerCommonConf)) {
|
||||||
for _, listener := range pxy.listeners {
|
for _, listener := range pxy.listeners {
|
||||||
go func(l frpNet.Listener) {
|
go func(l frpNet.Listener) {
|
||||||
for {
|
for {
|
||||||
@@ -113,14 +138,14 @@ func (pxy *BaseProxy) startListenHandler(p Proxy, handler func(Proxy, frpNet.Con
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
pxy.Debug("get a user connection [%s]", c.RemoteAddr().String())
|
pxy.Debug("get a user connection [%s]", c.RemoteAddr().String())
|
||||||
go handler(p, c, pxy.statsCollector)
|
go handler(p, c, pxy.statsCollector, pxy.serverCfg)
|
||||||
}
|
}
|
||||||
}(listener)
|
}(listener)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewProxy(runId string, rc *controller.ResourceController, statsCollector stats.Collector, poolCount int,
|
func NewProxy(runId string, rc *controller.ResourceController, statsCollector stats.Collector, poolCount int,
|
||||||
getWorkConnFn GetWorkConnFn, pxyConf config.ProxyConf) (pxy Proxy, err error) {
|
getWorkConnFn GetWorkConnFn, pxyConf config.ProxyConf, serverCfg config.ServerCommonConf) (pxy Proxy, err error) {
|
||||||
|
|
||||||
basePxy := BaseProxy{
|
basePxy := BaseProxy{
|
||||||
name: pxyConf.GetBaseInfo().ProxyName,
|
name: pxyConf.GetBaseInfo().ProxyName,
|
||||||
@@ -130,38 +155,39 @@ func NewProxy(runId string, rc *controller.ResourceController, statsCollector st
|
|||||||
poolCount: poolCount,
|
poolCount: poolCount,
|
||||||
getWorkConnFn: getWorkConnFn,
|
getWorkConnFn: getWorkConnFn,
|
||||||
Logger: log.NewPrefixLogger(runId),
|
Logger: log.NewPrefixLogger(runId),
|
||||||
|
serverCfg: serverCfg,
|
||||||
}
|
}
|
||||||
switch cfg := pxyConf.(type) {
|
switch cfg := pxyConf.(type) {
|
||||||
case *config.TcpProxyConf:
|
case *config.TcpProxyConf:
|
||||||
basePxy.usedPortsNum = 1
|
basePxy.usedPortsNum = 1
|
||||||
pxy = &TcpProxy{
|
pxy = &TcpProxy{
|
||||||
BaseProxy: basePxy,
|
BaseProxy: &basePxy,
|
||||||
cfg: cfg,
|
cfg: cfg,
|
||||||
}
|
}
|
||||||
case *config.HttpProxyConf:
|
case *config.HttpProxyConf:
|
||||||
pxy = &HttpProxy{
|
pxy = &HttpProxy{
|
||||||
BaseProxy: basePxy,
|
BaseProxy: &basePxy,
|
||||||
cfg: cfg,
|
cfg: cfg,
|
||||||
}
|
}
|
||||||
case *config.HttpsProxyConf:
|
case *config.HttpsProxyConf:
|
||||||
pxy = &HttpsProxy{
|
pxy = &HttpsProxy{
|
||||||
BaseProxy: basePxy,
|
BaseProxy: &basePxy,
|
||||||
cfg: cfg,
|
cfg: cfg,
|
||||||
}
|
}
|
||||||
case *config.UdpProxyConf:
|
case *config.UdpProxyConf:
|
||||||
basePxy.usedPortsNum = 1
|
basePxy.usedPortsNum = 1
|
||||||
pxy = &UdpProxy{
|
pxy = &UdpProxy{
|
||||||
BaseProxy: basePxy,
|
BaseProxy: &basePxy,
|
||||||
cfg: cfg,
|
cfg: cfg,
|
||||||
}
|
}
|
||||||
case *config.StcpProxyConf:
|
case *config.StcpProxyConf:
|
||||||
pxy = &StcpProxy{
|
pxy = &StcpProxy{
|
||||||
BaseProxy: basePxy,
|
BaseProxy: &basePxy,
|
||||||
cfg: cfg,
|
cfg: cfg,
|
||||||
}
|
}
|
||||||
case *config.XtcpProxyConf:
|
case *config.XtcpProxyConf:
|
||||||
pxy = &XtcpProxy{
|
pxy = &XtcpProxy{
|
||||||
BaseProxy: basePxy,
|
BaseProxy: &basePxy,
|
||||||
cfg: cfg,
|
cfg: cfg,
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
@@ -173,11 +199,11 @@ func NewProxy(runId string, rc *controller.ResourceController, statsCollector st
|
|||||||
|
|
||||||
// HandleUserTcpConnection is used for incoming tcp user connections.
|
// HandleUserTcpConnection is used for incoming tcp user connections.
|
||||||
// It can be used for tcp, http, https type.
|
// It can be used for tcp, http, https type.
|
||||||
func HandleUserTcpConnection(pxy Proxy, userConn frpNet.Conn, statsCollector stats.Collector) {
|
func HandleUserTcpConnection(pxy Proxy, userConn frpNet.Conn, statsCollector stats.Collector, serverCfg config.ServerCommonConf) {
|
||||||
defer userConn.Close()
|
defer userConn.Close()
|
||||||
|
|
||||||
// try all connections from the pool
|
// try all connections from the pool
|
||||||
workConn, err := pxy.GetWorkConnFromPool()
|
workConn, err := pxy.GetWorkConnFromPool(userConn.RemoteAddr(), userConn.LocalAddr())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -186,7 +212,7 @@ func HandleUserTcpConnection(pxy Proxy, userConn frpNet.Conn, statsCollector sta
|
|||||||
var local io.ReadWriteCloser = workConn
|
var local io.ReadWriteCloser = workConn
|
||||||
cfg := pxy.GetConf().GetBaseInfo()
|
cfg := pxy.GetConf().GetBaseInfo()
|
||||||
if cfg.UseEncryption {
|
if cfg.UseEncryption {
|
||||||
local, err = frpIo.WithEncryption(local, []byte(g.GlbServerCfg.Token))
|
local, err = frpIo.WithEncryption(local, []byte(serverCfg.Token))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
pxy.Error("create encryption stream error: %v", err)
|
pxy.Error("create encryption stream error: %v", err)
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type StcpProxy struct {
|
type StcpProxy struct {
|
||||||
BaseProxy
|
*BaseProxy
|
||||||
cfg *config.StcpProxyConf
|
cfg *config.StcpProxyConf
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,13 +17,12 @@ package proxy
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/fatedier/frp/g"
|
|
||||||
"github.com/fatedier/frp/models/config"
|
"github.com/fatedier/frp/models/config"
|
||||||
frpNet "github.com/fatedier/frp/utils/net"
|
frpNet "github.com/fatedier/frp/utils/net"
|
||||||
)
|
)
|
||||||
|
|
||||||
type TcpProxy struct {
|
type TcpProxy struct {
|
||||||
BaseProxy
|
*BaseProxy
|
||||||
cfg *config.TcpProxyConf
|
cfg *config.TcpProxyConf
|
||||||
|
|
||||||
realPort int
|
realPort int
|
||||||
@@ -31,7 +30,7 @@ type TcpProxy struct {
|
|||||||
|
|
||||||
func (pxy *TcpProxy) Run() (remoteAddr string, err error) {
|
func (pxy *TcpProxy) Run() (remoteAddr string, err error) {
|
||||||
if pxy.cfg.Group != "" {
|
if pxy.cfg.Group != "" {
|
||||||
l, realPort, errRet := pxy.rc.TcpGroupCtl.Listen(pxy.name, pxy.cfg.Group, pxy.cfg.GroupKey, g.GlbServerCfg.ProxyBindAddr, pxy.cfg.RemotePort)
|
l, realPort, errRet := pxy.rc.TcpGroupCtl.Listen(pxy.name, pxy.cfg.Group, pxy.cfg.GroupKey, pxy.serverCfg.ProxyBindAddr, pxy.cfg.RemotePort)
|
||||||
if errRet != nil {
|
if errRet != nil {
|
||||||
err = errRet
|
err = errRet
|
||||||
return
|
return
|
||||||
@@ -56,7 +55,7 @@ func (pxy *TcpProxy) Run() (remoteAddr string, err error) {
|
|||||||
pxy.rc.TcpPortManager.Release(pxy.realPort)
|
pxy.rc.TcpPortManager.Release(pxy.realPort)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
listener, errRet := frpNet.ListenTcp(g.GlbServerCfg.ProxyBindAddr, pxy.realPort)
|
listener, errRet := frpNet.ListenTcp(pxy.serverCfg.ProxyBindAddr, pxy.realPort)
|
||||||
if errRet != nil {
|
if errRet != nil {
|
||||||
err = errRet
|
err = errRet
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"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/models/proto/udp"
|
"github.com/fatedier/frp/models/proto/udp"
|
||||||
@@ -30,7 +29,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type UdpProxy struct {
|
type UdpProxy struct {
|
||||||
BaseProxy
|
*BaseProxy
|
||||||
cfg *config.UdpProxyConf
|
cfg *config.UdpProxyConf
|
||||||
|
|
||||||
realPort int
|
realPort int
|
||||||
@@ -67,7 +66,7 @@ func (pxy *UdpProxy) Run() (remoteAddr string, err error) {
|
|||||||
|
|
||||||
remoteAddr = fmt.Sprintf(":%d", pxy.realPort)
|
remoteAddr = fmt.Sprintf(":%d", pxy.realPort)
|
||||||
pxy.cfg.RemotePort = pxy.realPort
|
pxy.cfg.RemotePort = pxy.realPort
|
||||||
addr, errRet := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", g.GlbServerCfg.ProxyBindAddr, pxy.realPort))
|
addr, errRet := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", pxy.serverCfg.ProxyBindAddr, pxy.realPort))
|
||||||
if errRet != nil {
|
if errRet != nil {
|
||||||
err = errRet
|
err = errRet
|
||||||
return
|
return
|
||||||
@@ -160,7 +159,7 @@ func (pxy *UdpProxy) Run() (remoteAddr string, err error) {
|
|||||||
// Sleep a while for waiting control send the NewProxyResp to client.
|
// Sleep a while for waiting control send the NewProxyResp to client.
|
||||||
time.Sleep(500 * time.Millisecond)
|
time.Sleep(500 * time.Millisecond)
|
||||||
for {
|
for {
|
||||||
workConn, err := pxy.GetWorkConnFromPool()
|
workConn, err := pxy.GetWorkConnFromPool(nil, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
time.Sleep(1 * time.Second)
|
time.Sleep(1 * time.Second)
|
||||||
// check if proxy is closed
|
// check if proxy is closed
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type XtcpProxy struct {
|
type XtcpProxy struct {
|
||||||
BaseProxy
|
*BaseProxy
|
||||||
cfg *config.XtcpProxyConf
|
cfg *config.XtcpProxyConf
|
||||||
|
|
||||||
closeCh chan struct{}
|
closeCh chan struct{}
|
||||||
@@ -42,18 +42,40 @@ func (pxy *XtcpProxy) Run() (remoteAddr string, err error) {
|
|||||||
select {
|
select {
|
||||||
case <-pxy.closeCh:
|
case <-pxy.closeCh:
|
||||||
break
|
break
|
||||||
case sid := <-sidCh:
|
case sidRequest := <-sidCh:
|
||||||
workConn, errRet := pxy.GetWorkConnFromPool()
|
sr := sidRequest
|
||||||
|
workConn, errRet := pxy.GetWorkConnFromPool(nil, nil)
|
||||||
if errRet != nil {
|
if errRet != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
m := &msg.NatHoleSid{
|
m := &msg.NatHoleSid{
|
||||||
Sid: sid,
|
Sid: sr.Sid,
|
||||||
}
|
}
|
||||||
errRet = msg.WriteMsg(workConn, m)
|
errRet = msg.WriteMsg(workConn, m)
|
||||||
if errRet != nil {
|
if errRet != nil {
|
||||||
pxy.Warn("write nat hole sid package error, %v", errRet)
|
pxy.Warn("write nat hole sid package error, %v", errRet)
|
||||||
|
workConn.Close()
|
||||||
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
raw, errRet := msg.ReadMsg(workConn)
|
||||||
|
if errRet != nil {
|
||||||
|
pxy.Warn("read nat hole client ok package error: %v", errRet)
|
||||||
|
workConn.Close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if _, ok := raw.(*msg.NatHoleClientDetectOK); !ok {
|
||||||
|
pxy.Warn("read nat hole client ok package format error")
|
||||||
|
workConn.Close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
case sr.NotifyCh <- struct{}{}:
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
}()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|||||||
@@ -16,14 +16,20 @@ package server
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/rsa"
|
||||||
|
"crypto/tls"
|
||||||
|
"crypto/x509"
|
||||||
|
"encoding/pem"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
"math/big"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/fatedier/frp/assets"
|
"github.com/fatedier/frp/assets"
|
||||||
"github.com/fatedier/frp/g"
|
"github.com/fatedier/frp/models/config"
|
||||||
"github.com/fatedier/frp/models/msg"
|
"github.com/fatedier/frp/models/msg"
|
||||||
"github.com/fatedier/frp/models/nathole"
|
"github.com/fatedier/frp/models/nathole"
|
||||||
"github.com/fatedier/frp/server/controller"
|
"github.com/fatedier/frp/server/controller"
|
||||||
@@ -45,8 +51,6 @@ const (
|
|||||||
connReadTimeout time.Duration = 10 * time.Second
|
connReadTimeout time.Duration = 10 * time.Second
|
||||||
)
|
)
|
||||||
|
|
||||||
var ServerService *Service
|
|
||||||
|
|
||||||
// Server service
|
// Server service
|
||||||
type Service struct {
|
type Service struct {
|
||||||
// Dispatch connections to different handlers listen on same port
|
// Dispatch connections to different handlers listen on same port
|
||||||
@@ -61,21 +65,30 @@ type Service struct {
|
|||||||
// Accept connections using websocket
|
// Accept connections using websocket
|
||||||
websocketListener frpNet.Listener
|
websocketListener frpNet.Listener
|
||||||
|
|
||||||
|
// Accept frp tls connections
|
||||||
|
tlsListener frpNet.Listener
|
||||||
|
|
||||||
// Manage all controllers
|
// Manage all controllers
|
||||||
ctlManager *ControlManager
|
ctlManager *ControlManager
|
||||||
|
|
||||||
// Manage all proxies
|
// Manage all proxies
|
||||||
pxyManager *proxy.ProxyManager
|
pxyManager *proxy.ProxyManager
|
||||||
|
|
||||||
|
// HTTP vhost router
|
||||||
|
httpVhostRouter *vhost.VhostRouters
|
||||||
|
|
||||||
// All resource managers and controllers
|
// All resource managers and controllers
|
||||||
rc *controller.ResourceController
|
rc *controller.ResourceController
|
||||||
|
|
||||||
// stats collector to store server and proxies stats info
|
// stats collector to store server and proxies stats info
|
||||||
statsCollector stats.Collector
|
statsCollector stats.Collector
|
||||||
|
|
||||||
|
tlsConfig *tls.Config
|
||||||
|
|
||||||
|
cfg config.ServerCommonConf
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewService() (svr *Service, err error) {
|
func NewService(cfg config.ServerCommonConf) (svr *Service, err error) {
|
||||||
cfg := &g.GlbServerCfg.ServerCommonConf
|
|
||||||
svr = &Service{
|
svr = &Service{
|
||||||
ctlManager: NewControlManager(),
|
ctlManager: NewControlManager(),
|
||||||
pxyManager: proxy.NewProxyManager(),
|
pxyManager: proxy.NewProxyManager(),
|
||||||
@@ -84,17 +97,19 @@ func NewService() (svr *Service, err error) {
|
|||||||
TcpPortManager: ports.NewPortManager("tcp", cfg.ProxyBindAddr, cfg.AllowPorts),
|
TcpPortManager: ports.NewPortManager("tcp", cfg.ProxyBindAddr, cfg.AllowPorts),
|
||||||
UdpPortManager: ports.NewPortManager("udp", cfg.ProxyBindAddr, cfg.AllowPorts),
|
UdpPortManager: ports.NewPortManager("udp", cfg.ProxyBindAddr, cfg.AllowPorts),
|
||||||
},
|
},
|
||||||
|
httpVhostRouter: vhost.NewVhostRouters(),
|
||||||
|
tlsConfig: generateTLSConfig(),
|
||||||
|
cfg: cfg,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Init group controller
|
// Init group controller
|
||||||
svr.rc.TcpGroupCtl = group.NewTcpGroupCtl(svr.rc.TcpPortManager)
|
svr.rc.TcpGroupCtl = group.NewTcpGroupCtl(svr.rc.TcpPortManager)
|
||||||
|
|
||||||
// Init assets.
|
// Init HTTP group controller
|
||||||
err = assets.Load(cfg.AssetsDir)
|
svr.rc.HTTPGroupCtl = group.NewHTTPGroupController(svr.httpVhostRouter)
|
||||||
if err != nil {
|
|
||||||
err = fmt.Errorf("Load assets error: %v", err)
|
// Init 404 not found page
|
||||||
return
|
vhost.NotFoundPagePath = cfg.Custom404Page
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
var (
|
||||||
httpMuxOn bool
|
httpMuxOn bool
|
||||||
@@ -144,7 +159,7 @@ func NewService() (svr *Service, err error) {
|
|||||||
if cfg.VhostHttpPort > 0 {
|
if cfg.VhostHttpPort > 0 {
|
||||||
rp := vhost.NewHttpReverseProxy(vhost.HttpReverseProxyOptions{
|
rp := vhost.NewHttpReverseProxy(vhost.HttpReverseProxyOptions{
|
||||||
ResponseHeaderTimeoutS: cfg.VhostHttpTimeout,
|
ResponseHeaderTimeoutS: cfg.VhostHttpTimeout,
|
||||||
})
|
}, svr.httpVhostRouter)
|
||||||
svr.rc.HttpReverseProxy = rp
|
svr.rc.HttpReverseProxy = rp
|
||||||
|
|
||||||
address := fmt.Sprintf("%s:%d", cfg.ProxyBindAddr, cfg.VhostHttpPort)
|
address := fmt.Sprintf("%s:%d", cfg.ProxyBindAddr, cfg.VhostHttpPort)
|
||||||
@@ -187,6 +202,12 @@ func NewService() (svr *Service, err error) {
|
|||||||
log.Info("https service listen on %s:%d", cfg.ProxyBindAddr, cfg.VhostHttpsPort)
|
log.Info("https service listen on %s:%d", cfg.ProxyBindAddr, cfg.VhostHttpsPort)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// frp tls listener
|
||||||
|
tlsListener := svr.muxer.Listen(1, 1, func(data []byte) bool {
|
||||||
|
return int(data[0]) == frpNet.FRP_TLS_HEAD_BYTE
|
||||||
|
})
|
||||||
|
svr.tlsListener = frpNet.WrapLogListener(tlsListener)
|
||||||
|
|
||||||
// Create nat hole controller.
|
// Create nat hole controller.
|
||||||
if cfg.BindUdpPort > 0 {
|
if cfg.BindUdpPort > 0 {
|
||||||
var nc *nathole.NatHoleController
|
var nc *nathole.NatHoleController
|
||||||
@@ -203,6 +224,13 @@ func NewService() (svr *Service, err error) {
|
|||||||
var statsEnable bool
|
var statsEnable bool
|
||||||
// Create dashboard web server.
|
// Create dashboard web server.
|
||||||
if cfg.DashboardPort > 0 {
|
if cfg.DashboardPort > 0 {
|
||||||
|
// Init dashboard assets
|
||||||
|
err = assets.Load(cfg.AssetsDir)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("Load assets error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
err = svr.RunDashboardServer(cfg.DashboardAddr, cfg.DashboardPort)
|
err = svr.RunDashboardServer(cfg.DashboardAddr, cfg.DashboardPort)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = fmt.Errorf("Create dashboard web server error, %v", err)
|
err = fmt.Errorf("Create dashboard web server error, %v", err)
|
||||||
@@ -220,11 +248,12 @@ func (svr *Service) Run() {
|
|||||||
if svr.rc.NatHoleController != nil {
|
if svr.rc.NatHoleController != nil {
|
||||||
go svr.rc.NatHoleController.Run()
|
go svr.rc.NatHoleController.Run()
|
||||||
}
|
}
|
||||||
if g.GlbServerCfg.KcpBindPort > 0 {
|
if svr.cfg.KcpBindPort > 0 {
|
||||||
go svr.HandleListener(svr.kcpListener)
|
go svr.HandleListener(svr.kcpListener)
|
||||||
}
|
}
|
||||||
|
|
||||||
go svr.HandleListener(svr.websocketListener)
|
go svr.HandleListener(svr.websocketListener)
|
||||||
|
go svr.HandleListener(svr.tlsListener)
|
||||||
|
|
||||||
svr.HandleListener(svr.listener)
|
svr.HandleListener(svr.listener)
|
||||||
}
|
}
|
||||||
@@ -238,6 +267,16 @@ func (svr *Service) HandleListener(l frpNet.Listener) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log.Trace("start check TLS connection...")
|
||||||
|
originConn := c
|
||||||
|
c, err = frpNet.CheckAndEnableTLSServerConnWithTimeout(c, svr.tlsConfig, connReadTimeout)
|
||||||
|
if err != nil {
|
||||||
|
log.Warn("CheckAndEnableTLSServerConnWithTimeout error: %v", err)
|
||||||
|
originConn.Close()
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
log.Trace("success check TLS connection")
|
||||||
|
|
||||||
// Start a new goroutine for dealing connections.
|
// Start a new goroutine for dealing connections.
|
||||||
go func(frpConn frpNet.Conn) {
|
go func(frpConn frpNet.Conn) {
|
||||||
dealFn := func(conn frpNet.Conn) {
|
dealFn := func(conn frpNet.Conn) {
|
||||||
@@ -285,7 +324,7 @@ func (svr *Service) HandleListener(l frpNet.Listener) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if g.GlbServerCfg.TcpMux {
|
if svr.cfg.TcpMux {
|
||||||
fmuxCfg := fmux.DefaultConfig()
|
fmuxCfg := fmux.DefaultConfig()
|
||||||
fmuxCfg.KeepAliveInterval = 20 * time.Second
|
fmuxCfg.KeepAliveInterval = 20 * time.Second
|
||||||
fmuxCfg.LogOutput = ioutil.Discard
|
fmuxCfg.LogOutput = ioutil.Discard
|
||||||
@@ -324,7 +363,7 @@ func (svr *Service) RegisterControl(ctlConn frpNet.Conn, loginMsg *msg.Login) (e
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check auth.
|
// Check auth.
|
||||||
if util.GetAuthKey(g.GlbServerCfg.Token, loginMsg.Timestamp) != loginMsg.PrivilegeKey {
|
if util.GetAuthKey(svr.cfg.Token, loginMsg.Timestamp) != loginMsg.PrivilegeKey {
|
||||||
err = fmt.Errorf("authorization failed")
|
err = fmt.Errorf("authorization failed")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -338,7 +377,7 @@ func (svr *Service) RegisterControl(ctlConn frpNet.Conn, loginMsg *msg.Login) (e
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ctl := NewControl(svr.rc, svr.pxyManager, svr.statsCollector, ctlConn, loginMsg)
|
ctl := NewControl(svr.rc, svr.pxyManager, svr.statsCollector, ctlConn, loginMsg, svr.cfg)
|
||||||
|
|
||||||
if oldCtl := svr.ctlManager.Add(loginMsg.RunId, ctl); oldCtl != nil {
|
if oldCtl := svr.ctlManager.Add(loginMsg.RunId, ctl); oldCtl != nil {
|
||||||
oldCtl.allShutdown.WaitDone()
|
oldCtl.allShutdown.WaitDone()
|
||||||
@@ -353,7 +392,7 @@ func (svr *Service) RegisterControl(ctlConn frpNet.Conn, loginMsg *msg.Login) (e
|
|||||||
go func() {
|
go func() {
|
||||||
// block until control closed
|
// block until control closed
|
||||||
ctl.WaitClosed()
|
ctl.WaitClosed()
|
||||||
svr.ctlManager.Del(loginMsg.RunId)
|
svr.ctlManager.Del(loginMsg.RunId, ctl)
|
||||||
}()
|
}()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -373,3 +412,24 @@ func (svr *Service) RegisterVisitorConn(visitorConn frpNet.Conn, newMsg *msg.New
|
|||||||
return svr.rc.VisitorManager.NewConn(newMsg.ProxyName, visitorConn, newMsg.Timestamp, newMsg.SignKey,
|
return svr.rc.VisitorManager.NewConn(newMsg.ProxyName, visitorConn, newMsg.Timestamp, newMsg.SignKey,
|
||||||
newMsg.UseEncryption, newMsg.UseCompression)
|
newMsg.UseEncryption, newMsg.UseCompression)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Setup a bare-bones TLS config for the server
|
||||||
|
func generateTLSConfig() *tls.Config {
|
||||||
|
key, err := rsa.GenerateKey(rand.Reader, 1024)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
template := x509.Certificate{SerialNumber: big.NewInt(1)}
|
||||||
|
certDER, err := x509.CreateCertificate(rand.Reader, &template, &template, &key.PublicKey, key)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
keyPEM := pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(key)})
|
||||||
|
certPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certDER})
|
||||||
|
|
||||||
|
tlsCert, err := tls.X509KeyPair(certPEM, keyPEM)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return &tls.Config{Certificates: []tls.Certificate{tlsCert}}
|
||||||
|
}
|
||||||
|
|||||||
@@ -127,6 +127,12 @@ custom_domains = test6.frp.com
|
|||||||
host_header_rewrite = test6.frp.com
|
host_header_rewrite = test6.frp.com
|
||||||
header_X-From-Where = frp
|
header_X-From-Where = frp
|
||||||
|
|
||||||
|
[wildcard_http]
|
||||||
|
type = http
|
||||||
|
local_ip = 127.0.0.1
|
||||||
|
local_port = 10704
|
||||||
|
custom_domains = *.frp1.com
|
||||||
|
|
||||||
[subhost01]
|
[subhost01]
|
||||||
type = http
|
type = http
|
||||||
local_ip = 127.0.0.1
|
local_ip = 127.0.0.1
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ func TestCmdTcp(t *testing.T) {
|
|||||||
if assert.NoError(err) {
|
if assert.NoError(err) {
|
||||||
defer s.Stop()
|
defer s.Stop()
|
||||||
}
|
}
|
||||||
time.Sleep(100 * time.Millisecond)
|
time.Sleep(200 * time.Millisecond)
|
||||||
|
|
||||||
c := util.NewProcess(consts.FRPC_BIN_PATH, []string{"tcp", "-s", "127.0.0.1:20000", "-t", "123", "-u", "test",
|
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"})
|
"-l", "10701", "-r", "20801", "-n", "tcp_test"})
|
||||||
@@ -27,7 +27,7 @@ func TestCmdTcp(t *testing.T) {
|
|||||||
if assert.NoError(err) {
|
if assert.NoError(err) {
|
||||||
defer c.Stop()
|
defer c.Stop()
|
||||||
}
|
}
|
||||||
time.Sleep(250 * time.Millisecond)
|
time.Sleep(500 * time.Millisecond)
|
||||||
|
|
||||||
res, err := util.SendTcpMsg("127.0.0.1:20801", consts.TEST_TCP_ECHO_STR)
|
res, err := util.SendTcpMsg("127.0.0.1:20801", consts.TEST_TCP_ECHO_STR)
|
||||||
assert.NoError(err)
|
assert.NoError(err)
|
||||||
@@ -43,7 +43,7 @@ func TestCmdUdp(t *testing.T) {
|
|||||||
if assert.NoError(err) {
|
if assert.NoError(err) {
|
||||||
defer s.Stop()
|
defer s.Stop()
|
||||||
}
|
}
|
||||||
time.Sleep(100 * time.Millisecond)
|
time.Sleep(200 * time.Millisecond)
|
||||||
|
|
||||||
c := util.NewProcess(consts.FRPC_BIN_PATH, []string{"udp", "-s", "127.0.0.1:20000", "-t", "123", "-u", "test",
|
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"})
|
"-l", "10702", "-r", "20802", "-n", "udp_test"})
|
||||||
@@ -51,7 +51,7 @@ func TestCmdUdp(t *testing.T) {
|
|||||||
if assert.NoError(err) {
|
if assert.NoError(err) {
|
||||||
defer c.Stop()
|
defer c.Stop()
|
||||||
}
|
}
|
||||||
time.Sleep(250 * time.Millisecond)
|
time.Sleep(500 * time.Millisecond)
|
||||||
|
|
||||||
res, err := util.SendUdpMsg("127.0.0.1:20802", consts.TEST_UDP_ECHO_STR)
|
res, err := util.SendUdpMsg("127.0.0.1:20802", consts.TEST_UDP_ECHO_STR)
|
||||||
assert.NoError(err)
|
assert.NoError(err)
|
||||||
@@ -67,7 +67,7 @@ func TestCmdHttp(t *testing.T) {
|
|||||||
if assert.NoError(err) {
|
if assert.NoError(err) {
|
||||||
defer s.Stop()
|
defer s.Stop()
|
||||||
}
|
}
|
||||||
time.Sleep(100 * time.Millisecond)
|
time.Sleep(200 * time.Millisecond)
|
||||||
|
|
||||||
c := util.NewProcess(consts.FRPC_BIN_PATH, []string{"http", "-s", "127.0.0.1:20000", "-t", "123", "-u", "test",
|
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"})
|
"-n", "udp_test", "-l", "10704", "--custom_domain", "127.0.0.1"})
|
||||||
@@ -75,7 +75,7 @@ func TestCmdHttp(t *testing.T) {
|
|||||||
if assert.NoError(err) {
|
if assert.NoError(err) {
|
||||||
defer c.Stop()
|
defer c.Stop()
|
||||||
}
|
}
|
||||||
time.Sleep(250 * time.Millisecond)
|
time.Sleep(500 * time.Millisecond)
|
||||||
|
|
||||||
code, body, _, err := util.SendHttpMsg("GET", "http://127.0.0.1:20001", "", nil, "")
|
code, body, _, err := util.SendHttpMsg("GET", "http://127.0.0.1:20001", "", nil, "")
|
||||||
if assert.NoError(err) {
|
if assert.NoError(err) {
|
||||||
|
|||||||
@@ -67,12 +67,26 @@ custom_domains = test2.com
|
|||||||
health_check_type = http
|
health_check_type = http
|
||||||
health_check_interval_s = 1
|
health_check_interval_s = 1
|
||||||
health_check_url = /health
|
health_check_url = /health
|
||||||
|
|
||||||
|
[http3]
|
||||||
|
type = http
|
||||||
|
local_port = 15005
|
||||||
|
custom_domains = test.balancing.com
|
||||||
|
group = test-balancing
|
||||||
|
group_key = 123
|
||||||
|
|
||||||
|
[http4]
|
||||||
|
type = http
|
||||||
|
local_port = 15006
|
||||||
|
custom_domains = test.balancing.com
|
||||||
|
group = test-balancing
|
||||||
|
group_key = 123
|
||||||
`
|
`
|
||||||
|
|
||||||
func TestHealthCheck(t *testing.T) {
|
func TestHealthCheck(t *testing.T) {
|
||||||
assert := assert.New(t)
|
assert := assert.New(t)
|
||||||
|
|
||||||
// ****** start backgroud services ******
|
// ****** start background services ******
|
||||||
echoSvc1 := mock.NewEchoServer(15001, 1, "echo1")
|
echoSvc1 := mock.NewEchoServer(15001, 1, "echo1")
|
||||||
err := echoSvc1.Start()
|
err := echoSvc1.Start()
|
||||||
if assert.NoError(err) {
|
if assert.NoError(err) {
|
||||||
@@ -124,6 +138,22 @@ func TestHealthCheck(t *testing.T) {
|
|||||||
defer httpSvc2.Stop()
|
defer httpSvc2.Stop()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
httpSvc3 := mock.NewHttpServer(15005, func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Write([]byte("http3"))
|
||||||
|
})
|
||||||
|
err = httpSvc3.Start()
|
||||||
|
if assert.NoError(err) {
|
||||||
|
defer httpSvc3.Stop()
|
||||||
|
}
|
||||||
|
|
||||||
|
httpSvc4 := mock.NewHttpServer(15006, func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Write([]byte("http4"))
|
||||||
|
})
|
||||||
|
err = httpSvc4.Start()
|
||||||
|
if assert.NoError(err) {
|
||||||
|
defer httpSvc4.Stop()
|
||||||
|
}
|
||||||
|
|
||||||
time.Sleep(200 * time.Millisecond)
|
time.Sleep(200 * time.Millisecond)
|
||||||
|
|
||||||
// ****** start frps and frpc ******
|
// ****** start frps and frpc ******
|
||||||
@@ -244,4 +274,20 @@ func TestHealthCheck(t *testing.T) {
|
|||||||
assert.NoError(err)
|
assert.NoError(err)
|
||||||
assert.Equal(200, code)
|
assert.Equal(200, code)
|
||||||
assert.Equal("http2", body)
|
assert.Equal("http2", body)
|
||||||
|
|
||||||
|
// ****** load balancing type http ******
|
||||||
|
result = make([]string, 0)
|
||||||
|
|
||||||
|
code, body, _, err = util.SendHttpMsg("GET", "http://127.0.0.1:14000/xxx", "test.balancing.com", nil, "")
|
||||||
|
assert.NoError(err)
|
||||||
|
assert.Equal(200, code)
|
||||||
|
result = append(result, body)
|
||||||
|
|
||||||
|
code, body, _, err = util.SendHttpMsg("GET", "http://127.0.0.1:14000/xxx", "test.balancing.com", nil, "")
|
||||||
|
assert.NoError(err)
|
||||||
|
assert.Equal(200, code)
|
||||||
|
result = append(result, body)
|
||||||
|
|
||||||
|
assert.Contains(result, "http3")
|
||||||
|
assert.Contains(result, "http4")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -182,6 +182,21 @@ func TestHttp(t *testing.T) {
|
|||||||
assert.Equal("true", header.Get("X-Header-Set"))
|
assert.Equal("true", header.Get("X-Header-Set"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// wildcard_http
|
||||||
|
// test.frp1.com match *.frp1.com
|
||||||
|
code, body, _, err = util.SendHttpMsg("GET", fmt.Sprintf("http://127.0.0.1:%d", consts.TEST_HTTP_FRP_PORT), "test.frp1.com", nil, "")
|
||||||
|
if assert.NoError(err) {
|
||||||
|
assert.Equal(200, code)
|
||||||
|
assert.Equal(consts.TEST_HTTP_NORMAL_STR, body)
|
||||||
|
}
|
||||||
|
|
||||||
|
// new.test.frp1.com also match *.frp1.com
|
||||||
|
code, body, _, err = util.SendHttpMsg("GET", fmt.Sprintf("http://127.0.0.1:%d", consts.TEST_HTTP_FRP_PORT), "new.test.frp1.com", nil, "")
|
||||||
|
if assert.NoError(err) {
|
||||||
|
assert.Equal(200, code)
|
||||||
|
assert.Equal(consts.TEST_HTTP_NORMAL_STR, body)
|
||||||
|
}
|
||||||
|
|
||||||
// subhost01
|
// subhost01
|
||||||
code, body, _, err = util.SendHttpMsg("GET", fmt.Sprintf("http://127.0.0.1:%d", consts.TEST_HTTP_FRP_PORT), "test01.sub.com", nil, "")
|
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) {
|
if assert.NoError(err) {
|
||||||
|
|||||||
@@ -56,14 +56,14 @@ func TestReconnect(t *testing.T) {
|
|||||||
defer frpsProcess.Stop()
|
defer frpsProcess.Stop()
|
||||||
}
|
}
|
||||||
|
|
||||||
time.Sleep(100 * time.Millisecond)
|
time.Sleep(200 * time.Millisecond)
|
||||||
|
|
||||||
frpcProcess := util.NewProcess(consts.FRPC_BIN_PATH, []string{"-c", frpcCfgPath})
|
frpcProcess := util.NewProcess(consts.FRPC_BIN_PATH, []string{"-c", frpcCfgPath})
|
||||||
err = frpcProcess.Start()
|
err = frpcProcess.Start()
|
||||||
if assert.NoError(err) {
|
if assert.NoError(err) {
|
||||||
defer frpcProcess.Stop()
|
defer frpcProcess.Stop()
|
||||||
}
|
}
|
||||||
time.Sleep(250 * time.Millisecond)
|
time.Sleep(500 * time.Millisecond)
|
||||||
|
|
||||||
// test tcp
|
// test tcp
|
||||||
res, err := util.SendTcpMsg("127.0.0.1:20801", consts.TEST_TCP_ECHO_STR)
|
res, err := util.SendTcpMsg("127.0.0.1:20801", consts.TEST_TCP_ECHO_STR)
|
||||||
@@ -72,7 +72,7 @@ func TestReconnect(t *testing.T) {
|
|||||||
|
|
||||||
// stop frpc
|
// stop frpc
|
||||||
frpcProcess.Stop()
|
frpcProcess.Stop()
|
||||||
time.Sleep(100 * time.Millisecond)
|
time.Sleep(200 * time.Millisecond)
|
||||||
|
|
||||||
// test tcp, expect failed
|
// test tcp, expect failed
|
||||||
_, err = util.SendTcpMsg("127.0.0.1:20801", consts.TEST_TCP_ECHO_STR)
|
_, err = util.SendTcpMsg("127.0.0.1:20801", consts.TEST_TCP_ECHO_STR)
|
||||||
@@ -84,7 +84,7 @@ func TestReconnect(t *testing.T) {
|
|||||||
if assert.NoError(err) {
|
if assert.NoError(err) {
|
||||||
defer newFrpcProcess.Stop()
|
defer newFrpcProcess.Stop()
|
||||||
}
|
}
|
||||||
time.Sleep(250 * time.Millisecond)
|
time.Sleep(500 * time.Millisecond)
|
||||||
|
|
||||||
// test tcp
|
// test tcp
|
||||||
res, err = util.SendTcpMsg("127.0.0.1:20801", consts.TEST_TCP_ECHO_STR)
|
res, err = util.SendTcpMsg("127.0.0.1:20801", consts.TEST_TCP_ECHO_STR)
|
||||||
@@ -93,7 +93,7 @@ func TestReconnect(t *testing.T) {
|
|||||||
|
|
||||||
// stop frps
|
// stop frps
|
||||||
frpsProcess.Stop()
|
frpsProcess.Stop()
|
||||||
time.Sleep(100 * time.Millisecond)
|
time.Sleep(200 * time.Millisecond)
|
||||||
|
|
||||||
// test tcp, expect failed
|
// test tcp, expect failed
|
||||||
_, err = util.SendTcpMsg("127.0.0.1:20801", consts.TEST_TCP_ECHO_STR)
|
_, err = util.SendTcpMsg("127.0.0.1:20801", consts.TEST_TCP_ECHO_STR)
|
||||||
|
|||||||
@@ -94,7 +94,7 @@ func TestReload(t *testing.T) {
|
|||||||
defer frpsProcess.Stop()
|
defer frpsProcess.Stop()
|
||||||
}
|
}
|
||||||
|
|
||||||
time.Sleep(100 * time.Millisecond)
|
time.Sleep(200 * time.Millisecond)
|
||||||
|
|
||||||
frpcProcess := util.NewProcess(consts.FRPC_BIN_PATH, []string{"-c", frpcCfgPath})
|
frpcProcess := util.NewProcess(consts.FRPC_BIN_PATH, []string{"-c", frpcCfgPath})
|
||||||
err = frpcProcess.Start()
|
err = frpcProcess.Start()
|
||||||
@@ -102,7 +102,7 @@ func TestReload(t *testing.T) {
|
|||||||
defer frpcProcess.Stop()
|
defer frpcProcess.Stop()
|
||||||
}
|
}
|
||||||
|
|
||||||
time.Sleep(250 * time.Millisecond)
|
time.Sleep(500 * time.Millisecond)
|
||||||
|
|
||||||
// test tcp1
|
// test tcp1
|
||||||
res, err := util.SendTcpMsg("127.0.0.1:20801", consts.TEST_TCP_ECHO_STR)
|
res, err := util.SendTcpMsg("127.0.0.1:20801", consts.TEST_TCP_ECHO_STR)
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ func TestConfTemplate(t *testing.T) {
|
|||||||
defer frpsProcess.Stop()
|
defer frpsProcess.Stop()
|
||||||
}
|
}
|
||||||
|
|
||||||
time.Sleep(100 * time.Millisecond)
|
time.Sleep(200 * time.Millisecond)
|
||||||
|
|
||||||
frpcProcess := util.NewProcess("env", []string{"FRP_TOKEN=123456", "TCP_REMOTE_PORT=20801", consts.FRPC_BIN_PATH, "-c", frpcCfgPath})
|
frpcProcess := util.NewProcess("env", []string{"FRP_TOKEN=123456", "TCP_REMOTE_PORT=20801", consts.FRPC_BIN_PATH, "-c", frpcCfgPath})
|
||||||
err = frpcProcess.Start()
|
err = frpcProcess.Start()
|
||||||
@@ -63,7 +63,7 @@ func TestConfTemplate(t *testing.T) {
|
|||||||
defer frpcProcess.Stop()
|
defer frpcProcess.Stop()
|
||||||
}
|
}
|
||||||
|
|
||||||
time.Sleep(250 * time.Millisecond)
|
time.Sleep(500 * time.Millisecond)
|
||||||
|
|
||||||
// test tcp1
|
// test tcp1
|
||||||
res, err := util.SendTcpMsg("127.0.0.1:20801", consts.TEST_TCP_ECHO_STR)
|
res, err := util.SendTcpMsg("127.0.0.1:20801", consts.TEST_TCP_ECHO_STR)
|
||||||
|
|||||||
188
tests/ci/tls_test.go
Normal file
188
tests/ci/tls_test.go
Normal file
@@ -0,0 +1,188 @@
|
|||||||
|
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_TLS_TCP_CONF = `
|
||||||
|
[common]
|
||||||
|
bind_addr = 0.0.0.0
|
||||||
|
bind_port = 20000
|
||||||
|
log_file = console
|
||||||
|
log_level = debug
|
||||||
|
token = 123456
|
||||||
|
`
|
||||||
|
|
||||||
|
const FRPC_TLS_TCP_CONF = `
|
||||||
|
[common]
|
||||||
|
server_addr = 127.0.0.1
|
||||||
|
server_port = 20000
|
||||||
|
log_file = console
|
||||||
|
log_level = debug
|
||||||
|
token = 123456
|
||||||
|
protocol = tcp
|
||||||
|
tls_enable = true
|
||||||
|
|
||||||
|
[tcp]
|
||||||
|
type = tcp
|
||||||
|
local_port = 10701
|
||||||
|
remote_port = 20801
|
||||||
|
`
|
||||||
|
|
||||||
|
func TestTlsOverTCP(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
frpsCfgPath, err := config.GenerateConfigFile(consts.FRPS_NORMAL_CONFIG, FRPS_TLS_TCP_CONF)
|
||||||
|
if assert.NoError(err) {
|
||||||
|
defer os.Remove(frpsCfgPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
frpcCfgPath, err := config.GenerateConfigFile(consts.FRPC_NORMAL_CONFIG, FRPC_TLS_TCP_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(200 * time.Millisecond)
|
||||||
|
|
||||||
|
frpcProcess := util.NewProcess(consts.FRPC_BIN_PATH, []string{"-c", frpcCfgPath})
|
||||||
|
err = frpcProcess.Start()
|
||||||
|
if assert.NoError(err) {
|
||||||
|
defer frpcProcess.Stop()
|
||||||
|
}
|
||||||
|
time.Sleep(500 * 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
const FRPS_TLS_KCP_CONF = `
|
||||||
|
[common]
|
||||||
|
bind_addr = 0.0.0.0
|
||||||
|
bind_port = 20000
|
||||||
|
kcp_bind_port = 20000
|
||||||
|
log_file = console
|
||||||
|
log_level = debug
|
||||||
|
token = 123456
|
||||||
|
`
|
||||||
|
|
||||||
|
const FRPC_TLS_KCP_CONF = `
|
||||||
|
[common]
|
||||||
|
server_addr = 127.0.0.1
|
||||||
|
server_port = 20000
|
||||||
|
log_file = console
|
||||||
|
log_level = debug
|
||||||
|
token = 123456
|
||||||
|
protocol = kcp
|
||||||
|
tls_enable = true
|
||||||
|
|
||||||
|
[tcp]
|
||||||
|
type = tcp
|
||||||
|
local_port = 10701
|
||||||
|
remote_port = 20801
|
||||||
|
`
|
||||||
|
|
||||||
|
func TestTLSOverKCP(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
frpsCfgPath, err := config.GenerateConfigFile(consts.FRPS_NORMAL_CONFIG, FRPS_TLS_KCP_CONF)
|
||||||
|
if assert.NoError(err) {
|
||||||
|
defer os.Remove(frpsCfgPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
frpcCfgPath, err := config.GenerateConfigFile(consts.FRPC_NORMAL_CONFIG, FRPC_TLS_KCP_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(200 * time.Millisecond)
|
||||||
|
|
||||||
|
frpcProcess := util.NewProcess(consts.FRPC_BIN_PATH, []string{"-c", frpcCfgPath})
|
||||||
|
err = frpcProcess.Start()
|
||||||
|
if assert.NoError(err) {
|
||||||
|
defer frpcProcess.Stop()
|
||||||
|
}
|
||||||
|
time.Sleep(500 * 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
const FRPS_TLS_WS_CONF = `
|
||||||
|
[common]
|
||||||
|
bind_addr = 0.0.0.0
|
||||||
|
bind_port = 20000
|
||||||
|
log_file = console
|
||||||
|
log_level = debug
|
||||||
|
token = 123456
|
||||||
|
`
|
||||||
|
|
||||||
|
const FRPC_TLS_WS_CONF = `
|
||||||
|
[common]
|
||||||
|
server_addr = 127.0.0.1
|
||||||
|
server_port = 20000
|
||||||
|
log_file = console
|
||||||
|
log_level = debug
|
||||||
|
token = 123456
|
||||||
|
protocol = websocket
|
||||||
|
tls_enable = true
|
||||||
|
|
||||||
|
[tcp]
|
||||||
|
type = tcp
|
||||||
|
local_port = 10701
|
||||||
|
remote_port = 20801
|
||||||
|
`
|
||||||
|
|
||||||
|
func TestTLSOverWebsocket(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
frpsCfgPath, err := config.GenerateConfigFile(consts.FRPS_NORMAL_CONFIG, FRPS_TLS_WS_CONF)
|
||||||
|
if assert.NoError(err) {
|
||||||
|
defer os.Remove(frpsCfgPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
frpcCfgPath, err := config.GenerateConfigFile(consts.FRPC_NORMAL_CONFIG, FRPC_TLS_WS_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(200 * time.Millisecond)
|
||||||
|
|
||||||
|
frpcProcess := util.NewProcess(consts.FRPC_BIN_PATH, []string{"-c", frpcCfgPath})
|
||||||
|
err = frpcProcess.Start()
|
||||||
|
if assert.NoError(err) {
|
||||||
|
defer frpcProcess.Stop()
|
||||||
|
}
|
||||||
|
time.Sleep(500 * 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)
|
||||||
|
}
|
||||||
@@ -88,8 +88,10 @@ func handleHttp(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if strings.Contains(r.Host, "127.0.0.1") || strings.Contains(r.Host, "test2.frp.com") ||
|
if strings.HasPrefix(r.Host, "127.0.0.1") || strings.HasPrefix(r.Host, "test2.frp.com") ||
|
||||||
strings.Contains(r.Host, "test5.frp.com") || strings.Contains(r.Host, "test6.frp.com") {
|
strings.HasPrefix(r.Host, "test5.frp.com") || strings.HasPrefix(r.Host, "test6.frp.com") ||
|
||||||
|
strings.HasPrefix(r.Host, "test.frp1.com") || strings.HasPrefix(r.Host, "new.test.frp1.com") {
|
||||||
|
|
||||||
w.WriteHeader(200)
|
w.WriteHeader(200)
|
||||||
w.Write([]byte(consts.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") {
|
||||||
|
|||||||
@@ -28,51 +28,51 @@ func GetProxyStatus(statusAddr string, user string, passwd string, name string)
|
|||||||
resp, err := http.DefaultClient.Do(req)
|
resp, err := http.DefaultClient.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return status, err
|
return status, err
|
||||||
} else {
|
}
|
||||||
if resp.StatusCode != 200 {
|
defer resp.Body.Close()
|
||||||
return status, fmt.Errorf("admin api status code [%d]", resp.StatusCode)
|
if resp.StatusCode != 200 {
|
||||||
}
|
return status, fmt.Errorf("admin api status code [%d]", resp.StatusCode)
|
||||||
defer resp.Body.Close()
|
}
|
||||||
body, err := ioutil.ReadAll(resp.Body)
|
body, err := ioutil.ReadAll(resp.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return status, err
|
return status, err
|
||||||
}
|
}
|
||||||
allStatus := &client.StatusResp{}
|
allStatus := &client.StatusResp{}
|
||||||
err = json.Unmarshal(body, &allStatus)
|
err = json.Unmarshal(body, &allStatus)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return status, fmt.Errorf("unmarshal http response error: %s", strings.TrimSpace(string(body)))
|
return status, fmt.Errorf("unmarshal http response error: %s", strings.TrimSpace(string(body)))
|
||||||
}
|
}
|
||||||
for _, s := range allStatus.Tcp {
|
for _, s := range allStatus.Tcp {
|
||||||
if s.Name == name {
|
if s.Name == name {
|
||||||
return &s, nil
|
return &s, nil
|
||||||
}
|
|
||||||
}
|
|
||||||
for _, s := range allStatus.Udp {
|
|
||||||
if s.Name == name {
|
|
||||||
return &s, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for _, s := range allStatus.Http {
|
|
||||||
if s.Name == name {
|
|
||||||
return &s, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for _, s := range allStatus.Https {
|
|
||||||
if s.Name == name {
|
|
||||||
return &s, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for _, s := range allStatus.Stcp {
|
|
||||||
if s.Name == name {
|
|
||||||
return &s, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for _, s := range allStatus.Xtcp {
|
|
||||||
if s.Name == name {
|
|
||||||
return &s, nil
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
for _, s := range allStatus.Udp {
|
||||||
|
if s.Name == name {
|
||||||
|
return &s, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, s := range allStatus.Http {
|
||||||
|
if s.Name == name {
|
||||||
|
return &s, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, s := range allStatus.Https {
|
||||||
|
if s.Name == name {
|
||||||
|
return &s, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, s := range allStatus.Stcp {
|
||||||
|
if s.Name == name {
|
||||||
|
return &s, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, s := range allStatus.Xtcp {
|
||||||
|
if s.Name == name {
|
||||||
|
return &s, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return status, errors.New("no proxy status found")
|
return status, errors.New("no proxy status found")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -87,13 +87,13 @@ func ReloadConf(reloadAddr string, user string, passwd string) error {
|
|||||||
resp, err := http.DefaultClient.Do(req)
|
resp, err := http.DefaultClient.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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)
|
|
||||||
}
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
if resp.StatusCode != 200 {
|
||||||
|
return fmt.Errorf("admin api status code [%d]", resp.StatusCode)
|
||||||
|
}
|
||||||
|
io.Copy(ioutil.Discard, resp.Body)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import (
|
|||||||
"github.com/fatedier/beego/logs"
|
"github.com/fatedier/beego/logs"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Log is the under log object
|
||||||
var Log *logs.BeeLogger
|
var Log *logs.BeeLogger
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@@ -28,21 +29,27 @@ func init() {
|
|||||||
Log.SetLogFuncCallDepth(Log.GetLogFuncCallDepth() + 1)
|
Log.SetLogFuncCallDepth(Log.GetLogFuncCallDepth() + 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
func InitLog(logWay string, logFile string, logLevel string, maxdays int64) {
|
func InitLog(logWay string, logFile string, logLevel string, maxdays int64, disableLogColor bool) {
|
||||||
SetLogFile(logWay, logFile, maxdays)
|
SetLogFile(logWay, logFile, maxdays, disableLogColor)
|
||||||
SetLogLevel(logLevel)
|
SetLogLevel(logLevel)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetLogFile to configure log params
|
||||||
// logWay: file or console
|
// logWay: file or console
|
||||||
func SetLogFile(logWay string, logFile string, maxdays int64) {
|
func SetLogFile(logWay string, logFile string, maxdays int64, disableLogColor bool) {
|
||||||
if logWay == "console" {
|
if logWay == "console" {
|
||||||
Log.SetLogger("console", "")
|
params := ""
|
||||||
|
if disableLogColor {
|
||||||
|
params = fmt.Sprintf(`{"color": false}`)
|
||||||
|
}
|
||||||
|
Log.SetLogger("console", params)
|
||||||
} else {
|
} else {
|
||||||
params := fmt.Sprintf(`{"filename": "%s", "maxdays": %d}`, logFile, maxdays)
|
params := fmt.Sprintf(`{"filename": "%s", "maxdays": %d}`, logFile, maxdays)
|
||||||
Log.SetLogger("file", params)
|
Log.SetLogger("file", params)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetLogLevel set log level, default is warning
|
||||||
// value: error, warning, info, debug, trace
|
// value: error, warning, info, debug, trace
|
||||||
func SetLogLevel(logLevel string) {
|
func SetLogLevel(logLevel string) {
|
||||||
level := 4 // warning
|
level := 4 // warning
|
||||||
@@ -85,7 +92,7 @@ func Trace(format string, v ...interface{}) {
|
|||||||
Log.Trace(format, v...)
|
Log.Trace(format, v...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Logger
|
// Logger is the log interface
|
||||||
type Logger interface {
|
type Logger interface {
|
||||||
AddLogPrefix(string)
|
AddLogPrefix(string)
|
||||||
GetPrefixStr() string
|
GetPrefixStr() string
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
package net
|
package net
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/tls"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
@@ -207,3 +208,17 @@ func ConnectServerByProxy(proxyUrl string, protocol string, addr string) (c Conn
|
|||||||
return nil, fmt.Errorf("unsupport protocol: %s", protocol)
|
return nil, fmt.Errorf("unsupport protocol: %s", protocol)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ConnectServerByProxyWithTLS(proxyUrl string, protocol string, addr string, tlsConfig *tls.Config) (c Conn, err error) {
|
||||||
|
c, err = ConnectServerByProxy(proxyUrl, protocol, addr)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if tlsConfig == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c = WrapTLSClientConn(c, tlsConfig)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|||||||
52
utils/net/tls.go
Normal file
52
utils/net/tls.go
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
// Copyright 2019 fatedier, fatedier@gmail.com
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package net
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/tls"
|
||||||
|
"net"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
gnet "github.com/fatedier/golib/net"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
FRP_TLS_HEAD_BYTE = 0x17
|
||||||
|
)
|
||||||
|
|
||||||
|
func WrapTLSClientConn(c net.Conn, tlsConfig *tls.Config) (out Conn) {
|
||||||
|
c.Write([]byte{byte(FRP_TLS_HEAD_BYTE)})
|
||||||
|
out = WrapConn(tls.Client(c, tlsConfig))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func CheckAndEnableTLSServerConnWithTimeout(c net.Conn, tlsConfig *tls.Config, timeout time.Duration) (out Conn, err error) {
|
||||||
|
sc, r := gnet.NewSharedConnSize(c, 2)
|
||||||
|
buf := make([]byte, 1)
|
||||||
|
var n int
|
||||||
|
c.SetReadDeadline(time.Now().Add(timeout))
|
||||||
|
n, err = r.Read(buf)
|
||||||
|
c.SetReadDeadline(time.Time{})
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if n == 1 && int(buf[0]) == FRP_TLS_HEAD_BYTE {
|
||||||
|
out = WrapConn(tls.Server(c, tlsConfig))
|
||||||
|
} else {
|
||||||
|
out = WrapConn(sc)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
@@ -31,6 +31,7 @@ type WebsocketListener struct {
|
|||||||
httpMutex *http.ServeMux
|
httpMutex *http.ServeMux
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewWebsocketListener to handle websocket connections
|
||||||
// ln: tcp listener for websocket connections
|
// ln: tcp listener for websocket connections
|
||||||
func NewWebsocketListener(ln net.Listener) (wl *WebsocketListener) {
|
func NewWebsocketListener(ln net.Listener) (wl *WebsocketListener) {
|
||||||
wl = &WebsocketListener{
|
wl = &WebsocketListener{
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
var version string = "0.23.1"
|
var version string = "0.29.0"
|
||||||
|
|
||||||
func Full() string {
|
func Full() string {
|
||||||
return version
|
return version
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// Copyright 2016 fatedier, fatedier@gmail.com
|
// Copyright 2017 fatedier, fatedier@gmail.com
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// 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.
|
||||||
@@ -15,221 +15,202 @@
|
|||||||
package vhost
|
package vhost
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/base64"
|
"context"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"log"
|
||||||
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
frpNet "github.com/fatedier/frp/utils/net"
|
frpLog "github.com/fatedier/frp/utils/log"
|
||||||
|
|
||||||
gnet "github.com/fatedier/golib/net"
|
|
||||||
"github.com/fatedier/golib/pool"
|
"github.com/fatedier/golib/pool"
|
||||||
)
|
)
|
||||||
|
|
||||||
type HttpMuxer struct {
|
var (
|
||||||
*VhostMuxer
|
ErrNoDomain = errors.New("no such domain")
|
||||||
|
)
|
||||||
|
|
||||||
|
func getHostFromAddr(addr string) (host string) {
|
||||||
|
strs := strings.Split(addr, ":")
|
||||||
|
if len(strs) > 1 {
|
||||||
|
host = strs[0]
|
||||||
|
} else {
|
||||||
|
host = addr
|
||||||
|
}
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetHttpRequestInfo(c frpNet.Conn) (_ frpNet.Conn, _ map[string]string, err error) {
|
type HttpReverseProxyOptions struct {
|
||||||
reqInfoMap := make(map[string]string, 0)
|
ResponseHeaderTimeoutS int64
|
||||||
sc, rd := gnet.NewSharedConn(c)
|
|
||||||
|
|
||||||
request, err := http.ReadRequest(bufio.NewReader(rd))
|
|
||||||
if err != nil {
|
|
||||||
return nil, reqInfoMap, err
|
|
||||||
}
|
|
||||||
// hostName
|
|
||||||
tmpArr := strings.Split(request.Host, ":")
|
|
||||||
reqInfoMap["Host"] = tmpArr[0]
|
|
||||||
reqInfoMap["Path"] = request.URL.Path
|
|
||||||
reqInfoMap["Scheme"] = request.URL.Scheme
|
|
||||||
|
|
||||||
// Authorization
|
|
||||||
authStr := request.Header.Get("Authorization")
|
|
||||||
if authStr != "" {
|
|
||||||
reqInfoMap["Authorization"] = authStr
|
|
||||||
}
|
|
||||||
request.Body.Close()
|
|
||||||
return frpNet.WrapConn(sc), reqInfoMap, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewHttpMuxer(listener frpNet.Listener, timeout time.Duration) (*HttpMuxer, error) {
|
type HttpReverseProxy struct {
|
||||||
mux, err := NewVhostMuxer(listener, GetHttpRequestInfo, HttpAuthFunc, ModifyHttpRequest, timeout)
|
proxy *ReverseProxy
|
||||||
return &HttpMuxer{mux}, err
|
vhostRouter *VhostRouters
|
||||||
|
|
||||||
|
responseHeaderTimeout time.Duration
|
||||||
}
|
}
|
||||||
|
|
||||||
func ModifyHttpRequest(c frpNet.Conn, rewriteHost string) (_ frpNet.Conn, err error) {
|
func NewHttpReverseProxy(option HttpReverseProxyOptions, vhostRouter *VhostRouters) *HttpReverseProxy {
|
||||||
sc, rd := gnet.NewSharedConn(c)
|
if option.ResponseHeaderTimeoutS <= 0 {
|
||||||
var buff []byte
|
option.ResponseHeaderTimeoutS = 60
|
||||||
remoteIP := strings.Split(c.RemoteAddr().String(), ":")[0]
|
|
||||||
if buff, err = hostNameRewrite(rd, rewriteHost, remoteIP); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
}
|
||||||
err = sc.ResetBuf(buff)
|
rp := &HttpReverseProxy{
|
||||||
return frpNet.WrapConn(sc), err
|
responseHeaderTimeout: time.Duration(option.ResponseHeaderTimeoutS) * time.Second,
|
||||||
}
|
vhostRouter: vhostRouter,
|
||||||
|
|
||||||
func hostNameRewrite(request io.Reader, rewriteHost string, remoteIP string) (_ []byte, err error) {
|
|
||||||
buf := pool.GetBuf(1024)
|
|
||||||
defer pool.PutBuf(buf)
|
|
||||||
|
|
||||||
var n int
|
|
||||||
n, err = request.Read(buf)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
retBuffer, err := parseRequest(buf[:n], rewriteHost, remoteIP)
|
proxy := &ReverseProxy{
|
||||||
return retBuffer, err
|
Director: func(req *http.Request) {
|
||||||
}
|
req.URL.Scheme = "http"
|
||||||
|
url := req.Context().Value("url").(string)
|
||||||
func parseRequest(org []byte, rewriteHost string, remoteIP string) (ret []byte, err error) {
|
oldHost := getHostFromAddr(req.Context().Value("host").(string))
|
||||||
tp := bytes.NewBuffer(org)
|
host := rp.GetRealHost(oldHost, url)
|
||||||
// First line: GET /index.html HTTP/1.0
|
if host != "" {
|
||||||
var b []byte
|
req.Host = host
|
||||||
if b, err = tp.ReadBytes('\n'); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
req := new(http.Request)
|
|
||||||
// we invoked ReadRequest in GetHttpHostname before, so we ignore error
|
|
||||||
req.Method, req.RequestURI, req.Proto, _ = parseRequestLine(string(b))
|
|
||||||
rawurl := req.RequestURI
|
|
||||||
// CONNECT www.google.com:443 HTTP/1.1
|
|
||||||
justAuthority := req.Method == "CONNECT" && !strings.HasPrefix(rawurl, "/")
|
|
||||||
if justAuthority {
|
|
||||||
rawurl = "http://" + rawurl
|
|
||||||
}
|
|
||||||
req.URL, _ = url.ParseRequestURI(rawurl)
|
|
||||||
if justAuthority {
|
|
||||||
// Strip the bogus "http://" back off.
|
|
||||||
req.URL.Scheme = ""
|
|
||||||
}
|
|
||||||
|
|
||||||
// RFC2616: first case
|
|
||||||
// GET /index.html HTTP/1.1
|
|
||||||
// Host: www.google.com
|
|
||||||
if req.URL.Host == "" {
|
|
||||||
var changedBuf []byte
|
|
||||||
if rewriteHost != "" {
|
|
||||||
changedBuf, err = changeHostName(tp, rewriteHost)
|
|
||||||
}
|
|
||||||
buf := new(bytes.Buffer)
|
|
||||||
buf.Write(b)
|
|
||||||
buf.WriteString(fmt.Sprintf("X-Forwarded-For: %s\r\n", remoteIP))
|
|
||||||
buf.WriteString(fmt.Sprintf("X-Real-IP: %s\r\n", remoteIP))
|
|
||||||
if len(changedBuf) == 0 {
|
|
||||||
tp.WriteTo(buf)
|
|
||||||
} else {
|
|
||||||
buf.Write(changedBuf)
|
|
||||||
}
|
|
||||||
return buf.Bytes(), err
|
|
||||||
}
|
|
||||||
|
|
||||||
// RFC2616: second case
|
|
||||||
// GET http://www.google.com/index.html HTTP/1.1
|
|
||||||
// Host: doesntmatter
|
|
||||||
// In this case, any Host line is ignored.
|
|
||||||
if rewriteHost != "" {
|
|
||||||
hostPort := strings.Split(req.URL.Host, ":")
|
|
||||||
if len(hostPort) == 1 {
|
|
||||||
req.URL.Host = rewriteHost
|
|
||||||
} else if len(hostPort) == 2 {
|
|
||||||
req.URL.Host = fmt.Sprintf("%s:%s", rewriteHost, hostPort[1])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
firstLine := req.Method + " " + req.URL.String() + " " + req.Proto
|
|
||||||
buf := new(bytes.Buffer)
|
|
||||||
buf.WriteString(firstLine)
|
|
||||||
buf.WriteString(fmt.Sprintf("X-Forwarded-For: %s\r\n", remoteIP))
|
|
||||||
buf.WriteString(fmt.Sprintf("X-Real-IP: %s\r\n", remoteIP))
|
|
||||||
tp.WriteTo(buf)
|
|
||||||
return buf.Bytes(), err
|
|
||||||
}
|
|
||||||
|
|
||||||
// parseRequestLine parses "GET /foo HTTP/1.1" into its three parts.
|
|
||||||
func parseRequestLine(line string) (method, requestURI, proto string, ok bool) {
|
|
||||||
s1 := strings.Index(line, " ")
|
|
||||||
s2 := strings.Index(line[s1+1:], " ")
|
|
||||||
if s1 < 0 || s2 < 0 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
s2 += s1 + 1
|
|
||||||
return line[:s1], line[s1+1 : s2], line[s2+1:], true
|
|
||||||
}
|
|
||||||
|
|
||||||
func changeHostName(buff *bytes.Buffer, rewriteHost string) (_ []byte, err error) {
|
|
||||||
retBuf := new(bytes.Buffer)
|
|
||||||
|
|
||||||
peek := buff.Bytes()
|
|
||||||
for len(peek) > 0 {
|
|
||||||
i := bytes.IndexByte(peek, '\n')
|
|
||||||
if i < 3 {
|
|
||||||
// Not present (-1) or found within the next few bytes,
|
|
||||||
// implying we're at the end ("\r\n\r\n" or "\n\n")
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
kv := peek[:i]
|
|
||||||
j := bytes.IndexByte(kv, ':')
|
|
||||||
if j < 0 {
|
|
||||||
return nil, fmt.Errorf("malformed MIME header line: " + string(kv))
|
|
||||||
}
|
|
||||||
if strings.Contains(strings.ToLower(string(kv[:j])), "host") {
|
|
||||||
var hostHeader string
|
|
||||||
portPos := bytes.IndexByte(kv[j+1:], ':')
|
|
||||||
if portPos == -1 {
|
|
||||||
hostHeader = fmt.Sprintf("Host: %s\r\n", rewriteHost)
|
|
||||||
} else {
|
|
||||||
hostHeader = fmt.Sprintf("Host: %s:%s\r\n", rewriteHost, kv[j+portPos+2:])
|
|
||||||
}
|
}
|
||||||
retBuf.WriteString(hostHeader)
|
req.URL.Host = req.Host
|
||||||
peek = peek[i+1:]
|
|
||||||
break
|
headers := rp.GetHeaders(oldHost, url)
|
||||||
} else {
|
for k, v := range headers {
|
||||||
retBuf.Write(peek[:i])
|
req.Header.Set(k, v)
|
||||||
retBuf.WriteByte('\n')
|
}
|
||||||
|
},
|
||||||
|
Transport: &http.Transport{
|
||||||
|
ResponseHeaderTimeout: rp.responseHeaderTimeout,
|
||||||
|
DisableKeepAlives: true,
|
||||||
|
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||||
|
url := ctx.Value("url").(string)
|
||||||
|
host := getHostFromAddr(ctx.Value("host").(string))
|
||||||
|
remote := ctx.Value("remote").(string)
|
||||||
|
return rp.CreateConnection(host, url, remote)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
BufferPool: newWrapPool(),
|
||||||
|
ErrorLog: log.New(newWrapLogger(), "", 0),
|
||||||
|
ErrorHandler: func(rw http.ResponseWriter, req *http.Request, err error) {
|
||||||
|
frpLog.Warn("do http proxy request error: %v", err)
|
||||||
|
rw.WriteHeader(http.StatusNotFound)
|
||||||
|
rw.Write(getNotFoundPageContent())
|
||||||
|
},
|
||||||
|
}
|
||||||
|
rp.proxy = proxy
|
||||||
|
return rp
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register register the route config to reverse proxy
|
||||||
|
// reverse proxy will use CreateConnFn from routeCfg to create a connection to the remote service
|
||||||
|
func (rp *HttpReverseProxy) Register(routeCfg VhostRouteConfig) error {
|
||||||
|
err := rp.vhostRouter.Add(routeCfg.Domain, routeCfg.Location, &routeCfg)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnRegister unregister route config by domain and location
|
||||||
|
func (rp *HttpReverseProxy) UnRegister(domain string, location string) {
|
||||||
|
rp.vhostRouter.Del(domain, location)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rp *HttpReverseProxy) GetRealHost(domain string, location string) (host string) {
|
||||||
|
vr, ok := rp.getVhost(domain, location)
|
||||||
|
if ok {
|
||||||
|
host = vr.payload.(*VhostRouteConfig).RewriteHost
|
||||||
|
}
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateConnection create a new connection by route config
|
||||||
|
func (rp *HttpReverseProxy) CreateConnection(domain string, location string, remoteAddr string) (net.Conn, error) {
|
||||||
|
vr, ok := rp.getVhost(domain, location)
|
||||||
|
if ok {
|
||||||
|
fn := vr.payload.(*VhostRouteConfig).CreateConnFn
|
||||||
|
if fn != nil {
|
||||||
|
return fn(remoteAddr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("%v: %s %s", ErrNoDomain, domain, location)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rp *HttpReverseProxy) CheckAuth(domain, location, user, passwd string) bool {
|
||||||
|
vr, ok := rp.getVhost(domain, location)
|
||||||
|
if ok {
|
||||||
|
checkUser := vr.payload.(*VhostRouteConfig).Username
|
||||||
|
checkPasswd := vr.payload.(*VhostRouteConfig).Password
|
||||||
|
if (checkUser != "" || checkPasswd != "") && (checkUser != user || checkPasswd != passwd) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// getVhost get vhost router by domain and location
|
||||||
|
func (rp *HttpReverseProxy) getVhost(domain string, location string) (vr *VhostRouter, ok bool) {
|
||||||
|
// first we check the full hostname
|
||||||
|
// if not exist, then check the wildcard_domain such as *.example.com
|
||||||
|
vr, ok = rp.vhostRouter.Get(domain, location)
|
||||||
|
if ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
domainSplit := strings.Split(domain, ".")
|
||||||
|
if len(domainSplit) < 3 {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
|
||||||
|
for {
|
||||||
|
if len(domainSplit) < 3 {
|
||||||
|
return nil, false
|
||||||
}
|
}
|
||||||
|
|
||||||
peek = peek[i+1:]
|
domainSplit[0] = "*"
|
||||||
|
domain = strings.Join(domainSplit, ".")
|
||||||
|
vr, ok = rp.vhostRouter.Get(domain, location)
|
||||||
|
if ok {
|
||||||
|
return vr, true
|
||||||
|
}
|
||||||
|
domainSplit = domainSplit[1:]
|
||||||
}
|
}
|
||||||
retBuf.Write(peek)
|
return
|
||||||
return retBuf.Bytes(), err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func HttpAuthFunc(c frpNet.Conn, userName, passWord, authorization string) (bAccess bool, err error) {
|
func (rp *HttpReverseProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||||
s := strings.SplitN(authorization, " ", 2)
|
domain := getHostFromAddr(req.Host)
|
||||||
if len(s) != 2 {
|
location := req.URL.Path
|
||||||
res := noAuthResponse()
|
user, passwd, _ := req.BasicAuth()
|
||||||
res.Write(c)
|
if !rp.CheckAuth(domain, location, user, passwd) {
|
||||||
|
rw.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`)
|
||||||
|
http.Error(rw, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
b, err := base64.StdEncoding.DecodeString(s[1])
|
rp.proxy.ServeHTTP(rw, req)
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
pair := strings.SplitN(string(b), ":", 2)
|
|
||||||
if len(pair) != 2 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if pair[0] != userName || pair[1] != passWord {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
return true, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func noAuthResponse() *http.Response {
|
type wrapPool struct{}
|
||||||
header := make(map[string][]string)
|
|
||||||
header["WWW-Authenticate"] = []string{`Basic realm="Restricted"`}
|
func newWrapPool() *wrapPool { return &wrapPool{} }
|
||||||
res := &http.Response{
|
|
||||||
Status: "401 Not authorized",
|
func (p *wrapPool) Get() []byte { return pool.GetBuf(32 * 1024) }
|
||||||
StatusCode: 401,
|
|
||||||
Proto: "HTTP/1.1",
|
func (p *wrapPool) Put(buf []byte) { pool.PutBuf(buf) }
|
||||||
ProtoMajor: 1,
|
|
||||||
ProtoMinor: 1,
|
type wrapLogger struct{}
|
||||||
Header: header,
|
|
||||||
}
|
func newWrapLogger() *wrapLogger { return &wrapLogger{} }
|
||||||
return res
|
|
||||||
|
func (l *wrapLogger) Write(p []byte) (n int, err error) {
|
||||||
|
frpLog.Warn("%s", string(bytes.TrimRight(p, "\n")))
|
||||||
|
return len(p), nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,211 +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 vhost
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"log"
|
|
||||||
"net"
|
|
||||||
"net/http"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
frpLog "github.com/fatedier/frp/utils/log"
|
|
||||||
|
|
||||||
"github.com/fatedier/golib/pool"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
ErrRouterConfigConflict = errors.New("router config conflict")
|
|
||||||
ErrNoDomain = errors.New("no such domain")
|
|
||||||
)
|
|
||||||
|
|
||||||
func getHostFromAddr(addr string) (host string) {
|
|
||||||
strs := strings.Split(addr, ":")
|
|
||||||
if len(strs) > 1 {
|
|
||||||
host = strs[0]
|
|
||||||
} else {
|
|
||||||
host = addr
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
type HttpReverseProxyOptions struct {
|
|
||||||
ResponseHeaderTimeoutS int64
|
|
||||||
}
|
|
||||||
|
|
||||||
type HttpReverseProxy struct {
|
|
||||||
proxy *ReverseProxy
|
|
||||||
|
|
||||||
vhostRouter *VhostRouters
|
|
||||||
|
|
||||||
responseHeaderTimeout time.Duration
|
|
||||||
cfgMu sync.RWMutex
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewHttpReverseProxy(option HttpReverseProxyOptions) *HttpReverseProxy {
|
|
||||||
if option.ResponseHeaderTimeoutS <= 0 {
|
|
||||||
option.ResponseHeaderTimeoutS = 60
|
|
||||||
}
|
|
||||||
rp := &HttpReverseProxy{
|
|
||||||
responseHeaderTimeout: time.Duration(option.ResponseHeaderTimeoutS) * time.Second,
|
|
||||||
vhostRouter: NewVhostRouters(),
|
|
||||||
}
|
|
||||||
proxy := &ReverseProxy{
|
|
||||||
Director: func(req *http.Request) {
|
|
||||||
req.URL.Scheme = "http"
|
|
||||||
url := req.Context().Value("url").(string)
|
|
||||||
oldHost := getHostFromAddr(req.Context().Value("host").(string))
|
|
||||||
host := rp.GetRealHost(oldHost, url)
|
|
||||||
if host != "" {
|
|
||||||
req.Host = host
|
|
||||||
}
|
|
||||||
req.URL.Host = req.Host
|
|
||||||
|
|
||||||
headers := rp.GetHeaders(oldHost, url)
|
|
||||||
for k, v := range headers {
|
|
||||||
req.Header.Set(k, v)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Transport: &http.Transport{
|
|
||||||
ResponseHeaderTimeout: rp.responseHeaderTimeout,
|
|
||||||
DisableKeepAlives: true,
|
|
||||||
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
|
||||||
url := ctx.Value("url").(string)
|
|
||||||
host := getHostFromAddr(ctx.Value("host").(string))
|
|
||||||
return rp.CreateConnection(host, url)
|
|
||||||
},
|
|
||||||
},
|
|
||||||
WebSocketDialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
|
||||||
url := ctx.Value("url").(string)
|
|
||||||
host := getHostFromAddr(ctx.Value("host").(string))
|
|
||||||
return rp.CreateConnection(host, url)
|
|
||||||
},
|
|
||||||
BufferPool: newWrapPool(),
|
|
||||||
ErrorLog: log.New(newWrapLogger(), "", 0),
|
|
||||||
}
|
|
||||||
rp.proxy = proxy
|
|
||||||
return rp
|
|
||||||
}
|
|
||||||
|
|
||||||
func (rp *HttpReverseProxy) Register(routeCfg VhostRouteConfig) error {
|
|
||||||
rp.cfgMu.Lock()
|
|
||||||
defer rp.cfgMu.Unlock()
|
|
||||||
_, ok := rp.vhostRouter.Exist(routeCfg.Domain, routeCfg.Location)
|
|
||||||
if ok {
|
|
||||||
return ErrRouterConfigConflict
|
|
||||||
} else {
|
|
||||||
rp.vhostRouter.Add(routeCfg.Domain, routeCfg.Location, &routeCfg)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (rp *HttpReverseProxy) UnRegister(domain string, location string) {
|
|
||||||
rp.cfgMu.Lock()
|
|
||||||
defer rp.cfgMu.Unlock()
|
|
||||||
rp.vhostRouter.Del(domain, location)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (rp *HttpReverseProxy) GetRealHost(domain string, location string) (host string) {
|
|
||||||
vr, ok := rp.getVhost(domain, location)
|
|
||||||
if ok {
|
|
||||||
host = vr.payload.(*VhostRouteConfig).RewriteHost
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (rp *HttpReverseProxy) GetHeaders(domain string, location string) (headers map[string]string) {
|
|
||||||
vr, ok := rp.getVhost(domain, location)
|
|
||||||
if ok {
|
|
||||||
headers = vr.payload.(*VhostRouteConfig).Headers
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (rp *HttpReverseProxy) CreateConnection(domain string, location string) (net.Conn, error) {
|
|
||||||
vr, ok := rp.getVhost(domain, location)
|
|
||||||
if ok {
|
|
||||||
fn := vr.payload.(*VhostRouteConfig).CreateConnFn
|
|
||||||
if fn != nil {
|
|
||||||
return fn()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil, ErrNoDomain
|
|
||||||
}
|
|
||||||
|
|
||||||
func (rp *HttpReverseProxy) CheckAuth(domain, location, user, passwd string) bool {
|
|
||||||
vr, ok := rp.getVhost(domain, location)
|
|
||||||
if ok {
|
|
||||||
checkUser := vr.payload.(*VhostRouteConfig).Username
|
|
||||||
checkPasswd := vr.payload.(*VhostRouteConfig).Password
|
|
||||||
if (checkUser != "" || checkPasswd != "") && (checkUser != user || checkPasswd != passwd) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (rp *HttpReverseProxy) getVhost(domain string, location string) (vr *VhostRouter, ok bool) {
|
|
||||||
rp.cfgMu.RLock()
|
|
||||||
defer rp.cfgMu.RUnlock()
|
|
||||||
|
|
||||||
// first we check the full hostname
|
|
||||||
// if not exist, then check the wildcard_domain such as *.example.com
|
|
||||||
vr, ok = rp.vhostRouter.Get(domain, location)
|
|
||||||
if ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
domainSplit := strings.Split(domain, ".")
|
|
||||||
if len(domainSplit) < 3 {
|
|
||||||
return vr, false
|
|
||||||
}
|
|
||||||
domainSplit[0] = "*"
|
|
||||||
domain = strings.Join(domainSplit, ".")
|
|
||||||
vr, ok = rp.vhostRouter.Get(domain, location)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (rp *HttpReverseProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
|
||||||
domain := getHostFromAddr(req.Host)
|
|
||||||
location := req.URL.Path
|
|
||||||
user, passwd, _ := req.BasicAuth()
|
|
||||||
if !rp.CheckAuth(domain, location, user, passwd) {
|
|
||||||
rw.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`)
|
|
||||||
http.Error(rw, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
rp.proxy.ServeHTTP(rw, req)
|
|
||||||
}
|
|
||||||
|
|
||||||
type wrapPool struct{}
|
|
||||||
|
|
||||||
func newWrapPool() *wrapPool { return &wrapPool{} }
|
|
||||||
|
|
||||||
func (p *wrapPool) Get() []byte { return pool.GetBuf(32 * 1024) }
|
|
||||||
|
|
||||||
func (p *wrapPool) Put(buf []byte) { pool.PutBuf(buf) }
|
|
||||||
|
|
||||||
type wrapLogger struct{}
|
|
||||||
|
|
||||||
func newWrapLogger() *wrapLogger { return &wrapLogger{} }
|
|
||||||
|
|
||||||
func (l *wrapLogger) Write(p []byte) (n int, err error) {
|
|
||||||
frpLog.Warn("%s", string(bytes.TrimRight(p, "\n")))
|
|
||||||
return len(p), nil
|
|
||||||
}
|
|
||||||
@@ -15,13 +15,18 @@
|
|||||||
package vhost
|
package vhost
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
|
||||||
|
|
||||||
|
frpLog "github.com/fatedier/frp/utils/log"
|
||||||
"github.com/fatedier/frp/utils/version"
|
"github.com/fatedier/frp/utils/version"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
NotFoundPagePath = ""
|
||||||
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
NotFound = `<!DOCTYPE html>
|
NotFound = `<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
@@ -46,10 +51,28 @@ Please try again later.</p>
|
|||||||
`
|
`
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func getNotFoundPageContent() []byte {
|
||||||
|
var (
|
||||||
|
buf []byte
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
if NotFoundPagePath != "" {
|
||||||
|
buf, err = ioutil.ReadFile(NotFoundPagePath)
|
||||||
|
if err != nil {
|
||||||
|
frpLog.Warn("read custom 404 page error: %v", err)
|
||||||
|
buf = []byte(NotFound)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
buf = []byte(NotFound)
|
||||||
|
}
|
||||||
|
return buf
|
||||||
|
}
|
||||||
|
|
||||||
func notFoundResponse() *http.Response {
|
func notFoundResponse() *http.Response {
|
||||||
header := make(http.Header)
|
header := make(http.Header)
|
||||||
header.Set("server", "frp/"+version.Full())
|
header.Set("server", "frp/"+version.Full())
|
||||||
header.Set("Content-Type", "text/html")
|
header.Set("Content-Type", "text/html")
|
||||||
|
|
||||||
res := &http.Response{
|
res := &http.Response{
|
||||||
Status: "Not Found",
|
Status: "Not Found",
|
||||||
StatusCode: 404,
|
StatusCode: 404,
|
||||||
@@ -57,7 +80,21 @@ func notFoundResponse() *http.Response {
|
|||||||
ProtoMajor: 1,
|
ProtoMajor: 1,
|
||||||
ProtoMinor: 0,
|
ProtoMinor: 0,
|
||||||
Header: header,
|
Header: header,
|
||||||
Body: ioutil.NopCloser(strings.NewReader(NotFound)),
|
Body: ioutil.NopCloser(bytes.NewReader(getNotFoundPageContent())),
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
func noAuthResponse() *http.Response {
|
||||||
|
header := make(map[string][]string)
|
||||||
|
header["WWW-Authenticate"] = []string{`Basic realm="Restricted"`}
|
||||||
|
res := &http.Response{
|
||||||
|
Status: "401 Not authorized",
|
||||||
|
StatusCode: 401,
|
||||||
|
Proto: "HTTP/1.1",
|
||||||
|
ProtoMajor: 1,
|
||||||
|
ProtoMinor: 1,
|
||||||
|
Header: header,
|
||||||
}
|
}
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ package vhost
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"log"
|
"log"
|
||||||
"net"
|
"net"
|
||||||
@@ -17,13 +18,9 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
frpIo "github.com/fatedier/golib/io"
|
"golang.org/x/net/http/httpguts"
|
||||||
)
|
)
|
||||||
|
|
||||||
// onExitFlushLoop is a callback set by tests to detect the state of the
|
|
||||||
// flushLoop() goroutine.
|
|
||||||
var onExitFlushLoop func()
|
|
||||||
|
|
||||||
// ReverseProxy is an HTTP Handler that takes an incoming request and
|
// ReverseProxy is an HTTP Handler that takes an incoming request and
|
||||||
// sends it to another server, proxying the response back to the
|
// sends it to another server, proxying the response back to the
|
||||||
// client.
|
// client.
|
||||||
@@ -44,12 +41,17 @@ type ReverseProxy struct {
|
|||||||
// to flush to the client while copying the
|
// to flush to the client while copying the
|
||||||
// response body.
|
// response body.
|
||||||
// If zero, no periodic flushing is done.
|
// If zero, no periodic flushing is done.
|
||||||
|
// A negative value means to flush immediately
|
||||||
|
// after each write to the client.
|
||||||
|
// The FlushInterval is ignored when ReverseProxy
|
||||||
|
// recognizes a response as a streaming response;
|
||||||
|
// for such responses, writes are flushed to the client
|
||||||
|
// immediately.
|
||||||
FlushInterval time.Duration
|
FlushInterval time.Duration
|
||||||
|
|
||||||
// ErrorLog specifies an optional logger for errors
|
// ErrorLog specifies an optional logger for errors
|
||||||
// that occur when attempting to proxy the request.
|
// that occur when attempting to proxy the request.
|
||||||
// If nil, logging goes to os.Stderr via the log package's
|
// If nil, logging is done via the log package's standard logger.
|
||||||
// standard logger.
|
|
||||||
ErrorLog *log.Logger
|
ErrorLog *log.Logger
|
||||||
|
|
||||||
// BufferPool optionally specifies a buffer pool to
|
// BufferPool optionally specifies a buffer pool to
|
||||||
@@ -57,12 +59,23 @@ type ReverseProxy struct {
|
|||||||
// copying HTTP response bodies.
|
// copying HTTP response bodies.
|
||||||
BufferPool BufferPool
|
BufferPool BufferPool
|
||||||
|
|
||||||
// ModifyResponse is an optional function that
|
// ModifyResponse is an optional function that modifies the
|
||||||
// modifies the Response from the backend.
|
// Response from the backend. It is called if the backend
|
||||||
// If it returns an error, the proxy returns a StatusBadGateway error.
|
// returns a response at all, with any HTTP status code.
|
||||||
|
// If the backend is unreachable, the optional ErrorHandler is
|
||||||
|
// called without any call to ModifyResponse.
|
||||||
|
//
|
||||||
|
// If ModifyResponse returns an error, ErrorHandler is called
|
||||||
|
// with its error value. If ErrorHandler is nil, its default
|
||||||
|
// implementation is used.
|
||||||
ModifyResponse func(*http.Response) error
|
ModifyResponse func(*http.Response) error
|
||||||
|
|
||||||
WebSocketDialContext func(ctx context.Context, network, addr string) (net.Conn, error)
|
// ErrorHandler is an optional function that handles errors
|
||||||
|
// reaching the backend or errors from ModifyResponse.
|
||||||
|
//
|
||||||
|
// If nil, the default is to log the provided error and return
|
||||||
|
// a 502 Status Bad Gateway response.
|
||||||
|
ErrorHandler func(http.ResponseWriter, *http.Request, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// A BufferPool is an interface for getting and returning temporary
|
// A BufferPool is an interface for getting and returning temporary
|
||||||
@@ -118,18 +131,11 @@ func copyHeader(dst, src http.Header) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func cloneHeader(h http.Header) http.Header {
|
|
||||||
h2 := make(http.Header, len(h))
|
|
||||||
for k, vv := range h {
|
|
||||||
vv2 := make([]string, len(vv))
|
|
||||||
copy(vv2, vv)
|
|
||||||
h2[k] = vv2
|
|
||||||
}
|
|
||||||
return h2
|
|
||||||
}
|
|
||||||
|
|
||||||
// Hop-by-hop headers. These are removed when sent to the backend.
|
// Hop-by-hop headers. These are removed when sent to the backend.
|
||||||
// http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html
|
// As of RFC 7230, hop-by-hop headers are required to appear in the
|
||||||
|
// Connection header field. These are the headers defined by the
|
||||||
|
// obsoleted RFC 2616 (section 13.5.1) and are used for backward
|
||||||
|
// compatibility.
|
||||||
var hopHeaders = []string{
|
var hopHeaders = []string{
|
||||||
"Connection",
|
"Connection",
|
||||||
"Proxy-Connection", // non-standard but still sent by libcurl and rejected by e.g. google
|
"Proxy-Connection", // non-standard but still sent by libcurl and rejected by e.g. google
|
||||||
@@ -137,54 +143,38 @@ var hopHeaders = []string{
|
|||||||
"Proxy-Authenticate",
|
"Proxy-Authenticate",
|
||||||
"Proxy-Authorization",
|
"Proxy-Authorization",
|
||||||
"Te", // canonicalized version of "TE"
|
"Te", // canonicalized version of "TE"
|
||||||
"Trailer", // not Trailers per URL above; http://www.rfc-editor.org/errata_search.php?eid=4522
|
"Trailer", // not Trailers per URL above; https://www.rfc-editor.org/errata_search.php?eid=4522
|
||||||
"Transfer-Encoding",
|
"Transfer-Encoding",
|
||||||
"Upgrade",
|
"Upgrade",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *ReverseProxy) defaultErrorHandler(rw http.ResponseWriter, req *http.Request, err error) {
|
||||||
|
p.logf("http: proxy error: %v", err)
|
||||||
|
rw.WriteHeader(http.StatusBadGateway)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *ReverseProxy) getErrorHandler() func(http.ResponseWriter, *http.Request, error) {
|
||||||
|
if p.ErrorHandler != nil {
|
||||||
|
return p.ErrorHandler
|
||||||
|
}
|
||||||
|
return p.defaultErrorHandler
|
||||||
|
}
|
||||||
|
|
||||||
|
// modifyResponse conditionally runs the optional ModifyResponse hook
|
||||||
|
// and reports whether the request should proceed.
|
||||||
|
func (p *ReverseProxy) modifyResponse(rw http.ResponseWriter, res *http.Response, req *http.Request) bool {
|
||||||
|
if p.ModifyResponse == nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if err := p.ModifyResponse(res); err != nil {
|
||||||
|
res.Body.Close()
|
||||||
|
p.getErrorHandler()(rw, req, err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
func (p *ReverseProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
func (p *ReverseProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||||
if IsWebsocketRequest(req) {
|
|
||||||
p.serveWebSocket(rw, req)
|
|
||||||
} else {
|
|
||||||
p.serveHTTP(rw, req)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *ReverseProxy) serveWebSocket(rw http.ResponseWriter, req *http.Request) {
|
|
||||||
if p.WebSocketDialContext == nil {
|
|
||||||
rw.WriteHeader(500)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
req = req.WithContext(context.WithValue(req.Context(), "url", req.URL.Path))
|
|
||||||
req = req.WithContext(context.WithValue(req.Context(), "host", req.Host))
|
|
||||||
|
|
||||||
targetConn, err := p.WebSocketDialContext(req.Context(), "tcp", "")
|
|
||||||
if err != nil {
|
|
||||||
rw.WriteHeader(501)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer targetConn.Close()
|
|
||||||
|
|
||||||
p.Director(req)
|
|
||||||
|
|
||||||
hijacker, ok := rw.(http.Hijacker)
|
|
||||||
if !ok {
|
|
||||||
rw.WriteHeader(500)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
conn, _, errHijack := hijacker.Hijack()
|
|
||||||
if errHijack != nil {
|
|
||||||
rw.WriteHeader(500)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer conn.Close()
|
|
||||||
|
|
||||||
req.Write(targetConn)
|
|
||||||
frpIo.Join(conn, targetConn)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *ReverseProxy) serveHTTP(rw http.ResponseWriter, req *http.Request) {
|
|
||||||
transport := p.Transport
|
transport := p.Transport
|
||||||
if transport == nil {
|
if transport == nil {
|
||||||
transport = http.DefaultTransport
|
transport = http.DefaultTransport
|
||||||
@@ -205,37 +195,49 @@ func (p *ReverseProxy) serveHTTP(rw http.ResponseWriter, req *http.Request) {
|
|||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
outreq := req.WithContext(ctx) // includes shallow copies of maps, but okay
|
outreq := req.WithContext(ctx)
|
||||||
if req.ContentLength == 0 {
|
if req.ContentLength == 0 {
|
||||||
outreq.Body = nil // Issue 16036: nil Body for http.Transport retries
|
outreq.Body = nil // Issue 16036: nil Body for http.Transport retries
|
||||||
}
|
}
|
||||||
|
|
||||||
outreq.Header = cloneHeader(req.Header)
|
// =============================
|
||||||
|
// Modified for frp
|
||||||
// Modify for frp
|
|
||||||
outreq = outreq.WithContext(context.WithValue(outreq.Context(), "url", req.URL.Path))
|
outreq = outreq.WithContext(context.WithValue(outreq.Context(), "url", req.URL.Path))
|
||||||
outreq = outreq.WithContext(context.WithValue(outreq.Context(), "host", req.Host))
|
outreq = outreq.WithContext(context.WithValue(outreq.Context(), "host", req.Host))
|
||||||
|
outreq = outreq.WithContext(context.WithValue(outreq.Context(), "remote", req.RemoteAddr))
|
||||||
|
// =============================
|
||||||
|
|
||||||
p.Director(outreq)
|
p.Director(outreq)
|
||||||
outreq.Close = false
|
outreq.Close = false
|
||||||
|
|
||||||
// Remove hop-by-hop headers listed in the "Connection" header.
|
reqUpType := upgradeType(outreq.Header)
|
||||||
// See RFC 2616, section 14.10.
|
removeConnectionHeaders(outreq.Header)
|
||||||
if c := outreq.Header.Get("Connection"); c != "" {
|
|
||||||
for _, f := range strings.Split(c, ",") {
|
|
||||||
if f = strings.TrimSpace(f); f != "" {
|
|
||||||
outreq.Header.Del(f)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove hop-by-hop headers to the backend. Especially
|
// Remove hop-by-hop headers to the backend. Especially
|
||||||
// important is "Connection" because we want a persistent
|
// important is "Connection" because we want a persistent
|
||||||
// connection, regardless of what the client sent to us.
|
// connection, regardless of what the client sent to us.
|
||||||
for _, h := range hopHeaders {
|
for _, h := range hopHeaders {
|
||||||
if outreq.Header.Get(h) != "" {
|
hv := outreq.Header.Get(h)
|
||||||
outreq.Header.Del(h)
|
if hv == "" {
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
|
if h == "Te" && hv == "trailers" {
|
||||||
|
// Issue 21096: tell backend applications that
|
||||||
|
// care about trailer support that we support
|
||||||
|
// trailers. (We do, but we don't go out of
|
||||||
|
// our way to advertise that unless the
|
||||||
|
// incoming client request thought it was
|
||||||
|
// worth mentioning)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
outreq.Header.Del(h)
|
||||||
|
}
|
||||||
|
|
||||||
|
// After stripping all the hop-by-hop connection headers above, add back any
|
||||||
|
// necessary for protocol upgrades, such as for websockets.
|
||||||
|
if reqUpType != "" {
|
||||||
|
outreq.Header.Set("Connection", "Upgrade")
|
||||||
|
outreq.Header.Set("Upgrade", reqUpType)
|
||||||
}
|
}
|
||||||
|
|
||||||
if clientIP, _, err := net.SplitHostPort(req.RemoteAddr); err == nil {
|
if clientIP, _, err := net.SplitHostPort(req.RemoteAddr); err == nil {
|
||||||
@@ -250,32 +252,27 @@ func (p *ReverseProxy) serveHTTP(rw http.ResponseWriter, req *http.Request) {
|
|||||||
|
|
||||||
res, err := transport.RoundTrip(outreq)
|
res, err := transport.RoundTrip(outreq)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
p.logf("http: proxy error: %v", err)
|
p.getErrorHandler()(rw, outreq, err)
|
||||||
rw.WriteHeader(http.StatusNotFound)
|
|
||||||
rw.Write([]byte(NotFound))
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove hop-by-hop headers listed in the
|
// Deal with 101 Switching Protocols responses: (WebSocket, h2c, etc)
|
||||||
// "Connection" header of the response.
|
if res.StatusCode == http.StatusSwitchingProtocols {
|
||||||
if c := res.Header.Get("Connection"); c != "" {
|
if !p.modifyResponse(rw, res, outreq) {
|
||||||
for _, f := range strings.Split(c, ",") {
|
return
|
||||||
if f = strings.TrimSpace(f); f != "" {
|
|
||||||
res.Header.Del(f)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
p.handleUpgradeResponse(rw, outreq, res)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
removeConnectionHeaders(res.Header)
|
||||||
|
|
||||||
for _, h := range hopHeaders {
|
for _, h := range hopHeaders {
|
||||||
res.Header.Del(h)
|
res.Header.Del(h)
|
||||||
}
|
}
|
||||||
|
|
||||||
if p.ModifyResponse != nil {
|
if !p.modifyResponse(rw, res, outreq) {
|
||||||
if err := p.ModifyResponse(res); err != nil {
|
return
|
||||||
p.logf("http: proxy error: %v", err)
|
|
||||||
rw.WriteHeader(http.StatusBadGateway)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
copyHeader(rw.Header(), res.Header)
|
copyHeader(rw.Header(), res.Header)
|
||||||
@@ -292,6 +289,21 @@ func (p *ReverseProxy) serveHTTP(rw http.ResponseWriter, req *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
rw.WriteHeader(res.StatusCode)
|
rw.WriteHeader(res.StatusCode)
|
||||||
|
|
||||||
|
err = p.copyResponse(rw, res.Body, p.flushInterval(req, res))
|
||||||
|
if err != nil {
|
||||||
|
defer res.Body.Close()
|
||||||
|
// Since we're streaming the response, if we run into an error all we can do
|
||||||
|
// is abort the request. Issue 23643: ReverseProxy should use ErrAbortHandler
|
||||||
|
// on read error while copying body.
|
||||||
|
if !shouldPanicOnCopyError(req) {
|
||||||
|
p.logf("suppressing panic for copyResponse error in test; copy error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
panic(http.ErrAbortHandler)
|
||||||
|
}
|
||||||
|
res.Body.Close() // close now, instead of defer, to populate res.Trailer
|
||||||
|
|
||||||
if len(res.Trailer) > 0 {
|
if len(res.Trailer) > 0 {
|
||||||
// Force chunking if we saw a response trailer.
|
// Force chunking if we saw a response trailer.
|
||||||
// This prevents net/http from calculating the length for short
|
// This prevents net/http from calculating the length for short
|
||||||
@@ -300,8 +312,6 @@ func (p *ReverseProxy) serveHTTP(rw http.ResponseWriter, req *http.Request) {
|
|||||||
fl.Flush()
|
fl.Flush()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
p.copyResponse(rw, res.Body)
|
|
||||||
res.Body.Close() // close now, instead of defer, to populate res.Trailer
|
|
||||||
|
|
||||||
if len(res.Trailer) == announcedTrailers {
|
if len(res.Trailer) == announcedTrailers {
|
||||||
copyHeader(rw.Header(), res.Trailer)
|
copyHeader(rw.Header(), res.Trailer)
|
||||||
@@ -316,16 +326,68 @@ func (p *ReverseProxy) serveHTTP(rw http.ResponseWriter, req *http.Request) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *ReverseProxy) copyResponse(dst io.Writer, src io.Reader) {
|
var inOurTests bool // whether we're in our own tests
|
||||||
if p.FlushInterval != 0 {
|
|
||||||
|
// shouldPanicOnCopyError reports whether the reverse proxy should
|
||||||
|
// panic with http.ErrAbortHandler. This is the right thing to do by
|
||||||
|
// default, but Go 1.10 and earlier did not, so existing unit tests
|
||||||
|
// weren't expecting panics. Only panic in our own tests, or when
|
||||||
|
// running under the HTTP server.
|
||||||
|
func shouldPanicOnCopyError(req *http.Request) bool {
|
||||||
|
if inOurTests {
|
||||||
|
// Our tests know to handle this panic.
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if req.Context().Value(http.ServerContextKey) != nil {
|
||||||
|
// We seem to be running under an HTTP server, so
|
||||||
|
// it'll recover the panic.
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
// Otherwise act like Go 1.10 and earlier to not break
|
||||||
|
// existing tests.
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// removeConnectionHeaders removes hop-by-hop headers listed in the "Connection" header of h.
|
||||||
|
// See RFC 7230, section 6.1
|
||||||
|
func removeConnectionHeaders(h http.Header) {
|
||||||
|
for _, f := range h["Connection"] {
|
||||||
|
for _, sf := range strings.Split(f, ",") {
|
||||||
|
if sf = strings.TrimSpace(sf); sf != "" {
|
||||||
|
h.Del(sf)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// flushInterval returns the p.FlushInterval value, conditionally
|
||||||
|
// overriding its value for a specific request/response.
|
||||||
|
func (p *ReverseProxy) flushInterval(req *http.Request, res *http.Response) time.Duration {
|
||||||
|
resCT := res.Header.Get("Content-Type")
|
||||||
|
|
||||||
|
// For Server-Sent Events responses, flush immediately.
|
||||||
|
// The MIME type is defined in https://www.w3.org/TR/eventsource/#text-event-stream
|
||||||
|
if resCT == "text/event-stream" {
|
||||||
|
return -1 // negative means immediately
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: more specific cases? e.g. res.ContentLength == -1?
|
||||||
|
return p.FlushInterval
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *ReverseProxy) copyResponse(dst io.Writer, src io.Reader, flushInterval time.Duration) error {
|
||||||
|
if flushInterval != 0 {
|
||||||
if wf, ok := dst.(writeFlusher); ok {
|
if wf, ok := dst.(writeFlusher); ok {
|
||||||
mlw := &maxLatencyWriter{
|
mlw := &maxLatencyWriter{
|
||||||
dst: wf,
|
dst: wf,
|
||||||
latency: p.FlushInterval,
|
latency: flushInterval,
|
||||||
done: make(chan bool),
|
|
||||||
}
|
}
|
||||||
go mlw.flushLoop()
|
|
||||||
defer mlw.stop()
|
defer mlw.stop()
|
||||||
|
|
||||||
|
// set up initial timer so headers get flushed even if body writes are delayed
|
||||||
|
mlw.flushPending = true
|
||||||
|
mlw.t = time.AfterFunc(flushInterval, mlw.delayedFlush)
|
||||||
|
|
||||||
dst = mlw
|
dst = mlw
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -333,13 +395,14 @@ func (p *ReverseProxy) copyResponse(dst io.Writer, src io.Reader) {
|
|||||||
var buf []byte
|
var buf []byte
|
||||||
if p.BufferPool != nil {
|
if p.BufferPool != nil {
|
||||||
buf = p.BufferPool.Get()
|
buf = p.BufferPool.Get()
|
||||||
|
defer p.BufferPool.Put(buf)
|
||||||
}
|
}
|
||||||
p.copyBuffer(dst, src, buf)
|
_, err := p.copyBuffer(dst, src, buf)
|
||||||
if p.BufferPool != nil {
|
return err
|
||||||
p.BufferPool.Put(buf)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// copyBuffer returns any write errors or non-EOF read errors, and the amount
|
||||||
|
// of bytes written.
|
||||||
func (p *ReverseProxy) copyBuffer(dst io.Writer, src io.Reader, buf []byte) (int64, error) {
|
func (p *ReverseProxy) copyBuffer(dst io.Writer, src io.Reader, buf []byte) (int64, error) {
|
||||||
if len(buf) == 0 {
|
if len(buf) == 0 {
|
||||||
buf = make([]byte, 32*1024)
|
buf = make([]byte, 32*1024)
|
||||||
@@ -363,6 +426,9 @@ func (p *ReverseProxy) copyBuffer(dst io.Writer, src io.Reader, buf []byte) (int
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if rerr != nil {
|
if rerr != nil {
|
||||||
|
if rerr == io.EOF {
|
||||||
|
rerr = nil
|
||||||
|
}
|
||||||
return written, rerr
|
return written, rerr
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -383,47 +449,115 @@ type writeFlusher interface {
|
|||||||
|
|
||||||
type maxLatencyWriter struct {
|
type maxLatencyWriter struct {
|
||||||
dst writeFlusher
|
dst writeFlusher
|
||||||
latency time.Duration
|
latency time.Duration // non-zero; negative means to flush immediately
|
||||||
|
|
||||||
mu sync.Mutex // protects Write + Flush
|
mu sync.Mutex // protects t, flushPending, and dst.Flush
|
||||||
done chan bool
|
t *time.Timer
|
||||||
|
flushPending bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *maxLatencyWriter) Write(p []byte) (int, error) {
|
func (m *maxLatencyWriter) Write(p []byte) (n int, err error) {
|
||||||
m.mu.Lock()
|
m.mu.Lock()
|
||||||
defer m.mu.Unlock()
|
defer m.mu.Unlock()
|
||||||
return m.dst.Write(p)
|
n, err = m.dst.Write(p)
|
||||||
|
if m.latency < 0 {
|
||||||
|
m.dst.Flush()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if m.flushPending {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if m.t == nil {
|
||||||
|
m.t = time.AfterFunc(m.latency, m.delayedFlush)
|
||||||
|
} else {
|
||||||
|
m.t.Reset(m.latency)
|
||||||
|
}
|
||||||
|
m.flushPending = true
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *maxLatencyWriter) flushLoop() {
|
func (m *maxLatencyWriter) delayedFlush() {
|
||||||
t := time.NewTicker(m.latency)
|
m.mu.Lock()
|
||||||
defer t.Stop()
|
defer m.mu.Unlock()
|
||||||
for {
|
if !m.flushPending { // if stop was called but AfterFunc already started this goroutine
|
||||||
select {
|
return
|
||||||
case <-m.done:
|
}
|
||||||
if onExitFlushLoop != nil {
|
m.dst.Flush()
|
||||||
onExitFlushLoop()
|
m.flushPending = false
|
||||||
}
|
}
|
||||||
return
|
|
||||||
case <-t.C:
|
func (m *maxLatencyWriter) stop() {
|
||||||
m.mu.Lock()
|
m.mu.Lock()
|
||||||
m.dst.Flush()
|
defer m.mu.Unlock()
|
||||||
m.mu.Unlock()
|
m.flushPending = false
|
||||||
}
|
if m.t != nil {
|
||||||
|
m.t.Stop()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *maxLatencyWriter) stop() { m.done <- true }
|
func upgradeType(h http.Header) string {
|
||||||
|
if !httpguts.HeaderValuesContainsToken(h["Connection"], "Upgrade") {
|
||||||
func IsWebsocketRequest(req *http.Request) bool {
|
return ""
|
||||||
containsHeader := func(name, value string) bool {
|
|
||||||
items := strings.Split(req.Header.Get(name), ",")
|
|
||||||
for _, item := range items {
|
|
||||||
if value == strings.ToLower(strings.TrimSpace(item)) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
return containsHeader("Connection", "upgrade") && containsHeader("Upgrade", "websocket")
|
return strings.ToLower(h.Get("Upgrade"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *ReverseProxy) handleUpgradeResponse(rw http.ResponseWriter, req *http.Request, res *http.Response) {
|
||||||
|
reqUpType := upgradeType(req.Header)
|
||||||
|
resUpType := upgradeType(res.Header)
|
||||||
|
if reqUpType != resUpType {
|
||||||
|
p.getErrorHandler()(rw, req, fmt.Errorf("backend tried to switch protocol %q when %q was requested", resUpType, reqUpType))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
copyHeader(res.Header, rw.Header())
|
||||||
|
|
||||||
|
hj, ok := rw.(http.Hijacker)
|
||||||
|
if !ok {
|
||||||
|
p.getErrorHandler()(rw, req, fmt.Errorf("can't switch protocols using non-Hijacker ResponseWriter type %T", rw))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
backConn, ok := res.Body.(io.ReadWriteCloser)
|
||||||
|
if !ok {
|
||||||
|
p.getErrorHandler()(rw, req, fmt.Errorf("internal error: 101 switching protocols response with non-writable body"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer backConn.Close()
|
||||||
|
conn, brw, err := hj.Hijack()
|
||||||
|
if err != nil {
|
||||||
|
p.getErrorHandler()(rw, req, fmt.Errorf("Hijack failed on protocol switch: %v", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer conn.Close()
|
||||||
|
res.Body = nil // so res.Write only writes the headers; we have res.Body in backConn above
|
||||||
|
if err := res.Write(brw); err != nil {
|
||||||
|
p.getErrorHandler()(rw, req, fmt.Errorf("response write: %v", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := brw.Flush(); err != nil {
|
||||||
|
p.getErrorHandler()(rw, req, fmt.Errorf("response flush: %v", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
errc := make(chan error, 1)
|
||||||
|
spc := switchProtocolCopier{user: conn, backend: backConn}
|
||||||
|
go spc.copyToBackend(errc)
|
||||||
|
go spc.copyFromBackend(errc)
|
||||||
|
<-errc
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// switchProtocolCopier exists so goroutines proxying data back and
|
||||||
|
// forth have nice names in stacks.
|
||||||
|
type switchProtocolCopier struct {
|
||||||
|
user, backend io.ReadWriter
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c switchProtocolCopier) copyFromBackend(errc chan<- error) {
|
||||||
|
_, err := io.Copy(c.user, c.backend)
|
||||||
|
errc <- err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c switchProtocolCopier) copyToBackend(errc chan<- error) {
|
||||||
|
_, err := io.Copy(c.backend, c.user)
|
||||||
|
errc <- err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,16 @@
|
|||||||
package vhost
|
package vhost
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrRouterConfigConflict = errors.New("router config conflict")
|
||||||
|
)
|
||||||
|
|
||||||
type VhostRouters struct {
|
type VhostRouters struct {
|
||||||
RouterByDomain map[string][]*VhostRouter
|
RouterByDomain map[string][]*VhostRouter
|
||||||
mutex sync.RWMutex
|
mutex sync.RWMutex
|
||||||
@@ -24,10 +29,14 @@ func NewVhostRouters() *VhostRouters {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *VhostRouters) Add(domain, location string, payload interface{}) {
|
func (r *VhostRouters) Add(domain, location string, payload interface{}) error {
|
||||||
r.mutex.Lock()
|
r.mutex.Lock()
|
||||||
defer r.mutex.Unlock()
|
defer r.mutex.Unlock()
|
||||||
|
|
||||||
|
if _, exist := r.exist(domain, location); exist {
|
||||||
|
return ErrRouterConfigConflict
|
||||||
|
}
|
||||||
|
|
||||||
vrs, found := r.RouterByDomain[domain]
|
vrs, found := r.RouterByDomain[domain]
|
||||||
if !found {
|
if !found {
|
||||||
vrs = make([]*VhostRouter, 0, 1)
|
vrs = make([]*VhostRouter, 0, 1)
|
||||||
@@ -42,6 +51,7 @@ func (r *VhostRouters) Add(domain, location string, payload interface{}) {
|
|||||||
|
|
||||||
sort.Sort(sort.Reverse(ByLocation(vrs)))
|
sort.Sort(sort.Reverse(ByLocation(vrs)))
|
||||||
r.RouterByDomain[domain] = vrs
|
r.RouterByDomain[domain] = vrs
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *VhostRouters) Del(domain, location string) {
|
func (r *VhostRouters) Del(domain, location string) {
|
||||||
@@ -80,10 +90,7 @@ func (r *VhostRouters) Get(host, path string) (vr *VhostRouter, exist bool) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *VhostRouters) Exist(host, path string) (vr *VhostRouter, exist bool) {
|
func (r *VhostRouters) exist(host, path string) (vr *VhostRouter, exist bool) {
|
||||||
r.mutex.RLock()
|
|
||||||
defer r.mutex.RUnlock()
|
|
||||||
|
|
||||||
vrs, found := r.RouterByDomain[host]
|
vrs, found := r.RouterByDomain[host]
|
||||||
if !found {
|
if !found {
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ package vhost
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/fatedier/frp/utils/log"
|
"github.com/fatedier/frp/utils/log"
|
||||||
@@ -35,7 +34,6 @@ type VhostMuxer struct {
|
|||||||
authFunc httpAuthFunc
|
authFunc httpAuthFunc
|
||||||
rewriteFunc hostRewriteFunc
|
rewriteFunc hostRewriteFunc
|
||||||
registryRouter *VhostRouters
|
registryRouter *VhostRouters
|
||||||
mutex sync.RWMutex
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewVhostMuxer(listener frpNet.Listener, vhostFunc muxFunc, authFunc httpAuthFunc, rewriteFunc hostRewriteFunc, timeout time.Duration) (mux *VhostMuxer, err error) {
|
func NewVhostMuxer(listener frpNet.Listener, vhostFunc muxFunc, authFunc httpAuthFunc, rewriteFunc hostRewriteFunc, timeout time.Duration) (mux *VhostMuxer, err error) {
|
||||||
@@ -51,8 +49,9 @@ func NewVhostMuxer(listener frpNet.Listener, vhostFunc muxFunc, authFunc httpAut
|
|||||||
return mux, nil
|
return mux, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type CreateConnFunc func() (frpNet.Conn, error)
|
type CreateConnFunc func(remoteAddr string) (frpNet.Conn, error)
|
||||||
|
|
||||||
|
// VhostRouteConfig is the params used to match HTTP requests
|
||||||
type VhostRouteConfig struct {
|
type VhostRouteConfig struct {
|
||||||
Domain string
|
Domain string
|
||||||
Location string
|
Location string
|
||||||
@@ -67,14 +66,6 @@ type VhostRouteConfig struct {
|
|||||||
// listen for a new domain name, if rewriteHost is not empty and rewriteFunc is not nil
|
// listen for a new domain name, if rewriteHost is not empty and rewriteFunc is not nil
|
||||||
// then rewrite the host header to rewriteHost
|
// then rewrite the host header to rewriteHost
|
||||||
func (v *VhostMuxer) Listen(cfg *VhostRouteConfig) (l *Listener, err error) {
|
func (v *VhostMuxer) Listen(cfg *VhostRouteConfig) (l *Listener, err error) {
|
||||||
v.mutex.Lock()
|
|
||||||
defer v.mutex.Unlock()
|
|
||||||
|
|
||||||
_, ok := v.registryRouter.Exist(cfg.Domain, cfg.Location)
|
|
||||||
if ok {
|
|
||||||
return nil, fmt.Errorf("hostname [%s] location [%s] is already registered", cfg.Domain, cfg.Location)
|
|
||||||
}
|
|
||||||
|
|
||||||
l = &Listener{
|
l = &Listener{
|
||||||
name: cfg.Domain,
|
name: cfg.Domain,
|
||||||
location: cfg.Location,
|
location: cfg.Location,
|
||||||
@@ -85,14 +76,14 @@ func (v *VhostMuxer) Listen(cfg *VhostRouteConfig) (l *Listener, err error) {
|
|||||||
accept: make(chan frpNet.Conn),
|
accept: make(chan frpNet.Conn),
|
||||||
Logger: log.NewPrefixLogger(""),
|
Logger: log.NewPrefixLogger(""),
|
||||||
}
|
}
|
||||||
v.registryRouter.Add(cfg.Domain, cfg.Location, l)
|
err = v.registryRouter.Add(cfg.Domain, cfg.Location, l)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
return l, nil
|
return l, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *VhostMuxer) getListener(name, path string) (l *Listener, exist bool) {
|
func (v *VhostMuxer) getListener(name, path string) (l *Listener, exist bool) {
|
||||||
v.mutex.RLock()
|
|
||||||
defer v.mutex.RUnlock()
|
|
||||||
|
|
||||||
// first we check the full hostname
|
// first we check the full hostname
|
||||||
// if not exist, then check the wildcard_domain such as *.example.com
|
// if not exist, then check the wildcard_domain such as *.example.com
|
||||||
vr, found := v.registryRouter.Get(name, path)
|
vr, found := v.registryRouter.Get(name, path)
|
||||||
@@ -102,17 +93,24 @@ func (v *VhostMuxer) getListener(name, path string) (l *Listener, exist bool) {
|
|||||||
|
|
||||||
domainSplit := strings.Split(name, ".")
|
domainSplit := strings.Split(name, ".")
|
||||||
if len(domainSplit) < 3 {
|
if len(domainSplit) < 3 {
|
||||||
return l, false
|
|
||||||
}
|
|
||||||
domainSplit[0] = "*"
|
|
||||||
name = strings.Join(domainSplit, ".")
|
|
||||||
|
|
||||||
vr, found = v.registryRouter.Get(name, path)
|
|
||||||
if !found {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
return vr.payload.(*Listener), true
|
for {
|
||||||
|
if len(domainSplit) < 3 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
domainSplit[0] = "*"
|
||||||
|
name = strings.Join(domainSplit, ".")
|
||||||
|
|
||||||
|
vr, found = v.registryRouter.Get(name, path)
|
||||||
|
if found {
|
||||||
|
return vr.payload.(*Listener), true
|
||||||
|
}
|
||||||
|
domainSplit = domainSplit[1:]
|
||||||
|
}
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *VhostMuxer) run() {
|
func (v *VhostMuxer) run() {
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user