mirror of
https://github.com/fatedier/frp.git
synced 2026-03-08 10:59:11 +08:00
Compare commits
16 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
76a1efccd9 | ||
|
|
980f084ad1 | ||
|
|
0c35863d97 | ||
|
|
184a0ff9ab | ||
|
|
8e25f13201 | ||
|
|
b5aee82ca9 | ||
|
|
0a2384a283 | ||
|
|
78b8bb7bc6 | ||
|
|
8fcd4f4a95 | ||
|
|
976fd81d4d | ||
|
|
52d5c9e25b | ||
|
|
fa89671452 | ||
|
|
3621aad1c1 | ||
|
|
3bf1eb8565 | ||
|
|
a821db3f45 | ||
|
|
ecb6ed9258 |
@@ -2,7 +2,7 @@ version: 2
|
||||
jobs:
|
||||
test1:
|
||||
docker:
|
||||
- image: circleci/golang:1.15-node
|
||||
- image: circleci/golang:1.16-node
|
||||
working_directory: /go/src/github.com/fatedier/frp
|
||||
steps:
|
||||
- checkout
|
||||
@@ -10,7 +10,7 @@ jobs:
|
||||
- run: make alltest
|
||||
test2:
|
||||
docker:
|
||||
- image: circleci/golang:1.14-node
|
||||
- image: circleci/golang:1.15-node
|
||||
working_directory: /go/src/github.com/fatedier/frp
|
||||
steps:
|
||||
- checkout
|
||||
|
||||
2
.github/workflows/goreleaser.yml
vendored
2
.github/workflows/goreleaser.yml
vendored
@@ -15,7 +15,7 @@ jobs:
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: 1.15
|
||||
go-version: 1.16
|
||||
|
||||
- name: Make All
|
||||
run: |
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -30,6 +30,7 @@ release/
|
||||
test/bin/
|
||||
vendor/
|
||||
dist/
|
||||
.idea/
|
||||
|
||||
# Cache
|
||||
*.swp
|
||||
|
||||
@@ -2,34 +2,24 @@ export PATH := $(GOPATH)/bin:$(PATH)
|
||||
export GO111MODULE=on
|
||||
LDFLAGS := -s -w
|
||||
|
||||
os-archs=darwin:amd64 darwin:arm64 freebsd:386 freebsd:amd64 linux:386 linux:amd64 linux:arm windows:386 windows:amd64 linux:mips64 linux:mips64le linux:mips:softfloat linux:mipsle:softfloat
|
||||
|
||||
all: build
|
||||
|
||||
build: app
|
||||
|
||||
app:
|
||||
env CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -trimpath -ldflags "$(LDFLAGS)" -o ./release/frpc_darwin_amd64 ./cmd/frpc
|
||||
env CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -trimpath -ldflags "$(LDFLAGS)" -o ./release/frps_darwin_amd64 ./cmd/frps
|
||||
env CGO_ENABLED=0 GOOS=freebsd GOARCH=386 go build -trimpath -ldflags "$(LDFLAGS)" -o ./release/frpc_freebsd_386 ./cmd/frpc
|
||||
env CGO_ENABLED=0 GOOS=freebsd GOARCH=386 go build -trimpath -ldflags "$(LDFLAGS)" -o ./release/frps_freebsd_386 ./cmd/frps
|
||||
env CGO_ENABLED=0 GOOS=freebsd GOARCH=amd64 go build -trimpath -ldflags "$(LDFLAGS)" -o ./release/frpc_freebsd_amd64 ./cmd/frpc
|
||||
env CGO_ENABLED=0 GOOS=freebsd GOARCH=amd64 go build -trimpath -ldflags "$(LDFLAGS)" -o ./release/frps_freebsd_amd64 ./cmd/frps
|
||||
env CGO_ENABLED=0 GOOS=linux GOARCH=386 go build -trimpath -ldflags "$(LDFLAGS)" -o ./release/frpc_linux_386 ./cmd/frpc
|
||||
env CGO_ENABLED=0 GOOS=linux GOARCH=386 go build -trimpath -ldflags "$(LDFLAGS)" -o ./release/frps_linux_386 ./cmd/frps
|
||||
env CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -trimpath -ldflags "$(LDFLAGS)" -o ./release/frpc_linux_amd64 ./cmd/frpc
|
||||
env CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -trimpath -ldflags "$(LDFLAGS)" -o ./release/frps_linux_amd64 ./cmd/frps
|
||||
env CGO_ENABLED=0 GOOS=linux GOARCH=arm go build -trimpath -ldflags "$(LDFLAGS)" -o ./release/frpc_linux_arm ./cmd/frpc
|
||||
env CGO_ENABLED=0 GOOS=linux GOARCH=arm go build -trimpath -ldflags "$(LDFLAGS)" -o ./release/frps_linux_arm ./cmd/frps
|
||||
env CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -trimpath -ldflags "$(LDFLAGS)" -o ./release/frpc_linux_arm64 ./cmd/frpc
|
||||
env CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -trimpath -ldflags "$(LDFLAGS)" -o ./release/frps_linux_arm64 ./cmd/frps
|
||||
env CGO_ENABLED=0 GOOS=windows GOARCH=386 go build -trimpath -ldflags "$(LDFLAGS)" -o ./release/frpc_windows_386.exe ./cmd/frpc
|
||||
env CGO_ENABLED=0 GOOS=windows GOARCH=386 go build -trimpath -ldflags "$(LDFLAGS)" -o ./release/frps_windows_386.exe ./cmd/frps
|
||||
env CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -trimpath -ldflags "$(LDFLAGS)" -o ./release/frpc_windows_amd64.exe ./cmd/frpc
|
||||
env CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -trimpath -ldflags "$(LDFLAGS)" -o ./release/frps_windows_amd64.exe ./cmd/frps
|
||||
env CGO_ENABLED=0 GOOS=linux GOARCH=mips64 go build -trimpath -ldflags "$(LDFLAGS)" -o ./release/frpc_linux_mips64 ./cmd/frpc
|
||||
env CGO_ENABLED=0 GOOS=linux GOARCH=mips64 go build -trimpath -ldflags "$(LDFLAGS)" -o ./release/frps_linux_mips64 ./cmd/frps
|
||||
env CGO_ENABLED=0 GOOS=linux GOARCH=mips64le go build -trimpath -ldflags "$(LDFLAGS)" -o ./release/frpc_linux_mips64le ./cmd/frpc
|
||||
env CGO_ENABLED=0 GOOS=linux GOARCH=mips64le go build -trimpath -ldflags "$(LDFLAGS)" -o ./release/frps_linux_mips64le ./cmd/frps
|
||||
env CGO_ENABLED=0 GOOS=linux GOARCH=mips GOMIPS=softfloat go build -trimpath -ldflags "$(LDFLAGS)" -o ./release/frpc_linux_mips ./cmd/frpc
|
||||
env CGO_ENABLED=0 GOOS=linux GOARCH=mips GOMIPS=softfloat go build -trimpath -ldflags "$(LDFLAGS)" -o ./release/frps_linux_mips ./cmd/frps
|
||||
env CGO_ENABLED=0 GOOS=linux GOARCH=mipsle GOMIPS=softfloat go build -trimpath -ldflags "$(LDFLAGS)" -o ./release/frpc_linux_mipsle ./cmd/frpc
|
||||
env CGO_ENABLED=0 GOOS=linux GOARCH=mipsle GOMIPS=softfloat go build -trimpath -ldflags "$(LDFLAGS)" -o ./release/frps_linux_mipsle ./cmd/frps
|
||||
@$(foreach n, $(os-archs),\
|
||||
os=$(shell echo "$(n)" | cut -d : -f 1);\
|
||||
arch=$(shell echo "$(n)" | cut -d : -f 2);\
|
||||
gomips=$(shell echo "$(n)" | cut -d : -f 3);\
|
||||
target_suffix=$${os}_$${arch};\
|
||||
echo "Build $${os}-$${arch}...";\
|
||||
env CGO_ENABLED=0 GOOS=$${os} GOARCH=$${arch} GOMIPS=$${gomips} go build -trimpath -ldflags "$(LDFLAGS)" -o ./release/frpc_$${target_suffix} ./cmd/frpc;\
|
||||
env CGO_ENABLED=0 GOOS=$${os} GOARCH=$${arch} GOMIPS=$${gomips} go build -trimpath -ldflags "$(LDFLAGS)" -o ./release/frps_$${target_suffix} ./cmd/frps;\
|
||||
echo "Build $${os}-$${arch} done";\
|
||||
)
|
||||
@mv ./release/frpc_windows_386 ./release/frpc_windows_386.exe
|
||||
@mv ./release/frps_windows_386 ./release/frps_windows_386.exe
|
||||
@mv ./release/frpc_windows_amd64 ./release/frpc_windows_amd64.exe
|
||||
@mv ./release/frps_windows_amd64 ./release/frps_windows_amd64.exe
|
||||
|
||||
106
README.md
106
README.md
@@ -1,3 +1,4 @@
|
||||
|
||||
# frp
|
||||
|
||||
[](https://circleci.com/gh/fatedier/frp)
|
||||
@@ -67,7 +68,7 @@ frp also has a P2P connect mode.
|
||||
* [Donation](#donation)
|
||||
* [AliPay](#alipay)
|
||||
* [Wechat Pay](#wechat-pay)
|
||||
* [Paypal](#paypal)
|
||||
* [PayPal](#paypal)
|
||||
|
||||
<!-- vim-markdown-toc -->
|
||||
|
||||
@@ -257,7 +258,9 @@ Configure `frps` same as above.
|
||||
|
||||
2. Visit `http://x.x.x.x:6000/static/` from your browser and specify correct user and password to view files in `/tmp/files` on the `frpc` machine.
|
||||
|
||||
### Enable HTTPS for local HTTP service
|
||||
### Enable HTTPS for local HTTP(S) service
|
||||
|
||||
You may substitute `https2https` for the plugin, and point the `plugin_local_addr` to a HTTPS endpoint.
|
||||
|
||||
1. Start `frpc` with configuration:
|
||||
|
||||
@@ -515,11 +518,100 @@ use_compression = true
|
||||
|
||||
frp supports the TLS protocol between `frpc` and `frps` since v0.25.0.
|
||||
|
||||
Config `tls_enable = true` in the `[common]` section to `frpc.ini` to enable this feature.
|
||||
|
||||
For port multiplexing, frp sends a first byte `0x17` to dial a TLS connection.
|
||||
|
||||
To enforce `frps` to only accept TLS connections - configure `tls_only = true` in the `[common]` section in `frps.ini`.
|
||||
Configure `tls_enable = true` in the `[common]` section to `frpc.ini` to enable this feature.
|
||||
|
||||
To **enforce** `frps` to only accept TLS connections - configure `tls_only = true` in the `[common]` section in `frps.ini`. **This is optional.**
|
||||
|
||||
**`frpc` TLS settings (under the `[common]` section):**
|
||||
```ini
|
||||
tls_enable = true
|
||||
tls_cert_file = certificate.crt
|
||||
tls_key_file = certificate.key
|
||||
tls_trusted_ca_file = ca.crt
|
||||
```
|
||||
|
||||
**`frps` TLS settings (under the `[common]` section):**
|
||||
```ini
|
||||
tls_only = true
|
||||
tls_enable = true
|
||||
tls_cert_file = certificate.crt
|
||||
tls_key_file = certificate.key
|
||||
tls_trusted_ca_file = ca.crt
|
||||
```
|
||||
|
||||
You will need **a root CA cert** and **at least one SSL/TLS certificate**. It **can** be self-signed or regular (such as Let's Encrypt or another SSL/TLS certificate provider).
|
||||
|
||||
If you using `frp` via IP address and not hostname, make sure to set the appropriate IP address in the Subject Alternative Name (SAN) area when generating SSL/TLS Certificates.
|
||||
|
||||
Given an example:
|
||||
|
||||
* Prepare openssl config file. It exists at `/etc/pki/tls/openssl.cnf` in Linux System and `/System/Library/OpenSSL/openssl.cnf` in MacOS, and you can copy it to current path, like `cp /etc/pki/tls/openssl.cnf ./my-openssl.cnf`. If not, you can build it by yourself, like:
|
||||
```
|
||||
cat > my-openssl.cnf << EOF
|
||||
[ ca ]
|
||||
default_ca = CA_default
|
||||
[ CA_default ]
|
||||
x509_extensions = usr_cert
|
||||
[ req ]
|
||||
default_bits = 2048
|
||||
default_md = sha256
|
||||
default_keyfile = privkey.pem
|
||||
distinguished_name = req_distinguished_name
|
||||
attributes = req_attributes
|
||||
x509_extensions = v3_ca
|
||||
string_mask = utf8only
|
||||
[ req_distinguished_name ]
|
||||
[ req_attributes ]
|
||||
[ usr_cert ]
|
||||
basicConstraints = CA:FALSE
|
||||
nsComment = "OpenSSL Generated Certificate"
|
||||
subjectKeyIdentifier = hash
|
||||
authorityKeyIdentifier = keyid,issuer
|
||||
[ v3_ca ]
|
||||
subjectKeyIdentifier = hash
|
||||
authorityKeyIdentifier = keyid:always,issuer
|
||||
basicConstraints = CA:true
|
||||
EOF
|
||||
```
|
||||
|
||||
* build ca certificates:
|
||||
```
|
||||
openssl genrsa -out ca.key 2048
|
||||
openssl req -x509 -new -nodes -key ca.key -subj "/CN=example.ca.com" -days 5000 -out ca.crt
|
||||
```
|
||||
|
||||
* build frps certificates:
|
||||
```
|
||||
openssl genrsa -out server.key 2048
|
||||
|
||||
openssl req -new -sha256 -key server.key \
|
||||
-subj "/C=XX/ST=DEFAULT/L=DEFAULT/O=DEFAULT/CN=server.com" \
|
||||
-reqexts SAN \
|
||||
-config <(cat my-openssl.cnf <(printf "\n[SAN]\nsubjectAltName=DNS:localhost,IP:127.0.0.1,DNS:example.server.com")) \
|
||||
-out server.csr
|
||||
|
||||
openssl x509 -req -days 365 \
|
||||
-in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial \
|
||||
-extfile <(printf "subjectAltName=DNS:localhost,IP:127.0.0.1,DNS:example.server.com") \
|
||||
-out server.crt
|
||||
```
|
||||
|
||||
* build frpc certificates:
|
||||
```
|
||||
openssl genrsa -out client.key 2048
|
||||
openssl req -new -sha256 -key client.key \
|
||||
-subj "/C=XX/ST=DEFAULT/L=DEFAULT/O=DEFAULT/CN=client.com" \
|
||||
-reqexts SAN \
|
||||
-config <(cat my-openssl.cnf <(printf "\n[SAN]\nsubjectAltName=DNS:client.com,DNS:example.client.com")) \
|
||||
-out client.csr
|
||||
|
||||
openssl x509 -req -days 365 \
|
||||
-in client.csr -CA ca.crt -CAkey ca.key -CAcreateserial \
|
||||
-extfile <(printf "subjectAltName=DNS:client.com,DNS:example.client.com") \
|
||||
-out client.crt
|
||||
```
|
||||
|
||||
### Hot-Reloading frpc configuration
|
||||
|
||||
@@ -967,6 +1059,6 @@ frp QQ group: 606194980
|
||||
|
||||

|
||||
|
||||
### Paypal
|
||||
### PayPal
|
||||
|
||||
Donate money by [paypal](https://www.paypal.me/fatedier) to my account **fatedier@gmail.com**.
|
||||
Donate money by [PayPal](https://www.paypal.me/fatedier) to my account **fatedier@gmail.com**.
|
||||
|
||||
10
Release.md
10
Release.md
@@ -1,8 +1,12 @@
|
||||
### New
|
||||
|
||||
* Server Plugin supports HTTPS.
|
||||
* New plugin `https2https`.
|
||||
* frpc supports `tls_server_name` to override the default value from `server_addr`.
|
||||
|
||||
### Improvement
|
||||
|
||||
* Increase reconnect frequency if it occurs an network error between frpc and frps.
|
||||
|
||||
### Fix
|
||||
|
||||
* Fix IPv6 address parse problem.
|
||||
* HTTP type proxy can't handle websocket protocol due to error `Connection` header value.
|
||||
* Fix panic issue about xtcp.
|
||||
|
||||
Binary file not shown.
@@ -1 +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>
|
||||
<!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?f30e0e5ff7dbde4611e0"></script><script type="text/javascript" src="vendor.js?a82aed5fb0b844cbdb29"></script></body> </html>
|
||||
@@ -1 +1 @@
|
||||
!function(e){function n(r){if(t[r])return t[r].exports;var o=t[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}var r=window.webpackJsonp;window.webpackJsonp=function(t,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}}([]);
|
||||
!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:"a82aed5fb0b844cbdb29"}[e];var a=setTimeout(r,12e4);return i.onerror=i.onload=r,u.appendChild(i),c},n.m=e,n.c=t,n.i=function(e){return e},n.d=function(e,r,t){n.o(e,r)||Object.defineProperty(e,r,{configurable:!1,enumerable:!0,get:t})},n.n=function(e){var r=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(r,"a",r),r},n.o=function(e,n){return Object.prototype.hasOwnProperty.call(e,n)},n.p="",n.oe=function(e){throw console.error(e),e}}([]);
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
BIN
assets/frps/static/535877f50039c0cb49a6196a5b7517cd.woff
Normal file
BIN
assets/frps/static/535877f50039c0cb49a6196a5b7517cd.woff
Normal file
Binary file not shown.
BIN
assets/frps/static/732389ded34cb9c52dd88271f1345af9.ttf
Normal file
BIN
assets/frps/static/732389ded34cb9c52dd88271f1345af9.ttf
Normal file
Binary file not shown.
@@ -1 +0,0 @@
|
||||
.el-form-item span{margin-left:15px}.demo-table-expand{font-size:0}.demo-table-expand label{width:90px;color:#99a9bf}.demo-table-expand .el-form-item{margin-right:0;margin-bottom:0;width:50%}body{background-color:#fafafa;margin:0;font-family:-apple-system,BlinkMacSystemFont,Helvetica Neue,sans-serif}header{width:100%;height:60px}.header-color{background:#58b7ff}#content{margin-top:20px;padding-right:40px}.brand{color:#fff;background-color:transparent;margin-left:20px;float:left;line-height:25px;font-size:25px;padding:15px 15px;height:30px;text-decoration:none}.source{border:1px solid #eaeefb;border-radius:4px;transition:.2s;padding:24px}.server_info{margin-left:40px;font-size:0}.server_info label{width:150px;color:#99a9bf}.server_info .el-form-item{margin-right:0;margin-bottom:0;width:100%}
|
||||
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
|
||||
<!DOCTYPE html><html lang="en"><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1"><link rel="icon" href="favicon.ico"><title>frps-dashboard</title><link href="css/app.808290ae.css" rel="preload" as="style"><link href="css/chunk-vendors.84bb20f7.css" rel="preload" as="style"><link href="js/app.bb942a48.js" rel="preload" as="script"><link href="js/chunk-vendors.4421b07d.js" rel="preload" as="script"><link href="css/chunk-vendors.84bb20f7.css" rel="stylesheet"><link href="css/app.808290ae.css" rel="stylesheet"></head><body><noscript><strong>We're sorry but frps-dashboard doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div><script src="js/chunk-vendors.4421b07d.js"></script><script src="js/app.bb942a48.js"></script></body></html>
|
||||
<!doctype html> <html lang=en> <head> <meta charset=utf-8> <title>frps dashboard</title> <link rel="shortcut icon" href="favicon.ico"></head> <body> <div id=app></div> <script type="text/javascript" src="manifest.js?b8b55d8156200869417b"></script><script type="text/javascript" src="vendor.js?3e078a9d741093b909de"></script></body> </html>
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
1
assets/frps/static/manifest.js
Normal file
1
assets/frps/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,u,c){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 u)Object.prototype.hasOwnProperty.call(u,i)&&(e[i]=u[i]);for(r&&r(t,u,c);s.length;)s.shift()();if(c)for(l=0;l<c.length;l++)f=n(n.s=c[l]);return f};var t={},o={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 u=new Promise(function(n,r){t=o[e]=[n,r]});t[2]=u;var c=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:"3e078a9d741093b909de"}[e];var a=setTimeout(r,12e4);return i.onerror=i.onload=r,c.appendChild(i),u},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/frps/static/vendor.js
Normal file
1
assets/frps/static/vendor.js
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -15,7 +15,6 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"time"
|
||||
@@ -31,7 +30,7 @@ var (
|
||||
httpServerWriteTimeout = 10 * time.Second
|
||||
)
|
||||
|
||||
func (svr *Service) RunAdminServer(addr string, port int) (err error) {
|
||||
func (svr *Service) RunAdminServer(address string) (err error) {
|
||||
// url router
|
||||
router := mux.NewRouter()
|
||||
|
||||
@@ -51,7 +50,6 @@ func (svr *Service) RunAdminServer(addr string, port int) (err error) {
|
||||
http.Redirect(w, r, "/static/", http.StatusMovedPermanently)
|
||||
})
|
||||
|
||||
address := fmt.Sprintf("%s:%d", addr, port)
|
||||
server := &http.Server{
|
||||
Addr: address,
|
||||
Handler: router,
|
||||
|
||||
@@ -62,7 +62,7 @@ func (svr *Service) apiReload(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
pxyCfgs, visitorCfgs, err := config.LoadAllConfFromIni(svr.cfg.User, content, newCommonCfg.Start)
|
||||
pxyCfgs, visitorCfgs, err := config.LoadAllProxyConfsFromIni(svr.cfg.User, content, newCommonCfg.Start)
|
||||
if err != nil {
|
||||
res.Code = 400
|
||||
res.Msg = err.Error()
|
||||
@@ -243,7 +243,7 @@ func (svr *Service) apiGetConfig(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
rows := strings.Split(content, "\n")
|
||||
rows := strings.Split(string(content), "\n")
|
||||
newRows := make([]string, 0, len(rows))
|
||||
for _, row := range rows {
|
||||
row = strings.TrimSpace(row)
|
||||
|
||||
@@ -209,13 +209,17 @@ func (ctl *Control) connectServer() (conn net.Conn, err error) {
|
||||
conn = stream
|
||||
} else {
|
||||
var tlsConfig *tls.Config
|
||||
sn := ctl.clientCfg.TLSServerName
|
||||
if sn == "" {
|
||||
sn = ctl.clientCfg.ServerAddr
|
||||
}
|
||||
|
||||
if ctl.clientCfg.TLSEnable {
|
||||
tlsConfig, err = transport.NewClientTLSConfig(
|
||||
ctl.clientCfg.TLSCertFile,
|
||||
ctl.clientCfg.TLSKeyFile,
|
||||
ctl.clientCfg.TLSTrustedCaFile,
|
||||
ctl.clientCfg.ServerAddr)
|
||||
sn)
|
||||
|
||||
if err != nil {
|
||||
xl.Warn("fail to build tls configuration when connecting to server, err: %v", err)
|
||||
|
||||
@@ -148,7 +148,7 @@ func (pxy *TCPProxy) Close() {
|
||||
}
|
||||
|
||||
func (pxy *TCPProxy) InWorkConn(conn net.Conn, m *msg.StartWorkConn) {
|
||||
HandleTCPWorkConnection(pxy.ctx, &pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, pxy.limiter,
|
||||
HandleTCPWorkConnection(pxy.ctx, &pxy.cfg.LocalSvrConf, pxy.proxyPlugin, pxy.cfg.GetBaseInfo(), pxy.limiter,
|
||||
conn, []byte(pxy.clientCfg.Token), m)
|
||||
}
|
||||
|
||||
@@ -177,7 +177,7 @@ func (pxy *TCPMuxProxy) Close() {
|
||||
}
|
||||
|
||||
func (pxy *TCPMuxProxy) InWorkConn(conn net.Conn, m *msg.StartWorkConn) {
|
||||
HandleTCPWorkConnection(pxy.ctx, &pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, pxy.limiter,
|
||||
HandleTCPWorkConnection(pxy.ctx, &pxy.cfg.LocalSvrConf, pxy.proxyPlugin, pxy.cfg.GetBaseInfo(), pxy.limiter,
|
||||
conn, []byte(pxy.clientCfg.Token), m)
|
||||
}
|
||||
|
||||
@@ -206,7 +206,7 @@ func (pxy *HTTPProxy) Close() {
|
||||
}
|
||||
|
||||
func (pxy *HTTPProxy) InWorkConn(conn net.Conn, m *msg.StartWorkConn) {
|
||||
HandleTCPWorkConnection(pxy.ctx, &pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, pxy.limiter,
|
||||
HandleTCPWorkConnection(pxy.ctx, &pxy.cfg.LocalSvrConf, pxy.proxyPlugin, pxy.cfg.GetBaseInfo(), pxy.limiter,
|
||||
conn, []byte(pxy.clientCfg.Token), m)
|
||||
}
|
||||
|
||||
@@ -235,7 +235,7 @@ func (pxy *HTTPSProxy) Close() {
|
||||
}
|
||||
|
||||
func (pxy *HTTPSProxy) InWorkConn(conn net.Conn, m *msg.StartWorkConn) {
|
||||
HandleTCPWorkConnection(pxy.ctx, &pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, pxy.limiter,
|
||||
HandleTCPWorkConnection(pxy.ctx, &pxy.cfg.LocalSvrConf, pxy.proxyPlugin, pxy.cfg.GetBaseInfo(), pxy.limiter,
|
||||
conn, []byte(pxy.clientCfg.Token), m)
|
||||
}
|
||||
|
||||
@@ -264,7 +264,7 @@ func (pxy *STCPProxy) Close() {
|
||||
}
|
||||
|
||||
func (pxy *STCPProxy) InWorkConn(conn net.Conn, m *msg.StartWorkConn) {
|
||||
HandleTCPWorkConnection(pxy.ctx, &pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, pxy.limiter,
|
||||
HandleTCPWorkConnection(pxy.ctx, &pxy.cfg.LocalSvrConf, pxy.proxyPlugin, pxy.cfg.GetBaseInfo(), pxy.limiter,
|
||||
conn, []byte(pxy.clientCfg.Token), m)
|
||||
}
|
||||
|
||||
@@ -309,6 +309,10 @@ func (pxy *XTCPProxy) InWorkConn(conn net.Conn, m *msg.StartWorkConn) {
|
||||
raddr, _ := net.ResolveUDPAddr("udp",
|
||||
fmt.Sprintf("%s:%d", pxy.clientCfg.ServerAddr, pxy.serverUDPPort))
|
||||
clientConn, err := net.DialUDP("udp", nil, raddr)
|
||||
if err != nil {
|
||||
xl.Error("dial server udp addr error: %v", err)
|
||||
return
|
||||
}
|
||||
defer clientConn.Close()
|
||||
|
||||
err = msg.WriteMsg(clientConn, natHoleClientMsg)
|
||||
@@ -410,7 +414,7 @@ func (pxy *XTCPProxy) InWorkConn(conn net.Conn, m *msg.StartWorkConn) {
|
||||
return
|
||||
}
|
||||
|
||||
HandleTCPWorkConnection(pxy.ctx, &pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, pxy.limiter,
|
||||
HandleTCPWorkConnection(pxy.ctx, &pxy.cfg.LocalSvrConf, pxy.proxyPlugin, pxy.cfg.GetBaseInfo(), pxy.limiter,
|
||||
muxConn, []byte(pxy.cfg.Sk), m)
|
||||
}
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ package client
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
@@ -128,7 +129,8 @@ func (svr *Service) Run() error {
|
||||
return fmt.Errorf("Load assets error: %v", err)
|
||||
}
|
||||
|
||||
err = svr.RunAdminServer(svr.cfg.AdminAddr, svr.cfg.AdminPort)
|
||||
address := net.JoinHostPort(svr.cfg.AdminAddr, strconv.Itoa(svr.cfg.AdminPort))
|
||||
err = svr.RunAdminServer(address)
|
||||
if err != nil {
|
||||
log.Warn("run admin server error: %v", err)
|
||||
}
|
||||
@@ -177,9 +179,16 @@ func (svr *Service) keepControllerWorking() {
|
||||
if err != nil {
|
||||
xl.Warn("reconnect to server error: %v", err)
|
||||
time.Sleep(delayTime)
|
||||
delayTime = delayTime * 2
|
||||
if delayTime > maxDelayTime {
|
||||
delayTime = maxDelayTime
|
||||
|
||||
opErr := &net.OpError{}
|
||||
// quick retry for dial error
|
||||
if errors.As(err, &opErr) && opErr.Op == "dial" {
|
||||
delayTime = 2 * time.Second
|
||||
} else {
|
||||
delayTime = delayTime * 2
|
||||
if delayTime > maxDelayTime {
|
||||
delayTime = maxDelayTime
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
@@ -206,11 +215,16 @@ func (svr *Service) login() (conn net.Conn, session *fmux.Session, err error) {
|
||||
xl := xlog.FromContextSafe(svr.ctx)
|
||||
var tlsConfig *tls.Config
|
||||
if svr.cfg.TLSEnable {
|
||||
sn := svr.cfg.TLSServerName
|
||||
if sn == "" {
|
||||
sn = svr.cfg.ServerAddr
|
||||
}
|
||||
|
||||
tlsConfig, err = transport.NewClientTLSConfig(
|
||||
svr.cfg.TLSCertFile,
|
||||
svr.cfg.TLSKeyFile,
|
||||
svr.cfg.TLSTrustedCaFile,
|
||||
svr.cfg.ServerAddr)
|
||||
sn)
|
||||
if err != nil {
|
||||
xl.Warn("fail to build tls configuration when service login, err: %v", err)
|
||||
return
|
||||
|
||||
@@ -47,7 +47,7 @@ var httpCmd = &cobra.Command{
|
||||
Use: "http",
|
||||
Short: "Run frpc with a single http proxy",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
clientCfg, err := parseClientCommonCfg(CfgFileTypeCmd, "")
|
||||
clientCfg, err := parseClientCommonCfg(CfgFileTypeCmd, nil)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
|
||||
@@ -43,7 +43,7 @@ var httpsCmd = &cobra.Command{
|
||||
Use: "https",
|
||||
Short: "Run frpc with a single https proxy",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
clientCfg, err := parseClientCommonCfg(CfgFileTypeCmd, "")
|
||||
clientCfg, err := parseClientCommonCfg(CfgFileTypeCmd, nil)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
|
||||
@@ -129,9 +129,9 @@ func handleSignal(svr *client.Service) {
|
||||
close(kcpDoneCh)
|
||||
}
|
||||
|
||||
func parseClientCommonCfg(fileType int, content string) (cfg config.ClientCommonConf, err error) {
|
||||
func parseClientCommonCfg(fileType int, source []byte) (cfg config.ClientCommonConf, err error) {
|
||||
if fileType == CfgFileTypeIni {
|
||||
cfg, err = parseClientCommonCfgFromIni(content)
|
||||
cfg, err = config.UnmarshalClientConfFromIni(source)
|
||||
} else if fileType == CfgFileTypeCmd {
|
||||
cfg, err = parseClientCommonCfgFromCmd()
|
||||
}
|
||||
@@ -146,14 +146,6 @@ func parseClientCommonCfg(fileType int, content string) (cfg config.ClientCommon
|
||||
return
|
||||
}
|
||||
|
||||
func parseClientCommonCfgFromIni(content string) (config.ClientCommonConf, error) {
|
||||
cfg, err := config.UnmarshalClientConfFromIni(content)
|
||||
if err != nil {
|
||||
return config.ClientCommonConf{}, err
|
||||
}
|
||||
return cfg, err
|
||||
}
|
||||
|
||||
func parseClientCommonCfgFromCmd() (cfg config.ClientCommonConf, err error) {
|
||||
cfg = config.GetDefaultClientConf()
|
||||
|
||||
@@ -191,7 +183,7 @@ func parseClientCommonCfgFromCmd() (cfg config.ClientCommonConf, err error) {
|
||||
}
|
||||
|
||||
func runClient(cfgFilePath string) (err error) {
|
||||
var content string
|
||||
var content []byte
|
||||
content, err = config.GetRenderedConfFromFile(cfgFilePath)
|
||||
if err != nil {
|
||||
return
|
||||
@@ -202,9 +194,9 @@ func runClient(cfgFilePath string) (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
pxyCfgs, visitorCfgs, err := config.LoadAllConfFromIni(cfg.User, content, cfg.Start)
|
||||
pxyCfgs, visitorCfgs, err := config.LoadAllProxyConfsFromIni(cfg.User, content, cfg.Start)
|
||||
if err != nil {
|
||||
return err
|
||||
return
|
||||
}
|
||||
|
||||
err = startService(cfg, pxyCfgs, visitorCfgs, cfgFilePath)
|
||||
|
||||
@@ -45,7 +45,7 @@ var stcpCmd = &cobra.Command{
|
||||
Use: "stcp",
|
||||
Short: "Run frpc with a single stcp proxy",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
clientCfg, err := parseClientCommonCfg(CfgFileTypeCmd, "")
|
||||
clientCfg, err := parseClientCommonCfg(CfgFileTypeCmd, nil)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
|
||||
@@ -45,7 +45,7 @@ var sudpCmd = &cobra.Command{
|
||||
Use: "sudp",
|
||||
Short: "Run frpc with a single sudp proxy",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
clientCfg, err := parseClientCommonCfg(CfgFileTypeCmd, "")
|
||||
clientCfg, err := parseClientCommonCfg(CfgFileTypeCmd, nil)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
|
||||
@@ -41,7 +41,7 @@ var tcpCmd = &cobra.Command{
|
||||
Use: "tcp",
|
||||
Short: "Run frpc with a single tcp proxy",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
clientCfg, err := parseClientCommonCfg(CfgFileTypeCmd, "")
|
||||
clientCfg, err := parseClientCommonCfg(CfgFileTypeCmd, nil)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
|
||||
@@ -44,7 +44,7 @@ var tcpMuxCmd = &cobra.Command{
|
||||
Use: "tcpmux",
|
||||
Short: "Run frpc with a single tcpmux proxy",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
clientCfg, err := parseClientCommonCfg(CfgFileTypeCmd, "")
|
||||
clientCfg, err := parseClientCommonCfg(CfgFileTypeCmd, nil)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
|
||||
@@ -41,7 +41,7 @@ var udpCmd = &cobra.Command{
|
||||
Use: "udp",
|
||||
Short: "Run frpc with a single udp proxy",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
clientCfg, err := parseClientCommonCfg(CfgFileTypeCmd, "")
|
||||
clientCfg, err := parseClientCommonCfg(CfgFileTypeCmd, nil)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
|
||||
@@ -45,7 +45,7 @@ var xtcpCmd = &cobra.Command{
|
||||
Use: "xtcp",
|
||||
Short: "Run frpc with a single xtcp proxy",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
clientCfg, err := parseClientCommonCfg(CfgFileTypeCmd, "")
|
||||
clientCfg, err := parseClientCommonCfg(CfgFileTypeCmd, nil)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
|
||||
@@ -106,7 +106,7 @@ var rootCmd = &cobra.Command{
|
||||
var err error
|
||||
if cfgFile != "" {
|
||||
log.Info("frps uses config file: %s", cfgFile)
|
||||
var content string
|
||||
var content []byte
|
||||
content, err = config.GetRenderedConfFromFile(cfgFile)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -114,7 +114,7 @@ var rootCmd = &cobra.Command{
|
||||
cfg, err = parseServerCommonCfg(CfgFileTypeIni, content)
|
||||
} else {
|
||||
log.Info("frps uses command line arguments for config")
|
||||
cfg, err = parseServerCommonCfg(CfgFileTypeCmd, "")
|
||||
cfg, err = parseServerCommonCfg(CfgFileTypeCmd, nil)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -135,9 +135,9 @@ func Execute() {
|
||||
}
|
||||
}
|
||||
|
||||
func parseServerCommonCfg(fileType int, content string) (cfg config.ServerCommonConf, err error) {
|
||||
func parseServerCommonCfg(fileType int, source []byte) (cfg config.ServerCommonConf, err error) {
|
||||
if fileType == CfgFileTypeIni {
|
||||
cfg, err = parseServerCommonCfgFromIni(content)
|
||||
cfg, err = config.UnmarshalServerConfFromIni(source)
|
||||
} else if fileType == CfgFileTypeCmd {
|
||||
cfg, err = parseServerCommonCfgFromCmd()
|
||||
}
|
||||
@@ -152,14 +152,6 @@ func parseServerCommonCfg(fileType int, content string) (cfg config.ServerCommon
|
||||
return
|
||||
}
|
||||
|
||||
func parseServerCommonCfgFromIni(content string) (config.ServerCommonConf, error) {
|
||||
cfg, err := config.UnmarshalServerConfFromIni(content)
|
||||
if err != nil {
|
||||
return config.ServerCommonConf{}, err
|
||||
}
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
func parseServerCommonCfgFromCmd() (cfg config.ServerCommonConf, err error) {
|
||||
cfg = config.GetDefaultServerConf()
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
[common]
|
||||
# A literal address or host name for IPv6 must be enclosed
|
||||
# in square brackets, as in "[::1]:80", "[ipv6-host]:http" or "[ipv6-host%zone]:80"
|
||||
# For single "server_addr" field, no need square brackets, like "server_addr = ::".
|
||||
server_addr = 0.0.0.0
|
||||
server_port = 7000
|
||||
|
||||
@@ -78,6 +79,7 @@ tls_enable = true
|
||||
# tls_cert_file = client.crt
|
||||
# tls_key_file = client.key
|
||||
# tls_trusted_ca_file = ca.crt
|
||||
# tls_server_name = example.com
|
||||
|
||||
# specify a dns server, so frpc will use this instead of default one
|
||||
# dns_server = 8.8.8.8
|
||||
@@ -246,6 +248,16 @@ plugin_key_path = ./server.key
|
||||
plugin_host_header_rewrite = 127.0.0.1
|
||||
plugin_header_X-From-Where = frp
|
||||
|
||||
[plugin_https2https]
|
||||
type = https
|
||||
custom_domains = test.yourdomain.com
|
||||
plugin = https2https
|
||||
plugin_local_addr = 127.0.0.1:443
|
||||
plugin_crt_path = ./server.crt
|
||||
plugin_key_path = ./server.key
|
||||
plugin_host_header_rewrite = 127.0.0.1
|
||||
plugin_header_X-From-Where = frp
|
||||
|
||||
[plugin_http2https]
|
||||
type = http
|
||||
custom_domains = test.yourdomain.com
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
[common]
|
||||
# A literal address or host name for IPv6 must be enclosed
|
||||
# in square brackets, as in "[::1]:80", "[ipv6-host]:http" or "[ipv6-host%zone]:80"
|
||||
# For single "bind_addr" field, no need square brackets, like "bind_addr = ::".
|
||||
bind_addr = 0.0.0.0
|
||||
bind_port = 7000
|
||||
|
||||
|
||||
5
go.mod
5
go.mod
@@ -1,6 +1,6 @@
|
||||
module github.com/fatedier/frp
|
||||
|
||||
go 1.15
|
||||
go 1.16
|
||||
|
||||
require (
|
||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5
|
||||
@@ -23,17 +23,18 @@ require (
|
||||
github.com/prometheus/client_golang v1.4.1
|
||||
github.com/rakyll/statik v0.1.1
|
||||
github.com/rodaine/table v1.0.0
|
||||
github.com/smartystreets/goconvey v1.6.4 // indirect
|
||||
github.com/spf13/cobra v0.0.3
|
||||
github.com/stretchr/testify v1.4.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-20200520004742-59133d7f0dd7
|
||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d
|
||||
golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980 // indirect
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0
|
||||
gopkg.in/ini.v1 v1.62.0
|
||||
gopkg.in/square/go-jose.v2 v2.4.1 // indirect
|
||||
k8s.io/apimachinery v0.18.3
|
||||
)
|
||||
|
||||
18
go.sum
18
go.sum
@@ -51,7 +51,6 @@ github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4er
|
||||
github.com/golang/protobuf v0.0.0-20161109072736-4bd1920723d7/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||
@@ -72,6 +71,8 @@ github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
|
||||
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY=
|
||||
github.com/googleapis/gnostic v0.1.0/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
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=
|
||||
@@ -85,6 +86,8 @@ github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANyt
|
||||
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
|
||||
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
||||
github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
@@ -154,6 +157,10 @@ github.com/rodaine/table v1.0.0 h1:UaCJG5Axc/cNXVGXqnCrffm1KxP0OfYLe1HuJLf5sFY=
|
||||
github.com/rodaine/table v1.0.0/go.mod h1:YAUzwPOji0DUJNEvggdxyQcUAl4g3hDRcFlyjnnR51I=
|
||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||
github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
|
||||
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
||||
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 v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||
@@ -171,8 +178,6 @@ github.com/templexxx/xor v0.0.0-20170926022130-0af8e873c554 h1:pexgSe+JCFuxG+uoM
|
||||
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-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
@@ -185,6 +190,7 @@ golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73r
|
||||
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190228165749-92fc7df08ae7/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7 h1:AeiKBIuRw3UomYXSbLy0Mc2dDLfdtbT/IVn4keq83P0=
|
||||
@@ -205,10 +211,8 @@ golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191022100944-742c48ecaeb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82 h1:ywK/j/KkyTHcdyYSZNXGjMwgmDSfjglYZ3vStQ/gSCU=
|
||||
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299 h1:DYfZAGf2WMFjMxbgTjaC+2HC7NkNAQs+6Q8b9WEB/F4=
|
||||
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980 h1:OjiUf46hAmXblsZdnoSXsEUSKU8r1UEzcL5RVZ4gO9Y=
|
||||
golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
@@ -221,6 +225,7 @@ golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxb
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508=
|
||||
@@ -239,6 +244,8 @@ gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogR
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
|
||||
gopkg.in/ini.v1 v1.62.0 h1:duBzk771uxoUuOlyRLkHsygud9+5lrlGjdFBb4mSKDU=
|
||||
gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/square/go-jose.v2 v2.4.1 h1:H0TmLt7/KmzlrDOpa1F+zr0Tk90PbJYBfsVUmRLrf9Y=
|
||||
gopkg.in/square/go-jose.v2 v2.4.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
||||
@@ -246,7 +253,6 @@ gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWD
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.5 h1:ymVxjfMaHvXD8RqPRmzHHsB3VvucivSkIAvJFDI5O3c=
|
||||
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
|
||||
|
||||
@@ -5,7 +5,7 @@ ROOT=$(unset CDPATH && cd $(dirname "${BASH_SOURCE[0]}")/.. && pwd)
|
||||
which ginkgo &> /dev/null
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "ginkgo not found, try to install..."
|
||||
go get -u github.com/onsi/ginkgo/ginkgo
|
||||
go install github.com/onsi/ginkgo/ginkgo
|
||||
fi
|
||||
|
||||
debug=false
|
||||
|
||||
@@ -19,101 +19,58 @@ import (
|
||||
|
||||
"github.com/fatedier/frp/pkg/consts"
|
||||
"github.com/fatedier/frp/pkg/msg"
|
||||
|
||||
"github.com/vaughan0/go-ini"
|
||||
)
|
||||
|
||||
type baseConfig struct {
|
||||
type BaseConfig struct {
|
||||
// AuthenticationMethod specifies what authentication method to use to
|
||||
// authenticate frpc with frps. If "token" is specified - token will be
|
||||
// read into login message. If "oidc" is specified - OIDC (Open ID Connect)
|
||||
// token will be issued using OIDC settings. By default, this value is "token".
|
||||
AuthenticationMethod string `json:"authentication_method"`
|
||||
AuthenticationMethod string `ini:"authentication_method" json:"authentication_method"`
|
||||
// AuthenticateHeartBeats specifies whether to include authentication token in
|
||||
// heartbeats sent to frps. By default, this value is false.
|
||||
AuthenticateHeartBeats bool `json:"authenticate_heartbeats"`
|
||||
AuthenticateHeartBeats bool `ini:"authenticate_heartbeats" json:"authenticate_heartbeats"`
|
||||
// AuthenticateNewWorkConns specifies whether to include authentication token in
|
||||
// new work connections sent to frps. By default, this value is false.
|
||||
AuthenticateNewWorkConns bool `json:"authenticate_new_work_conns"`
|
||||
AuthenticateNewWorkConns bool `ini:"authenticate_new_work_conns" json:"authenticate_new_work_conns"`
|
||||
}
|
||||
|
||||
func getDefaultBaseConf() baseConfig {
|
||||
return baseConfig{
|
||||
func getDefaultBaseConf() BaseConfig {
|
||||
return BaseConfig{
|
||||
AuthenticationMethod: "token",
|
||||
AuthenticateHeartBeats: false,
|
||||
AuthenticateNewWorkConns: false,
|
||||
}
|
||||
}
|
||||
|
||||
func unmarshalBaseConfFromIni(conf ini.File) baseConfig {
|
||||
var (
|
||||
tmpStr string
|
||||
ok bool
|
||||
)
|
||||
|
||||
cfg := getDefaultBaseConf()
|
||||
|
||||
if tmpStr, ok = conf.Get("common", "authentication_method"); ok {
|
||||
cfg.AuthenticationMethod = tmpStr
|
||||
}
|
||||
|
||||
if tmpStr, ok = conf.Get("common", "authenticate_heartbeats"); ok && tmpStr == "true" {
|
||||
cfg.AuthenticateHeartBeats = true
|
||||
} else {
|
||||
cfg.AuthenticateHeartBeats = false
|
||||
}
|
||||
|
||||
if tmpStr, ok = conf.Get("common", "authenticate_new_work_conns"); ok && tmpStr == "true" {
|
||||
cfg.AuthenticateNewWorkConns = true
|
||||
} else {
|
||||
cfg.AuthenticateNewWorkConns = false
|
||||
}
|
||||
|
||||
return cfg
|
||||
}
|
||||
|
||||
type ClientConfig struct {
|
||||
baseConfig
|
||||
oidcClientConfig
|
||||
tokenConfig
|
||||
BaseConfig `ini:",extends"`
|
||||
OidcClientConfig `ini:",extends"`
|
||||
TokenConfig `ini:",extends"`
|
||||
}
|
||||
|
||||
func GetDefaultClientConf() ClientConfig {
|
||||
return ClientConfig{
|
||||
baseConfig: getDefaultBaseConf(),
|
||||
oidcClientConfig: getDefaultOidcClientConf(),
|
||||
tokenConfig: getDefaultTokenConf(),
|
||||
BaseConfig: getDefaultBaseConf(),
|
||||
OidcClientConfig: getDefaultOidcClientConf(),
|
||||
TokenConfig: getDefaultTokenConf(),
|
||||
}
|
||||
}
|
||||
|
||||
func UnmarshalClientConfFromIni(conf ini.File) (cfg ClientConfig) {
|
||||
cfg.baseConfig = unmarshalBaseConfFromIni(conf)
|
||||
cfg.oidcClientConfig = unmarshalOidcClientConfFromIni(conf)
|
||||
cfg.tokenConfig = unmarshalTokenConfFromIni(conf)
|
||||
return cfg
|
||||
}
|
||||
|
||||
type ServerConfig struct {
|
||||
baseConfig
|
||||
oidcServerConfig
|
||||
tokenConfig
|
||||
BaseConfig `ini:",extends"`
|
||||
OidcServerConfig `ini:",extends"`
|
||||
TokenConfig `ini:",extends"`
|
||||
}
|
||||
|
||||
func GetDefaultServerConf() ServerConfig {
|
||||
return ServerConfig{
|
||||
baseConfig: getDefaultBaseConf(),
|
||||
oidcServerConfig: getDefaultOidcServerConf(),
|
||||
tokenConfig: getDefaultTokenConf(),
|
||||
BaseConfig: getDefaultBaseConf(),
|
||||
OidcServerConfig: getDefaultOidcServerConf(),
|
||||
TokenConfig: getDefaultTokenConf(),
|
||||
}
|
||||
}
|
||||
|
||||
func UnmarshalServerConfFromIni(conf ini.File) (cfg ServerConfig) {
|
||||
cfg.baseConfig = unmarshalBaseConfFromIni(conf)
|
||||
cfg.oidcServerConfig = unmarshalOidcServerConfFromIni(conf)
|
||||
cfg.tokenConfig = unmarshalTokenConfFromIni(conf)
|
||||
return cfg
|
||||
}
|
||||
|
||||
type Setter interface {
|
||||
SetLogin(*msg.Login) error
|
||||
SetPing(*msg.Ping) error
|
||||
@@ -123,9 +80,9 @@ type Setter interface {
|
||||
func NewAuthSetter(cfg ClientConfig) (authProvider Setter) {
|
||||
switch cfg.AuthenticationMethod {
|
||||
case consts.TokenAuthMethod:
|
||||
authProvider = NewTokenAuth(cfg.baseConfig, cfg.tokenConfig)
|
||||
authProvider = NewTokenAuth(cfg.BaseConfig, cfg.TokenConfig)
|
||||
case consts.OidcAuthMethod:
|
||||
authProvider = NewOidcAuthSetter(cfg.baseConfig, cfg.oidcClientConfig)
|
||||
authProvider = NewOidcAuthSetter(cfg.BaseConfig, cfg.OidcClientConfig)
|
||||
default:
|
||||
panic(fmt.Sprintf("wrong authentication method: '%s'", cfg.AuthenticationMethod))
|
||||
}
|
||||
@@ -142,9 +99,9 @@ type Verifier interface {
|
||||
func NewAuthVerifier(cfg ServerConfig) (authVerifier Verifier) {
|
||||
switch cfg.AuthenticationMethod {
|
||||
case consts.TokenAuthMethod:
|
||||
authVerifier = NewTokenAuth(cfg.baseConfig, cfg.tokenConfig)
|
||||
authVerifier = NewTokenAuth(cfg.BaseConfig, cfg.TokenConfig)
|
||||
case consts.OidcAuthMethod:
|
||||
authVerifier = NewOidcAuthVerifier(cfg.baseConfig, cfg.oidcServerConfig)
|
||||
authVerifier = NewOidcAuthVerifier(cfg.BaseConfig, cfg.OidcServerConfig)
|
||||
}
|
||||
|
||||
return authVerifier
|
||||
|
||||
@@ -21,30 +21,29 @@ import (
|
||||
"github.com/fatedier/frp/pkg/msg"
|
||||
|
||||
"github.com/coreos/go-oidc"
|
||||
"github.com/vaughan0/go-ini"
|
||||
"golang.org/x/oauth2/clientcredentials"
|
||||
)
|
||||
|
||||
type oidcClientConfig struct {
|
||||
type OidcClientConfig struct {
|
||||
// OidcClientID specifies the client ID to use to get a token in OIDC
|
||||
// authentication if AuthenticationMethod == "oidc". By default, this value
|
||||
// is "".
|
||||
OidcClientID string `json:"oidc_client_id"`
|
||||
OidcClientID string `ini:"oidc_client_id" json:"oidc_client_id"`
|
||||
// OidcClientSecret specifies the client secret to use to get a token in OIDC
|
||||
// authentication if AuthenticationMethod == "oidc". By default, this value
|
||||
// is "".
|
||||
OidcClientSecret string `json:"oidc_client_secret"`
|
||||
OidcClientSecret string `ini:"oidc_client_secret" json:"oidc_client_secret"`
|
||||
// OidcAudience specifies the audience of the token in OIDC authentication
|
||||
//if AuthenticationMethod == "oidc". By default, this value is "".
|
||||
OidcAudience string `json:"oidc_audience"`
|
||||
OidcAudience string `ini:"oidc_audience" json:"oidc_audience"`
|
||||
// OidcTokenEndpointURL specifies the URL which implements OIDC Token Endpoint.
|
||||
// It will be used to get an OIDC token if AuthenticationMethod == "oidc".
|
||||
// By default, this value is "".
|
||||
OidcTokenEndpointURL string `json:"oidc_token_endpoint_url"`
|
||||
OidcTokenEndpointURL string `ini:"oidc_token_endpoint_url" json:"oidc_token_endpoint_url"`
|
||||
}
|
||||
|
||||
func getDefaultOidcClientConf() oidcClientConfig {
|
||||
return oidcClientConfig{
|
||||
func getDefaultOidcClientConf() OidcClientConfig {
|
||||
return OidcClientConfig{
|
||||
OidcClientID: "",
|
||||
OidcClientSecret: "",
|
||||
OidcAudience: "",
|
||||
@@ -52,56 +51,29 @@ func getDefaultOidcClientConf() oidcClientConfig {
|
||||
}
|
||||
}
|
||||
|
||||
func unmarshalOidcClientConfFromIni(conf ini.File) oidcClientConfig {
|
||||
var (
|
||||
tmpStr string
|
||||
ok bool
|
||||
)
|
||||
|
||||
cfg := getDefaultOidcClientConf()
|
||||
|
||||
if tmpStr, ok = conf.Get("common", "oidc_client_id"); ok {
|
||||
cfg.OidcClientID = tmpStr
|
||||
}
|
||||
|
||||
if tmpStr, ok = conf.Get("common", "oidc_client_secret"); ok {
|
||||
cfg.OidcClientSecret = tmpStr
|
||||
}
|
||||
|
||||
if tmpStr, ok = conf.Get("common", "oidc_audience"); ok {
|
||||
cfg.OidcAudience = tmpStr
|
||||
}
|
||||
|
||||
if tmpStr, ok = conf.Get("common", "oidc_token_endpoint_url"); ok {
|
||||
cfg.OidcTokenEndpointURL = tmpStr
|
||||
}
|
||||
|
||||
return cfg
|
||||
}
|
||||
|
||||
type oidcServerConfig struct {
|
||||
type OidcServerConfig struct {
|
||||
// OidcIssuer specifies the issuer to verify OIDC tokens with. This issuer
|
||||
// will be used to load public keys to verify signature and will be compared
|
||||
// with the issuer claim in the OIDC token. It will be used if
|
||||
// AuthenticationMethod == "oidc". By default, this value is "".
|
||||
OidcIssuer string `json:"oidc_issuer"`
|
||||
OidcIssuer string `ini:"oidc_issuer" json:"oidc_issuer"`
|
||||
// OidcAudience specifies the audience OIDC tokens should contain when validated.
|
||||
// If this value is empty, audience ("client ID") verification will be skipped.
|
||||
// It will be used when AuthenticationMethod == "oidc". By default, this
|
||||
// value is "".
|
||||
OidcAudience string `json:"oidc_audience"`
|
||||
OidcAudience string `ini:"oidc_audience" json:"oidc_audience"`
|
||||
// OidcSkipExpiryCheck specifies whether to skip checking if the OIDC token is
|
||||
// expired. It will be used when AuthenticationMethod == "oidc". By default, this
|
||||
// value is false.
|
||||
OidcSkipExpiryCheck bool `json:"oidc_skip_expiry_check"`
|
||||
OidcSkipExpiryCheck bool `ini:"oidc_skip_expiry_check" json:"oidc_skip_expiry_check"`
|
||||
// OidcSkipIssuerCheck specifies whether to skip checking if the OIDC token's
|
||||
// issuer claim matches the issuer specified in OidcIssuer. It will be used when
|
||||
// AuthenticationMethod == "oidc". By default, this value is false.
|
||||
OidcSkipIssuerCheck bool `json:"oidc_skip_issuer_check"`
|
||||
OidcSkipIssuerCheck bool `ini:"oidc_skip_issuer_check" json:"oidc_skip_issuer_check"`
|
||||
}
|
||||
|
||||
func getDefaultOidcServerConf() oidcServerConfig {
|
||||
return oidcServerConfig{
|
||||
func getDefaultOidcServerConf() OidcServerConfig {
|
||||
return OidcServerConfig{
|
||||
OidcIssuer: "",
|
||||
OidcAudience: "",
|
||||
OidcSkipExpiryCheck: false,
|
||||
@@ -109,44 +81,13 @@ func getDefaultOidcServerConf() oidcServerConfig {
|
||||
}
|
||||
}
|
||||
|
||||
func unmarshalOidcServerConfFromIni(conf ini.File) oidcServerConfig {
|
||||
var (
|
||||
tmpStr string
|
||||
ok bool
|
||||
)
|
||||
|
||||
cfg := getDefaultOidcServerConf()
|
||||
|
||||
if tmpStr, ok = conf.Get("common", "oidc_issuer"); ok {
|
||||
cfg.OidcIssuer = tmpStr
|
||||
}
|
||||
|
||||
if tmpStr, ok = conf.Get("common", "oidc_audience"); ok {
|
||||
cfg.OidcAudience = tmpStr
|
||||
}
|
||||
|
||||
if tmpStr, ok = conf.Get("common", "oidc_skip_expiry_check"); ok && tmpStr == "true" {
|
||||
cfg.OidcSkipExpiryCheck = true
|
||||
} else {
|
||||
cfg.OidcSkipExpiryCheck = false
|
||||
}
|
||||
|
||||
if tmpStr, ok = conf.Get("common", "oidc_skip_issuer_check"); ok && tmpStr == "true" {
|
||||
cfg.OidcSkipIssuerCheck = true
|
||||
} else {
|
||||
cfg.OidcSkipIssuerCheck = false
|
||||
}
|
||||
|
||||
return cfg
|
||||
}
|
||||
|
||||
type OidcAuthProvider struct {
|
||||
baseConfig
|
||||
BaseConfig
|
||||
|
||||
tokenGenerator *clientcredentials.Config
|
||||
}
|
||||
|
||||
func NewOidcAuthSetter(baseCfg baseConfig, cfg oidcClientConfig) *OidcAuthProvider {
|
||||
func NewOidcAuthSetter(baseCfg BaseConfig, cfg OidcClientConfig) *OidcAuthProvider {
|
||||
tokenGenerator := &clientcredentials.Config{
|
||||
ClientID: cfg.OidcClientID,
|
||||
ClientSecret: cfg.OidcClientSecret,
|
||||
@@ -155,7 +96,7 @@ func NewOidcAuthSetter(baseCfg baseConfig, cfg oidcClientConfig) *OidcAuthProvid
|
||||
}
|
||||
|
||||
return &OidcAuthProvider{
|
||||
baseConfig: baseCfg,
|
||||
BaseConfig: baseCfg,
|
||||
tokenGenerator: tokenGenerator,
|
||||
}
|
||||
}
|
||||
@@ -192,13 +133,13 @@ func (auth *OidcAuthProvider) SetNewWorkConn(newWorkConnMsg *msg.NewWorkConn) (e
|
||||
}
|
||||
|
||||
type OidcAuthConsumer struct {
|
||||
baseConfig
|
||||
BaseConfig
|
||||
|
||||
verifier *oidc.IDTokenVerifier
|
||||
subjectFromLogin string
|
||||
}
|
||||
|
||||
func NewOidcAuthVerifier(baseCfg baseConfig, cfg oidcServerConfig) *OidcAuthConsumer {
|
||||
func NewOidcAuthVerifier(baseCfg BaseConfig, cfg OidcServerConfig) *OidcAuthConsumer {
|
||||
provider, err := oidc.NewProvider(context.Background(), cfg.OidcIssuer)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
@@ -210,7 +151,7 @@ func NewOidcAuthVerifier(baseCfg baseConfig, cfg oidcServerConfig) *OidcAuthCons
|
||||
SkipIssuerCheck: cfg.OidcSkipIssuerCheck,
|
||||
}
|
||||
return &OidcAuthConsumer{
|
||||
baseConfig: baseCfg,
|
||||
BaseConfig: baseCfg,
|
||||
verifier: provider.Verifier(&verifierConf),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,47 +20,30 @@ import (
|
||||
|
||||
"github.com/fatedier/frp/pkg/msg"
|
||||
"github.com/fatedier/frp/pkg/util/util"
|
||||
|
||||
"github.com/vaughan0/go-ini"
|
||||
)
|
||||
|
||||
type tokenConfig struct {
|
||||
type TokenConfig struct {
|
||||
// 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"`
|
||||
Token string `ini:"token" json:"token"`
|
||||
}
|
||||
|
||||
func getDefaultTokenConf() tokenConfig {
|
||||
return tokenConfig{
|
||||
func getDefaultTokenConf() TokenConfig {
|
||||
return TokenConfig{
|
||||
Token: "",
|
||||
}
|
||||
}
|
||||
|
||||
func unmarshalTokenConfFromIni(conf ini.File) tokenConfig {
|
||||
var (
|
||||
tmpStr string
|
||||
ok bool
|
||||
)
|
||||
|
||||
cfg := getDefaultTokenConf()
|
||||
|
||||
if tmpStr, ok = conf.Get("common", "token"); ok {
|
||||
cfg.Token = tmpStr
|
||||
}
|
||||
|
||||
return cfg
|
||||
}
|
||||
|
||||
type TokenAuthSetterVerifier struct {
|
||||
baseConfig
|
||||
BaseConfig
|
||||
|
||||
token string
|
||||
}
|
||||
|
||||
func NewTokenAuth(baseCfg baseConfig, cfg tokenConfig) *TokenAuthSetterVerifier {
|
||||
func NewTokenAuth(baseCfg BaseConfig, cfg TokenConfig) *TokenAuthSetterVerifier {
|
||||
return &TokenAuthSetterVerifier{
|
||||
baseConfig: baseCfg,
|
||||
BaseConfig: baseCfg,
|
||||
token: cfg.Token,
|
||||
}
|
||||
}
|
||||
|
||||
12
pkg/config/README.md
Normal file
12
pkg/config/README.md
Normal file
@@ -0,0 +1,12 @@
|
||||
So far, there is no mature Go project that does well in parsing `*.ini` files.
|
||||
|
||||
By comparison, we have selected an open source project: `https://github.com/go-ini/ini`.
|
||||
|
||||
This library helped us solve most of the key-value matching, but there are still some problems, such as not supporting parsing `map`.
|
||||
|
||||
We add our own logic on the basis of this library. In the current situationwhich, we need to complete the entire `Unmarshal` in two steps:
|
||||
|
||||
* Step#1, use `go-ini` to complete the basic parameter matching;
|
||||
* Step#2, parse our custom parameters to realize parsing special structure, like `map`, `array`.
|
||||
|
||||
Some of the keywords in `tag`(like inline, extends, etc.) may be different from standard libraries such as `json` and `protobuf` in Go. For details, please refer to the library documentation: https://ini.unknwon.io/docs/intro.
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2016 fatedier, fatedier@gmail.com
|
||||
// Copyright 2020 The frp Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
@@ -17,125 +17,131 @@ package config
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/fatedier/frp/pkg/auth"
|
||||
"github.com/fatedier/frp/pkg/util/util"
|
||||
|
||||
ini "github.com/vaughan0/go-ini"
|
||||
"gopkg.in/ini.v1"
|
||||
)
|
||||
|
||||
// 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 {
|
||||
auth.ClientConfig
|
||||
auth.ClientConfig `ini:",extends" json:"inline"`
|
||||
|
||||
// ServerAddr specifies the address of the server to connect to. By
|
||||
// default, this value is "0.0.0.0".
|
||||
ServerAddr string `json:"server_addr"`
|
||||
ServerAddr string `ini:"server_addr" josn:"server_addr"`
|
||||
// ServerPort specifies the port to connect to the server on. By default,
|
||||
// this value is 7000.
|
||||
ServerPort int `json:"server_port"`
|
||||
ServerPort int `ini:"server_port" json:"server_port"`
|
||||
// HTTPProxy specifies a proxy address to connect to the server through. If
|
||||
// this value is "", the server will be connected to directly. By default,
|
||||
// this value is read from the "http_proxy" environment variable.
|
||||
HTTPProxy string `json:"http_proxy"`
|
||||
HTTPProxy string `ini:"http_proxy" json:"http_proxy"`
|
||||
// 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"`
|
||||
LogFile string `ini:"log_file" 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"`
|
||||
LogWay string `ini:"log_way" 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"`
|
||||
LogLevel string `ini:"log_level" 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"`
|
||||
LogMaxDays int64 `ini:"log_max_days" 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"`
|
||||
DisableLogColor bool `ini:"disable_log_color" json:"disable_log_color"`
|
||||
// AdminAddr specifies the address that the admin server binds to. By
|
||||
// default, this value is "127.0.0.1".
|
||||
AdminAddr string `json:"admin_addr"`
|
||||
AdminAddr string `ini:"admin_addr" 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"`
|
||||
AdminPort int `ini:"admin_port" 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"`
|
||||
AdminUser string `ini:"admin_user" 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"`
|
||||
AdminPwd string `ini:"admin_pwd" 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"`
|
||||
AssetsDir string `ini:"assets_dir" 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"`
|
||||
PoolCount int `ini:"pool_count" 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"`
|
||||
TCPMux bool `ini:"tcp_mux" 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"`
|
||||
User string `ini:"user" 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"`
|
||||
DNSServer string `ini:"dns_server" 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"`
|
||||
LoginFailExit bool `ini:"login_fail_exit" 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"`
|
||||
Start []string `ini:"start" json:"start"`
|
||||
//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"`
|
||||
Protocol string `ini:"protocol" json:"protocol"`
|
||||
// TLSEnable specifies whether or not TLS should be used when communicating
|
||||
// with the server. If "tls_cert_file" and "tls_key_file" are valid,
|
||||
// client will load the supplied tls configuration.
|
||||
TLSEnable bool `json:"tls_enable"`
|
||||
// ClientTLSCertPath specifies the path of the cert file that client will
|
||||
TLSEnable bool `ini:"tls_enable" json:"tls_enable"`
|
||||
// TLSCertPath specifies the path of the cert file that client will
|
||||
// load. It only works when "tls_enable" is true and "tls_key_file" is valid.
|
||||
TLSCertFile string `json:"tls_cert_file"`
|
||||
// ClientTLSKeyPath specifies the path of the secret key file that client
|
||||
TLSCertFile string `ini:"tls_cert_file" json:"tls_cert_file"`
|
||||
// TLSKeyPath specifies the path of the secret key file that client
|
||||
// will load. It only works when "tls_enable" is true and "tls_cert_file"
|
||||
// are valid.
|
||||
TLSKeyFile string `json:"tls_key_file"`
|
||||
// TrustedCaFile specifies the path of the trusted ca file that will load.
|
||||
TLSKeyFile string `ini:"tls_key_file" json:"tls_key_file"`
|
||||
// TLSTrustedCaFile specifies the path of the trusted ca file that will load.
|
||||
// It only works when "tls_enable" is valid and tls configuration of server
|
||||
// has been specified.
|
||||
TLSTrustedCaFile string `json:"tls_trusted_ca_file"`
|
||||
TLSTrustedCaFile string `ini:"tls_trusted_ca_file" json:"tls_trusted_ca_file"`
|
||||
// TLSServerName specifices the custom server name of tls certificate. By
|
||||
// default, server name if same to ServerAddr.
|
||||
TLSServerName string `ini:"tls_server_name" json:"tls_server_name"`
|
||||
// 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"`
|
||||
HeartbeatInterval int64 `ini:"heartbeat_interval" 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"`
|
||||
HeartbeatTimeout int64 `ini:"heartbeat_timeout" json:"heartbeat_timeout"`
|
||||
// Client meta info
|
||||
Metas map[string]string `json:"metas"`
|
||||
Metas map[string]string `ini:"-" json:"metas"`
|
||||
// UDPPacketSize specifies the udp packet size
|
||||
// By default, this value is 1500
|
||||
UDPPacketSize int64 `json:"udp_packet_size"`
|
||||
UDPPacketSize int64 `ini:"udp_packet_size" json:"udp_packet_size"`
|
||||
}
|
||||
|
||||
// GetDefaultClientConf returns a client configuration with default values.
|
||||
func GetDefaultClientConf() ClientCommonConf {
|
||||
return ClientCommonConf{
|
||||
ClientConfig: auth.GetDefaultClientConf(),
|
||||
ServerAddr: "0.0.0.0",
|
||||
ServerPort: 7000,
|
||||
HTTPProxy: os.Getenv("http_proxy"),
|
||||
@@ -154,7 +160,7 @@ func GetDefaultClientConf() ClientCommonConf {
|
||||
User: "",
|
||||
DNSServer: "",
|
||||
LoginFailExit: true,
|
||||
Start: make(map[string]struct{}),
|
||||
Start: make([]string, 0),
|
||||
Protocol: "tcp",
|
||||
TLSEnable: false,
|
||||
TLSCertFile: "",
|
||||
@@ -167,185 +173,13 @@ func GetDefaultClientConf() ClientCommonConf {
|
||||
}
|
||||
}
|
||||
|
||||
func UnmarshalClientConfFromIni(content string) (cfg ClientCommonConf, err error) {
|
||||
cfg = GetDefaultClientConf()
|
||||
|
||||
conf, err := ini.Load(strings.NewReader(content))
|
||||
if err != nil {
|
||||
return ClientCommonConf{}, fmt.Errorf("parse ini conf file error: %v", err)
|
||||
}
|
||||
|
||||
cfg.ClientConfig = auth.UnmarshalClientConfFromIni(conf)
|
||||
|
||||
var (
|
||||
tmpStr string
|
||||
ok bool
|
||||
v int64
|
||||
)
|
||||
if tmpStr, ok = conf.Get("common", "server_addr"); ok {
|
||||
cfg.ServerAddr = tmpStr
|
||||
}
|
||||
|
||||
if tmpStr, ok = conf.Get("common", "server_port"); ok {
|
||||
v, err = strconv.ParseInt(tmpStr, 10, 64)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("Parse conf error: invalid server_port")
|
||||
return
|
||||
}
|
||||
cfg.ServerPort = int(v)
|
||||
}
|
||||
|
||||
if tmpStr, ok = conf.Get("common", "disable_log_color"); ok && tmpStr == "true" {
|
||||
cfg.DisableLogColor = true
|
||||
}
|
||||
|
||||
if tmpStr, ok = conf.Get("common", "http_proxy"); ok {
|
||||
cfg.HTTPProxy = tmpStr
|
||||
}
|
||||
|
||||
if tmpStr, ok = conf.Get("common", "log_file"); ok {
|
||||
cfg.LogFile = tmpStr
|
||||
if cfg.LogFile == "console" {
|
||||
cfg.LogWay = "console"
|
||||
} else {
|
||||
cfg.LogWay = "file"
|
||||
}
|
||||
}
|
||||
|
||||
if tmpStr, ok = conf.Get("common", "log_level"); ok {
|
||||
cfg.LogLevel = tmpStr
|
||||
}
|
||||
|
||||
if tmpStr, ok = conf.Get("common", "log_max_days"); ok {
|
||||
if v, err = strconv.ParseInt(tmpStr, 10, 64); err == nil {
|
||||
cfg.LogMaxDays = v
|
||||
}
|
||||
}
|
||||
|
||||
if tmpStr, ok = conf.Get("common", "admin_addr"); ok {
|
||||
cfg.AdminAddr = tmpStr
|
||||
}
|
||||
|
||||
if tmpStr, ok = conf.Get("common", "admin_port"); ok {
|
||||
if v, err = strconv.ParseInt(tmpStr, 10, 64); err == nil {
|
||||
cfg.AdminPort = int(v)
|
||||
} else {
|
||||
err = fmt.Errorf("Parse conf error: invalid admin_port")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if tmpStr, ok = conf.Get("common", "admin_user"); ok {
|
||||
cfg.AdminUser = tmpStr
|
||||
}
|
||||
|
||||
if tmpStr, ok = conf.Get("common", "admin_pwd"); ok {
|
||||
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 v, err = strconv.ParseInt(tmpStr, 10, 64); err == nil {
|
||||
cfg.PoolCount = int(v)
|
||||
}
|
||||
}
|
||||
|
||||
if tmpStr, ok = conf.Get("common", "tcp_mux"); ok && tmpStr == "false" {
|
||||
cfg.TCPMux = false
|
||||
} else {
|
||||
cfg.TCPMux = true
|
||||
}
|
||||
|
||||
if tmpStr, ok = conf.Get("common", "user"); ok {
|
||||
cfg.User = tmpStr
|
||||
}
|
||||
|
||||
if tmpStr, ok = conf.Get("common", "dns_server"); ok {
|
||||
cfg.DNSServer = tmpStr
|
||||
}
|
||||
|
||||
if tmpStr, ok = conf.Get("common", "start"); ok {
|
||||
proxyNames := strings.Split(tmpStr, ",")
|
||||
for _, name := range proxyNames {
|
||||
cfg.Start[strings.TrimSpace(name)] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
if tmpStr, ok = conf.Get("common", "login_fail_exit"); ok && tmpStr == "false" {
|
||||
cfg.LoginFailExit = false
|
||||
} else {
|
||||
cfg.LoginFailExit = true
|
||||
}
|
||||
|
||||
if tmpStr, ok = conf.Get("common", "protocol"); ok {
|
||||
// Now it only support tcp and kcp and websocket.
|
||||
if tmpStr != "tcp" && tmpStr != "kcp" && tmpStr != "websocket" {
|
||||
err = fmt.Errorf("Parse conf error: invalid protocol")
|
||||
return
|
||||
}
|
||||
cfg.Protocol = tmpStr
|
||||
}
|
||||
|
||||
if tmpStr, ok = conf.Get("common", "tls_enable"); ok && tmpStr == "true" {
|
||||
cfg.TLSEnable = true
|
||||
} else {
|
||||
cfg.TLSEnable = false
|
||||
}
|
||||
|
||||
if tmpStr, ok = conf.Get("common", "tls_cert_file"); ok {
|
||||
cfg.TLSCertFile = tmpStr
|
||||
}
|
||||
|
||||
if tmpStr, ok := conf.Get("common", "tls_key_file"); ok {
|
||||
cfg.TLSKeyFile = tmpStr
|
||||
}
|
||||
|
||||
if tmpStr, ok := conf.Get("common", "tls_trusted_ca_file"); ok {
|
||||
cfg.TLSTrustedCaFile = tmpStr
|
||||
}
|
||||
|
||||
if tmpStr, ok = conf.Get("common", "heartbeat_timeout"); ok {
|
||||
if v, err = strconv.ParseInt(tmpStr, 10, 64); err != nil {
|
||||
err = fmt.Errorf("Parse conf error: invalid heartbeat_timeout")
|
||||
return
|
||||
}
|
||||
cfg.HeartbeatTimeout = v
|
||||
}
|
||||
|
||||
if tmpStr, ok = conf.Get("common", "heartbeat_interval"); ok {
|
||||
if v, err = strconv.ParseInt(tmpStr, 10, 64); err != nil {
|
||||
err = fmt.Errorf("Parse conf error: invalid heartbeat_interval")
|
||||
return
|
||||
}
|
||||
cfg.HeartbeatInterval = v
|
||||
}
|
||||
for k, v := range conf.Section("common") {
|
||||
if strings.HasPrefix(k, "meta_") {
|
||||
cfg.Metas[strings.TrimPrefix(k, "meta_")] = v
|
||||
}
|
||||
}
|
||||
if tmpStr, ok = conf.Get("common", "udp_packet_size"); ok {
|
||||
if v, err = strconv.ParseInt(tmpStr, 10, 64); err != nil {
|
||||
err = fmt.Errorf("Parse conf error: invalid udp_packet_size")
|
||||
return
|
||||
}
|
||||
cfg.UDPPacketSize = v
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (cfg *ClientCommonConf) Check() (err error) {
|
||||
func (cfg *ClientCommonConf) Check() error {
|
||||
if cfg.HeartbeatInterval <= 0 {
|
||||
err = fmt.Errorf("Parse conf error: invalid heartbeat_interval")
|
||||
return
|
||||
return fmt.Errorf("Parse conf error: invalid heartbeat_interval")
|
||||
}
|
||||
|
||||
if cfg.HeartbeatTimeout < cfg.HeartbeatInterval {
|
||||
err = fmt.Errorf("Parse conf error: invalid heartbeat_timeout, heartbeat_timeout is less than heartbeat_interval")
|
||||
return
|
||||
return fmt.Errorf("Parse conf error: invalid heartbeat_timeout, heartbeat_timeout is less than heartbeat_interval")
|
||||
}
|
||||
|
||||
if cfg.TLSEnable == false {
|
||||
@@ -361,5 +195,178 @@ func (cfg *ClientCommonConf) Check() (err error) {
|
||||
fmt.Println("WARNING! tls_trusted_ca_file is invalid when tls_enable is false")
|
||||
}
|
||||
}
|
||||
return
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Supported sources including: string(file path), []byte, Reader interface.
|
||||
func UnmarshalClientConfFromIni(source interface{}) (ClientCommonConf, error) {
|
||||
f, err := ini.LoadSources(ini.LoadOptions{
|
||||
Insensitive: false,
|
||||
InsensitiveSections: false,
|
||||
InsensitiveKeys: false,
|
||||
IgnoreInlineComment: true,
|
||||
AllowBooleanKeys: true,
|
||||
}, source)
|
||||
if err != nil {
|
||||
return ClientCommonConf{}, err
|
||||
}
|
||||
|
||||
s, err := f.GetSection("common")
|
||||
if err != nil {
|
||||
return ClientCommonConf{}, fmt.Errorf("invalid configuration file, not found [common] section")
|
||||
}
|
||||
|
||||
common := GetDefaultClientConf()
|
||||
err = s.MapTo(&common)
|
||||
if err != nil {
|
||||
return ClientCommonConf{}, err
|
||||
}
|
||||
|
||||
common.Metas = GetMapWithoutPrefix(s.KeysHash(), "meta_")
|
||||
|
||||
return common, nil
|
||||
}
|
||||
|
||||
// if len(startProxy) is 0, start all
|
||||
// otherwise just start proxies in startProxy map
|
||||
func LoadAllProxyConfsFromIni(
|
||||
prefix string,
|
||||
source interface{},
|
||||
start []string,
|
||||
) (map[string]ProxyConf, map[string]VisitorConf, error) {
|
||||
|
||||
f, err := ini.LoadSources(ini.LoadOptions{
|
||||
Insensitive: false,
|
||||
InsensitiveSections: false,
|
||||
InsensitiveKeys: false,
|
||||
IgnoreInlineComment: true,
|
||||
AllowBooleanKeys: true,
|
||||
}, source)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
proxyConfs := make(map[string]ProxyConf)
|
||||
visitorConfs := make(map[string]VisitorConf)
|
||||
|
||||
if prefix != "" {
|
||||
prefix += "."
|
||||
}
|
||||
|
||||
startProxy := make(map[string]struct{})
|
||||
for _, s := range start {
|
||||
startProxy[s] = struct{}{}
|
||||
}
|
||||
|
||||
startAll := true
|
||||
if len(startProxy) > 0 {
|
||||
startAll = false
|
||||
}
|
||||
|
||||
// Build template sections from range section And append to ini.File.
|
||||
rangeSections := make([]*ini.Section, 0)
|
||||
for _, section := range f.Sections() {
|
||||
|
||||
if !strings.HasPrefix(section.Name(), "range:") {
|
||||
continue
|
||||
}
|
||||
|
||||
rangeSections = append(rangeSections, section)
|
||||
}
|
||||
|
||||
for _, section := range rangeSections {
|
||||
err = renderRangeProxyTemplates(f, section)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("fail to render range-section[%s] with error: %v", section.Name(), err)
|
||||
}
|
||||
}
|
||||
|
||||
for _, section := range f.Sections() {
|
||||
name := section.Name()
|
||||
|
||||
if name == ini.DefaultSection || name == "common" || strings.HasPrefix(name, "range:") {
|
||||
continue
|
||||
}
|
||||
|
||||
_, shouldStart := startProxy[name]
|
||||
if !startAll && !shouldStart {
|
||||
continue
|
||||
}
|
||||
|
||||
roleType := section.Key("role").String()
|
||||
if roleType == "" {
|
||||
roleType = "server"
|
||||
}
|
||||
|
||||
switch roleType {
|
||||
case "server":
|
||||
newConf, newErr := NewProxyConfFromIni(prefix, name, section)
|
||||
if newErr != nil {
|
||||
return nil, nil, fmt.Errorf("fail to parse section[%s], err: %v", name, newErr)
|
||||
}
|
||||
proxyConfs[prefix+name] = newConf
|
||||
case "visitor":
|
||||
newConf, newErr := NewVisitorConfFromIni(prefix, name, section)
|
||||
if newErr != nil {
|
||||
return nil, nil, newErr
|
||||
}
|
||||
visitorConfs[prefix+name] = newConf
|
||||
default:
|
||||
return nil, nil, fmt.Errorf("section[%s] role should be 'server' or 'visitor'", name)
|
||||
}
|
||||
}
|
||||
return proxyConfs, visitorConfs, nil
|
||||
}
|
||||
|
||||
func renderRangeProxyTemplates(f *ini.File, section *ini.Section) error {
|
||||
|
||||
// Validation
|
||||
localPortStr := section.Key("local_port").String()
|
||||
remotePortStr := section.Key("remote_port").String()
|
||||
if localPortStr == "" || remotePortStr == "" {
|
||||
return fmt.Errorf("local_port or remote_port is empty")
|
||||
}
|
||||
|
||||
localPorts, err := util.ParseRangeNumbers(localPortStr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
remotePorts, err := util.ParseRangeNumbers(remotePortStr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(localPorts) != len(remotePorts) {
|
||||
return fmt.Errorf("local ports number should be same with remote ports number")
|
||||
}
|
||||
|
||||
if len(localPorts) == 0 {
|
||||
return fmt.Errorf("local_port and remote_port is necessary")
|
||||
}
|
||||
|
||||
// Templates
|
||||
prefix := strings.TrimSpace(strings.TrimPrefix(section.Name(), "range:"))
|
||||
|
||||
for i := range localPorts {
|
||||
tmpname := fmt.Sprintf("%s_%d", prefix, i)
|
||||
|
||||
tmpsection, err := f.NewSection(tmpname)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
copySection(section, tmpsection)
|
||||
tmpsection.NewKey("local_port", fmt.Sprintf("%d", localPorts[i]))
|
||||
tmpsection.NewKey("remote_port", fmt.Sprintf("%d", remotePorts[i]))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func copySection(source, target *ini.Section) {
|
||||
for key, value := range source.KeysHash() {
|
||||
target.NewKey(key, value)
|
||||
}
|
||||
}
|
||||
645
pkg/config/client_test.go
Normal file
645
pkg/config/client_test.go
Normal file
@@ -0,0 +1,645 @@
|
||||
// Copyright 2020 The frp Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package config
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/fatedier/frp/pkg/auth"
|
||||
"github.com/fatedier/frp/pkg/consts"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
const (
|
||||
testUser = "test"
|
||||
)
|
||||
|
||||
var (
|
||||
testClientBytesWithFull = []byte(`
|
||||
# [common] is integral section
|
||||
[common]
|
||||
server_addr = 0.0.0.9
|
||||
server_port = 7009
|
||||
http_proxy = http://user:passwd@192.168.1.128:8080
|
||||
log_file = ./frpc.log9
|
||||
log_way = file
|
||||
log_level = info9
|
||||
log_max_days = 39
|
||||
disable_log_color = false
|
||||
authenticate_heartbeats = false
|
||||
authenticate_new_work_conns = false
|
||||
token = 12345678
|
||||
oidc_client_id = client-id
|
||||
oidc_client_secret = client-secret
|
||||
oidc_audience = audience
|
||||
oidc_token_endpoint_url = endpoint_url
|
||||
admin_addr = 127.0.0.9
|
||||
admin_port = 7409
|
||||
admin_user = admin9
|
||||
admin_pwd = admin9
|
||||
assets_dir = ./static9
|
||||
pool_count = 59
|
||||
tcp_mux
|
||||
user = your_name
|
||||
login_fail_exit
|
||||
protocol = tcp
|
||||
tls_enable = true
|
||||
tls_cert_file = client.crt
|
||||
tls_key_file = client.key
|
||||
tls_trusted_ca_file = ca.crt
|
||||
tls_server_name = example.com
|
||||
dns_server = 8.8.8.9
|
||||
start = ssh,dns
|
||||
heartbeat_interval = 39
|
||||
heartbeat_timeout = 99
|
||||
meta_var1 = 123
|
||||
meta_var2 = 234
|
||||
udp_packet_size = 1509
|
||||
|
||||
# all proxy
|
||||
[ssh]
|
||||
type = tcp
|
||||
local_ip = 127.0.0.9
|
||||
local_port = 29
|
||||
bandwidth_limit = 19MB
|
||||
use_encryption
|
||||
use_compression
|
||||
remote_port = 6009
|
||||
group = test_group
|
||||
group_key = 123456
|
||||
health_check_type = tcp
|
||||
health_check_timeout_s = 3
|
||||
health_check_max_failed = 3
|
||||
health_check_interval_s = 19
|
||||
meta_var1 = 123
|
||||
meta_var2 = 234
|
||||
|
||||
[ssh_random]
|
||||
type = tcp
|
||||
local_ip = 127.0.0.9
|
||||
local_port = 29
|
||||
remote_port = 9
|
||||
|
||||
[range:tcp_port]
|
||||
type = tcp
|
||||
local_ip = 127.0.0.9
|
||||
local_port = 6010-6011,6019
|
||||
remote_port = 6010-6011,6019
|
||||
use_encryption = false
|
||||
use_compression = false
|
||||
|
||||
[dns]
|
||||
type = udp
|
||||
local_ip = 114.114.114.114
|
||||
local_port = 59
|
||||
remote_port = 6009
|
||||
use_encryption
|
||||
use_compression
|
||||
|
||||
[range:udp_port]
|
||||
type = udp
|
||||
local_ip = 114.114.114.114
|
||||
local_port = 6000,6010-6011
|
||||
remote_port = 6000,6010-6011
|
||||
use_encryption
|
||||
use_compression
|
||||
|
||||
[web01]
|
||||
type = http
|
||||
local_ip = 127.0.0.9
|
||||
local_port = 89
|
||||
use_encryption
|
||||
use_compression
|
||||
http_user = admin
|
||||
http_pwd = admin
|
||||
subdomain = web01
|
||||
custom_domains = web02.yourdomain.com
|
||||
locations = /,/pic
|
||||
host_header_rewrite = example.com
|
||||
header_X-From-Where = frp
|
||||
health_check_type = http
|
||||
health_check_url = /status
|
||||
health_check_interval_s = 19
|
||||
health_check_max_failed = 3
|
||||
health_check_timeout_s = 3
|
||||
|
||||
[web02]
|
||||
type = https
|
||||
local_ip = 127.0.0.9
|
||||
local_port = 8009
|
||||
use_encryption
|
||||
use_compression
|
||||
subdomain = web01
|
||||
custom_domains = web02.yourdomain.com
|
||||
proxy_protocol_version = v2
|
||||
|
||||
[secret_tcp]
|
||||
type = stcp
|
||||
sk = abcdefg
|
||||
local_ip = 127.0.0.1
|
||||
local_port = 22
|
||||
use_encryption = false
|
||||
use_compression = false
|
||||
|
||||
[p2p_tcp]
|
||||
type = xtcp
|
||||
sk = abcdefg
|
||||
local_ip = 127.0.0.1
|
||||
local_port = 22
|
||||
use_encryption = false
|
||||
use_compression = false
|
||||
|
||||
[tcpmuxhttpconnect]
|
||||
type = tcpmux
|
||||
multiplexer = httpconnect
|
||||
local_ip = 127.0.0.1
|
||||
local_port = 10701
|
||||
custom_domains = tunnel1
|
||||
|
||||
[plugin_unix_domain_socket]
|
||||
type = tcp
|
||||
remote_port = 6003
|
||||
plugin = unix_domain_socket
|
||||
plugin_unix_path = /var/run/docker.sock
|
||||
|
||||
[plugin_http_proxy]
|
||||
type = tcp
|
||||
remote_port = 6004
|
||||
plugin = http_proxy
|
||||
plugin_http_user = abc
|
||||
plugin_http_passwd = abc
|
||||
|
||||
[plugin_socks5]
|
||||
type = tcp
|
||||
remote_port = 6005
|
||||
plugin = socks5
|
||||
plugin_user = abc
|
||||
plugin_passwd = abc
|
||||
|
||||
[plugin_static_file]
|
||||
type = tcp
|
||||
remote_port = 6006
|
||||
plugin = static_file
|
||||
plugin_local_path = /var/www/blog
|
||||
plugin_strip_prefix = static
|
||||
plugin_http_user = abc
|
||||
plugin_http_passwd = abc
|
||||
|
||||
[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
|
||||
|
||||
[plugin_http2https]
|
||||
type = http
|
||||
custom_domains = test.yourdomain.com
|
||||
plugin = http2https
|
||||
plugin_local_addr = 127.0.0.1:443
|
||||
plugin_host_header_rewrite = 127.0.0.1
|
||||
plugin_header_X-From-Where = frp
|
||||
|
||||
# visitor
|
||||
[secret_tcp_visitor]
|
||||
role = visitor
|
||||
type = stcp
|
||||
server_name = secret_tcp
|
||||
sk = abcdefg
|
||||
bind_addr = 127.0.0.1
|
||||
bind_port = 9000
|
||||
use_encryption = false
|
||||
use_compression = false
|
||||
|
||||
[p2p_tcp_visitor]
|
||||
role = visitor
|
||||
type = xtcp
|
||||
server_name = p2p_tcp
|
||||
sk = abcdefg
|
||||
bind_addr = 127.0.0.1
|
||||
bind_port = 9001
|
||||
use_encryption = false
|
||||
use_compression = false
|
||||
`)
|
||||
)
|
||||
|
||||
func Test_LoadClientCommonConf(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
expected := ClientCommonConf{
|
||||
ClientConfig: auth.ClientConfig{
|
||||
BaseConfig: auth.BaseConfig{
|
||||
AuthenticationMethod: "token",
|
||||
AuthenticateHeartBeats: false,
|
||||
AuthenticateNewWorkConns: false,
|
||||
},
|
||||
TokenConfig: auth.TokenConfig{
|
||||
Token: "12345678",
|
||||
},
|
||||
OidcClientConfig: auth.OidcClientConfig{
|
||||
OidcClientID: "client-id",
|
||||
OidcClientSecret: "client-secret",
|
||||
OidcAudience: "audience",
|
||||
OidcTokenEndpointURL: "endpoint_url",
|
||||
},
|
||||
},
|
||||
ServerAddr: "0.0.0.9",
|
||||
ServerPort: 7009,
|
||||
HTTPProxy: "http://user:passwd@192.168.1.128:8080",
|
||||
LogFile: "./frpc.log9",
|
||||
LogWay: "file",
|
||||
LogLevel: "info9",
|
||||
LogMaxDays: 39,
|
||||
DisableLogColor: false,
|
||||
AdminAddr: "127.0.0.9",
|
||||
AdminPort: 7409,
|
||||
AdminUser: "admin9",
|
||||
AdminPwd: "admin9",
|
||||
AssetsDir: "./static9",
|
||||
PoolCount: 59,
|
||||
TCPMux: true,
|
||||
User: "your_name",
|
||||
LoginFailExit: true,
|
||||
Protocol: "tcp",
|
||||
TLSEnable: true,
|
||||
TLSCertFile: "client.crt",
|
||||
TLSKeyFile: "client.key",
|
||||
TLSTrustedCaFile: "ca.crt",
|
||||
TLSServerName: "example.com",
|
||||
DNSServer: "8.8.8.9",
|
||||
Start: []string{"ssh", "dns"},
|
||||
HeartbeatInterval: 39,
|
||||
HeartbeatTimeout: 99,
|
||||
Metas: map[string]string{
|
||||
"var1": "123",
|
||||
"var2": "234",
|
||||
},
|
||||
UDPPacketSize: 1509,
|
||||
}
|
||||
|
||||
common, err := UnmarshalClientConfFromIni(testClientBytesWithFull)
|
||||
assert.NoError(err)
|
||||
assert.Equal(expected, common)
|
||||
}
|
||||
|
||||
func Test_LoadClientBasicConf(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
proxyExpected := map[string]ProxyConf{
|
||||
testUser + ".ssh": &TCPProxyConf{
|
||||
BaseProxyConf: BaseProxyConf{
|
||||
ProxyName: testUser + ".ssh",
|
||||
ProxyType: consts.TCPProxy,
|
||||
UseCompression: true,
|
||||
UseEncryption: true,
|
||||
Group: "test_group",
|
||||
GroupKey: "123456",
|
||||
BandwidthLimit: MustBandwidthQuantity("19MB"),
|
||||
Metas: map[string]string{
|
||||
"var1": "123",
|
||||
"var2": "234",
|
||||
},
|
||||
LocalSvrConf: LocalSvrConf{
|
||||
LocalIP: "127.0.0.9",
|
||||
LocalPort: 29,
|
||||
},
|
||||
HealthCheckConf: HealthCheckConf{
|
||||
HealthCheckType: consts.TCPProxy,
|
||||
HealthCheckTimeoutS: 3,
|
||||
HealthCheckMaxFailed: 3,
|
||||
HealthCheckIntervalS: 19,
|
||||
HealthCheckAddr: "127.0.0.9:29",
|
||||
},
|
||||
},
|
||||
RemotePort: 6009,
|
||||
},
|
||||
testUser + ".ssh_random": &TCPProxyConf{
|
||||
BaseProxyConf: BaseProxyConf{
|
||||
ProxyName: testUser + ".ssh_random",
|
||||
ProxyType: consts.TCPProxy,
|
||||
LocalSvrConf: LocalSvrConf{
|
||||
LocalIP: "127.0.0.9",
|
||||
LocalPort: 29,
|
||||
},
|
||||
},
|
||||
RemotePort: 9,
|
||||
},
|
||||
testUser + ".tcp_port_0": &TCPProxyConf{
|
||||
BaseProxyConf: BaseProxyConf{
|
||||
ProxyName: testUser + ".tcp_port_0",
|
||||
ProxyType: consts.TCPProxy,
|
||||
LocalSvrConf: LocalSvrConf{
|
||||
LocalIP: "127.0.0.9",
|
||||
LocalPort: 6010,
|
||||
},
|
||||
},
|
||||
RemotePort: 6010,
|
||||
},
|
||||
testUser + ".tcp_port_1": &TCPProxyConf{
|
||||
BaseProxyConf: BaseProxyConf{
|
||||
ProxyName: testUser + ".tcp_port_1",
|
||||
ProxyType: consts.TCPProxy,
|
||||
LocalSvrConf: LocalSvrConf{
|
||||
LocalIP: "127.0.0.9",
|
||||
LocalPort: 6011,
|
||||
},
|
||||
},
|
||||
RemotePort: 6011,
|
||||
},
|
||||
testUser + ".tcp_port_2": &TCPProxyConf{
|
||||
BaseProxyConf: BaseProxyConf{
|
||||
ProxyName: testUser + ".tcp_port_2",
|
||||
ProxyType: consts.TCPProxy,
|
||||
LocalSvrConf: LocalSvrConf{
|
||||
LocalIP: "127.0.0.9",
|
||||
LocalPort: 6019,
|
||||
},
|
||||
},
|
||||
RemotePort: 6019,
|
||||
},
|
||||
testUser + ".dns": &UDPProxyConf{
|
||||
BaseProxyConf: BaseProxyConf{
|
||||
ProxyName: testUser + ".dns",
|
||||
ProxyType: consts.UDPProxy,
|
||||
UseEncryption: true,
|
||||
UseCompression: true,
|
||||
LocalSvrConf: LocalSvrConf{
|
||||
LocalIP: "114.114.114.114",
|
||||
LocalPort: 59,
|
||||
},
|
||||
},
|
||||
RemotePort: 6009,
|
||||
},
|
||||
testUser + ".udp_port_0": &UDPProxyConf{
|
||||
BaseProxyConf: BaseProxyConf{
|
||||
ProxyName: testUser + ".udp_port_0",
|
||||
ProxyType: consts.UDPProxy,
|
||||
UseEncryption: true,
|
||||
UseCompression: true,
|
||||
LocalSvrConf: LocalSvrConf{
|
||||
LocalIP: "114.114.114.114",
|
||||
LocalPort: 6000,
|
||||
},
|
||||
},
|
||||
RemotePort: 6000,
|
||||
},
|
||||
testUser + ".udp_port_1": &UDPProxyConf{
|
||||
BaseProxyConf: BaseProxyConf{
|
||||
ProxyName: testUser + ".udp_port_1",
|
||||
ProxyType: consts.UDPProxy,
|
||||
UseEncryption: true,
|
||||
UseCompression: true,
|
||||
LocalSvrConf: LocalSvrConf{
|
||||
LocalIP: "114.114.114.114",
|
||||
LocalPort: 6010,
|
||||
},
|
||||
},
|
||||
RemotePort: 6010,
|
||||
},
|
||||
testUser + ".udp_port_2": &UDPProxyConf{
|
||||
BaseProxyConf: BaseProxyConf{
|
||||
ProxyName: testUser + ".udp_port_2",
|
||||
ProxyType: consts.UDPProxy,
|
||||
UseEncryption: true,
|
||||
UseCompression: true,
|
||||
LocalSvrConf: LocalSvrConf{
|
||||
LocalIP: "114.114.114.114",
|
||||
LocalPort: 6011,
|
||||
},
|
||||
},
|
||||
RemotePort: 6011,
|
||||
},
|
||||
testUser + ".web01": &HTTPProxyConf{
|
||||
BaseProxyConf: BaseProxyConf{
|
||||
ProxyName: testUser + ".web01",
|
||||
ProxyType: consts.HTTPProxy,
|
||||
UseCompression: true,
|
||||
UseEncryption: true,
|
||||
LocalSvrConf: LocalSvrConf{
|
||||
LocalIP: "127.0.0.9",
|
||||
LocalPort: 89,
|
||||
},
|
||||
HealthCheckConf: HealthCheckConf{
|
||||
HealthCheckType: consts.HTTPProxy,
|
||||
HealthCheckTimeoutS: 3,
|
||||
HealthCheckMaxFailed: 3,
|
||||
HealthCheckIntervalS: 19,
|
||||
HealthCheckURL: "http://127.0.0.9:89/status",
|
||||
},
|
||||
},
|
||||
DomainConf: DomainConf{
|
||||
CustomDomains: []string{"web02.yourdomain.com"},
|
||||
SubDomain: "web01",
|
||||
},
|
||||
Locations: []string{"/", "/pic"},
|
||||
HTTPUser: "admin",
|
||||
HTTPPwd: "admin",
|
||||
HostHeaderRewrite: "example.com",
|
||||
Headers: map[string]string{
|
||||
"X-From-Where": "frp",
|
||||
},
|
||||
},
|
||||
testUser + ".web02": &HTTPSProxyConf{
|
||||
BaseProxyConf: BaseProxyConf{
|
||||
ProxyName: testUser + ".web02",
|
||||
ProxyType: consts.HTTPSProxy,
|
||||
UseCompression: true,
|
||||
UseEncryption: true,
|
||||
LocalSvrConf: LocalSvrConf{
|
||||
LocalIP: "127.0.0.9",
|
||||
LocalPort: 8009,
|
||||
},
|
||||
ProxyProtocolVersion: "v2",
|
||||
},
|
||||
DomainConf: DomainConf{
|
||||
CustomDomains: []string{"web02.yourdomain.com"},
|
||||
SubDomain: "web01",
|
||||
},
|
||||
},
|
||||
testUser + ".secret_tcp": &STCPProxyConf{
|
||||
BaseProxyConf: BaseProxyConf{
|
||||
ProxyName: testUser + ".secret_tcp",
|
||||
ProxyType: consts.STCPProxy,
|
||||
LocalSvrConf: LocalSvrConf{
|
||||
LocalIP: "127.0.0.1",
|
||||
LocalPort: 22,
|
||||
},
|
||||
},
|
||||
Role: "server",
|
||||
Sk: "abcdefg",
|
||||
},
|
||||
testUser + ".p2p_tcp": &XTCPProxyConf{
|
||||
BaseProxyConf: BaseProxyConf{
|
||||
ProxyName: testUser + ".p2p_tcp",
|
||||
ProxyType: consts.XTCPProxy,
|
||||
LocalSvrConf: LocalSvrConf{
|
||||
LocalIP: "127.0.0.1",
|
||||
LocalPort: 22,
|
||||
},
|
||||
},
|
||||
Role: "server",
|
||||
Sk: "abcdefg",
|
||||
},
|
||||
testUser + ".tcpmuxhttpconnect": &TCPMuxProxyConf{
|
||||
BaseProxyConf: BaseProxyConf{
|
||||
ProxyName: testUser + ".tcpmuxhttpconnect",
|
||||
ProxyType: consts.TCPMuxProxy,
|
||||
LocalSvrConf: LocalSvrConf{
|
||||
LocalIP: "127.0.0.1",
|
||||
LocalPort: 10701,
|
||||
},
|
||||
},
|
||||
DomainConf: DomainConf{
|
||||
CustomDomains: []string{"tunnel1"},
|
||||
SubDomain: "",
|
||||
},
|
||||
Multiplexer: "httpconnect",
|
||||
},
|
||||
testUser + ".plugin_unix_domain_socket": &TCPProxyConf{
|
||||
BaseProxyConf: BaseProxyConf{
|
||||
ProxyName: testUser + ".plugin_unix_domain_socket",
|
||||
ProxyType: consts.TCPProxy,
|
||||
LocalSvrConf: LocalSvrConf{
|
||||
LocalIP: "127.0.0.1",
|
||||
Plugin: "unix_domain_socket",
|
||||
PluginParams: map[string]string{
|
||||
"plugin_unix_path": "/var/run/docker.sock",
|
||||
},
|
||||
},
|
||||
},
|
||||
RemotePort: 6003,
|
||||
},
|
||||
testUser + ".plugin_http_proxy": &TCPProxyConf{
|
||||
BaseProxyConf: BaseProxyConf{
|
||||
ProxyName: testUser + ".plugin_http_proxy",
|
||||
ProxyType: consts.TCPProxy,
|
||||
LocalSvrConf: LocalSvrConf{
|
||||
LocalIP: "127.0.0.1",
|
||||
Plugin: "http_proxy",
|
||||
PluginParams: map[string]string{
|
||||
"plugin_http_user": "abc",
|
||||
"plugin_http_passwd": "abc",
|
||||
},
|
||||
},
|
||||
},
|
||||
RemotePort: 6004,
|
||||
},
|
||||
testUser + ".plugin_socks5": &TCPProxyConf{
|
||||
BaseProxyConf: BaseProxyConf{
|
||||
ProxyName: testUser + ".plugin_socks5",
|
||||
ProxyType: consts.TCPProxy,
|
||||
LocalSvrConf: LocalSvrConf{
|
||||
LocalIP: "127.0.0.1",
|
||||
Plugin: "socks5",
|
||||
PluginParams: map[string]string{
|
||||
"plugin_user": "abc",
|
||||
"plugin_passwd": "abc",
|
||||
},
|
||||
},
|
||||
},
|
||||
RemotePort: 6005,
|
||||
},
|
||||
testUser + ".plugin_static_file": &TCPProxyConf{
|
||||
BaseProxyConf: BaseProxyConf{
|
||||
ProxyName: testUser + ".plugin_static_file",
|
||||
ProxyType: consts.TCPProxy,
|
||||
LocalSvrConf: LocalSvrConf{
|
||||
LocalIP: "127.0.0.1",
|
||||
Plugin: "static_file",
|
||||
PluginParams: map[string]string{
|
||||
"plugin_local_path": "/var/www/blog",
|
||||
"plugin_strip_prefix": "static",
|
||||
"plugin_http_user": "abc",
|
||||
"plugin_http_passwd": "abc",
|
||||
},
|
||||
},
|
||||
},
|
||||
RemotePort: 6006,
|
||||
},
|
||||
testUser + ".plugin_https2http": &HTTPSProxyConf{
|
||||
BaseProxyConf: BaseProxyConf{
|
||||
ProxyName: testUser + ".plugin_https2http",
|
||||
ProxyType: consts.HTTPSProxy,
|
||||
LocalSvrConf: LocalSvrConf{
|
||||
LocalIP: "127.0.0.1",
|
||||
Plugin: "https2http",
|
||||
PluginParams: map[string]string{
|
||||
"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",
|
||||
},
|
||||
},
|
||||
},
|
||||
DomainConf: DomainConf{
|
||||
CustomDomains: []string{"test.yourdomain.com"},
|
||||
},
|
||||
},
|
||||
testUser + ".plugin_http2https": &HTTPProxyConf{
|
||||
BaseProxyConf: BaseProxyConf{
|
||||
ProxyName: testUser + ".plugin_http2https",
|
||||
ProxyType: consts.HTTPProxy,
|
||||
LocalSvrConf: LocalSvrConf{
|
||||
LocalIP: "127.0.0.1",
|
||||
Plugin: "http2https",
|
||||
PluginParams: map[string]string{
|
||||
"plugin_local_addr": "127.0.0.1:443",
|
||||
"plugin_host_header_rewrite": "127.0.0.1",
|
||||
"plugin_header_X-From-Where": "frp",
|
||||
},
|
||||
},
|
||||
},
|
||||
DomainConf: DomainConf{
|
||||
CustomDomains: []string{"test.yourdomain.com"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
visitorExpected := map[string]VisitorConf{
|
||||
testUser + ".secret_tcp_visitor": &STCPVisitorConf{
|
||||
BaseVisitorConf: BaseVisitorConf{
|
||||
ProxyName: testUser + ".secret_tcp_visitor",
|
||||
ProxyType: consts.STCPProxy,
|
||||
Role: "visitor",
|
||||
Sk: "abcdefg",
|
||||
ServerName: testVisitorPrefix + "secret_tcp",
|
||||
BindAddr: "127.0.0.1",
|
||||
BindPort: 9000,
|
||||
},
|
||||
},
|
||||
testUser + ".p2p_tcp_visitor": &XTCPVisitorConf{
|
||||
BaseVisitorConf: BaseVisitorConf{
|
||||
ProxyName: testUser + ".p2p_tcp_visitor",
|
||||
ProxyType: consts.XTCPProxy,
|
||||
Role: "visitor",
|
||||
Sk: "abcdefg",
|
||||
ServerName: testProxyPrefix + "p2p_tcp",
|
||||
BindAddr: "127.0.0.1",
|
||||
BindPort: 9001,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
proxyActual, visitorActual, err := LoadAllProxyConfsFromIni(testUser, testClientBytesWithFull, nil)
|
||||
assert.NoError(err)
|
||||
assert.Equal(proxyExpected, proxyActual)
|
||||
assert.Equal(visitorExpected, visitorActual)
|
||||
|
||||
}
|
||||
1271
pkg/config/proxy.go
1271
pkg/config/proxy.go
File diff suppressed because it is too large
Load Diff
461
pkg/config/proxy_test.go
Normal file
461
pkg/config/proxy_test.go
Normal file
@@ -0,0 +1,461 @@
|
||||
// Copyright 2020 The frp Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package config
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/fatedier/frp/pkg/consts"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"gopkg.in/ini.v1"
|
||||
)
|
||||
|
||||
var (
|
||||
testLoadOptions = ini.LoadOptions{
|
||||
Insensitive: false,
|
||||
InsensitiveSections: false,
|
||||
InsensitiveKeys: false,
|
||||
IgnoreInlineComment: true,
|
||||
AllowBooleanKeys: true,
|
||||
}
|
||||
|
||||
testProxyPrefix = "test."
|
||||
)
|
||||
|
||||
func Test_Proxy_Interface(t *testing.T) {
|
||||
for name := range proxyConfTypeMap {
|
||||
NewConfByType(name)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_Proxy_UnmarshalFromIni(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
testcases := []struct {
|
||||
sname string
|
||||
source []byte
|
||||
expected ProxyConf
|
||||
}{
|
||||
|
||||
{
|
||||
sname: "ssh",
|
||||
source: []byte(`
|
||||
[ssh]
|
||||
# tcp | udp | http | https | stcp | xtcp, default is tcp
|
||||
type = tcp
|
||||
local_ip = 127.0.0.9
|
||||
local_port = 29
|
||||
bandwidth_limit = 19MB
|
||||
use_encryption
|
||||
use_compression
|
||||
remote_port = 6009
|
||||
group = test_group
|
||||
group_key = 123456
|
||||
health_check_type = tcp
|
||||
health_check_timeout_s = 3
|
||||
health_check_max_failed = 3
|
||||
health_check_interval_s = 19
|
||||
meta_var1 = 123
|
||||
meta_var2 = 234`),
|
||||
expected: &TCPProxyConf{
|
||||
BaseProxyConf: BaseProxyConf{
|
||||
ProxyName: testProxyPrefix + "ssh",
|
||||
ProxyType: consts.TCPProxy,
|
||||
UseCompression: true,
|
||||
UseEncryption: true,
|
||||
Group: "test_group",
|
||||
GroupKey: "123456",
|
||||
BandwidthLimit: MustBandwidthQuantity("19MB"),
|
||||
Metas: map[string]string{
|
||||
"var1": "123",
|
||||
"var2": "234",
|
||||
},
|
||||
LocalSvrConf: LocalSvrConf{
|
||||
LocalIP: "127.0.0.9",
|
||||
LocalPort: 29,
|
||||
},
|
||||
HealthCheckConf: HealthCheckConf{
|
||||
HealthCheckType: consts.TCPProxy,
|
||||
HealthCheckTimeoutS: 3,
|
||||
HealthCheckMaxFailed: 3,
|
||||
HealthCheckIntervalS: 19,
|
||||
HealthCheckAddr: "127.0.0.9:29",
|
||||
},
|
||||
},
|
||||
RemotePort: 6009,
|
||||
},
|
||||
},
|
||||
{
|
||||
sname: "ssh_random",
|
||||
source: []byte(`
|
||||
[ssh_random]
|
||||
type = tcp
|
||||
local_ip = 127.0.0.9
|
||||
local_port = 29
|
||||
remote_port = 9
|
||||
`),
|
||||
expected: &TCPProxyConf{
|
||||
BaseProxyConf: BaseProxyConf{
|
||||
ProxyName: testProxyPrefix + "ssh_random",
|
||||
ProxyType: consts.TCPProxy,
|
||||
LocalSvrConf: LocalSvrConf{
|
||||
LocalIP: "127.0.0.9",
|
||||
LocalPort: 29,
|
||||
},
|
||||
},
|
||||
RemotePort: 9,
|
||||
},
|
||||
},
|
||||
{
|
||||
sname: "dns",
|
||||
source: []byte(`
|
||||
[dns]
|
||||
type = udp
|
||||
local_ip = 114.114.114.114
|
||||
local_port = 59
|
||||
remote_port = 6009
|
||||
use_encryption
|
||||
use_compression
|
||||
`),
|
||||
expected: &UDPProxyConf{
|
||||
BaseProxyConf: BaseProxyConf{
|
||||
ProxyName: testProxyPrefix + "dns",
|
||||
ProxyType: consts.UDPProxy,
|
||||
UseEncryption: true,
|
||||
UseCompression: true,
|
||||
LocalSvrConf: LocalSvrConf{
|
||||
LocalIP: "114.114.114.114",
|
||||
LocalPort: 59,
|
||||
},
|
||||
},
|
||||
RemotePort: 6009,
|
||||
},
|
||||
},
|
||||
{
|
||||
sname: "web01",
|
||||
source: []byte(`
|
||||
[web01]
|
||||
type = http
|
||||
local_ip = 127.0.0.9
|
||||
local_port = 89
|
||||
use_encryption
|
||||
use_compression
|
||||
http_user = admin
|
||||
http_pwd = admin
|
||||
subdomain = web01
|
||||
custom_domains = web02.yourdomain.com
|
||||
locations = /,/pic
|
||||
host_header_rewrite = example.com
|
||||
header_X-From-Where = frp
|
||||
health_check_type = http
|
||||
health_check_url = /status
|
||||
health_check_interval_s = 19
|
||||
health_check_max_failed = 3
|
||||
health_check_timeout_s = 3
|
||||
`),
|
||||
expected: &HTTPProxyConf{
|
||||
BaseProxyConf: BaseProxyConf{
|
||||
ProxyName: testProxyPrefix + "web01",
|
||||
ProxyType: consts.HTTPProxy,
|
||||
UseCompression: true,
|
||||
UseEncryption: true,
|
||||
LocalSvrConf: LocalSvrConf{
|
||||
LocalIP: "127.0.0.9",
|
||||
LocalPort: 89,
|
||||
},
|
||||
HealthCheckConf: HealthCheckConf{
|
||||
HealthCheckType: consts.HTTPProxy,
|
||||
HealthCheckTimeoutS: 3,
|
||||
HealthCheckMaxFailed: 3,
|
||||
HealthCheckIntervalS: 19,
|
||||
HealthCheckURL: "http://127.0.0.9:89/status",
|
||||
},
|
||||
},
|
||||
DomainConf: DomainConf{
|
||||
CustomDomains: []string{"web02.yourdomain.com"},
|
||||
SubDomain: "web01",
|
||||
},
|
||||
Locations: []string{"/", "/pic"},
|
||||
HTTPUser: "admin",
|
||||
HTTPPwd: "admin",
|
||||
HostHeaderRewrite: "example.com",
|
||||
Headers: map[string]string{
|
||||
"X-From-Where": "frp",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
sname: "web02",
|
||||
source: []byte(`
|
||||
[web02]
|
||||
type = https
|
||||
local_ip = 127.0.0.9
|
||||
local_port = 8009
|
||||
use_encryption
|
||||
use_compression
|
||||
subdomain = web01
|
||||
custom_domains = web02.yourdomain.com
|
||||
proxy_protocol_version = v2
|
||||
`),
|
||||
expected: &HTTPSProxyConf{
|
||||
BaseProxyConf: BaseProxyConf{
|
||||
ProxyName: testProxyPrefix + "web02",
|
||||
ProxyType: consts.HTTPSProxy,
|
||||
UseCompression: true,
|
||||
UseEncryption: true,
|
||||
LocalSvrConf: LocalSvrConf{
|
||||
LocalIP: "127.0.0.9",
|
||||
LocalPort: 8009,
|
||||
},
|
||||
ProxyProtocolVersion: "v2",
|
||||
},
|
||||
DomainConf: DomainConf{
|
||||
CustomDomains: []string{"web02.yourdomain.com"},
|
||||
SubDomain: "web01",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
sname: "secret_tcp",
|
||||
source: []byte(`
|
||||
[secret_tcp]
|
||||
type = stcp
|
||||
sk = abcdefg
|
||||
local_ip = 127.0.0.1
|
||||
local_port = 22
|
||||
use_encryption = false
|
||||
use_compression = false
|
||||
`),
|
||||
expected: &STCPProxyConf{
|
||||
BaseProxyConf: BaseProxyConf{
|
||||
ProxyName: testProxyPrefix + "secret_tcp",
|
||||
ProxyType: consts.STCPProxy,
|
||||
LocalSvrConf: LocalSvrConf{
|
||||
LocalIP: "127.0.0.1",
|
||||
LocalPort: 22,
|
||||
},
|
||||
},
|
||||
Role: "server",
|
||||
Sk: "abcdefg",
|
||||
},
|
||||
},
|
||||
{
|
||||
sname: "p2p_tcp",
|
||||
source: []byte(`
|
||||
[p2p_tcp]
|
||||
type = xtcp
|
||||
sk = abcdefg
|
||||
local_ip = 127.0.0.1
|
||||
local_port = 22
|
||||
use_encryption = false
|
||||
use_compression = false
|
||||
`),
|
||||
expected: &XTCPProxyConf{
|
||||
BaseProxyConf: BaseProxyConf{
|
||||
ProxyName: testProxyPrefix + "p2p_tcp",
|
||||
ProxyType: consts.XTCPProxy,
|
||||
LocalSvrConf: LocalSvrConf{
|
||||
LocalIP: "127.0.0.1",
|
||||
LocalPort: 22,
|
||||
},
|
||||
},
|
||||
Role: "server",
|
||||
Sk: "abcdefg",
|
||||
},
|
||||
},
|
||||
{
|
||||
sname: "tcpmuxhttpconnect",
|
||||
source: []byte(`
|
||||
[tcpmuxhttpconnect]
|
||||
type = tcpmux
|
||||
multiplexer = httpconnect
|
||||
local_ip = 127.0.0.1
|
||||
local_port = 10701
|
||||
custom_domains = tunnel1
|
||||
`),
|
||||
expected: &TCPMuxProxyConf{
|
||||
BaseProxyConf: BaseProxyConf{
|
||||
ProxyName: testProxyPrefix + "tcpmuxhttpconnect",
|
||||
ProxyType: consts.TCPMuxProxy,
|
||||
LocalSvrConf: LocalSvrConf{
|
||||
LocalIP: "127.0.0.1",
|
||||
LocalPort: 10701,
|
||||
},
|
||||
},
|
||||
DomainConf: DomainConf{
|
||||
CustomDomains: []string{"tunnel1"},
|
||||
SubDomain: "",
|
||||
},
|
||||
Multiplexer: "httpconnect",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, c := range testcases {
|
||||
f, err := ini.LoadSources(testLoadOptions, c.source)
|
||||
assert.NoError(err)
|
||||
|
||||
proxyType := f.Section(c.sname).Key("type").String()
|
||||
assert.NotEmpty(proxyType)
|
||||
|
||||
actual := DefaultProxyConf(proxyType)
|
||||
assert.NotNil(actual)
|
||||
|
||||
err = actual.UnmarshalFromIni(testProxyPrefix, c.sname, f.Section(c.sname))
|
||||
assert.NoError(err)
|
||||
assert.Equal(c.expected, actual)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_RangeProxy_UnmarshalFromIni(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
testcases := []struct {
|
||||
sname string
|
||||
source []byte
|
||||
expected map[string]ProxyConf
|
||||
}{
|
||||
{
|
||||
sname: "range:tcp_port",
|
||||
source: []byte(`
|
||||
[range:tcp_port]
|
||||
type = tcp
|
||||
local_ip = 127.0.0.9
|
||||
local_port = 6010-6011,6019
|
||||
remote_port = 6010-6011,6019
|
||||
use_encryption = false
|
||||
use_compression = false
|
||||
`),
|
||||
expected: map[string]ProxyConf{
|
||||
"tcp_port_0": &TCPProxyConf{
|
||||
BaseProxyConf: BaseProxyConf{
|
||||
ProxyName: testProxyPrefix + "tcp_port_0",
|
||||
ProxyType: consts.TCPProxy,
|
||||
LocalSvrConf: LocalSvrConf{
|
||||
LocalIP: "127.0.0.9",
|
||||
LocalPort: 6010,
|
||||
},
|
||||
},
|
||||
RemotePort: 6010,
|
||||
},
|
||||
"tcp_port_1": &TCPProxyConf{
|
||||
BaseProxyConf: BaseProxyConf{
|
||||
ProxyName: testProxyPrefix + "tcp_port_1",
|
||||
ProxyType: consts.TCPProxy,
|
||||
LocalSvrConf: LocalSvrConf{
|
||||
LocalIP: "127.0.0.9",
|
||||
LocalPort: 6011,
|
||||
},
|
||||
},
|
||||
RemotePort: 6011,
|
||||
},
|
||||
"tcp_port_2": &TCPProxyConf{
|
||||
BaseProxyConf: BaseProxyConf{
|
||||
ProxyName: testProxyPrefix + "tcp_port_2",
|
||||
ProxyType: consts.TCPProxy,
|
||||
LocalSvrConf: LocalSvrConf{
|
||||
LocalIP: "127.0.0.9",
|
||||
LocalPort: 6019,
|
||||
},
|
||||
},
|
||||
RemotePort: 6019,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
sname: "range:udp_port",
|
||||
source: []byte(`
|
||||
[range:udp_port]
|
||||
type = udp
|
||||
local_ip = 114.114.114.114
|
||||
local_port = 6000,6010-6011
|
||||
remote_port = 6000,6010-6011
|
||||
use_encryption
|
||||
use_compression
|
||||
`),
|
||||
expected: map[string]ProxyConf{
|
||||
"udp_port_0": &UDPProxyConf{
|
||||
BaseProxyConf: BaseProxyConf{
|
||||
ProxyName: testProxyPrefix + "udp_port_0",
|
||||
ProxyType: consts.UDPProxy,
|
||||
UseEncryption: true,
|
||||
UseCompression: true,
|
||||
LocalSvrConf: LocalSvrConf{
|
||||
LocalIP: "114.114.114.114",
|
||||
LocalPort: 6000,
|
||||
},
|
||||
},
|
||||
RemotePort: 6000,
|
||||
},
|
||||
"udp_port_1": &UDPProxyConf{
|
||||
BaseProxyConf: BaseProxyConf{
|
||||
ProxyName: testProxyPrefix + "udp_port_1",
|
||||
ProxyType: consts.UDPProxy,
|
||||
UseEncryption: true,
|
||||
UseCompression: true,
|
||||
LocalSvrConf: LocalSvrConf{
|
||||
LocalIP: "114.114.114.114",
|
||||
LocalPort: 6010,
|
||||
},
|
||||
},
|
||||
RemotePort: 6010,
|
||||
},
|
||||
"udp_port_2": &UDPProxyConf{
|
||||
BaseProxyConf: BaseProxyConf{
|
||||
ProxyName: testProxyPrefix + "udp_port_2",
|
||||
ProxyType: consts.UDPProxy,
|
||||
UseEncryption: true,
|
||||
UseCompression: true,
|
||||
LocalSvrConf: LocalSvrConf{
|
||||
LocalIP: "114.114.114.114",
|
||||
LocalPort: 6011,
|
||||
},
|
||||
},
|
||||
RemotePort: 6011,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, c := range testcases {
|
||||
|
||||
f, err := ini.LoadSources(testLoadOptions, c.source)
|
||||
assert.NoError(err)
|
||||
|
||||
actual := make(map[string]ProxyConf)
|
||||
s := f.Section(c.sname)
|
||||
|
||||
err = renderRangeProxyTemplates(f, s)
|
||||
assert.NoError(err)
|
||||
|
||||
f.DeleteSection(ini.DefaultSection)
|
||||
f.DeleteSection(c.sname)
|
||||
|
||||
for _, section := range f.Sections() {
|
||||
proxyType := section.Key("type").String()
|
||||
newsname := section.Name()
|
||||
|
||||
tmp := DefaultProxyConf(proxyType)
|
||||
err = tmp.UnmarshalFromIni(testProxyPrefix, newsname, section)
|
||||
assert.NoError(err)
|
||||
|
||||
actual[newsname] = tmp
|
||||
}
|
||||
|
||||
assert.Equal(c.expected, actual)
|
||||
}
|
||||
|
||||
}
|
||||
284
pkg/config/server.go
Normal file
284
pkg/config/server.go
Normal file
@@ -0,0 +1,284 @@
|
||||
// Copyright 2020 The frp Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/fatedier/frp/pkg/auth"
|
||||
plugin "github.com/fatedier/frp/pkg/plugin/server"
|
||||
"github.com/fatedier/frp/pkg/util/util"
|
||||
|
||||
"gopkg.in/ini.v1"
|
||||
)
|
||||
|
||||
// ServerCommonConf contains information for a server service. It is
|
||||
// recommended to use GetDefaultServerConf instead of creating this object
|
||||
// directly, so that all unspecified fields have reasonable default values.
|
||||
type ServerCommonConf struct {
|
||||
auth.ServerConfig `ini:",extends" json:"inline"`
|
||||
|
||||
// BindAddr specifies the address that the server binds to. By default,
|
||||
// this value is "0.0.0.0".
|
||||
BindAddr string `ini:"bind_addr" json:"bind_addr"`
|
||||
// BindPort specifies the port that the server listens on. By default, this
|
||||
// value is 7000.
|
||||
BindPort int `ini:"bind_port" 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 `ini:"bind_udp_port" json:"bind_udp_port"`
|
||||
// KCPBindPort 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 `ini:"kcp_bind_port" 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 `ini:"proxy_bind_addr" json:"proxy_bind_addr"`
|
||||
// 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 `ini:"vhost_http_port" json:"vhost_http_port"`
|
||||
// VhostHTTPSPort specifies the port that the server listens for HTTPS
|
||||
// Vhost requests. If this value is 0, the server will not listen for HTTPS
|
||||
// requests. By default, this value is 0.
|
||||
VhostHTTPSPort int `ini:"vhost_https_port" json:"vhost_https_port"`
|
||||
// TCPMuxHTTPConnectPort specifies the port that the server listens for TCP
|
||||
// HTTP CONNECT requests. If the value is 0, the server will not multiplex TCP
|
||||
// requests on one single port. If it's not - it will listen on this value for
|
||||
// HTTP CONNECT requests. By default, this value is 0.
|
||||
TCPMuxHTTPConnectPort int `ini:"tcpmux_httpconnect_port" json:"tcpmux_httpconnect_port"`
|
||||
// VhostHTTPTimeout specifies the response header timeout for the Vhost
|
||||
// HTTP server, in seconds. By default, this value is 60.
|
||||
VhostHTTPTimeout int64 `ini:"vhost_http_timeout" json:"vhost_http_timeout"`
|
||||
// DashboardAddr specifies the address that the dashboard binds to. By
|
||||
// default, this value is "0.0.0.0".
|
||||
DashboardAddr string `ini:"dashboard_addr" json:"dashboard_addr"`
|
||||
// DashboardPort specifies the port that the dashboard listens on. If this
|
||||
// value is 0, the dashboard will not be started. By default, this value is
|
||||
// 0.
|
||||
DashboardPort int `ini:"dashboard_port" json:"dashboard_port"`
|
||||
// DashboardUser specifies the username that the dashboard will use for
|
||||
// login. By default, this value is "admin".
|
||||
DashboardUser string `ini:"dashboard_user" json:"dashboard_user"`
|
||||
// DashboardUser specifies the password that the dashboard will use for
|
||||
// login. By default, this value is "admin".
|
||||
DashboardPwd string `ini:"dashboard_pwd" json:"dashboard_pwd"`
|
||||
// EnablePrometheus will export prometheus metrics on {dashboard_addr}:{dashboard_port}
|
||||
// in /metrics api.
|
||||
EnablePrometheus bool `ini:"enable_prometheus" json:"enable_prometheus"`
|
||||
// AssetsDir specifies the local directory that the dashboard 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 `ini:"assets_dir" json:"assets_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 `ini:"log_file" 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 `ini:"log_way" 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 `ini:"log_level" 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 `ini:"log_max_days" json:"log_max_days"`
|
||||
// DisableLogColor disables log colors when LogWay == "console" when set to
|
||||
// true. By default, this value is false.
|
||||
DisableLogColor bool `ini:"disable_log_color" json:"disable_log_color"`
|
||||
// DetailedErrorsToClient defines whether to send the specific error (with
|
||||
// debug info) to frpc. By default, this value is true.
|
||||
DetailedErrorsToClient bool `ini:"detailed_errors_to_client" json:"detailed_errors_to_client"`
|
||||
|
||||
// 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 `ini:"subdomain_host" json:"subdomain_host"`
|
||||
// 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 `ini:"tcp_mux" 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 `ini:"custom_404_page" json:"custom_404_page"`
|
||||
|
||||
// AllowPorts specifies a set of ports that clients are able to proxy to.
|
||||
// If the length of this value is 0, all ports are allowed. By default,
|
||||
// this value is an empty set.
|
||||
AllowPorts map[int]struct{} `ini:"-" json:"-"`
|
||||
// MaxPoolCount specifies the maximum pool size for each proxy. By default,
|
||||
// this value is 5.
|
||||
MaxPoolCount int64 `ini:"max_pool_count" 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 `ini:"max_ports_per_client" json:"max_ports_per_client"`
|
||||
// TLSOnly specifies whether to only accept TLS-encrypted connections.
|
||||
// By default, the value is false.
|
||||
TLSOnly bool `ini:"tls_only" json:"tls_only"`
|
||||
// TLSCertFile specifies the path of the cert file that the server will
|
||||
// load. If "tls_cert_file", "tls_key_file" are valid, the server will use this
|
||||
// supplied tls configuration. Otherwise, the server will use the tls
|
||||
// configuration generated by itself.
|
||||
TLSCertFile string `ini:"tls_cert_file" json:"tls_cert_file"`
|
||||
// TLSKeyFile specifies the path of the secret key that the server will
|
||||
// load. If "tls_cert_file", "tls_key_file" are valid, the server will use this
|
||||
// supplied tls configuration. Otherwise, the server will use the tls
|
||||
// configuration generated by itself.
|
||||
TLSKeyFile string `ini:"tls_key_file" json:"tls_key_file"`
|
||||
// TLSTrustedCaFile specifies the paths of the client cert files that the
|
||||
// server will load. It only works when "tls_only" is true. If
|
||||
// "tls_trusted_ca_file" is valid, the server will verify each client's
|
||||
// certificate.
|
||||
TLSTrustedCaFile string `ini:"tls_trusted_ca_file" json:"tls_trusted_ca_file"`
|
||||
// HeartBeatTimeout specifies the maximum time to wait for a heartbeat
|
||||
// before terminating the connection. It is not recommended to change this
|
||||
// value. By default, this value is 90.
|
||||
HeartbeatTimeout int64 `ini:"heartbeat_timeout" json:"heartbeat_timeout"`
|
||||
// UserConnTimeout specifies the maximum time to wait for a work
|
||||
// connection. By default, this value is 10.
|
||||
UserConnTimeout int64 `ini:"user_conn_timeout" json:"user_conn_timeout"`
|
||||
// HTTPPlugins specify the server plugins support HTTP protocol.
|
||||
HTTPPlugins map[string]plugin.HTTPPluginOptions `ini:"-" json:"http_plugins"`
|
||||
// UDPPacketSize specifies the UDP packet size
|
||||
// By default, this value is 1500
|
||||
UDPPacketSize int64 `ini:"udp_packet_size" json:"udp_packet_size"`
|
||||
}
|
||||
|
||||
// GetDefaultServerConf returns a server configuration with reasonable
|
||||
// defaults.
|
||||
func GetDefaultServerConf() ServerCommonConf {
|
||||
return ServerCommonConf{
|
||||
ServerConfig: auth.GetDefaultServerConf(),
|
||||
BindAddr: "0.0.0.0",
|
||||
BindPort: 7000,
|
||||
BindUDPPort: 0,
|
||||
KCPBindPort: 0,
|
||||
ProxyBindAddr: "0.0.0.0",
|
||||
VhostHTTPPort: 0,
|
||||
VhostHTTPSPort: 0,
|
||||
TCPMuxHTTPConnectPort: 0,
|
||||
VhostHTTPTimeout: 60,
|
||||
DashboardAddr: "0.0.0.0",
|
||||
DashboardPort: 0,
|
||||
DashboardUser: "admin",
|
||||
DashboardPwd: "admin",
|
||||
EnablePrometheus: false,
|
||||
AssetsDir: "",
|
||||
LogFile: "console",
|
||||
LogWay: "console",
|
||||
LogLevel: "info",
|
||||
LogMaxDays: 3,
|
||||
DisableLogColor: false,
|
||||
DetailedErrorsToClient: true,
|
||||
SubDomainHost: "",
|
||||
TCPMux: true,
|
||||
AllowPorts: make(map[int]struct{}),
|
||||
MaxPoolCount: 5,
|
||||
MaxPortsPerClient: 0,
|
||||
TLSOnly: false,
|
||||
TLSCertFile: "",
|
||||
TLSKeyFile: "",
|
||||
TLSTrustedCaFile: "",
|
||||
HeartbeatTimeout: 90,
|
||||
UserConnTimeout: 10,
|
||||
Custom404Page: "",
|
||||
HTTPPlugins: make(map[string]plugin.HTTPPluginOptions),
|
||||
UDPPacketSize: 1500,
|
||||
}
|
||||
}
|
||||
|
||||
func (cfg *ServerCommonConf) Check() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func UnmarshalServerConfFromIni(source interface{}) (ServerCommonConf, error) {
|
||||
|
||||
f, err := ini.LoadSources(ini.LoadOptions{
|
||||
Insensitive: false,
|
||||
InsensitiveSections: false,
|
||||
InsensitiveKeys: false,
|
||||
IgnoreInlineComment: true,
|
||||
AllowBooleanKeys: true,
|
||||
}, source)
|
||||
if err != nil {
|
||||
return ServerCommonConf{}, err
|
||||
}
|
||||
|
||||
s, err := f.GetSection("common")
|
||||
if err != nil {
|
||||
// TODO: add error info
|
||||
return ServerCommonConf{}, err
|
||||
}
|
||||
|
||||
common := GetDefaultServerConf()
|
||||
err = s.MapTo(&common)
|
||||
if err != nil {
|
||||
return ServerCommonConf{}, err
|
||||
}
|
||||
|
||||
// allow_ports
|
||||
allowPortStr := s.Key("allow_ports").String()
|
||||
if allowPortStr != "" {
|
||||
allowPorts, err := util.ParseRangeNumbers(allowPortStr)
|
||||
if err != nil {
|
||||
return ServerCommonConf{}, fmt.Errorf("Parse conf error: allow_ports: %v", err)
|
||||
}
|
||||
for _, port := range allowPorts {
|
||||
common.AllowPorts[int(port)] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
// plugin.xxx
|
||||
pluginOpts := make(map[string]plugin.HTTPPluginOptions)
|
||||
for _, section := range f.Sections() {
|
||||
name := section.Name()
|
||||
if !strings.HasPrefix(name, "plugin.") {
|
||||
continue
|
||||
}
|
||||
|
||||
opt, err := loadHTTPPluginOpt(section)
|
||||
if err != nil {
|
||||
return ServerCommonConf{}, err
|
||||
}
|
||||
|
||||
pluginOpts[opt.Name] = *opt
|
||||
}
|
||||
common.HTTPPlugins = pluginOpts
|
||||
|
||||
return common, nil
|
||||
}
|
||||
|
||||
func loadHTTPPluginOpt(section *ini.Section) (*plugin.HTTPPluginOptions, error) {
|
||||
name := strings.TrimSpace(strings.TrimPrefix(section.Name(), "plugin."))
|
||||
|
||||
opt := new(plugin.HTTPPluginOptions)
|
||||
err := section.MapTo(opt)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
opt.Name = name
|
||||
|
||||
return opt, nil
|
||||
}
|
||||
@@ -1,482 +0,0 @@
|
||||
// Copyright 2016 fatedier, fatedier@gmail.com
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/fatedier/frp/pkg/auth"
|
||||
plugin "github.com/fatedier/frp/pkg/plugin/server"
|
||||
"github.com/fatedier/frp/pkg/util/util"
|
||||
|
||||
ini "github.com/vaughan0/go-ini"
|
||||
)
|
||||
|
||||
// ServerCommonConf contains information for a server service. It is
|
||||
// recommended to use GetDefaultServerConf instead of creating this object
|
||||
// directly, so that all unspecified fields have reasonable default values.
|
||||
type ServerCommonConf struct {
|
||||
auth.ServerConfig
|
||||
// BindAddr specifies the address that the server binds to. By default,
|
||||
// this value is "0.0.0.0".
|
||||
BindAddr string `json:"bind_addr"`
|
||||
// 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"`
|
||||
// KCPBindPort 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"`
|
||||
// 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"`
|
||||
// VhostHTTPSPort specifies the port that the server listens for HTTPS
|
||||
// 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"`
|
||||
// TCPMuxHTTPConnectPort specifies the port that the server listens for TCP
|
||||
// HTTP CONNECT requests. If the value is 0, the server will not multiplex TCP
|
||||
// requests on one single port. If it's not - it will listen on this value for
|
||||
// HTTP CONNECT requests. By default, this value is 0.
|
||||
TCPMuxHTTPConnectPort int `json:"tcpmux_httpconnect_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"`
|
||||
// DashboardAddr specifies the address that the dashboard binds to. By
|
||||
// default, this value is "0.0.0.0".
|
||||
DashboardAddr string `json:"dashboard_addr"`
|
||||
// DashboardPort specifies the port that the dashboard listens on. If this
|
||||
// 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 specifies the password that the dashboard will use for
|
||||
// login. By default, this value is "admin".
|
||||
DashboardPwd string `json:"dashboard_pwd"`
|
||||
// EnablePrometheus will export prometheus metrics on {dashboard_addr}:{dashboard_port}
|
||||
// in /metrics api.
|
||||
EnablePrometheus bool `json:"enable_prometheus"`
|
||||
// AssetsDir specifies the local directory that the dashboard 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"`
|
||||
// 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"`
|
||||
// DetailedErrorsToClient defines whether to send the specific error (with
|
||||
// debug info) to frpc. By default, this value is true.
|
||||
DetailedErrorsToClient bool `json:"detailed_errors_to_client"`
|
||||
|
||||
// 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"`
|
||||
// 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 specifies a set of ports that clients are able to proxy to.
|
||||
// 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"`
|
||||
// TLSOnly specifies whether to only accept TLS-encrypted connections.
|
||||
// By default, the value is false.
|
||||
TLSOnly bool `json:"tls_only"`
|
||||
// TLSCertFile specifies the path of the cert file that the server will
|
||||
// load. If "tls_cert_file", "tls_key_file" are valid, the server will use this
|
||||
// supplied tls configuration. Otherwise, the server will use the tls
|
||||
// configuration generated by itself.
|
||||
TLSCertFile string `json:"tls_cert_file"`
|
||||
// TLSKeyFile specifies the path of the secret key that the server will
|
||||
// load. If "tls_cert_file", "tls_key_file" are valid, the server will use this
|
||||
// supplied tls configuration. Otherwise, the server will use the tls
|
||||
// configuration generated by itself.
|
||||
TLSKeyFile string `json:"tls_key_file"`
|
||||
// TLSTrustedCaFile specifies the paths of the client cert files that the
|
||||
// server will load. It only works when "tls_only" is true. If
|
||||
// "tls_trusted_ca_file" is valid, the server will verify each client's
|
||||
// certificate.
|
||||
TLSTrustedCaFile string `json:"tls_trusted_ca_file"`
|
||||
// HeartBeatTimeout specifies the maximum time to wait for a heartbeat
|
||||
// before terminating the connection. It is not recommended to change this
|
||||
// value. By default, this value is 90.
|
||||
HeartbeatTimeout int64 `json:"heartbeat_timeout"`
|
||||
// UserConnTimeout specifies the maximum time to wait for a work
|
||||
// connection. By default, this value is 10.
|
||||
UserConnTimeout int64 `json:"user_conn_timeout"`
|
||||
// HTTPPlugins specify the server plugins support HTTP protocol.
|
||||
HTTPPlugins map[string]plugin.HTTPPluginOptions `json:"http_plugins"`
|
||||
// UDPPacketSize specifies the UDP packet size
|
||||
// By default, this value is 1500
|
||||
UDPPacketSize int64 `json:"udp_packet_size"`
|
||||
}
|
||||
|
||||
// GetDefaultServerConf returns a server configuration with reasonable
|
||||
// defaults.
|
||||
func GetDefaultServerConf() ServerCommonConf {
|
||||
return ServerCommonConf{
|
||||
BindAddr: "0.0.0.0",
|
||||
BindPort: 7000,
|
||||
BindUDPPort: 0,
|
||||
KCPBindPort: 0,
|
||||
ProxyBindAddr: "0.0.0.0",
|
||||
VhostHTTPPort: 0,
|
||||
VhostHTTPSPort: 0,
|
||||
TCPMuxHTTPConnectPort: 0,
|
||||
VhostHTTPTimeout: 60,
|
||||
DashboardAddr: "0.0.0.0",
|
||||
DashboardPort: 0,
|
||||
DashboardUser: "admin",
|
||||
DashboardPwd: "admin",
|
||||
EnablePrometheus: false,
|
||||
AssetsDir: "",
|
||||
LogFile: "console",
|
||||
LogWay: "console",
|
||||
LogLevel: "info",
|
||||
LogMaxDays: 3,
|
||||
DisableLogColor: false,
|
||||
DetailedErrorsToClient: true,
|
||||
SubDomainHost: "",
|
||||
TCPMux: true,
|
||||
AllowPorts: make(map[int]struct{}),
|
||||
MaxPoolCount: 5,
|
||||
MaxPortsPerClient: 0,
|
||||
TLSOnly: false,
|
||||
TLSCertFile: "",
|
||||
TLSKeyFile: "",
|
||||
TLSTrustedCaFile: "",
|
||||
HeartbeatTimeout: 90,
|
||||
UserConnTimeout: 10,
|
||||
Custom404Page: "",
|
||||
HTTPPlugins: make(map[string]plugin.HTTPPluginOptions),
|
||||
UDPPacketSize: 1500,
|
||||
}
|
||||
}
|
||||
|
||||
// UnmarshalServerConfFromIni parses the contents of a server configuration ini
|
||||
// file and returns the resulting server configuration.
|
||||
func UnmarshalServerConfFromIni(content string) (cfg ServerCommonConf, err error) {
|
||||
cfg = GetDefaultServerConf()
|
||||
|
||||
conf, err := ini.Load(strings.NewReader(content))
|
||||
if err != nil {
|
||||
err = fmt.Errorf("parse ini conf file error: %v", err)
|
||||
return ServerCommonConf{}, err
|
||||
}
|
||||
|
||||
UnmarshalPluginsFromIni(conf, &cfg)
|
||||
|
||||
cfg.ServerConfig = auth.UnmarshalServerConfFromIni(conf)
|
||||
|
||||
var (
|
||||
tmpStr string
|
||||
ok bool
|
||||
v int64
|
||||
)
|
||||
if tmpStr, ok = conf.Get("common", "bind_addr"); ok {
|
||||
cfg.BindAddr = tmpStr
|
||||
}
|
||||
|
||||
if tmpStr, ok = conf.Get("common", "bind_port"); ok {
|
||||
if v, err = strconv.ParseInt(tmpStr, 10, 64); err != nil {
|
||||
err = fmt.Errorf("Parse conf error: invalid bind_port")
|
||||
return
|
||||
}
|
||||
cfg.BindPort = int(v)
|
||||
}
|
||||
|
||||
if tmpStr, ok = conf.Get("common", "bind_udp_port"); ok {
|
||||
if v, err = strconv.ParseInt(tmpStr, 10, 64); err != nil {
|
||||
err = fmt.Errorf("Parse conf error: invalid bind_udp_port")
|
||||
return
|
||||
}
|
||||
cfg.BindUDPPort = int(v)
|
||||
}
|
||||
|
||||
if tmpStr, ok = conf.Get("common", "kcp_bind_port"); ok {
|
||||
if v, err = strconv.ParseInt(tmpStr, 10, 64); err != nil {
|
||||
err = fmt.Errorf("Parse conf error: invalid kcp_bind_port")
|
||||
return
|
||||
}
|
||||
cfg.KCPBindPort = int(v)
|
||||
}
|
||||
|
||||
if tmpStr, ok = conf.Get("common", "proxy_bind_addr"); ok {
|
||||
cfg.ProxyBindAddr = tmpStr
|
||||
} else {
|
||||
cfg.ProxyBindAddr = cfg.BindAddr
|
||||
}
|
||||
|
||||
if tmpStr, ok = conf.Get("common", "vhost_http_port"); ok {
|
||||
if v, err = strconv.ParseInt(tmpStr, 10, 64); err != nil {
|
||||
err = fmt.Errorf("Parse conf error: invalid vhost_http_port")
|
||||
return
|
||||
}
|
||||
cfg.VhostHTTPPort = int(v)
|
||||
} else {
|
||||
cfg.VhostHTTPPort = 0
|
||||
}
|
||||
|
||||
if tmpStr, ok = conf.Get("common", "vhost_https_port"); ok {
|
||||
if v, err = strconv.ParseInt(tmpStr, 10, 64); err != nil {
|
||||
err = fmt.Errorf("Parse conf error: invalid vhost_https_port")
|
||||
return
|
||||
}
|
||||
cfg.VhostHTTPSPort = int(v)
|
||||
} else {
|
||||
cfg.VhostHTTPSPort = 0
|
||||
}
|
||||
|
||||
if tmpStr, ok = conf.Get("common", "tcpmux_httpconnect_port"); ok {
|
||||
if v, err = strconv.ParseInt(tmpStr, 10, 64); err != nil {
|
||||
err = fmt.Errorf("Parse conf error: invalid tcpmux_httpconnect_port")
|
||||
return
|
||||
}
|
||||
cfg.TCPMuxHTTPConnectPort = int(v)
|
||||
} else {
|
||||
cfg.TCPMuxHTTPConnectPort = 0
|
||||
}
|
||||
|
||||
if tmpStr, ok = conf.Get("common", "vhost_http_timeout"); ok {
|
||||
v, errRet := strconv.ParseInt(tmpStr, 10, 64)
|
||||
if errRet != nil || v < 0 {
|
||||
err = fmt.Errorf("Parse conf error: invalid vhost_http_timeout")
|
||||
return
|
||||
}
|
||||
cfg.VhostHTTPTimeout = v
|
||||
}
|
||||
|
||||
if tmpStr, ok = conf.Get("common", "dashboard_addr"); ok {
|
||||
cfg.DashboardAddr = tmpStr
|
||||
} else {
|
||||
cfg.DashboardAddr = cfg.BindAddr
|
||||
}
|
||||
|
||||
if tmpStr, ok = conf.Get("common", "dashboard_port"); ok {
|
||||
if v, err = strconv.ParseInt(tmpStr, 10, 64); err != nil {
|
||||
err = fmt.Errorf("Parse conf error: invalid dashboard_port")
|
||||
return
|
||||
}
|
||||
cfg.DashboardPort = int(v)
|
||||
} else {
|
||||
cfg.DashboardPort = 0
|
||||
}
|
||||
|
||||
if tmpStr, ok = conf.Get("common", "dashboard_user"); ok {
|
||||
cfg.DashboardUser = tmpStr
|
||||
}
|
||||
|
||||
if tmpStr, ok = conf.Get("common", "dashboard_pwd"); ok {
|
||||
cfg.DashboardPwd = tmpStr
|
||||
}
|
||||
|
||||
if tmpStr, ok = conf.Get("common", "enable_prometheus"); ok && tmpStr == "true" {
|
||||
cfg.EnablePrometheus = true
|
||||
}
|
||||
|
||||
if tmpStr, ok = conf.Get("common", "assets_dir"); ok {
|
||||
cfg.AssetsDir = tmpStr
|
||||
}
|
||||
|
||||
if tmpStr, ok = conf.Get("common", "log_file"); ok {
|
||||
cfg.LogFile = tmpStr
|
||||
if cfg.LogFile == "console" {
|
||||
cfg.LogWay = "console"
|
||||
} else {
|
||||
cfg.LogWay = "file"
|
||||
}
|
||||
}
|
||||
|
||||
if tmpStr, ok = conf.Get("common", "log_level"); ok {
|
||||
cfg.LogLevel = tmpStr
|
||||
}
|
||||
|
||||
if tmpStr, ok = conf.Get("common", "log_max_days"); ok {
|
||||
v, err = strconv.ParseInt(tmpStr, 10, 64)
|
||||
if err == nil {
|
||||
cfg.LogMaxDays = v
|
||||
}
|
||||
}
|
||||
|
||||
if tmpStr, ok = conf.Get("common", "disable_log_color"); ok && tmpStr == "true" {
|
||||
cfg.DisableLogColor = true
|
||||
}
|
||||
|
||||
if tmpStr, ok = conf.Get("common", "detailed_errors_to_client"); ok && tmpStr == "false" {
|
||||
cfg.DetailedErrorsToClient = false
|
||||
} else {
|
||||
cfg.DetailedErrorsToClient = true
|
||||
}
|
||||
|
||||
if allowPortsStr, ok := conf.Get("common", "allow_ports"); ok {
|
||||
// e.g. 1000-2000,2001,2002,3000-4000
|
||||
ports, errRet := util.ParseRangeNumbers(allowPortsStr)
|
||||
if errRet != nil {
|
||||
err = fmt.Errorf("Parse conf error: allow_ports: %v", errRet)
|
||||
return
|
||||
}
|
||||
|
||||
for _, port := range ports {
|
||||
cfg.AllowPorts[int(port)] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
if tmpStr, ok = conf.Get("common", "max_pool_count"); ok {
|
||||
if v, err = strconv.ParseInt(tmpStr, 10, 64); err != nil {
|
||||
err = fmt.Errorf("Parse conf error: invalid max_pool_count")
|
||||
return
|
||||
}
|
||||
|
||||
if v < 0 {
|
||||
err = fmt.Errorf("Parse conf error: invalid max_pool_count")
|
||||
return
|
||||
}
|
||||
cfg.MaxPoolCount = v
|
||||
}
|
||||
|
||||
if tmpStr, ok = conf.Get("common", "max_ports_per_client"); ok {
|
||||
if v, err = strconv.ParseInt(tmpStr, 10, 64); err != nil {
|
||||
err = fmt.Errorf("Parse conf error: invalid max_ports_per_client")
|
||||
return
|
||||
}
|
||||
|
||||
if v < 0 {
|
||||
err = fmt.Errorf("Parse conf error: invalid max_ports_per_client")
|
||||
return
|
||||
}
|
||||
cfg.MaxPortsPerClient = v
|
||||
}
|
||||
|
||||
if tmpStr, ok = conf.Get("common", "subdomain_host"); ok {
|
||||
cfg.SubDomainHost = strings.ToLower(strings.TrimSpace(tmpStr))
|
||||
}
|
||||
|
||||
if tmpStr, ok = conf.Get("common", "tcp_mux"); ok && tmpStr == "false" {
|
||||
cfg.TCPMux = false
|
||||
} else {
|
||||
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 {
|
||||
v, errRet := strconv.ParseInt(tmpStr, 10, 64)
|
||||
if errRet != nil {
|
||||
err = fmt.Errorf("Parse conf error: heartbeat_timeout is incorrect")
|
||||
return
|
||||
}
|
||||
cfg.HeartbeatTimeout = v
|
||||
}
|
||||
|
||||
if tmpStr, ok = conf.Get("common", "tls_only"); ok && tmpStr == "true" {
|
||||
cfg.TLSOnly = true
|
||||
} else {
|
||||
cfg.TLSOnly = false
|
||||
}
|
||||
|
||||
if tmpStr, ok = conf.Get("common", "udp_packet_size"); ok {
|
||||
if v, err = strconv.ParseInt(tmpStr, 10, 64); err != nil {
|
||||
err = fmt.Errorf("Parse conf error: invalid udp_packet_size")
|
||||
return
|
||||
}
|
||||
cfg.UDPPacketSize = v
|
||||
}
|
||||
|
||||
if tmpStr, ok := conf.Get("common", "tls_cert_file"); ok {
|
||||
cfg.TLSCertFile = tmpStr
|
||||
}
|
||||
|
||||
if tmpStr, ok := conf.Get("common", "tls_key_file"); ok {
|
||||
cfg.TLSKeyFile = tmpStr
|
||||
}
|
||||
|
||||
if tmpStr, ok := conf.Get("common", "tls_trusted_ca_file"); ok {
|
||||
cfg.TLSTrustedCaFile = tmpStr
|
||||
cfg.TLSOnly = true
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func UnmarshalPluginsFromIni(sections ini.File, cfg *ServerCommonConf) {
|
||||
for name, section := range sections {
|
||||
if strings.HasPrefix(name, "plugin.") {
|
||||
name = strings.TrimSpace(strings.TrimPrefix(name, "plugin."))
|
||||
var tls_verify, err = strconv.ParseBool(section["tls_verify"])
|
||||
if err != nil {
|
||||
tls_verify = true
|
||||
}
|
||||
options := plugin.HTTPPluginOptions{
|
||||
Name: name,
|
||||
Addr: section["addr"],
|
||||
Path: section["path"],
|
||||
Ops: strings.Split(section["ops"], ","),
|
||||
TLSVerify: tls_verify,
|
||||
}
|
||||
for i := range options.Ops {
|
||||
options.Ops[i] = strings.TrimSpace(options.Ops[i])
|
||||
}
|
||||
cfg.HTTPPlugins[name] = options
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (cfg *ServerCommonConf) Check() error {
|
||||
return nil
|
||||
}
|
||||
207
pkg/config/server_test.go
Normal file
207
pkg/config/server_test.go
Normal file
@@ -0,0 +1,207 @@
|
||||
// Copyright 2020 The frp Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package config
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/fatedier/frp/pkg/auth"
|
||||
"github.com/fatedier/frp/pkg/plugin/server"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func Test_LoadServerCommonConf(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
testcases := []struct {
|
||||
source []byte
|
||||
expected ServerCommonConf
|
||||
}{
|
||||
{
|
||||
source: []byte(`
|
||||
# [common] is integral section
|
||||
[common]
|
||||
bind_addr = 0.0.0.9
|
||||
bind_port = 7009
|
||||
bind_udp_port = 7008
|
||||
kcp_bind_port = 7007
|
||||
proxy_bind_addr = 127.0.0.9
|
||||
vhost_http_port = 89
|
||||
vhost_https_port = 449
|
||||
vhost_http_timeout = 69
|
||||
tcpmux_httpconnect_port = 1339
|
||||
dashboard_addr = 0.0.0.9
|
||||
dashboard_port = 7509
|
||||
dashboard_user = admin9
|
||||
dashboard_pwd = admin9
|
||||
enable_prometheus
|
||||
assets_dir = ./static9
|
||||
log_file = ./frps.log9
|
||||
log_way = file
|
||||
log_level = info9
|
||||
log_max_days = 39
|
||||
disable_log_color = false
|
||||
detailed_errors_to_client
|
||||
authentication_method = token
|
||||
authenticate_heartbeats = false
|
||||
authenticate_new_work_conns = false
|
||||
token = 123456789
|
||||
oidc_issuer = test9
|
||||
oidc_audience = test9
|
||||
oidc_skip_expiry_check
|
||||
oidc_skip_issuer_check
|
||||
heartbeat_timeout = 99
|
||||
user_conn_timeout = 9
|
||||
allow_ports = 10-12,99
|
||||
max_pool_count = 59
|
||||
max_ports_per_client = 9
|
||||
tls_only = false
|
||||
tls_cert_file = server.crt
|
||||
tls_key_file = server.key
|
||||
tls_trusted_ca_file = ca.crt
|
||||
subdomain_host = frps.com
|
||||
tcp_mux
|
||||
udp_packet_size = 1509
|
||||
[plugin.user-manager]
|
||||
addr = 127.0.0.1:9009
|
||||
path = /handler
|
||||
ops = Login
|
||||
[plugin.port-manager]
|
||||
addr = 127.0.0.1:9009
|
||||
path = /handler
|
||||
ops = NewProxy
|
||||
tls_verify
|
||||
`),
|
||||
expected: ServerCommonConf{
|
||||
ServerConfig: auth.ServerConfig{
|
||||
BaseConfig: auth.BaseConfig{
|
||||
AuthenticationMethod: "token",
|
||||
AuthenticateHeartBeats: false,
|
||||
AuthenticateNewWorkConns: false,
|
||||
},
|
||||
TokenConfig: auth.TokenConfig{
|
||||
Token: "123456789",
|
||||
},
|
||||
OidcServerConfig: auth.OidcServerConfig{
|
||||
OidcIssuer: "test9",
|
||||
OidcAudience: "test9",
|
||||
OidcSkipExpiryCheck: true,
|
||||
OidcSkipIssuerCheck: true,
|
||||
},
|
||||
},
|
||||
BindAddr: "0.0.0.9",
|
||||
BindPort: 7009,
|
||||
BindUDPPort: 7008,
|
||||
KCPBindPort: 7007,
|
||||
ProxyBindAddr: "127.0.0.9",
|
||||
VhostHTTPPort: 89,
|
||||
VhostHTTPSPort: 449,
|
||||
VhostHTTPTimeout: 69,
|
||||
TCPMuxHTTPConnectPort: 1339,
|
||||
DashboardAddr: "0.0.0.9",
|
||||
DashboardPort: 7509,
|
||||
DashboardUser: "admin9",
|
||||
DashboardPwd: "admin9",
|
||||
EnablePrometheus: true,
|
||||
AssetsDir: "./static9",
|
||||
LogFile: "./frps.log9",
|
||||
LogWay: "file",
|
||||
LogLevel: "info9",
|
||||
LogMaxDays: 39,
|
||||
DisableLogColor: false,
|
||||
DetailedErrorsToClient: true,
|
||||
HeartbeatTimeout: 99,
|
||||
UserConnTimeout: 9,
|
||||
AllowPorts: map[int]struct{}{
|
||||
10: struct{}{},
|
||||
11: struct{}{},
|
||||
12: struct{}{},
|
||||
99: struct{}{},
|
||||
},
|
||||
MaxPoolCount: 59,
|
||||
MaxPortsPerClient: 9,
|
||||
TLSOnly: false,
|
||||
TLSCertFile: "server.crt",
|
||||
TLSKeyFile: "server.key",
|
||||
TLSTrustedCaFile: "ca.crt",
|
||||
SubDomainHost: "frps.com",
|
||||
TCPMux: true,
|
||||
UDPPacketSize: 1509,
|
||||
|
||||
HTTPPlugins: map[string]plugin.HTTPPluginOptions{
|
||||
"user-manager": {
|
||||
Name: "user-manager",
|
||||
Addr: "127.0.0.1:9009",
|
||||
Path: "/handler",
|
||||
Ops: []string{"Login"},
|
||||
},
|
||||
"port-manager": {
|
||||
Name: "port-manager",
|
||||
Addr: "127.0.0.1:9009",
|
||||
Path: "/handler",
|
||||
Ops: []string{"NewProxy"},
|
||||
TLSVerify: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
source: []byte(`
|
||||
# [common] is integral section
|
||||
[common]
|
||||
bind_addr = 0.0.0.9
|
||||
bind_port = 7009
|
||||
bind_udp_port = 7008
|
||||
`),
|
||||
expected: ServerCommonConf{
|
||||
ServerConfig: auth.ServerConfig{
|
||||
BaseConfig: auth.BaseConfig{
|
||||
AuthenticationMethod: "token",
|
||||
AuthenticateHeartBeats: false,
|
||||
AuthenticateNewWorkConns: false,
|
||||
},
|
||||
},
|
||||
BindAddr: "0.0.0.9",
|
||||
BindPort: 7009,
|
||||
BindUDPPort: 7008,
|
||||
ProxyBindAddr: "0.0.0.0",
|
||||
VhostHTTPTimeout: 60,
|
||||
DashboardAddr: "0.0.0.0",
|
||||
DashboardUser: "admin",
|
||||
DashboardPwd: "admin",
|
||||
EnablePrometheus: false,
|
||||
LogFile: "console",
|
||||
LogWay: "console",
|
||||
LogLevel: "info",
|
||||
LogMaxDays: 3,
|
||||
DetailedErrorsToClient: true,
|
||||
TCPMux: true,
|
||||
AllowPorts: make(map[int]struct{}),
|
||||
MaxPoolCount: 5,
|
||||
HeartbeatTimeout: 90,
|
||||
UserConnTimeout: 10,
|
||||
HTTPPlugins: make(map[string]plugin.HTTPPluginOptions),
|
||||
UDPPacketSize: 1500,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, c := range testcases {
|
||||
actual, err := UnmarshalServerConfFromIni(c.source)
|
||||
assert.NoError(err)
|
||||
assert.Equal(c.expected, actual)
|
||||
}
|
||||
}
|
||||
@@ -41,6 +41,15 @@ func NewBandwidthQuantity(s string) (BandwidthQuantity, error) {
|
||||
return q, nil
|
||||
}
|
||||
|
||||
func MustBandwidthQuantity(s string) BandwidthQuantity {
|
||||
q := BandwidthQuantity{}
|
||||
err := q.UnmarshalString(s)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return q
|
||||
}
|
||||
|
||||
func (q *BandwidthQuantity) Equal(u *BandwidthQuantity) bool {
|
||||
if q == nil && u == nil {
|
||||
return true
|
||||
|
||||
51
pkg/config/utils.go
Normal file
51
pkg/config/utils.go
Normal file
@@ -0,0 +1,51 @@
|
||||
// Copyright 2020 The frp Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package config
|
||||
|
||||
import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
func GetMapWithoutPrefix(set map[string]string, prefix string) map[string]string {
|
||||
m := make(map[string]string)
|
||||
|
||||
for key, value := range set {
|
||||
if strings.HasPrefix(key, prefix) {
|
||||
m[strings.TrimPrefix(key, prefix)] = value
|
||||
}
|
||||
}
|
||||
|
||||
if len(m) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return m
|
||||
}
|
||||
|
||||
func GetMapByPrefix(set map[string]string, prefix string) map[string]string {
|
||||
m := make(map[string]string)
|
||||
|
||||
for key, value := range set {
|
||||
if strings.HasPrefix(key, prefix) {
|
||||
m[key] = value
|
||||
}
|
||||
}
|
||||
|
||||
if len(m) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return m
|
||||
}
|
||||
@@ -1,3 +1,17 @@
|
||||
// Copyright 2020 The frp Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package config
|
||||
|
||||
import (
|
||||
@@ -34,8 +48,8 @@ func GetValues() *Values {
|
||||
}
|
||||
}
|
||||
|
||||
func RenderContent(in string) (out string, err error) {
|
||||
tmpl, errRet := template.New("frp").Parse(in)
|
||||
func RenderContent(in []byte) (out []byte, err error) {
|
||||
tmpl, errRet := template.New("frp").Parse(string(in))
|
||||
if errRet != nil {
|
||||
err = errRet
|
||||
return
|
||||
@@ -47,18 +61,17 @@ func RenderContent(in string) (out string, err error) {
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
out = buffer.String()
|
||||
out = buffer.Bytes()
|
||||
return
|
||||
}
|
||||
|
||||
func GetRenderedConfFromFile(path string) (out string, err error) {
|
||||
func GetRenderedConfFromFile(path string) (out []byte, err error) {
|
||||
var b []byte
|
||||
b, err = ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
content := string(b)
|
||||
|
||||
out, err = RenderContent(content)
|
||||
out, err = RenderContent(b)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -17,72 +17,89 @@ package config
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strconv"
|
||||
|
||||
"github.com/fatedier/frp/pkg/consts"
|
||||
|
||||
ini "github.com/vaughan0/go-ini"
|
||||
"gopkg.in/ini.v1"
|
||||
)
|
||||
|
||||
// Visitor
|
||||
var (
|
||||
visitorConfTypeMap map[string]reflect.Type
|
||||
visitorConfTypeMap = map[string]reflect.Type{
|
||||
consts.STCPProxy: reflect.TypeOf(STCPVisitorConf{}),
|
||||
consts.XTCPProxy: reflect.TypeOf(XTCPVisitorConf{}),
|
||||
consts.SUDPProxy: reflect.TypeOf(SUDPVisitorConf{}),
|
||||
}
|
||||
)
|
||||
|
||||
func init() {
|
||||
visitorConfTypeMap = make(map[string]reflect.Type)
|
||||
visitorConfTypeMap[consts.STCPProxy] = reflect.TypeOf(STCPVisitorConf{})
|
||||
visitorConfTypeMap[consts.XTCPProxy] = reflect.TypeOf(XTCPVisitorConf{})
|
||||
visitorConfTypeMap[consts.SUDPProxy] = reflect.TypeOf(SUDPVisitorConf{})
|
||||
}
|
||||
|
||||
type VisitorConf interface {
|
||||
GetBaseInfo() *BaseVisitorConf
|
||||
Compare(cmp VisitorConf) bool
|
||||
UnmarshalFromIni(prefix string, name string, section ini.Section) error
|
||||
UnmarshalFromIni(prefix string, name string, section *ini.Section) error
|
||||
Check() error
|
||||
}
|
||||
|
||||
func NewVisitorConfByType(cfgType string) VisitorConf {
|
||||
v, ok := visitorConfTypeMap[cfgType]
|
||||
type BaseVisitorConf struct {
|
||||
ProxyName string `ini:"name" json:"name"`
|
||||
ProxyType string `ini:"type" json:"type"`
|
||||
UseEncryption bool `ini:"use_encryption" json:"use_encryption"`
|
||||
UseCompression bool `ini:"use_compression" json:"use_compression"`
|
||||
Role string `ini:"role" json:"role"`
|
||||
Sk string `ini:"sk" json:"sk"`
|
||||
ServerName string `ini:"server_name" json:"server_name"`
|
||||
BindAddr string `ini:"bind_addr" json:"bind_addr"`
|
||||
BindPort int `ini:"bind_port" json:"bind_port"`
|
||||
}
|
||||
|
||||
type SUDPVisitorConf struct {
|
||||
BaseVisitorConf `ini:",extends" json:"inline"`
|
||||
}
|
||||
|
||||
type STCPVisitorConf struct {
|
||||
BaseVisitorConf `ini:",extends" json:"inline"`
|
||||
}
|
||||
|
||||
type XTCPVisitorConf struct {
|
||||
BaseVisitorConf `ini:",extends" json:"inline"`
|
||||
}
|
||||
|
||||
// DefaultVisitorConf creates a empty VisitorConf object by visitorType.
|
||||
// If visitorType doesn't exist, return nil.
|
||||
func DefaultVisitorConf(visitorType string) VisitorConf {
|
||||
v, ok := visitorConfTypeMap[visitorType]
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
cfg := reflect.New(v).Interface().(VisitorConf)
|
||||
return cfg
|
||||
|
||||
return reflect.New(v).Interface().(VisitorConf)
|
||||
}
|
||||
|
||||
func NewVisitorConfFromIni(prefix string, name string, section ini.Section) (cfg VisitorConf, err error) {
|
||||
cfgType := section["type"]
|
||||
if cfgType == "" {
|
||||
err = fmt.Errorf("visitor [%s] type shouldn't be empty", name)
|
||||
return
|
||||
// Visitor loaded from ini
|
||||
func NewVisitorConfFromIni(prefix string, name string, section *ini.Section) (VisitorConf, error) {
|
||||
// section.Key: if key not exists, section will set it with default value.
|
||||
visitorType := section.Key("type").String()
|
||||
|
||||
if visitorType == "" {
|
||||
return nil, fmt.Errorf("visitor [%s] type shouldn't be empty", name)
|
||||
}
|
||||
cfg = NewVisitorConfByType(cfgType)
|
||||
if cfg == nil {
|
||||
err = fmt.Errorf("visitor [%s] type [%s] error", name, cfgType)
|
||||
return
|
||||
|
||||
conf := DefaultVisitorConf(visitorType)
|
||||
if conf == nil {
|
||||
return nil, fmt.Errorf("visitor [%s] type [%s] error", name, visitorType)
|
||||
}
|
||||
if err = cfg.UnmarshalFromIni(prefix, name, section); err != nil {
|
||||
return
|
||||
|
||||
if err := conf.UnmarshalFromIni(prefix, name, section); err != nil {
|
||||
return nil, fmt.Errorf("visitor [%s] type [%s] error", name, visitorType)
|
||||
}
|
||||
if err = cfg.Check(); err != nil {
|
||||
return
|
||||
|
||||
if err := conf.Check(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
type BaseVisitorConf struct {
|
||||
ProxyName string `json:"proxy_name"`
|
||||
ProxyType string `json:"proxy_type"`
|
||||
UseEncryption bool `json:"use_encryption"`
|
||||
UseCompression bool `json:"use_compression"`
|
||||
Role string `json:"role"`
|
||||
Sk string `json:"sk"`
|
||||
ServerName string `json:"server_name"`
|
||||
BindAddr string `json:"bind_addr"`
|
||||
BindPort int `json:"bind_port"`
|
||||
|
||||
return conf, nil
|
||||
}
|
||||
|
||||
// Base
|
||||
func (cfg *BaseVisitorConf) GetBaseInfo() *BaseVisitorConf {
|
||||
return cfg
|
||||
}
|
||||
@@ -118,45 +135,40 @@ func (cfg *BaseVisitorConf) check() (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func (cfg *BaseVisitorConf) UnmarshalFromIni(prefix string, name string, section ini.Section) (err error) {
|
||||
var (
|
||||
tmpStr string
|
||||
ok bool
|
||||
)
|
||||
func (cfg *BaseVisitorConf) unmarshalFromIni(prefix string, name string, section *ini.Section) error {
|
||||
|
||||
// Custom decoration after basic unmarshal:
|
||||
// proxy name
|
||||
cfg.ProxyName = prefix + name
|
||||
cfg.ProxyType = section["type"]
|
||||
|
||||
if tmpStr, ok = section["use_encryption"]; ok && tmpStr == "true" {
|
||||
cfg.UseEncryption = true
|
||||
}
|
||||
if tmpStr, ok = section["use_compression"]; ok && tmpStr == "true" {
|
||||
cfg.UseCompression = true
|
||||
}
|
||||
// server_name
|
||||
cfg.ServerName = prefix + cfg.ServerName
|
||||
|
||||
cfg.Role = section["role"]
|
||||
if cfg.Role != "visitor" {
|
||||
return fmt.Errorf("Parse conf error: proxy [%s] incorrect role [%s]", name, cfg.Role)
|
||||
}
|
||||
cfg.Sk = section["sk"]
|
||||
cfg.ServerName = prefix + section["server_name"]
|
||||
if cfg.BindAddr = section["bind_addr"]; cfg.BindAddr == "" {
|
||||
// bind_addr
|
||||
if cfg.BindAddr == "" {
|
||||
cfg.BindAddr = "127.0.0.1"
|
||||
}
|
||||
|
||||
if tmpStr, ok = section["bind_port"]; ok {
|
||||
if cfg.BindPort, err = strconv.Atoi(tmpStr); err != nil {
|
||||
return fmt.Errorf("Parse conf error: proxy [%s] bind_port incorrect", name)
|
||||
}
|
||||
} else {
|
||||
return fmt.Errorf("Parse conf error: proxy [%s] bind_port not found", name)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type SUDPVisitorConf struct {
|
||||
BaseVisitorConf
|
||||
func preVisitorUnmarshalFromIni(cfg VisitorConf, prefix string, name string, section *ini.Section) error {
|
||||
err := section.MapTo(cfg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = cfg.GetBaseInfo().unmarshalFromIni(prefix, name, section)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// SUDP
|
||||
var _ VisitorConf = &SUDPVisitorConf{}
|
||||
|
||||
func (cfg *SUDPVisitorConf) Compare(cmp VisitorConf) bool {
|
||||
cmpConf, ok := cmp.(*SUDPVisitorConf)
|
||||
if !ok {
|
||||
@@ -166,13 +178,20 @@ func (cfg *SUDPVisitorConf) Compare(cmp VisitorConf) bool {
|
||||
if !cfg.BaseVisitorConf.compare(&cmpConf.BaseVisitorConf) {
|
||||
return false
|
||||
}
|
||||
|
||||
// Add custom login equal, if exists
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (cfg *SUDPVisitorConf) UnmarshalFromIni(prefix string, name string, section ini.Section) (err error) {
|
||||
if err = cfg.BaseVisitorConf.UnmarshalFromIni(prefix, name, section); err != nil {
|
||||
func (cfg *SUDPVisitorConf) UnmarshalFromIni(prefix string, name string, section *ini.Section) (err error) {
|
||||
err = preVisitorUnmarshalFromIni(cfg, prefix, name, section)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Add custom logic unmarshal, if exists
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@@ -180,12 +199,14 @@ func (cfg *SUDPVisitorConf) Check() (err error) {
|
||||
if err = cfg.BaseVisitorConf.check(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Add custom logic validate, if exists
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
type STCPVisitorConf struct {
|
||||
BaseVisitorConf
|
||||
}
|
||||
// STCP
|
||||
var _ VisitorConf = &STCPVisitorConf{}
|
||||
|
||||
func (cfg *STCPVisitorConf) Compare(cmp VisitorConf) bool {
|
||||
cmpConf, ok := cmp.(*STCPVisitorConf)
|
||||
@@ -196,13 +217,20 @@ func (cfg *STCPVisitorConf) Compare(cmp VisitorConf) bool {
|
||||
if !cfg.BaseVisitorConf.compare(&cmpConf.BaseVisitorConf) {
|
||||
return false
|
||||
}
|
||||
|
||||
// Add custom login equal, if exists
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (cfg *STCPVisitorConf) UnmarshalFromIni(prefix string, name string, section ini.Section) (err error) {
|
||||
if err = cfg.BaseVisitorConf.UnmarshalFromIni(prefix, name, section); err != nil {
|
||||
func (cfg *STCPVisitorConf) UnmarshalFromIni(prefix string, name string, section *ini.Section) (err error) {
|
||||
err = preVisitorUnmarshalFromIni(cfg, prefix, name, section)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Add custom logic unmarshal, if exists
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@@ -210,12 +238,14 @@ func (cfg *STCPVisitorConf) Check() (err error) {
|
||||
if err = cfg.BaseVisitorConf.check(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Add custom logic validate, if exists
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
type XTCPVisitorConf struct {
|
||||
BaseVisitorConf
|
||||
}
|
||||
// XTCP
|
||||
var _ VisitorConf = &XTCPVisitorConf{}
|
||||
|
||||
func (cfg *XTCPVisitorConf) Compare(cmp VisitorConf) bool {
|
||||
cmpConf, ok := cmp.(*XTCPVisitorConf)
|
||||
@@ -226,13 +256,20 @@ func (cfg *XTCPVisitorConf) Compare(cmp VisitorConf) bool {
|
||||
if !cfg.BaseVisitorConf.compare(&cmpConf.BaseVisitorConf) {
|
||||
return false
|
||||
}
|
||||
|
||||
// Add custom login equal, if exists
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (cfg *XTCPVisitorConf) UnmarshalFromIni(prefix string, name string, section ini.Section) (err error) {
|
||||
if err = cfg.BaseVisitorConf.UnmarshalFromIni(prefix, name, section); err != nil {
|
||||
func (cfg *XTCPVisitorConf) UnmarshalFromIni(prefix string, name string, section *ini.Section) (err error) {
|
||||
err = preVisitorUnmarshalFromIni(cfg, prefix, name, section)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Add custom logic unmarshal, if exists
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@@ -240,5 +277,8 @@ func (cfg *XTCPVisitorConf) Check() (err error) {
|
||||
if err = cfg.BaseVisitorConf.check(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Add custom logic validate, if exists
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
108
pkg/config/visitor_test.go
Normal file
108
pkg/config/visitor_test.go
Normal file
@@ -0,0 +1,108 @@
|
||||
// Copyright 2020 The frp Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package config
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/fatedier/frp/pkg/consts"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"gopkg.in/ini.v1"
|
||||
)
|
||||
|
||||
const testVisitorPrefix = "test."
|
||||
|
||||
func Test_Visitor_Interface(t *testing.T) {
|
||||
for name := range visitorConfTypeMap {
|
||||
DefaultVisitorConf(name)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_Visitor_UnmarshalFromIni(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
testcases := []struct {
|
||||
sname string
|
||||
source []byte
|
||||
expected VisitorConf
|
||||
}{
|
||||
{
|
||||
sname: "secret_tcp_visitor",
|
||||
source: []byte(`
|
||||
[secret_tcp_visitor]
|
||||
role = visitor
|
||||
type = stcp
|
||||
server_name = secret_tcp
|
||||
sk = abcdefg
|
||||
bind_addr = 127.0.0.1
|
||||
bind_port = 9000
|
||||
use_encryption = false
|
||||
use_compression = false
|
||||
`),
|
||||
expected: &STCPVisitorConf{
|
||||
BaseVisitorConf: BaseVisitorConf{
|
||||
ProxyName: testVisitorPrefix + "secret_tcp_visitor",
|
||||
ProxyType: consts.STCPProxy,
|
||||
Role: "visitor",
|
||||
Sk: "abcdefg",
|
||||
ServerName: testVisitorPrefix + "secret_tcp",
|
||||
BindAddr: "127.0.0.1",
|
||||
BindPort: 9000,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
sname: "p2p_tcp_visitor",
|
||||
source: []byte(`
|
||||
[p2p_tcp_visitor]
|
||||
role = visitor
|
||||
type = xtcp
|
||||
server_name = p2p_tcp
|
||||
sk = abcdefg
|
||||
bind_addr = 127.0.0.1
|
||||
bind_port = 9001
|
||||
use_encryption = false
|
||||
use_compression = false
|
||||
`),
|
||||
expected: &XTCPVisitorConf{
|
||||
BaseVisitorConf: BaseVisitorConf{
|
||||
ProxyName: testVisitorPrefix + "p2p_tcp_visitor",
|
||||
ProxyType: consts.XTCPProxy,
|
||||
Role: "visitor",
|
||||
Sk: "abcdefg",
|
||||
ServerName: testProxyPrefix + "p2p_tcp",
|
||||
BindAddr: "127.0.0.1",
|
||||
BindPort: 9001,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, c := range testcases {
|
||||
f, err := ini.LoadSources(testLoadOptions, c.source)
|
||||
assert.NoError(err)
|
||||
|
||||
visitorType := f.Section(c.sname).Key("type").String()
|
||||
assert.NotEmpty(visitorType)
|
||||
|
||||
actual := DefaultVisitorConf(visitorType)
|
||||
assert.NotNil(actual)
|
||||
|
||||
err = actual.UnmarshalFromIni(testVisitorPrefix, c.sname, f.Section(c.sname))
|
||||
assert.NoError(err)
|
||||
assert.Equal(c.expected, actual)
|
||||
}
|
||||
}
|
||||
138
pkg/plugin/client/https2https.go
Normal file
138
pkg/plugin/client/https2https.go
Normal file
@@ -0,0 +1,138 @@
|
||||
// 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"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"strings"
|
||||
|
||||
frpNet "github.com/fatedier/frp/pkg/util/net"
|
||||
)
|
||||
|
||||
const PluginHTTPS2HTTPS = "https2https"
|
||||
|
||||
func init() {
|
||||
Register(PluginHTTPS2HTTPS, NewHTTPS2HTTPSPlugin)
|
||||
}
|
||||
|
||||
type HTTPS2HTTPSPlugin struct {
|
||||
crtPath string
|
||||
keyPath string
|
||||
hostHeaderRewrite string
|
||||
localAddr string
|
||||
headers map[string]string
|
||||
|
||||
l *Listener
|
||||
s *http.Server
|
||||
}
|
||||
|
||||
func NewHTTPS2HTTPSPlugin(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 := &HTTPS2HTTPSPlugin{
|
||||
crtPath: crtPath,
|
||||
keyPath: keyPath,
|
||||
localAddr: localAddr,
|
||||
hostHeaderRewrite: hostHeaderRewrite,
|
||||
headers: headers,
|
||||
l: listener,
|
||||
}
|
||||
|
||||
tr := &http.Transport{
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||
}
|
||||
|
||||
rp := &httputil.ReverseProxy{
|
||||
Director: func(req *http.Request) {
|
||||
req.URL.Scheme = "https"
|
||||
req.URL.Host = p.localAddr
|
||||
if p.hostHeaderRewrite != "" {
|
||||
req.Host = p.hostHeaderRewrite
|
||||
}
|
||||
for k, v := range p.headers {
|
||||
req.Header.Set(k, v)
|
||||
}
|
||||
},
|
||||
Transport: tr,
|
||||
}
|
||||
|
||||
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 *HTTPS2HTTPSPlugin) 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 *HTTPS2HTTPSPlugin) Handle(conn io.ReadWriteCloser, realConn net.Conn, extraBufToLocal []byte) {
|
||||
wrapConn := frpNet.WrapReadWriteCloserToConn(conn, realConn)
|
||||
p.l.PutConn(wrapConn)
|
||||
}
|
||||
|
||||
func (p *HTTPS2HTTPSPlugin) Name() string {
|
||||
return PluginHTTPS2HTTP
|
||||
}
|
||||
|
||||
func (p *HTTPS2HTTPSPlugin) Close() error {
|
||||
if err := p.s.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -28,11 +28,11 @@ import (
|
||||
)
|
||||
|
||||
type HTTPPluginOptions struct {
|
||||
Name string
|
||||
Addr string
|
||||
Path string
|
||||
Ops []string
|
||||
TLSVerify bool
|
||||
Name string `ini:"name"`
|
||||
Addr string `ini:"addr"`
|
||||
Path string `ini:"path"`
|
||||
Ops []string `ini:"ops"`
|
||||
TLSVerify bool `ini:"tls_verify"`
|
||||
}
|
||||
|
||||
type httpPlugin struct {
|
||||
|
||||
@@ -43,7 +43,7 @@ func newRandomTLSKeyPair() *tls.Certificate {
|
||||
return &tlsCert
|
||||
}
|
||||
|
||||
// Only supprt one ca file to add
|
||||
// Only support one ca file to add
|
||||
func newCertPool(caPath string) (*x509.CertPool, error) {
|
||||
pool := x509.NewCertPool()
|
||||
|
||||
|
||||
@@ -27,8 +27,8 @@ type KCPListener struct {
|
||||
closeFlag bool
|
||||
}
|
||||
|
||||
func ListenKcp(bindAddr string, bindPort int) (l *KCPListener, err error) {
|
||||
listener, err := kcp.ListenWithOptions(fmt.Sprintf("%s:%d", bindAddr, bindPort), nil, 10, 3)
|
||||
func ListenKcp(address string) (l *KCPListener, err error) {
|
||||
listener, err := kcp.ListenWithOptions(address, nil, 10, 3)
|
||||
if err != nil {
|
||||
return l, err
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
var version string = "0.35.0"
|
||||
var version string = "0.36.0"
|
||||
|
||||
func Full() string {
|
||||
return version
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"time"
|
||||
@@ -32,7 +31,7 @@ var (
|
||||
httpServerWriteTimeout = 10 * time.Second
|
||||
)
|
||||
|
||||
func (svr *Service) RunDashboardServer(addr string, port int) (err error) {
|
||||
func (svr *Service) RunDashboardServer(address string) (err error) {
|
||||
// url router
|
||||
router := mux.NewRouter()
|
||||
|
||||
@@ -58,14 +57,13 @@ func (svr *Service) RunDashboardServer(addr string, port int) (err error) {
|
||||
http.Redirect(w, r, "/static/", http.StatusMovedPermanently)
|
||||
})
|
||||
|
||||
address := fmt.Sprintf("%s:%d", addr, port)
|
||||
server := &http.Server{
|
||||
Addr: address,
|
||||
Handler: router,
|
||||
ReadTimeout: httpServerReadTimeout,
|
||||
WriteTimeout: httpServerWriteTimeout,
|
||||
}
|
||||
if address == "" {
|
||||
if address == "" || address == ":" {
|
||||
address = ":http"
|
||||
}
|
||||
ln, err := net.Listen("tcp", address)
|
||||
|
||||
@@ -23,6 +23,7 @@ import (
|
||||
"net"
|
||||
"net/http"
|
||||
"sort"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/fatedier/frp/assets"
|
||||
@@ -176,7 +177,8 @@ func NewService(cfg config.ServerCommonConf) (svr *Service, err error) {
|
||||
}
|
||||
|
||||
// Listen for accepting connections from client.
|
||||
ln, err := net.Listen("tcp", fmt.Sprintf("%s:%d", cfg.BindAddr, cfg.BindPort))
|
||||
address := net.JoinHostPort(cfg.BindAddr, strconv.Itoa(cfg.BindPort))
|
||||
ln, err := net.Listen("tcp", address)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("Create server listener error, %v", err)
|
||||
return
|
||||
@@ -187,13 +189,14 @@ func NewService(cfg config.ServerCommonConf) (svr *Service, err error) {
|
||||
ln = svr.muxer.DefaultListener()
|
||||
|
||||
svr.listener = ln
|
||||
log.Info("frps tcp listen on %s:%d", cfg.BindAddr, cfg.BindPort)
|
||||
log.Info("frps tcp listen on %s", address)
|
||||
|
||||
// Listen for accepting connections from client using kcp protocol.
|
||||
if cfg.KCPBindPort > 0 {
|
||||
svr.kcpListener, err = frpNet.ListenKcp(cfg.BindAddr, cfg.KCPBindPort)
|
||||
address := net.JoinHostPort(cfg.BindAddr, strconv.Itoa(cfg.KCPBindPort))
|
||||
svr.kcpListener, err = frpNet.ListenKcp(address)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("Listen on kcp address udp [%s:%d] error: %v", cfg.BindAddr, cfg.KCPBindPort, err)
|
||||
err = fmt.Errorf("Listen on kcp address udp %s error: %v", address, err)
|
||||
return
|
||||
}
|
||||
log.Info("frps kcp listen on udp %s:%d", cfg.BindAddr, cfg.KCPBindPort)
|
||||
@@ -213,7 +216,7 @@ func NewService(cfg config.ServerCommonConf) (svr *Service, err error) {
|
||||
}, svr.httpVhostRouter)
|
||||
svr.rc.HTTPReverseProxy = rp
|
||||
|
||||
address := fmt.Sprintf("%s:%d", cfg.ProxyBindAddr, cfg.VhostHTTPPort)
|
||||
address := net.JoinHostPort(cfg.ProxyBindAddr, strconv.Itoa(cfg.VhostHTTPPort))
|
||||
server := &http.Server{
|
||||
Addr: address,
|
||||
Handler: rp,
|
||||
@@ -238,11 +241,13 @@ func NewService(cfg config.ServerCommonConf) (svr *Service, err error) {
|
||||
if httpsMuxOn {
|
||||
l = svr.muxer.ListenHttps(1)
|
||||
} else {
|
||||
l, err = net.Listen("tcp", fmt.Sprintf("%s:%d", cfg.ProxyBindAddr, cfg.VhostHTTPSPort))
|
||||
address := net.JoinHostPort(cfg.ProxyBindAddr, strconv.Itoa(cfg.VhostHTTPSPort))
|
||||
l, err = net.Listen("tcp", address)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("Create server listener error, %v", err)
|
||||
return
|
||||
}
|
||||
log.Info("https service listen on %s", address)
|
||||
}
|
||||
|
||||
svr.rc.VhostHTTPSMuxer, err = vhost.NewHTTPSMuxer(l, vhostReadWriteTimeout)
|
||||
@@ -250,7 +255,6 @@ func NewService(cfg config.ServerCommonConf) (svr *Service, err error) {
|
||||
err = fmt.Errorf("Create vhost httpsMuxer error, %v", err)
|
||||
return
|
||||
}
|
||||
log.Info("https service listen on %s:%d", cfg.ProxyBindAddr, cfg.VhostHTTPSPort)
|
||||
}
|
||||
|
||||
// frp tls listener
|
||||
@@ -261,14 +265,14 @@ func NewService(cfg config.ServerCommonConf) (svr *Service, err error) {
|
||||
// Create nat hole controller.
|
||||
if cfg.BindUDPPort > 0 {
|
||||
var nc *nathole.Controller
|
||||
addr := fmt.Sprintf("%s:%d", cfg.BindAddr, cfg.BindUDPPort)
|
||||
nc, err = nathole.NewController(addr)
|
||||
address := net.JoinHostPort(cfg.BindAddr, strconv.Itoa(cfg.BindPort))
|
||||
nc, err = nathole.NewController(address)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("Create nat hole controller error, %v", err)
|
||||
return
|
||||
}
|
||||
svr.rc.NatHoleController = nc
|
||||
log.Info("nat hole udp service listen on %s:%d", cfg.BindAddr, cfg.BindUDPPort)
|
||||
log.Info("nat hole udp service listen on %s", address)
|
||||
}
|
||||
|
||||
var statsEnable bool
|
||||
@@ -281,7 +285,8 @@ func NewService(cfg config.ServerCommonConf) (svr *Service, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
err = svr.RunDashboardServer(cfg.DashboardAddr, cfg.DashboardPort)
|
||||
address := net.JoinHostPort(cfg.DashboardAddr, strconv.Itoa(cfg.DashboardPort))
|
||||
err = svr.RunDashboardServer(address)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("Create dashboard web server error, %v", err)
|
||||
return
|
||||
|
||||
@@ -19,7 +19,7 @@ func TestCmdTCP(t *testing.T) {
|
||||
if assert.NoError(err) {
|
||||
defer s.Stop()
|
||||
}
|
||||
time.Sleep(200 * time.Millisecond)
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
|
||||
c := util.NewProcess(consts.FRPC_BIN_PATH, []string{"tcp", "-s", "127.0.0.1:20000", "-t", "123", "-u", "test",
|
||||
"-l", "10701", "-r", "20801", "-n", "tcp_test"})
|
||||
@@ -43,7 +43,7 @@ func TestCmdUDP(t *testing.T) {
|
||||
if assert.NoError(err) {
|
||||
defer s.Stop()
|
||||
}
|
||||
time.Sleep(200 * time.Millisecond)
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
|
||||
c := util.NewProcess(consts.FRPC_BIN_PATH, []string{"udp", "-s", "127.0.0.1:20000", "-t", "123", "-u", "test",
|
||||
"-l", "10702", "-r", "20802", "-n", "udp_test"})
|
||||
@@ -67,7 +67,7 @@ func TestCmdHTTP(t *testing.T) {
|
||||
if assert.NoError(err) {
|
||||
defer s.Stop()
|
||||
}
|
||||
time.Sleep(200 * time.Millisecond)
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
|
||||
c := util.NewProcess(consts.FRPC_BIN_PATH, []string{"http", "-s", "127.0.0.1:20000", "-t", "123", "-u", "test",
|
||||
"-n", "udp_test", "-l", "10704", "--custom_domain", "127.0.0.1"})
|
||||
|
||||
@@ -175,7 +175,7 @@ func TestHealthCheck(t *testing.T) {
|
||||
defer frpsProcess.Stop()
|
||||
}
|
||||
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
|
||||
frpcProcess := util.NewProcess(consts.FRPC_SUB_BIN_PATH, []string{"-c", frpcCfgPath})
|
||||
err = frpcProcess.Start()
|
||||
|
||||
@@ -42,7 +42,7 @@ func TestMain(m *testing.M) {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
time.Sleep(200 * time.Millisecond)
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
p2 := util.NewProcess(consts.FRPC_BIN_PATH, []string{"-c", "./auto_test_frpc.ini"})
|
||||
if err = p2.Start(); err != nil {
|
||||
panic(err)
|
||||
|
||||
@@ -56,7 +56,7 @@ func TestReconnect(t *testing.T) {
|
||||
defer frpsProcess.Stop()
|
||||
}
|
||||
|
||||
time.Sleep(200 * time.Millisecond)
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
|
||||
frpcProcess := util.NewProcess(consts.FRPC_BIN_PATH, []string{"-c", frpcCfgPath})
|
||||
err = frpcProcess.Start()
|
||||
|
||||
@@ -94,7 +94,7 @@ func TestReload(t *testing.T) {
|
||||
defer frpsProcess.Stop()
|
||||
}
|
||||
|
||||
time.Sleep(200 * time.Millisecond)
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
|
||||
frpcProcess := util.NewProcess(consts.FRPC_BIN_PATH, []string{"-c", frpcCfgPath})
|
||||
err = frpcProcess.Start()
|
||||
|
||||
@@ -55,7 +55,7 @@ func TestConfTemplate(t *testing.T) {
|
||||
defer frpsProcess.Stop()
|
||||
}
|
||||
|
||||
time.Sleep(200 * time.Millisecond)
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
|
||||
frpcProcess := util.NewProcess("env", []string{"FRP_TOKEN=123456", "TCP_REMOTE_PORT=20801", consts.FRPC_BIN_PATH, "-c", frpcCfgPath})
|
||||
err = frpcProcess.Start()
|
||||
|
||||
9334
web/frpc/package-lock.json
generated
9334
web/frpc/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
3454
web/frpc/yarn.lock
3454
web/frpc/yarn.lock
File diff suppressed because it is too large
Load Diff
14
web/frps/.babelrc
Normal file
14
web/frps/.babelrc
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"presets": [
|
||||
["es2015", { "modules": false }]
|
||||
],
|
||||
"plugins": [
|
||||
[
|
||||
"component",
|
||||
{
|
||||
"libraryName": "element-ui",
|
||||
"styleLibraryName": "theme-chalk"
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
[*.{js,jsx,ts,tsx,vue}]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
@@ -1,2 +0,0 @@
|
||||
# just a flag
|
||||
ENV = 'development'
|
||||
@@ -1,2 +0,0 @@
|
||||
# just a flag
|
||||
ENV = 'production'
|
||||
@@ -1,267 +0,0 @@
|
||||
module.exports = {
|
||||
root: true,
|
||||
parserOptions: {
|
||||
parser: 'babel-eslint',
|
||||
sourceType: 'module'
|
||||
},
|
||||
env: {
|
||||
browser: true,
|
||||
node: true,
|
||||
es6: true
|
||||
},
|
||||
extends: ['plugin:vue/recommended', 'eslint:recommended'],
|
||||
|
||||
// add your custom rules here
|
||||
// it is base on https://github.com/vuejs/eslint-config-vue
|
||||
rules: {
|
||||
'vue/max-attributes-per-line': [
|
||||
2,
|
||||
{
|
||||
singleline: 10,
|
||||
multiline: {
|
||||
max: 1,
|
||||
allowFirstLine: false
|
||||
}
|
||||
}
|
||||
],
|
||||
'vue/singleline-html-element-content-newline': 'off',
|
||||
'vue/multiline-html-element-content-newline': 'off',
|
||||
'vue/name-property-casing': ['error', 'PascalCase'],
|
||||
'vue/no-v-html': 'off',
|
||||
'accessor-pairs': 2,
|
||||
'arrow-spacing': [
|
||||
2,
|
||||
{
|
||||
before: true,
|
||||
after: true
|
||||
}
|
||||
],
|
||||
'block-spacing': [2, 'always'],
|
||||
'brace-style': [
|
||||
2,
|
||||
'1tbs',
|
||||
{
|
||||
allowSingleLine: true
|
||||
}
|
||||
],
|
||||
camelcase: [
|
||||
0,
|
||||
{
|
||||
properties: 'always'
|
||||
}
|
||||
],
|
||||
'comma-dangle': [2, 'never'],
|
||||
'comma-spacing': [
|
||||
2,
|
||||
{
|
||||
before: false,
|
||||
after: true
|
||||
}
|
||||
],
|
||||
'comma-style': [2, 'last'],
|
||||
'constructor-super': 2,
|
||||
curly: [2, 'multi-line'],
|
||||
'dot-location': [2, 'property'],
|
||||
'eol-last': 2,
|
||||
eqeqeq: ['error', 'always', { null: 'ignore' }],
|
||||
'generator-star-spacing': [
|
||||
2,
|
||||
{
|
||||
before: true,
|
||||
after: true
|
||||
}
|
||||
],
|
||||
'handle-callback-err': [2, '^(err|error)$'],
|
||||
indent: [
|
||||
2,
|
||||
2,
|
||||
{
|
||||
SwitchCase: 1
|
||||
}
|
||||
],
|
||||
'jsx-quotes': [2, 'prefer-single'],
|
||||
'key-spacing': [
|
||||
2,
|
||||
{
|
||||
beforeColon: false,
|
||||
afterColon: true
|
||||
}
|
||||
],
|
||||
'keyword-spacing': [
|
||||
2,
|
||||
{
|
||||
before: true,
|
||||
after: true
|
||||
}
|
||||
],
|
||||
'new-cap': [
|
||||
2,
|
||||
{
|
||||
newIsCap: true,
|
||||
capIsNew: false
|
||||
}
|
||||
],
|
||||
'new-parens': 2,
|
||||
'no-array-constructor': 2,
|
||||
'no-caller': 2,
|
||||
'no-console': 'off',
|
||||
'no-class-assign': 2,
|
||||
'no-cond-assign': 2,
|
||||
'no-const-assign': 2,
|
||||
'no-control-regex': 0,
|
||||
'no-delete-var': 2,
|
||||
'no-dupe-args': 2,
|
||||
'no-dupe-class-members': 2,
|
||||
'no-dupe-keys': 2,
|
||||
'no-duplicate-case': 2,
|
||||
'no-empty-character-class': 2,
|
||||
'no-empty-pattern': 2,
|
||||
'no-eval': 2,
|
||||
'no-ex-assign': 2,
|
||||
'no-extend-native': 2,
|
||||
'no-extra-bind': 2,
|
||||
'no-extra-boolean-cast': 2,
|
||||
'no-extra-parens': [2, 'functions'],
|
||||
'no-fallthrough': 2,
|
||||
'no-floating-decimal': 2,
|
||||
'no-func-assign': 2,
|
||||
'no-implied-eval': 2,
|
||||
'no-inner-declarations': [2, 'functions'],
|
||||
'no-invalid-regexp': 2,
|
||||
'no-irregular-whitespace': 2,
|
||||
'no-iterator': 2,
|
||||
'no-label-var': 2,
|
||||
'no-labels': [
|
||||
2,
|
||||
{
|
||||
allowLoop: false,
|
||||
allowSwitch: false
|
||||
}
|
||||
],
|
||||
'no-lone-blocks': 2,
|
||||
'no-mixed-spaces-and-tabs': 2,
|
||||
'no-multi-spaces': 2,
|
||||
'no-multi-str': 2,
|
||||
'no-multiple-empty-lines': [
|
||||
2,
|
||||
{
|
||||
max: 1
|
||||
}
|
||||
],
|
||||
'no-native-reassign': 2,
|
||||
'no-negated-in-lhs': 2,
|
||||
'no-new-object': 2,
|
||||
'no-new-require': 2,
|
||||
'no-new-symbol': 2,
|
||||
'no-new-wrappers': 2,
|
||||
'no-obj-calls': 2,
|
||||
'no-octal': 2,
|
||||
'no-octal-escape': 2,
|
||||
'no-path-concat': 2,
|
||||
'no-proto': 2,
|
||||
'no-redeclare': 2,
|
||||
'no-regex-spaces': 2,
|
||||
'no-return-assign': [2, 'except-parens'],
|
||||
'no-self-assign': 2,
|
||||
'no-self-compare': 2,
|
||||
'no-sequences': 2,
|
||||
'no-shadow-restricted-names': 2,
|
||||
'no-spaced-func': 2,
|
||||
'no-sparse-arrays': 2,
|
||||
'no-this-before-super': 2,
|
||||
'no-throw-literal': 2,
|
||||
'no-trailing-spaces': 2,
|
||||
'no-undef': 2,
|
||||
'no-undef-init': 2,
|
||||
'no-unexpected-multiline': 2,
|
||||
'no-unmodified-loop-condition': 2,
|
||||
'no-unneeded-ternary': [
|
||||
2,
|
||||
{
|
||||
defaultAssignment: false
|
||||
}
|
||||
],
|
||||
'no-unreachable': 2,
|
||||
'no-unsafe-finally': 2,
|
||||
'no-unused-vars': [
|
||||
2,
|
||||
{
|
||||
vars: 'all',
|
||||
args: 'none'
|
||||
}
|
||||
],
|
||||
'no-useless-call': 2,
|
||||
'no-useless-computed-key': 2,
|
||||
'no-useless-constructor': 2,
|
||||
'no-useless-escape': 0,
|
||||
'no-whitespace-before-property': 2,
|
||||
'no-with': 2,
|
||||
'one-var': [
|
||||
2,
|
||||
{
|
||||
initialized: 'never'
|
||||
}
|
||||
],
|
||||
'operator-linebreak': [
|
||||
2,
|
||||
'after',
|
||||
{
|
||||
overrides: {
|
||||
'?': 'before',
|
||||
':': 'before'
|
||||
}
|
||||
}
|
||||
],
|
||||
'padded-blocks': [2, 'never'],
|
||||
quotes: [
|
||||
2,
|
||||
'single',
|
||||
{
|
||||
avoidEscape: true,
|
||||
allowTemplateLiterals: true
|
||||
}
|
||||
],
|
||||
semi: [2, 'never'],
|
||||
'semi-spacing': [
|
||||
2,
|
||||
{
|
||||
before: false,
|
||||
after: true
|
||||
}
|
||||
],
|
||||
'space-before-blocks': [2, 'always'],
|
||||
// 'space-before-function-paren': [2, 'never'],
|
||||
'space-in-parens': [2, 'never'],
|
||||
'space-infix-ops': 2,
|
||||
'space-unary-ops': [
|
||||
2,
|
||||
{
|
||||
words: true,
|
||||
nonwords: false
|
||||
}
|
||||
],
|
||||
'spaced-comment': [
|
||||
2,
|
||||
'always',
|
||||
{
|
||||
markers: ['global', 'globals', 'eslint', 'eslint-disable', '*package', '!', ',']
|
||||
}
|
||||
],
|
||||
'template-curly-spacing': [2, 'never'],
|
||||
'use-isnan': 2,
|
||||
'valid-typeof': 2,
|
||||
'wrap-iife': [2, 'any'],
|
||||
'yield-star-spacing': [2, 'both'],
|
||||
yoda: [2, 'never'],
|
||||
'prefer-const': 2,
|
||||
'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0,
|
||||
'object-curly-spacing': [
|
||||
2,
|
||||
'always',
|
||||
{
|
||||
objectsInObjects: false
|
||||
}
|
||||
],
|
||||
'array-bracket-spacing': [2, 'never']
|
||||
}
|
||||
}
|
||||
27
web/frps/.gitignore
vendored
27
web/frps/.gitignore
vendored
@@ -1,25 +1,6 @@
|
||||
.DS_Store
|
||||
node_modules
|
||||
/dist
|
||||
|
||||
|
||||
# local env files
|
||||
.env.local
|
||||
.env.*.local
|
||||
|
||||
# Log files
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
|
||||
# Editor directories and files
|
||||
node_modules/
|
||||
dist/
|
||||
npm-debug.log
|
||||
.idea
|
||||
.vscode
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
|
||||
package-lock.json
|
||||
.vscode/settings.json
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
{
|
||||
"bracketSpacing": true,
|
||||
"printWidth": 160,
|
||||
"semi": false,
|
||||
"singleQuote": true,
|
||||
"trailingComma": "none",
|
||||
"arrowParens": "avoid"
|
||||
}
|
||||
@@ -1,10 +1,7 @@
|
||||
.PHONY: dist build
|
||||
|
||||
build: install
|
||||
build:
|
||||
@npm run build
|
||||
|
||||
dev: install
|
||||
@npm run serve
|
||||
|
||||
install:
|
||||
@npm install
|
||||
@npm run dev
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
# frps
|
||||
|
||||
## Project setup
|
||||
|
||||
```
|
||||
npm install
|
||||
```
|
||||
|
||||
### Compiles and hot-reloads for development
|
||||
|
||||
```
|
||||
npm run serve
|
||||
```
|
||||
|
||||
### Compiles and minifies for production
|
||||
|
||||
```
|
||||
npm run build
|
||||
```
|
||||
|
||||
### Lints and fixes files
|
||||
|
||||
```
|
||||
npm run lint
|
||||
```
|
||||
@@ -1,3 +0,0 @@
|
||||
module.exports = {
|
||||
presets: ['@vue/cli-plugin-babel/preset']
|
||||
}
|
||||
@@ -4,43 +4,45 @@
|
||||
"author": "fatedier",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"serve": "vue-cli-service serve",
|
||||
"build": "vue-cli-service build",
|
||||
"lint": "vue-cli-service lint"
|
||||
"dev": "webpack-dev-server -d --inline --hot --env.dev",
|
||||
"build": "rimraf dist && webpack -p --progress --hide-modules"
|
||||
},
|
||||
"dependencies": {
|
||||
"core-js": "^3.7.0",
|
||||
"echarts": "^4.9.0",
|
||||
"element-ui": "^2.14.1",
|
||||
"bootstrap": "^3.3.7",
|
||||
"echarts": "^3.5.0",
|
||||
"element-ui": "^2.3.8",
|
||||
"humanize-plus": "^1.8.2",
|
||||
"vue": "^2.6.12",
|
||||
"vue-router": "^3.4.9",
|
||||
"vuex": "^3.5.1",
|
||||
"whatwg-fetch": "^3.5.0"
|
||||
"vue": "^2.5.16",
|
||||
"vue-resource": "^1.2.1",
|
||||
"vue-router": "^2.3.0",
|
||||
"whatwg-fetch": "^2.0.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vue/cli-plugin-babel": "~4.5.9",
|
||||
"@vue/cli-plugin-eslint": "~4.5.9",
|
||||
"@vue/cli-plugin-router": "~4.5.9",
|
||||
"@vue/cli-plugin-vuex": "~4.5.9",
|
||||
"@vue/cli-service": "~4.5.9",
|
||||
"@vue/eslint-config-standard": "^5.1.2",
|
||||
"babel-eslint": "^10.1.0",
|
||||
"eslint": "^7.14.0",
|
||||
"eslint-plugin-import": "^2.22.1",
|
||||
"eslint-plugin-node": "^11.1.0",
|
||||
"eslint-plugin-promise": "^4.2.1",
|
||||
"eslint-plugin-standard": "^4.1.0",
|
||||
"eslint-plugin-vue": "^7.1.0",
|
||||
"less": "^3.12.2",
|
||||
"less-loader": "^7.1.0",
|
||||
"node-sass": "^5.0.0",
|
||||
"sass-loader": "^10.1.0",
|
||||
"vue-template-compiler": "^2.6.12"
|
||||
},
|
||||
"browserslist": [
|
||||
"> 1%",
|
||||
"last 2 versions",
|
||||
"not dead"
|
||||
]
|
||||
"autoprefixer": "^6.6.0",
|
||||
"babel-core": "^6.21.0",
|
||||
"babel-eslint": "^7.1.1",
|
||||
"babel-loader": "^6.4.0",
|
||||
"babel-plugin-component": "^1.1.1",
|
||||
"babel-preset-es2015": "^6.13.2",
|
||||
"css-loader": "^0.27.0",
|
||||
"eslint": "^3.12.2",
|
||||
"eslint-config-enough": "^0.2.2",
|
||||
"eslint-loader": "^1.6.3",
|
||||
"file-loader": "^0.10.1",
|
||||
"html-loader": "^0.4.5",
|
||||
"html-webpack-plugin": "^2.24.1",
|
||||
"less": "^3.0.4",
|
||||
"less-loader": "^4.1.0",
|
||||
"postcss-loader": "^1.3.3",
|
||||
"rimraf": "^2.5.4",
|
||||
"style-loader": "^0.13.2",
|
||||
"url-loader": "^1.0.1",
|
||||
"vue-loader": "^15.0.10",
|
||||
"vue-template-compiler": "^2.1.8",
|
||||
"webpack": "^2.2.0-rc.4",
|
||||
"webpack-dev-server": "^3.1.4"
|
||||
}
|
||||
}
|
||||
|
||||
5
web/frps/postcss.config.js
Normal file
5
web/frps/postcss.config.js
Normal file
@@ -0,0 +1,5 @@
|
||||
module.exports = {
|
||||
plugins: [
|
||||
require('autoprefixer')()
|
||||
]
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||||
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
|
||||
<title><%= htmlWebpackPlugin.options.title %></title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>
|
||||
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
|
||||
</noscript>
|
||||
<div id="app"></div>
|
||||
<!-- built files will be auto injected -->
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,87 +1,80 @@
|
||||
<template>
|
||||
<div id="app">
|
||||
<header class="grid-content header-color">
|
||||
<el-row>
|
||||
<a class="brand" href="#">frp</a>
|
||||
</el-row>
|
||||
</header>
|
||||
<section>
|
||||
<el-row>
|
||||
<el-col id="side-nav" :xs="24" :md="4">
|
||||
<el-menu default-active="1" mode="vertical" theme="light" router @select="handleSelect">
|
||||
<el-menu-item index="/">Overview</el-menu-item>
|
||||
<el-submenu index="/proxies">
|
||||
<template slot="title">Proxies</template>
|
||||
<el-menu-item index="/proxies/tcp">TCP</el-menu-item>
|
||||
<el-menu-item index="/proxies/udp">UDP</el-menu-item>
|
||||
<el-menu-item index="/proxies/http">HTTP</el-menu-item>
|
||||
<el-menu-item index="/proxies/https">HTTPS</el-menu-item>
|
||||
<el-menu-item index="/proxies/stcp">STCP</el-menu-item>
|
||||
</el-submenu>
|
||||
<el-menu-item index="">Help</el-menu-item>
|
||||
</el-menu>
|
||||
</el-col>
|
||||
<div id="app">
|
||||
<header class="grid-content header-color">
|
||||
<el-row>
|
||||
<a class="brand" href="#">frp</a>
|
||||
</el-row>
|
||||
</header>
|
||||
<section>
|
||||
<el-row>
|
||||
<el-col id="side-nav" :xs="24" :md="4">
|
||||
<el-menu default-active="1" mode="vertical" theme="light" router="false" @select="handleSelect">
|
||||
<el-menu-item index="/">Overview</el-menu-item>
|
||||
<el-submenu index="/proxies">
|
||||
<template slot="title">Proxies</template>
|
||||
<el-menu-item index="/proxies/tcp">TCP</el-menu-item>
|
||||
<el-menu-item index="/proxies/udp">UDP</el-menu-item>
|
||||
<el-menu-item index="/proxies/http">HTTP</el-menu-item>
|
||||
<el-menu-item index="/proxies/https">HTTPS</el-menu-item>
|
||||
<el-menu-item index="/proxies/stcp">STCP</el-menu-item>
|
||||
</el-submenu>
|
||||
<el-menu-item index="">Help</el-menu-item>
|
||||
</el-menu>
|
||||
</el-col>
|
||||
|
||||
<el-col :xs="24" :md="20">
|
||||
<div id="content">
|
||||
<router-view v-if="serverInfo" />
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</section>
|
||||
</div>
|
||||
<el-col :xs="24" :md="20">
|
||||
<div id="content">
|
||||
<router-view></router-view>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</section>
|
||||
<footer></footer>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
computed: {
|
||||
serverInfo() {
|
||||
return this.$store.state.serverInfo
|
||||
export default {
|
||||
methods: {
|
||||
handleSelect(key, path) {
|
||||
if (key == '') {
|
||||
window.open("https://github.com/fatedier/frp")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
async created() {
|
||||
this.$store.dispatch('fetchServerInfo')
|
||||
},
|
||||
methods: {
|
||||
handleSelect(key, path) {
|
||||
if (key === '') {
|
||||
window.open('https://github.com/fatedier/frp')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
body {
|
||||
background-color: #fafafa;
|
||||
margin: 0px;
|
||||
font-family: -apple-system, BlinkMacSystemFont, Helvetica Neue, sans-serif;
|
||||
}
|
||||
|
||||
header {
|
||||
width: 100%;
|
||||
height: 60px;
|
||||
}
|
||||
|
||||
.header-color {
|
||||
background: #58b7ff;
|
||||
}
|
||||
|
||||
#content {
|
||||
margin-top: 20px;
|
||||
padding-right: 40px;
|
||||
}
|
||||
|
||||
.brand {
|
||||
color: #fff;
|
||||
background-color: transparent;
|
||||
margin-left: 20px;
|
||||
float: left;
|
||||
line-height: 25px;
|
||||
font-size: 25px;
|
||||
padding: 15px 15px;
|
||||
height: 30px;
|
||||
text-decoration: none;
|
||||
}
|
||||
body {
|
||||
background-color: #fafafa;
|
||||
margin: 0px;
|
||||
font-family: -apple-system,BlinkMacSystemFont,Helvetica Neue,sans-serif;
|
||||
}
|
||||
|
||||
header {
|
||||
width: 100%;
|
||||
height: 60px;
|
||||
}
|
||||
|
||||
.header-color {
|
||||
background: #58B7FF;
|
||||
}
|
||||
|
||||
#content {
|
||||
margin-top: 20px;
|
||||
padding-right: 40px;
|
||||
}
|
||||
|
||||
.brand {
|
||||
color: #fff;
|
||||
background-color: transparent;
|
||||
margin-left: 20px;
|
||||
float: left;
|
||||
line-height: 25px;
|
||||
font-size: 25px;
|
||||
padding: 15px 15px;
|
||||
height: 30px;
|
||||
text-decoration: none;
|
||||
}
|
||||
</style>
|
||||
|
||||
|
Before Width: | Height: | Size: 9.4 KiB After Width: | Height: | Size: 9.4 KiB |
@@ -1,160 +1,166 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-row>
|
||||
<el-col :md="12">
|
||||
<div class="source">
|
||||
<el-form label-position="left" class="server_info">
|
||||
<el-form-item label="Version">
|
||||
<span>{{ version }}</span>
|
||||
</el-form-item>
|
||||
<el-form-item label="BindPort">
|
||||
<span>{{ bind_port }}</span>
|
||||
</el-form-item>
|
||||
<el-form-item label="BindUdpPort">
|
||||
<span>{{ bind_udp_port }}</span>
|
||||
</el-form-item>
|
||||
<el-form-item label="Http Port">
|
||||
<span>{{ vhost_http_port }}</span>
|
||||
</el-form-item>
|
||||
<el-form-item label="Https Port">
|
||||
<span>{{ vhost_https_port }}</span>
|
||||
</el-form-item>
|
||||
<el-form-item label="Subdomain Host">
|
||||
<span>{{ subdomain_host }}</span>
|
||||
</el-form-item>
|
||||
<el-form-item label="Max PoolCount">
|
||||
<span>{{ max_pool_count }}</span>
|
||||
</el-form-item>
|
||||
<el-form-item label="Max Ports Per Client">
|
||||
<span>{{ max_ports_per_client }}</span>
|
||||
</el-form-item>
|
||||
<el-form-item label="HeartBeat Timeout">
|
||||
<span>{{ heart_beat_timeout }}</span>
|
||||
</el-form-item>
|
||||
<el-form-item label="Client Counts">
|
||||
<span>{{ client_counts }}</span>
|
||||
</el-form-item>
|
||||
<el-form-item label="Current Connections">
|
||||
<span>{{ cur_conns }}</span>
|
||||
</el-form-item>
|
||||
<el-form-item label="Proxy Counts">
|
||||
<span>{{ proxy_counts }}</span>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :md="12">
|
||||
<div id="traffic" style="width: 400px; height: 250px; margin-bottom: 30px" />
|
||||
<div id="proxies" style="width: 400px; height: 250px" />
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
<div>
|
||||
<el-row>
|
||||
<el-col :md="12">
|
||||
<div class="source">
|
||||
<el-form label-position="left" class="server_info">
|
||||
<el-form-item label="Version">
|
||||
<span>{{ version }}</span>
|
||||
</el-form-item>
|
||||
<el-form-item label="BindPort">
|
||||
<span>{{ bind_port }}</span>
|
||||
</el-form-item>
|
||||
<el-form-item label="BindUdpPort">
|
||||
<span>{{ bind_udp_port }}</span>
|
||||
</el-form-item>
|
||||
<el-form-item label="Http Port">
|
||||
<span>{{ vhost_http_port }}</span>
|
||||
</el-form-item>
|
||||
<el-form-item label="Https Port">
|
||||
<span>{{ vhost_https_port }}</span>
|
||||
</el-form-item>
|
||||
<el-form-item label="Subdomain Host">
|
||||
<span>{{ subdomain_host }}</span>
|
||||
</el-form-item>
|
||||
<el-form-item label="Max PoolCount">
|
||||
<span>{{ max_pool_count }}</span>
|
||||
</el-form-item>
|
||||
<el-form-item label="Max Ports Per Client">
|
||||
<span>{{ max_ports_per_client }}</span>
|
||||
</el-form-item>
|
||||
<el-form-item label="HeartBeat Timeout">
|
||||
<span>{{ heart_beat_timeout }}</span>
|
||||
</el-form-item>
|
||||
<el-form-item label="Client Counts">
|
||||
<span>{{ client_counts }}</span>
|
||||
</el-form-item>
|
||||
<el-form-item label="Current Connections">
|
||||
<span>{{ cur_conns }}</span>
|
||||
</el-form-item>
|
||||
<el-form-item label="Proxy Counts">
|
||||
<span>{{ proxy_counts }}</span>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :md="12">
|
||||
<div id="traffic" style="width: 400px;height:250px;margin-bottom: 30px;"></div>
|
||||
<div id="proxies" style="width: 400px;height:250px;"></div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { DrawTrafficChart, DrawProxyChart } from '../utils/chart.js'
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
version: '',
|
||||
bind_port: '',
|
||||
bind_udp_port: '',
|
||||
vhost_http_port: '',
|
||||
vhost_https_port: '',
|
||||
subdomain_host: '',
|
||||
max_pool_count: '',
|
||||
max_ports_per_client: '',
|
||||
heart_beat_timeout: '',
|
||||
client_counts: '',
|
||||
cur_conns: '',
|
||||
proxy_counts: ''
|
||||
import {DrawTrafficChart, DrawProxyChart} from '../utils/chart.js'
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
version: '',
|
||||
bind_port: '',
|
||||
bind_udp_port: '',
|
||||
vhost_http_port: '',
|
||||
vhost_https_port: '',
|
||||
subdomain_host: '',
|
||||
max_pool_count: '',
|
||||
max_ports_per_client: '',
|
||||
heart_beat_timeout: '',
|
||||
client_counts: '',
|
||||
cur_conns: '',
|
||||
proxy_counts: ''
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.fetchData()
|
||||
},
|
||||
watch: {
|
||||
'$route': 'fetchData'
|
||||
},
|
||||
methods: {
|
||||
fetchData() {
|
||||
fetch('/api/serverinfo', {credentials: 'include'})
|
||||
.then(res => {
|
||||
return res.json()
|
||||
}).then(json => {
|
||||
this.version = json.version
|
||||
this.bind_port = json.bind_port
|
||||
this.bind_udp_port = json.bind_udp_port
|
||||
if (this.bind_udp_port == 0) {
|
||||
this.bind_udp_port = "disable"
|
||||
}
|
||||
this.vhost_http_port = json.vhost_http_port
|
||||
if (this.vhost_http_port == 0) {
|
||||
this.vhost_http_port = "disable"
|
||||
}
|
||||
this.vhost_https_port = json.vhost_https_port
|
||||
if (this.vhost_https_port == 0) {
|
||||
this.vhost_https_port = "disable"
|
||||
}
|
||||
this.subdomain_host = json.subdomain_host
|
||||
this.max_pool_count = json.max_pool_count
|
||||
this.max_ports_per_client = json.max_ports_per_client
|
||||
if (this.max_ports_per_client == 0) {
|
||||
this.max_ports_per_client = "no limit"
|
||||
}
|
||||
this.heart_beat_timeout = json.heart_beat_timeout
|
||||
this.client_counts = json.client_counts
|
||||
this.cur_conns = json.cur_conns
|
||||
this.proxy_counts = 0
|
||||
if (json.proxy_type_count != null) {
|
||||
if (json.proxy_type_count.tcp != null) {
|
||||
this.proxy_counts += json.proxy_type_count.tcp
|
||||
}
|
||||
if (json.proxy_type_count.udp != null) {
|
||||
this.proxy_counts += json.proxy_type_count.udp
|
||||
}
|
||||
if (json.proxy_type_count.http != null) {
|
||||
this.proxy_counts += json.proxy_type_count.http
|
||||
}
|
||||
if (json.proxy_type_count.https != null) {
|
||||
this.proxy_counts += json.proxy_type_count.https
|
||||
}
|
||||
if (json.proxy_type_count.stcp != null) {
|
||||
this.proxy_counts += json.proxy_type_count.stcp
|
||||
}
|
||||
if (json.proxy_type_count.xtcp != null) {
|
||||
this.proxy_counts += json.proxy_type_count.xtcp
|
||||
}
|
||||
}
|
||||
DrawTrafficChart('traffic', json.total_traffic_in, json.total_traffic_out)
|
||||
DrawProxyChart('proxies', json)
|
||||
}).catch( err => {
|
||||
this.$message({
|
||||
showClose: true,
|
||||
message: 'Get server info from frps failed!',
|
||||
type: 'warning'
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
serverInfo() {
|
||||
return this.$store.state.serverInfo
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.initData()
|
||||
},
|
||||
methods: {
|
||||
initData() {
|
||||
console.log(!!this.serverInfo, this.serverInfo)
|
||||
if (!this.serverInfo) return
|
||||
|
||||
this.version = this.serverInfo.version
|
||||
this.bind_port = this.serverInfo.bind_port
|
||||
this.bind_udp_port = this.serverInfo.bind_udp_port
|
||||
if (this.bind_udp_port === 0) {
|
||||
this.bind_udp_port = 'disable'
|
||||
}
|
||||
this.vhost_http_port = this.serverInfo.vhost_http_port
|
||||
if (this.vhost_http_port === 0) {
|
||||
this.vhost_http_port = 'disable'
|
||||
}
|
||||
this.vhost_https_port = this.serverInfo.vhost_https_port
|
||||
if (this.vhost_https_port === 0) {
|
||||
this.vhost_https_port = 'disable'
|
||||
}
|
||||
this.subdomain_host = this.serverInfo.subdomain_host
|
||||
this.max_pool_count = this.serverInfo.max_pool_count
|
||||
this.max_ports_per_client = this.serverInfo.max_ports_per_client
|
||||
if (this.max_ports_per_client === 0) {
|
||||
this.max_ports_per_client = 'no limit'
|
||||
}
|
||||
this.heart_beat_timeout = this.serverInfo.heart_beat_timeout
|
||||
this.client_counts = this.serverInfo.client_counts
|
||||
this.cur_conns = this.serverInfo.cur_conns
|
||||
this.proxy_counts = 0
|
||||
if (this.serverInfo.proxy_type_count != null) {
|
||||
if (this.serverInfo.proxy_type_count.tcp != null) {
|
||||
this.proxy_counts += this.serverInfo.proxy_type_count.tcp
|
||||
}
|
||||
if (this.serverInfo.proxy_type_count.udp != null) {
|
||||
this.proxy_counts += this.serverInfo.proxy_type_count.udp
|
||||
}
|
||||
if (this.serverInfo.proxy_type_count.http != null) {
|
||||
this.proxy_counts += this.serverInfo.proxy_type_count.http
|
||||
}
|
||||
if (this.serverInfo.proxy_type_count.https != null) {
|
||||
this.proxy_counts += this.serverInfo.proxy_type_count.https
|
||||
}
|
||||
if (this.serverInfo.proxy_type_count.stcp != null) {
|
||||
this.proxy_counts += this.serverInfo.proxy_type_count.stcp
|
||||
}
|
||||
if (this.serverInfo.proxy_type_count.xtcp != null) {
|
||||
this.proxy_counts += this.serverInfo.proxy_type_count.xtcp
|
||||
}
|
||||
}
|
||||
DrawTrafficChart('traffic', this.serverInfo.total_traffic_in, this.serverInfo.total_traffic_out)
|
||||
DrawProxyChart('proxies', this.serverInfo)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.source {
|
||||
border: 1px solid #eaeefb;
|
||||
border-radius: 4px;
|
||||
transition: 0.2s;
|
||||
padding: 24px;
|
||||
border: 1px solid #eaeefb;
|
||||
border-radius: 4px;
|
||||
transition: .2s;
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
.server_info {
|
||||
margin-left: 40px;
|
||||
font-size: 0px;
|
||||
margin-left: 40px;
|
||||
font-size: 0px;
|
||||
}
|
||||
|
||||
.server_info label {
|
||||
width: 150px;
|
||||
color: #99a9bf;
|
||||
width: 150px;
|
||||
color: #99a9bf;
|
||||
}
|
||||
|
||||
.server_info .el-form-item {
|
||||
margin-right: 0;
|
||||
margin-bottom: 0;
|
||||
width: 100%;
|
||||
margin-right: 0;
|
||||
margin-bottom: 0;
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,14 +1,19 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-table :data="proxies" :default-sort="{ prop: 'name', order: 'ascending' }" style="width: 100%">
|
||||
<el-table :data="proxies" :default-sort="{prop: 'name', order: 'ascending'}" style="width: 100%">
|
||||
<el-table-column type="expand">
|
||||
<template slot-scope="props">
|
||||
<el-popover ref="popover4" placement="right" width="600" style="margin-left: 0px" trigger="click">
|
||||
<my-traffic-chart :proxy-name="props.row.name" />
|
||||
<el-popover
|
||||
ref="popover4"
|
||||
placement="right"
|
||||
width="600"
|
||||
style="margin-left:0px"
|
||||
trigger="click">
|
||||
<my-traffic-chart :proxy_name="props.row.name"></my-traffic-chart>
|
||||
</el-popover>
|
||||
|
||||
<el-button v-popover:popover4 type="primary" size="small" icon="view" style="margin-bottom: 10px">Traffic Statistics</el-button>
|
||||
|
||||
|
||||
<el-button v-popover:popover4 type="primary" size="small" icon="view" style="margin-bottom:10px">Traffic Statistics</el-button>
|
||||
|
||||
<el-form label-position="left" inline class="demo-table-expand">
|
||||
<el-form-item label="Name">
|
||||
<span>{{ props.row.name }}</span>
|
||||
@@ -40,68 +45,104 @@
|
||||
<el-form-item label="Last Close">
|
||||
<span>{{ props.row.last_close_time }}</span>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="Name" prop="name" sortable />
|
||||
<el-table-column label="Port" prop="port" sortable />
|
||||
<el-table-column label="Connections" prop="conns" sortable />
|
||||
<el-table-column label="Traffic In" prop="traffic_in" :formatter="formatTrafficIn" sortable />
|
||||
<el-table-column label="Traffic Out" prop="traffic_out" :formatter="formatTrafficOut" sortable />
|
||||
<el-table-column label="status" prop="status" sortable>
|
||||
<template slot-scope="scope">
|
||||
<el-tag v-if="scope.row.status === 'online'" type="success">{{ scope.row.status }}</el-tag>
|
||||
<el-tag v-else type="danger">{{ scope.row.status }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
</el-form>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
label="Name"
|
||||
prop="name"
|
||||
sortable>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
label="Port"
|
||||
prop="port"
|
||||
sortable>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
label="Connections"
|
||||
prop="conns"
|
||||
sortable>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
label="Traffic In"
|
||||
prop="traffic_in"
|
||||
:formatter="formatTrafficIn"
|
||||
sortable>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
label="Traffic Out"
|
||||
prop="traffic_out"
|
||||
:formatter="formatTrafficOut"
|
||||
sortable>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
label="status"
|
||||
prop="status"
|
||||
sortable>
|
||||
<template slot-scope="scope">
|
||||
<el-tag type="success" v-if="scope.row.status === 'online'">{{ scope.row.status }}</el-tag>
|
||||
<el-tag type="danger" v-else>{{ scope.row.status }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Humanize from 'humanize-plus'
|
||||
import Traffic from './Traffic.vue'
|
||||
import { HttpProxy } from '../utils/proxy.js'
|
||||
export default {
|
||||
components: {
|
||||
'my-traffic-chart': Traffic
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
proxies: [],
|
||||
vhost_http_port: '',
|
||||
subdomain_host: ''
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
serverInfo() {
|
||||
return this.$store.state.serverInfo
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.initData()
|
||||
},
|
||||
methods: {
|
||||
formatTrafficIn(row, column) {
|
||||
return Humanize.fileSize(row.traffic_in)
|
||||
},
|
||||
formatTrafficOut(row, column) {
|
||||
return Humanize.fileSize(row.traffic_out)
|
||||
},
|
||||
async initData() {
|
||||
if (!this.serverInfo) return
|
||||
this.vhost_http_port = this.serverInfo.vhost_http_port
|
||||
this.subdomain_host = this.serverInfo.subdomain_host
|
||||
if (this.vhost_http_port == null || this.vhost_http_port === 0) return
|
||||
|
||||
const json = await this.$fetch('proxy/http')
|
||||
if (!json) return
|
||||
|
||||
this.proxies = []
|
||||
for (const proxyStats of json.proxies) {
|
||||
this.proxies.push(new HttpProxy(proxyStats, this.vhost_http_port, this.subdomain_host))
|
||||
import Humanize from 'humanize-plus';
|
||||
import Traffic from './Traffic.vue'
|
||||
import {
|
||||
HttpProxy
|
||||
} from '../utils/proxy.js'
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
proxies: null,
|
||||
vhost_http_port: "",
|
||||
subdomain_host: ""
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.fetchData()
|
||||
},
|
||||
watch: {
|
||||
'$route': 'fetchData'
|
||||
},
|
||||
methods: {
|
||||
formatTrafficIn(row, column) {
|
||||
return Humanize.fileSize(row.traffic_in)
|
||||
},
|
||||
formatTrafficOut(row, column) {
|
||||
return Humanize.fileSize(row.traffic_out)
|
||||
},
|
||||
fetchData() {
|
||||
fetch('/api/serverinfo', {credentials: 'include'})
|
||||
.then(res => {
|
||||
return res.json()
|
||||
}).then(json => {
|
||||
this.vhost_http_port = json.vhost_http_port
|
||||
this.subdomain_host = json.subdomain_host
|
||||
if (this.vhost_http_port == null || this.vhost_http_port == 0) {
|
||||
return
|
||||
} else {
|
||||
fetch('/api/proxy/http', {credentials: 'include'})
|
||||
.then(res => {
|
||||
return res.json()
|
||||
}).then(json => {
|
||||
this.proxies = new Array()
|
||||
for (let proxyStats of json.proxies) {
|
||||
this.proxies.push(new HttpProxy(proxyStats, this.vhost_http_port, this.subdomain_host))
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
components: {
|
||||
'my-traffic-chart': Traffic
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
|
||||
@@ -1,14 +1,19 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-table :data="proxies" :default-sort="{ prop: 'name', order: 'ascending' }" style="width: 100%">
|
||||
<el-table :data="proxies" :default-sort="{prop: 'name', order: 'ascending'}" style="width: 100%">
|
||||
<el-table-column type="expand">
|
||||
<template slot-scope="props">
|
||||
<el-popover ref="popover4" placement="right" width="600" style="margin-left: 0px" trigger="click">
|
||||
<my-traffic-chart :proxy-name="props.row.name" />
|
||||
<el-popover
|
||||
ref="popover4"
|
||||
placement="right"
|
||||
width="600"
|
||||
style="margin-left:0px"
|
||||
trigger="click">
|
||||
<my-traffic-chart :proxy_name="props.row.name"></my-traffic-chart>
|
||||
</el-popover>
|
||||
|
||||
<el-button v-popover:popover4 type="primary" size="small" icon="view" style="margin-bottom: 10px">Traffic Statistics</el-button>
|
||||
|
||||
|
||||
<el-button v-popover:popover4 type="primary" size="small" icon="view" style="margin-bottom:10px">Traffic Statistics</el-button>
|
||||
|
||||
<el-form label-position="left" inline class="demo-table-expand">
|
||||
<el-form-item label="Name">
|
||||
<span>{{ props.row.name }}</span>
|
||||
@@ -34,69 +39,105 @@
|
||||
<el-form-item label="Last Close">
|
||||
<span>{{ props.row.last_close_time }}</span>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="Name" prop="name" sortable />
|
||||
<el-table-column label="Port" prop="port" sortable />
|
||||
<el-table-column label="Connections" prop="conns" sortable />
|
||||
<el-table-column label="Traffic In" prop="traffic_in" :formatter="formatTrafficIn" sortable />
|
||||
<el-table-column label="Traffic Out" prop="traffic_out" :formatter="formatTrafficOut" sortable />
|
||||
<el-table-column label="status" prop="status" sortable>
|
||||
<template slot-scope="scope">
|
||||
<el-tag v-if="scope.row.status === 'online'" type="success">{{ scope.row.status }}</el-tag>
|
||||
<el-tag v-else type="danger">{{ scope.row.status }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
</el-form>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
label="Name"
|
||||
prop="name"
|
||||
sortable>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
label="Port"
|
||||
prop="port"
|
||||
sortable>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
label="Connections"
|
||||
prop="conns"
|
||||
sortable>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
label="Traffic In"
|
||||
prop="traffic_in"
|
||||
:formatter="formatTrafficIn"
|
||||
sortable>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
label="Traffic Out"
|
||||
prop="traffic_out"
|
||||
:formatter="formatTrafficOut"
|
||||
sortable>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
label="status"
|
||||
prop="status"
|
||||
sortable>
|
||||
<template slot-scope="scope">
|
||||
<el-tag type="success" v-if="scope.row.status === 'online'">{{ scope.row.status }}</el-tag>
|
||||
<el-tag type="danger" v-else>{{ scope.row.status }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Humanize from 'humanize-plus'
|
||||
import Traffic from './Traffic.vue'
|
||||
import { HttpsProxy } from '../utils/proxy.js'
|
||||
export default {
|
||||
components: {
|
||||
'my-traffic-chart': Traffic
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
proxies: [],
|
||||
vhost_https_port: '',
|
||||
subdomain_host: ''
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
serverInfo() {
|
||||
return this.$store.state.serverInfo
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.initData()
|
||||
},
|
||||
methods: {
|
||||
formatTrafficIn(row, column) {
|
||||
return Humanize.fileSize(row.traffic_in)
|
||||
},
|
||||
formatTrafficOut(row, column) {
|
||||
return Humanize.fileSize(row.traffic_out)
|
||||
},
|
||||
async initData() {
|
||||
if (!this.serverInfo) return
|
||||
|
||||
this.vhost_https_port = this.serverInfo.vhost_https_port
|
||||
this.subdomain_host = this.serverInfo.subdomain_host
|
||||
if (this.vhost_https_port == null || this.vhost_https_port === 0) return
|
||||
|
||||
const json = await this.$fetch('proxy/https')
|
||||
if (!json) return
|
||||
|
||||
this.proxies = []
|
||||
for (const proxyStats of json.proxies) {
|
||||
this.proxies.push(new HttpsProxy(proxyStats, this.vhost_https_port, this.subdomain_host))
|
||||
import Humanize from 'humanize-plus';
|
||||
import Traffic from './Traffic.vue'
|
||||
import {
|
||||
HttpsProxy
|
||||
} from '../utils/proxy.js'
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
proxies: null,
|
||||
vhost_https_port: '',
|
||||
subdomain_host: ''
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.fetchData()
|
||||
},
|
||||
watch: {
|
||||
'$route': 'fetchData'
|
||||
},
|
||||
methods: {
|
||||
formatTrafficIn(row, column) {
|
||||
return Humanize.fileSize(row.traffic_in)
|
||||
},
|
||||
formatTrafficOut(row, column) {
|
||||
return Humanize.fileSize(row.traffic_out)
|
||||
},
|
||||
fetchData() {
|
||||
fetch('/api/serverinfo', {credentials: 'include'})
|
||||
.then(res => {
|
||||
return res.json()
|
||||
}).then(json => {
|
||||
this.vhost_https_port = json.vhost_https_port
|
||||
this.subdomain_host = json.subdomain_host
|
||||
if (this.vhost_https_port == null || this.vhost_https_port == 0) {
|
||||
return
|
||||
} else {
|
||||
fetch('/api/proxy/https', {credentials: 'include'})
|
||||
.then(res => {
|
||||
return res.json()
|
||||
}).then(json => {
|
||||
this.proxies = new Array()
|
||||
for (let proxyStats of json.proxies) {
|
||||
this.proxies.push(new HttpsProxy(proxyStats, this.vhost_https_port, this.subdomain_host))
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
components: {
|
||||
'my-traffic-chart': Traffic
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
|
||||
@@ -1,16 +1,19 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-table :data="proxies" :default-sort="{ prop: 'name', order: 'ascending' }" style="width: 100%">
|
||||
<el-table :data="proxies" :default-sort="{prop: 'name', order: 'ascending'}" style="width: 100%">
|
||||
<el-table-column type="expand">
|
||||
<template slot-scope="props">
|
||||
<el-popover ref="popover4" placement="right" width="600" style="margin-left: 0px" trigger="click">
|
||||
<my-traffic-chart :proxy-name="props.row.name" />
|
||||
<el-popover
|
||||
ref="popover4"
|
||||
placement="right"
|
||||
width="600"
|
||||
style="margin-left:0px"
|
||||
trigger="click">
|
||||
<my-traffic-chart :proxy_name="props.row.name"></my-traffic-chart>
|
||||
</el-popover>
|
||||
|
||||
<el-button v-popover:popover4 type="primary" size="small" icon="view" :name="props.row.name" style="margin-bottom: 10px" @click="fetchData2">
|
||||
Traffic Statistics
|
||||
</el-button>
|
||||
|
||||
|
||||
<el-button v-popover:popover4 type="primary" size="small" icon="view" :name="props.row.name" style="margin-bottom:10px" @click="fetchData2">Traffic Statistics</el-button>
|
||||
|
||||
<el-form label-position="left" inline class="demo-table-expand">
|
||||
<el-form-item label="Name">
|
||||
<span>{{ props.row.name }}</span>
|
||||
@@ -30,57 +33,83 @@
|
||||
<el-form-item label="Last Close">
|
||||
<span>{{ props.row.last_close_time }}</span>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-form>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="Name" prop="name" sortable />
|
||||
<el-table-column label="Connections" prop="conns" sortable />
|
||||
<el-table-column label="Traffic In" prop="traffic_in" :formatter="formatTrafficIn" sortable />
|
||||
<el-table-column label="Traffic Out" prop="traffic_out" :formatter="formatTrafficOut" sortable />
|
||||
<el-table-column label="status" prop="status" sortable>
|
||||
<template slot-scope="scope">
|
||||
<el-tag v-if="scope.row.status === 'online'" type="success">{{ scope.row.status }}</el-tag>
|
||||
<el-tag v-else type="danger">{{ scope.row.status }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
label="Name"
|
||||
prop="name"
|
||||
sortable>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
label="Connections"
|
||||
prop="conns"
|
||||
sortable>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
label="Traffic In"
|
||||
prop="traffic_in"
|
||||
:formatter="formatTrafficIn"
|
||||
sortable>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
label="Traffic Out"
|
||||
prop="traffic_out"
|
||||
:formatter="formatTrafficOut"
|
||||
sortable>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
label="status"
|
||||
prop="status"
|
||||
sortable>
|
||||
<template slot-scope="scope">
|
||||
<el-tag type="success" v-if="scope.row.status === 'online'">{{ scope.row.status }}</el-tag>
|
||||
<el-tag type="danger" v-else>{{ scope.row.status }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Humanize from 'humanize-plus'
|
||||
import Traffic from './Traffic.vue'
|
||||
import { StcpProxy } from '../utils/proxy.js'
|
||||
export default {
|
||||
components: {
|
||||
'my-traffic-chart': Traffic
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
proxies: []
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.initData()
|
||||
},
|
||||
methods: {
|
||||
formatTrafficIn(row, column) {
|
||||
return Humanize.fileSize(row.traffic_in)
|
||||
},
|
||||
formatTrafficOut(row, column) {
|
||||
return Humanize.fileSize(row.traffic_out)
|
||||
},
|
||||
async initData() {
|
||||
const json = await this.$fetch('proxy/stcp')
|
||||
if (!json) return
|
||||
|
||||
this.proxies = []
|
||||
for (const proxyStats of json.proxies) {
|
||||
this.proxies.push(new StcpProxy(proxyStats))
|
||||
import Humanize from 'humanize-plus'
|
||||
import Traffic from './Traffic.vue'
|
||||
import { StcpProxy } from '../utils/proxy.js'
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
proxies: null
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.fetchData()
|
||||
},
|
||||
watch: {
|
||||
'$route': 'fetchData'
|
||||
},
|
||||
methods: {
|
||||
formatTrafficIn(row, column) {
|
||||
return Humanize.fileSize(row.traffic_in)
|
||||
},
|
||||
formatTrafficOut(row, column) {
|
||||
return Humanize.fileSize(row.traffic_out)
|
||||
},
|
||||
fetchData() {
|
||||
fetch('/api/proxy/stcp', {credentials: 'include'})
|
||||
.then(res => {
|
||||
return res.json()
|
||||
}).then(json => {
|
||||
this.proxies = new Array()
|
||||
for (let proxyStats of json.proxies) {
|
||||
this.proxies.push(new StcpProxy(proxyStats))
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
components: {
|
||||
'my-traffic-chart': Traffic
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
@@ -1,16 +1,19 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-table :data="proxies" :default-sort="{ prop: 'name', order: 'ascending' }" style="width: 100%">
|
||||
<el-table :data="proxies" :default-sort="{prop: 'name', order: 'ascending'}" style="width: 100%">
|
||||
<el-table-column type="expand">
|
||||
<template slot-scope="props">
|
||||
<el-popover placement="right" width="600" style="margin-left: 0px" trigger="click">
|
||||
<my-traffic-chart :proxy-name="props.row.name" />
|
||||
|
||||
<el-button slot="reference" type="primary" size="small" icon="view" :name="props.row.name" style="margin-bottom: 10px">
|
||||
Traffic Statistics
|
||||
</el-button>
|
||||
<el-popover
|
||||
ref="popover4"
|
||||
placement="right"
|
||||
width="600"
|
||||
style="margin-left:0px"
|
||||
trigger="click">
|
||||
<my-traffic-chart :proxy_name="props.row.name"></my-traffic-chart>
|
||||
</el-popover>
|
||||
|
||||
|
||||
<el-button v-popover:popover4 type="primary" size="small" icon="view" :name="props.row.name" style="margin-bottom:10px" @click="fetchData2">Traffic Statistics</el-button>
|
||||
|
||||
<el-form label-position="left" inline class="demo-table-expand">
|
||||
<el-form-item label="Name">
|
||||
<span>{{ props.row.name }}</span>
|
||||
@@ -33,58 +36,88 @@
|
||||
<el-form-item label="Last Close">
|
||||
<span>{{ props.row.last_close_time }}</span>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-form>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="Name" prop="name" sortable />
|
||||
<el-table-column label="Port" prop="port" sortable />
|
||||
<el-table-column label="Connections" prop="conns" sortable />
|
||||
<el-table-column label="Traffic In" prop="traffic_in" :formatter="formatTrafficIn" sortable />
|
||||
<el-table-column label="Traffic Out" prop="traffic_out" :formatter="formatTrafficOut" sortable />
|
||||
<el-table-column label="status" prop="status" sortable>
|
||||
<template slot-scope="scope">
|
||||
<el-tag v-if="scope.row.status === 'online'" type="success">{{ scope.row.status }}</el-tag>
|
||||
<el-tag v-else type="danger">{{ scope.row.status }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
label="Name"
|
||||
prop="name"
|
||||
sortable>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
label="Port"
|
||||
prop="port"
|
||||
sortable>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
label="Connections"
|
||||
prop="conns"
|
||||
sortable>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
label="Traffic In"
|
||||
prop="traffic_in"
|
||||
:formatter="formatTrafficIn"
|
||||
sortable>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
label="Traffic Out"
|
||||
prop="traffic_out"
|
||||
:formatter="formatTrafficOut"
|
||||
sortable>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
label="status"
|
||||
prop="status"
|
||||
sortable>
|
||||
<template slot-scope="scope">
|
||||
<el-tag type="success" v-if="scope.row.status === 'online'">{{ scope.row.status }}</el-tag>
|
||||
<el-tag type="danger" v-else>{{ scope.row.status }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Humanize from 'humanize-plus'
|
||||
import Traffic from './Traffic.vue'
|
||||
import { TcpProxy } from '../utils/proxy.js'
|
||||
export default {
|
||||
components: {
|
||||
'my-traffic-chart': Traffic
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
proxies: []
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.initData()
|
||||
},
|
||||
methods: {
|
||||
formatTrafficIn(row, column) {
|
||||
return Humanize.fileSize(row.traffic_in)
|
||||
},
|
||||
formatTrafficOut(row, column) {
|
||||
return Humanize.fileSize(row.traffic_out)
|
||||
},
|
||||
async initData() {
|
||||
const json = await this.$fetch('proxy/tcp')
|
||||
if (!json) return
|
||||
|
||||
this.proxies = []
|
||||
for (const proxyStats of json.proxies) {
|
||||
this.proxies.push(new TcpProxy(proxyStats))
|
||||
import Humanize from 'humanize-plus'
|
||||
import Traffic from './Traffic.vue'
|
||||
import { TcpProxy } from '../utils/proxy.js'
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
proxies: null
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.fetchData()
|
||||
},
|
||||
watch: {
|
||||
'$route': 'fetchData'
|
||||
},
|
||||
methods: {
|
||||
formatTrafficIn(row, column) {
|
||||
return Humanize.fileSize(row.traffic_in)
|
||||
},
|
||||
formatTrafficOut(row, column) {
|
||||
return Humanize.fileSize(row.traffic_out)
|
||||
},
|
||||
fetchData() {
|
||||
fetch('/api/proxy/tcp', {credentials: 'include'})
|
||||
.then(res => {
|
||||
return res.json()
|
||||
}).then(json => {
|
||||
this.proxies = new Array()
|
||||
for (let proxyStats of json.proxies) {
|
||||
this.proxies.push(new TcpProxy(proxyStats))
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
components: {
|
||||
'my-traffic-chart': Traffic
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
@@ -1,14 +1,19 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-table :data="proxies" :default-sort="{ prop: 'name', order: 'ascending' }" style="width: 100%">
|
||||
<el-table :data="proxies" :default-sort="{prop: 'name', order: 'ascending'}" style="width: 100%">
|
||||
<el-table-column type="expand">
|
||||
<template slot-scope="props">
|
||||
<el-popover ref="popover4" placement="right" width="600" style="margin-left: 0px" trigger="click">
|
||||
<my-traffic-chart :proxy-name="props.row.name" />
|
||||
<el-popover
|
||||
ref="popover4"
|
||||
placement="right"
|
||||
width="600"
|
||||
style="margin-left:0px"
|
||||
trigger="click">
|
||||
<my-traffic-chart :proxy_name="props.row.name"></my-traffic-chart>
|
||||
</el-popover>
|
||||
|
||||
<el-button v-popover:popover4 type="primary" size="small" icon="view" style="margin-bottom: 10px">Traffic Statistics</el-button>
|
||||
|
||||
|
||||
<el-button v-popover:popover4 type="primary" size="small" icon="view" style="margin-bottom:10px">Traffic Statistics</el-button>
|
||||
|
||||
<el-form label-position="left" inline class="demo-table-expand">
|
||||
<el-form-item label="Name">
|
||||
<span>{{ props.row.name }}</span>
|
||||
@@ -31,57 +36,91 @@
|
||||
<el-form-item label="Last Close">
|
||||
<span>{{ props.row.last_close_time }}</span>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="Name" prop="name" sortable />
|
||||
<el-table-column label="Port" prop="port" sortable />
|
||||
<el-table-column label="Connections" prop="conns" sortable />
|
||||
<el-table-column label="Traffic In" prop="traffic_in" :formatter="formatTrafficIn" sortable />
|
||||
<el-table-column label="Traffic Out" prop="traffic_out" :formatter="formatTrafficOut" sortable />
|
||||
<el-table-column label="status" prop="status" sortable>
|
||||
<template slot-scope="scope">
|
||||
<el-tag v-if="scope.row.status === 'online'" type="success">{{ scope.row.status }}</el-tag>
|
||||
<el-tag v-else type="danger">{{ scope.row.status }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
</el-form>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
label="Name"
|
||||
prop="name"
|
||||
sortable>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
label="Port"
|
||||
prop="port"
|
||||
sortable>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
label="Connections"
|
||||
prop="conns"
|
||||
sortable>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
label="Traffic In"
|
||||
prop="traffic_in"
|
||||
:formatter="formatTrafficIn"
|
||||
sortable>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
label="Traffic Out"
|
||||
prop="traffic_out"
|
||||
:formatter="formatTrafficOut"
|
||||
sortable>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
label="status"
|
||||
prop="status"
|
||||
sortable>
|
||||
<template slot-scope="scope">
|
||||
<el-tag type="success" v-if="scope.row.status === 'online'">{{ scope.row.status }}</el-tag>
|
||||
<el-tag type="danger" v-else>{{ scope.row.status }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Humanize from 'humanize-plus'
|
||||
import Traffic from './Traffic.vue'
|
||||
import { UdpProxy } from '../utils/proxy.js'
|
||||
export default {
|
||||
components: {
|
||||
'my-traffic-chart': Traffic
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
proxies: []
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.initData()
|
||||
},
|
||||
methods: {
|
||||
formatTrafficIn(row, column) {
|
||||
return Humanize.fileSize(row.traffic_in)
|
||||
},
|
||||
formatTrafficOut(row, column) {
|
||||
return Humanize.fileSize(row.traffic_out)
|
||||
},
|
||||
async initData() {
|
||||
const json = await this.$fetch('proxy/udp')
|
||||
if (!json) return
|
||||
|
||||
this.proxies = []
|
||||
for (const proxyStats of json.proxies) {
|
||||
this.proxies.push(new UdpProxy(proxyStats))
|
||||
import Humanize from 'humanize-plus';
|
||||
import Traffic from './Traffic.vue'
|
||||
import {
|
||||
UdpProxy
|
||||
} from '../utils/proxy.js'
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
proxies: null
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.fetchData()
|
||||
},
|
||||
watch: {
|
||||
'$route': 'fetchData'
|
||||
},
|
||||
methods: {
|
||||
formatTrafficIn(row, column) {
|
||||
return Humanize.fileSize(row.traffic_in)
|
||||
},
|
||||
formatTrafficOut(row, column) {
|
||||
return Humanize.fileSize(row.traffic_out)
|
||||
},
|
||||
fetchData() {
|
||||
fetch('/api/proxy/udp', {credentials: 'include'})
|
||||
.then(res => {
|
||||
return res.json()
|
||||
}).then(json => {
|
||||
this.proxies = new Array()
|
||||
for (let proxyStats of json.proxies) {
|
||||
this.proxies.push(new UdpProxy(proxyStats))
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
components: {
|
||||
'my-traffic-chart': Traffic
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
|
||||
@@ -1,26 +1,36 @@
|
||||
<template>
|
||||
<div :id="proxyName" style="width: 600px; height: 400px" />
|
||||
<div :id="proxy_name" style="width: 600px;height:400px;"></div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { DrawProxyTrafficChart } from '../utils/chart.js'
|
||||
import {DrawProxyTrafficChart} from '../utils/chart.js'
|
||||
export default {
|
||||
props: {
|
||||
proxyName: {
|
||||
type: String,
|
||||
required: true
|
||||
props: ['proxy_name'],
|
||||
created() {
|
||||
this.fetchData()
|
||||
},
|
||||
//watch: {
|
||||
//'$route': 'fetchData'
|
||||
//},
|
||||
methods: {
|
||||
fetchData() {
|
||||
let url = '/api/traffic/' + this.proxy_name
|
||||
fetch(url, {credentials: 'include'})
|
||||
.then(res => {
|
||||
return res.json()
|
||||
}).then(json => {
|
||||
DrawProxyTrafficChart(this.proxy_name, json.traffic_in, json.traffic_out)
|
||||
}).catch( err => {
|
||||
this.$message({
|
||||
showClose: true,
|
||||
message: 'Get server info from frps failed!' + err,
|
||||
type: 'warning'
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.initData()
|
||||
},
|
||||
methods: {
|
||||
async initData() {
|
||||
const json = await this.$fetch(`traffic/${this.proxyName}`)
|
||||
if (!json) return
|
||||
|
||||
DrawProxyTrafficChart(this.proxyName, json.traffic_in, json.traffic_out)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
|
||||
15
web/frps/src/index.html
Normal file
15
web/frps/src/index.html
Normal file
@@ -0,0 +1,15 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>frps dashboard</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<!--<script src="https://code.jquery.com/jquery-3.2.0.min.js"></script>-->
|
||||
<!--<script src="//cdn.bootcss.com/echarts/3.4.0/echarts.min.js"></script>-->
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -1,6 +1,19 @@
|
||||
import Vue from 'vue'
|
||||
// import ElementUI from 'element-ui'
|
||||
import { Button, Form, FormItem, Row, Col, Table, TableColumn, Popover, Menu, Submenu, MenuItem, Tag, Message } from 'element-ui'
|
||||
//import ElementUI from 'element-ui'
|
||||
import {
|
||||
Button,
|
||||
Form,
|
||||
FormItem,
|
||||
Row,
|
||||
Col,
|
||||
Table,
|
||||
TableColumn,
|
||||
Popover,
|
||||
Menu,
|
||||
Submenu,
|
||||
MenuItem,
|
||||
Tag
|
||||
} from 'element-ui'
|
||||
import lang from 'element-ui/lib/locale/lang/en'
|
||||
import locale from 'element-ui/lib/locale'
|
||||
import 'element-ui/lib/theme-chalk/index.css'
|
||||
@@ -8,7 +21,6 @@ import './utils/less/custom.less'
|
||||
|
||||
import App from './App.vue'
|
||||
import router from './router'
|
||||
import store from '@/store'
|
||||
import 'whatwg-fetch'
|
||||
|
||||
locale.use(lang)
|
||||
@@ -25,15 +37,12 @@ Vue.use(Menu)
|
||||
Vue.use(Submenu)
|
||||
Vue.use(MenuItem)
|
||||
Vue.use(Tag)
|
||||
Vue.prototype.$message = Message
|
||||
|
||||
import fetch from '@/utils/fetch'
|
||||
Vue.prototype.$fetch = fetch
|
||||
|
||||
Vue.config.productionTip = false
|
||||
|
||||
new Vue({
|
||||
router,
|
||||
store,
|
||||
render: h => h(App)
|
||||
}).$mount('#app')
|
||||
el: '#app',
|
||||
router,
|
||||
template: '<App/>',
|
||||
components: { App }
|
||||
})
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user