Compare commits

...

35 Commits

Author SHA1 Message Date
fatedier
90b7f2080f Merge pull request #1122 from fatedier/dev
bump version to v0.25.0
2019-03-11 17:40:39 +08:00
fatedier
d1f1c72a55 update ci 2019-03-11 17:11:26 +08:00
fatedier
1925847ef8 update doc 2019-03-11 16:24:54 +08:00
fatedier
8b216b0ca9 Merge pull request #1121 from fatedier/new
new feature
2019-03-11 16:05:18 +08:00
fatedier
dbfeea99f3 update .travis.yml, support go1.12 2019-03-11 16:02:45 +08:00
fatedier
5e64bbfa7c vendor: update package 2019-03-11 15:54:55 +08:00
fatedier
e691a40260 improve the stability of xtcp 2019-03-11 15:53:58 +08:00
fatedier
d812488767 support tls connection 2019-03-11 14:14:31 +08:00
fatedier
3c03690ab7 Merge pull request #1112 from fatedier/p2p
xtcp: wrap yamux on kcp connections, fix #1103
2019-03-05 11:27:15 +08:00
fatedier
3df27b9c04 xtcp: wrap yamux on kcp connections 2019-03-05 11:18:17 +08:00
fatedier
ba45d29b7c fix xtcp cmd 2019-03-03 23:44:44 +08:00
fatedier
3cf83f57a8 update yamux version 2019-03-03 22:29:08 +08:00
fatedier
03e4318d79 Merge pull request #1107 from likev/patch-1
Update instruction of 'Rewriting the Host Header'
2019-03-03 21:57:24 +08:00
xufanglu
178d134f46 Update instruction of 'Rewriting the Host Header'
Update instruction of 'Rewriting the Host Header' in README.md
2019-03-02 21:33:23 +08:00
fatedier
cbf9c731a0 Merge pull request #1088 from fatedier/dev
bump version to v0.24.1
2019-02-12 15:10:43 +08:00
fatedier
de4bfcc43c bump version to v0.24.1 2019-02-12 15:03:40 +08:00
fatedier
9737978f28 Merge pull request #1087 from fatedier/fix
fix PUT /api/config without token
2019-02-12 15:03:00 +08:00
fatedier
5bc7fe2cea fix PUT /api/config without token 2019-02-12 14:59:30 +08:00
fatedier
65d8fe37c5 Merge pull request #1081 from fatedier/dev
bump version to v0.24.0
2019-02-11 14:46:23 +08:00
fatedier
1723d7b651 Merge pull request #1080 from fatedier/client
frpc: support admin UI
2019-02-11 14:42:48 +08:00
fatedier
2481dfab64 fix api 2019-02-11 14:37:52 +08:00
fatedier
95a881a7d3 frps: update server dashboard_api 2019-02-11 11:42:07 +08:00
fatedier
fe403ab328 frpc: update admin_api 2019-02-11 11:26:06 +08:00
fatedier
66555dbb00 frpc admin: not allow empty PUT /api/config body 2019-02-02 11:46:53 +08:00
fatedier
7f9ea48405 bump version to v0.24.0 2019-02-01 19:28:38 +08:00
fatedier
96d7e2da6f add admin UI for frpc 2019-02-01 19:28:05 +08:00
fatedier
d879b8208b frpc: add api PUT api/config 2019-01-31 18:35:44 +08:00
fatedier
3585e456d4 frpc: add api GET api/config 2019-01-31 17:17:34 +08:00
fatedier
1de8c3fc87 Merge pull request #1069 from fatedier/vet
go vet & golint
2019-01-31 16:59:03 +08:00
fatedier
bbab3fe9ca go lint 2019-01-31 16:54:46 +08:00
fatedier
48990da22e go vet 2019-01-31 16:49:23 +08:00
fatedier
5543fc2a9a Merge pull request #1068 from fatedier/dev
bump version to v0.23.3
2019-01-30 11:38:39 +08:00
fatedier
c41de6fd28 bump version 2019-01-30 11:22:25 +08:00
fatedier
8c8fd9790e Merge pull request #1067 from fatedier/fix
frpc: reload proxy not saved after reconnecting
2019-01-30 11:22:41 +08:00
fatedier
5a7ef3be74 frpc: reload proxy not saved after reconnecting 2019-01-30 11:12:28 +08:00
78 changed files with 17022 additions and 290 deletions

View File

@@ -2,8 +2,8 @@ sudo: false
language: go language: go
go: go:
- 1.10.x
- 1.11.x - 1.11.x
- 1.12.x
install: install:
- make - make

View File

@@ -6,11 +6,12 @@ build: frps frpc
# compile assets into binary file # compile assets into binary file
file: file:
rm -rf ./assets/static/* rm -rf ./assets/frps/static/*
cp -rf ./web/frps/dist/* ./assets/static rm -rf ./assets/frpc/static/*
go get -d github.com/rakyll/statik cp -rf ./web/frps/dist/* ./assets/frps/static
go install github.com/rakyll/statik cp -rf ./web/frpc/dist/* ./assets/frpc/static
rm -rf ./assets/statik rm -rf ./assets/frps/statik
rm -rf ./assets/frpc/statik
go generate ./assets/... go generate ./assets/...
fmt: fmt:
@@ -18,7 +19,6 @@ fmt:
frps: frps:
go build -o bin/frps ./cmd/frps go build -o bin/frps ./cmd/frps
@cp -rf ./assets/static ./bin
frpc: frpc:
go build -o bin/frpc ./cmd/frpc go build -o bin/frpc ./cmd/frpc

View File

@@ -28,8 +28,10 @@ Now it also try to support p2p connect.
* [Configuration File](#configuration-file) * [Configuration File](#configuration-file)
* [Configuration file template](#configuration-file-template) * [Configuration file template](#configuration-file-template)
* [Dashboard](#dashboard) * [Dashboard](#dashboard)
* [Admin UI](#admin-ui)
* [Authentication](#authentication) * [Authentication](#authentication)
* [Encryption and Compression](#encryption-and-compression) * [Encryption and Compression](#encryption-and-compression)
* [TLS](#tls)
* [Hot-Reload frpc configuration](#hot-reload-frpc-configuration) * [Hot-Reload frpc configuration](#hot-reload-frpc-configuration)
* [Get proxy status from client](#get-proxy-status-from-client) * [Get proxy status from client](#get-proxy-status-from-client)
* [Port White List](#port-white-list) * [Port White List](#port-white-list)
@@ -389,6 +391,22 @@ Then visit `http://[server_addr]:7500` to see dashboard, default username and pa
![dashboard](/doc/pic/dashboard.png) ![dashboard](/doc/pic/dashboard.png)
### Admin UI
Admin UI help you check and manage frpc's configure.
Configure a address for admin UI to enable this feature:
```ini
[common]
admin_addr = 127.0.0.1
admin_port = 7400
admin_user = admin
admin_pwd = admin
```
Then visit `http://127.0.0.1:7400` to see admin UI, default username and password are both `admin`.
### Authentication ### Authentication
`token` in frps.ini and frpc.ini should be same. `token` in frps.ini and frpc.ini should be same.
@@ -407,6 +425,14 @@ use_encryption = true
use_compression = true use_compression = true
``` ```
#### TLS
frp support TLS protocol between frpc and frps since v0.25.0.
Config `tls_enable = true` in `common` section to frpc.ini to enable this feature.
For port multiplexing, frp send a first byte 0x17 to dial a TLS connection.
### Hot-Reload frpc configuration ### Hot-Reload frpc configuration
First you need to set admin port in frpc's configure file to let it provide HTTP API for more features. First you need to set admin port in frpc's configure file to let it provide HTTP API for more features.
@@ -592,7 +618,7 @@ custom_domains = test.yourdomain.com
host_header_rewrite = dev.yourdomain.com host_header_rewrite = dev.yourdomain.com
``` ```
If `host_header_rewrite` is specified, the host header will be rewritten to match the hostname portion of the forwarding address. The `Host` request header will be rewritten to `Host: dev.yourdomain.com` before it reach your local http server.
### Set Headers In HTTP Request ### Set Headers In HTTP Request
@@ -736,8 +762,6 @@ plugin_http_passwd = abc
## Development Plan ## Development Plan
* Log http request information in frps. * Log http request information in frps.
* Direct reverse proxy, like haproxy.
* kubernetes ingress support.
## Contributing ## Contributing

View File

@@ -24,8 +24,10 @@ frp 是一个可用于内网穿透的高性能的反向代理应用,支持 tcp
* [配置文件](#配置文件) * [配置文件](#配置文件)
* [配置文件模版渲染](#配置文件模版渲染) * [配置文件模版渲染](#配置文件模版渲染)
* [Dashboard](#dashboard) * [Dashboard](#dashboard)
* [Admin UI](#admin-ui)
* [身份验证](#身份验证) * [身份验证](#身份验证)
* [加密与压缩](#加密与压缩) * [加密与压缩](#加密与压缩)
* [TLS](#tls)
* [客户端热加载配置文件](#客户端热加载配置文件) * [客户端热加载配置文件](#客户端热加载配置文件)
* [客户端查看代理状态](#客户端查看代理状态) * [客户端查看代理状态](#客户端查看代理状态)
* [端口白名单](#端口白名单) * [端口白名单](#端口白名单)
@@ -47,6 +49,7 @@ frp 是一个可用于内网穿透的高性能的反向代理应用,支持 tcp
* [开发计划](#开发计划) * [开发计划](#开发计划)
* [为 frp 做贡献](#为-frp-做贡献) * [为 frp 做贡献](#为-frp-做贡献)
* [捐助](#捐助) * [捐助](#捐助)
* [知识星球](#知识星球)
* [支付宝扫码捐赠](#支付宝扫码捐赠) * [支付宝扫码捐赠](#支付宝扫码捐赠)
* [微信支付捐赠](#微信支付捐赠) * [微信支付捐赠](#微信支付捐赠)
* [Paypal 捐赠](#paypal-捐赠) * [Paypal 捐赠](#paypal-捐赠)
@@ -404,6 +407,24 @@ dashboard_pwd = admin
![dashboard](/doc/pic/dashboard.png) ![dashboard](/doc/pic/dashboard.png)
### Admin UI
Admin UI 可以帮助用户通过浏览器来查询和管理客户端的 proxy 状态和配置。
需要在 frpc.ini 中指定 admin 服务使用的端口,即可开启此功能:
```ini
[common]
admin_addr = 127.0.0.1
admin_port = 7400
admin_user = admin
admin_pwd = admin
```
打开浏览器通过 `http://127.0.0.1:7400` 访问 Admin UI用户名密码默认为 `admin`。
如果想要在外网环境访问 Admin UI将 7400 端口映射出去即可,但需要重视安全风险。
### 身份验证 ### 身份验证
服务端和客户端的 common 配置中的 `token` 参数一致则身份验证通过。 服务端和客户端的 common 配置中的 `token` 参数一致则身份验证通过。
@@ -426,6 +447,14 @@ use_compression = true
如果传输的报文长度较长,通过设置 `use_compression = true` 对传输内容进行压缩,可以有效减小 frpc 与 frps 之间的网络流量,加快流量转发速度,但是会额外消耗一些 cpu 资源。 如果传输的报文长度较长,通过设置 `use_compression = true` 对传输内容进行压缩,可以有效减小 frpc 与 frps 之间的网络流量,加快流量转发速度,但是会额外消耗一些 cpu 资源。
#### TLS
从 v0.25.0 版本开始 frpc 和 frps 之间支持通过 TLS 协议加密传输。通过在 `frpc.ini` 的 `common` 中配置 `tls_enable = true` 来启用此功能,安全性更高。
为了端口复用frp 建立 TLS 连接的第一个字节为 0x17。
**注意: 启用此功能后除 xtcp 外,不需要再设置 use_encryption。**
### 客户端热加载配置文件 ### 客户端热加载配置文件
当修改了 frpc 中的代理配置,可以通过 `frpc reload` 命令来动态加载配置文件,通常会在 10 秒内完成代理的更新。 当修改了 frpc 中的代理配置,可以通过 `frpc reload` 命令来动态加载配置文件,通常会在 10 秒内完成代理的更新。

View File

@@ -14,8 +14,10 @@
package assets package assets
//go:generate statik -src=./static //go:generate statik -src=./frps/static -dest=./frps
//go:generate go fmt statik/statik.go //go:generate statik -src=./frpc/static -dest=./frpc
//go:generate go fmt ./frps/statik/statik.go
//go:generate go fmt ./frpc/statik/statik.go
import ( import (
"io/ioutil" "io/ioutil"
@@ -24,8 +26,6 @@ import (
"path" "path"
"github.com/rakyll/statik/fs" "github.com/rakyll/statik/fs"
_ "github.com/fatedier/frp/assets/statik"
) )
var ( var (

View File

Before

Width:  |  Height:  |  Size: 9.4 KiB

After

Width:  |  Height:  |  Size: 9.4 KiB

View File

@@ -0,0 +1 @@
<!doctype html> <html lang=en> <head> <meta charset=utf-8> <title>frp client admin UI</title> <link rel="shortcut icon" href="favicon.ico"></head> <body> <div id=app></div> <script type="text/javascript" src="manifest.js?d2cd6337d30c7b22e836"></script><script type="text/javascript" src="vendor.js?edb271e1d9c81f857840"></script></body> </html>

View File

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

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -20,6 +20,7 @@ import (
"net/http" "net/http"
"time" "time"
"github.com/fatedier/frp/assets"
"github.com/fatedier/frp/g" "github.com/fatedier/frp/g"
frpNet "github.com/fatedier/frp/utils/net" frpNet "github.com/fatedier/frp/utils/net"
@@ -41,6 +42,15 @@ func (svr *Service) RunAdminServer(addr string, port int) (err error) {
// api, see dashboard_api.go // api, see dashboard_api.go
router.HandleFunc("/api/reload", svr.apiReload).Methods("GET") router.HandleFunc("/api/reload", svr.apiReload).Methods("GET")
router.HandleFunc("/api/status", svr.apiStatus).Methods("GET") router.HandleFunc("/api/status", svr.apiStatus).Methods("GET")
router.HandleFunc("/api/config", svr.apiGetConfig).Methods("GET")
router.HandleFunc("/api/config", svr.apiPutConfig).Methods("PUT")
// view
router.Handle("/favicon.ico", http.FileServer(assets.FileSystem)).Methods("GET")
router.PathPrefix("/static/").Handler(frpNet.MakeHttpGzipHandler(http.StripPrefix("/static/", http.FileServer(assets.FileSystem)))).Methods("GET")
router.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, "/static/", http.StatusMovedPermanently)
})
address := fmt.Sprintf("%s:%d", addr, port) address := fmt.Sprintf("%s:%d", addr, port)
server := &http.Server{ server := &http.Server{

View File

@@ -17,6 +17,7 @@ package client
import ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"io/ioutil"
"net/http" "net/http"
"sort" "sort"
"strings" "strings"
@@ -28,57 +29,53 @@ import (
) )
type GeneralResponse struct { type GeneralResponse struct {
Code int64 `json:"code"` Code int
Msg string `json:"msg"` Msg string
} }
// api/reload // GET api/reload
type ReloadResp struct {
GeneralResponse
}
func (svr *Service) apiReload(w http.ResponseWriter, r *http.Request) { func (svr *Service) apiReload(w http.ResponseWriter, r *http.Request) {
var ( res := GeneralResponse{Code: 200}
buf []byte
res ReloadResp
)
defer func() {
log.Info("Http response [/api/reload]: code [%d]", res.Code)
buf, _ = json.Marshal(&res)
w.Write(buf)
}()
log.Info("Http request: [/api/reload]") log.Info("Http request [/api/reload]")
defer func() {
log.Info("Http response [/api/reload], code [%d]", res.Code)
w.WriteHeader(res.Code)
if len(res.Msg) > 0 {
w.Write([]byte(res.Msg))
}
}()
content, err := config.GetRenderedConfFromFile(g.GlbClientCfg.CfgFile) content, err := config.GetRenderedConfFromFile(g.GlbClientCfg.CfgFile)
if err != nil { if err != nil {
res.Code = 1 res.Code = 400
res.Msg = err.Error() res.Msg = err.Error()
log.Error("reload frpc config file error: %v", err) log.Warn("reload frpc config file error: %s", res.Msg)
return return
} }
newCommonCfg, err := config.UnmarshalClientConfFromIni(nil, content) newCommonCfg, err := config.UnmarshalClientConfFromIni(nil, content)
if err != nil { if err != nil {
res.Code = 2 res.Code = 400
res.Msg = err.Error() res.Msg = err.Error()
log.Error("reload frpc common section error: %v", err) log.Warn("reload frpc common section error: %s", res.Msg)
return return
} }
pxyCfgs, visitorCfgs, err := config.LoadAllConfFromIni(g.GlbClientCfg.User, content, newCommonCfg.Start) pxyCfgs, visitorCfgs, err := config.LoadAllConfFromIni(g.GlbClientCfg.User, content, newCommonCfg.Start)
if err != nil { if err != nil {
res.Code = 3 res.Code = 400
res.Msg = err.Error() res.Msg = err.Error()
log.Error("reload frpc proxy config error: %v", err) log.Warn("reload frpc proxy config error: %s", res.Msg)
return return
} }
err = svr.ctl.ReloadConf(pxyCfgs, visitorCfgs) err = svr.ReloadConf(pxyCfgs, visitorCfgs)
if err != nil { if err != nil {
res.Code = 4 res.Code = 500
res.Msg = err.Error() res.Msg = err.Error()
log.Error("reload frpc proxy config error: %v", err) log.Warn("reload frpc proxy config error: %s", res.Msg)
return return
} }
log.Info("success reload conf") log.Info("success reload conf")
@@ -163,7 +160,7 @@ func NewProxyStatusResp(status *proxy.ProxyStatus) ProxyStatusResp {
return psr return psr
} }
// api/status // GET api/status
func (svr *Service) apiStatus(w http.ResponseWriter, r *http.Request) { func (svr *Service) apiStatus(w http.ResponseWriter, r *http.Request) {
var ( var (
buf []byte buf []byte
@@ -175,14 +172,14 @@ func (svr *Service) apiStatus(w http.ResponseWriter, r *http.Request) {
res.Https = make([]ProxyStatusResp, 0) res.Https = make([]ProxyStatusResp, 0)
res.Stcp = make([]ProxyStatusResp, 0) res.Stcp = make([]ProxyStatusResp, 0)
res.Xtcp = make([]ProxyStatusResp, 0) res.Xtcp = make([]ProxyStatusResp, 0)
log.Info("Http request [/api/status]")
defer func() { defer func() {
log.Info("Http response [/api/status]") log.Info("Http response [/api/status]")
buf, _ = json.Marshal(&res) buf, _ = json.Marshal(&res)
w.Write(buf) w.Write(buf)
}() }()
log.Info("Http request: [/api/status]")
ps := svr.ctl.pm.GetAllProxyStatus() ps := svr.ctl.pm.GetAllProxyStatus()
for _, status := range ps { for _, status := range ps {
switch status.Type { switch status.Type {
@@ -208,3 +205,122 @@ func (svr *Service) apiStatus(w http.ResponseWriter, r *http.Request) {
sort.Sort(ByProxyStatusResp(res.Xtcp)) sort.Sort(ByProxyStatusResp(res.Xtcp))
return return
} }
// GET api/config
func (svr *Service) apiGetConfig(w http.ResponseWriter, r *http.Request) {
res := GeneralResponse{Code: 200}
log.Info("Http get request [/api/config]")
defer func() {
log.Info("Http get response [/api/config], code [%d]", res.Code)
w.WriteHeader(res.Code)
if len(res.Msg) > 0 {
w.Write([]byte(res.Msg))
}
}()
if g.GlbClientCfg.CfgFile == "" {
res.Code = 400
res.Msg = "frpc has no config file path"
log.Warn("%s", res.Msg)
return
}
content, err := config.GetRenderedConfFromFile(g.GlbClientCfg.CfgFile)
if err != nil {
res.Code = 400
res.Msg = err.Error()
log.Warn("load frpc config file error: %s", res.Msg)
return
}
rows := strings.Split(content, "\n")
newRows := make([]string, 0, len(rows))
for _, row := range rows {
row = strings.TrimSpace(row)
if strings.HasPrefix(row, "token") {
continue
}
newRows = append(newRows, row)
}
res.Msg = strings.Join(newRows, "\n")
}
// PUT api/config
func (svr *Service) apiPutConfig(w http.ResponseWriter, r *http.Request) {
res := GeneralResponse{Code: 200}
log.Info("Http put request [/api/config]")
defer func() {
log.Info("Http put response [/api/config], code [%d]", res.Code)
w.WriteHeader(res.Code)
if len(res.Msg) > 0 {
w.Write([]byte(res.Msg))
}
}()
// get new config content
body, err := ioutil.ReadAll(r.Body)
if err != nil {
res.Code = 400
res.Msg = fmt.Sprintf("read request body error: %v", err)
log.Warn("%s", res.Msg)
return
}
if len(body) == 0 {
res.Code = 400
res.Msg = "body can't be empty"
log.Warn("%s", res.Msg)
return
}
// get token from origin content
token := ""
b, err := ioutil.ReadFile(g.GlbClientCfg.CfgFile)
if err != nil {
res.Code = 400
res.Msg = err.Error()
log.Warn("load frpc config file error: %s", res.Msg)
return
}
content := string(b)
for _, row := range strings.Split(content, "\n") {
row = strings.TrimSpace(row)
if strings.HasPrefix(row, "token") {
token = row
break
}
}
tmpRows := make([]string, 0)
for _, row := range strings.Split(string(body), "\n") {
row = strings.TrimSpace(row)
if strings.HasPrefix(row, "token") {
continue
}
tmpRows = append(tmpRows, row)
}
newRows := make([]string, 0)
if token != "" {
for _, row := range tmpRows {
newRows = append(newRows, row)
if strings.HasPrefix(row, "[common]") {
newRows = append(newRows, token)
}
}
} else {
newRows = tmpRows
}
content = strings.Join(newRows, "\n")
err = ioutil.WriteFile(g.GlbClientCfg.CfgFile, []byte(content), 0644)
if err != nil {
res.Code = 500
res.Msg = fmt.Sprintf("write content to frpc config file error: %v", err)
log.Warn("%s", res.Msg)
return
}
}

View File

@@ -15,6 +15,7 @@
package client package client
import ( import (
"crypto/tls"
"fmt" "fmt"
"io" "io"
"runtime/debug" "runtime/debug"
@@ -166,8 +167,14 @@ func (ctl *Control) connectServer() (conn frpNet.Conn, err error) {
} }
conn = frpNet.WrapConn(stream) conn = frpNet.WrapConn(stream)
} else { } else {
conn, err = frpNet.ConnectServerByProxy(g.GlbClientCfg.HttpProxy, g.GlbClientCfg.Protocol, var tlsConfig *tls.Config
fmt.Sprintf("%s:%d", g.GlbClientCfg.ServerAddr, g.GlbClientCfg.ServerPort)) if g.GlbClientCfg.TLSEnable {
tlsConfig = &tls.Config{
InsecureSkipVerify: true,
}
}
conn, err = frpNet.ConnectServerByProxyWithTLS(g.GlbClientCfg.HttpProxy, g.GlbClientCfg.Protocol,
fmt.Sprintf("%s:%d", g.GlbClientCfg.ServerAddr, g.GlbClientCfg.ServerPort), tlsConfig)
if err != nil { if err != nil {
ctl.Warn("start new connection to server error: %v", err) ctl.Warn("start new connection to server error: %v", err)
return return

View File

@@ -18,7 +18,10 @@ import (
"bytes" "bytes"
"fmt" "fmt"
"io" "io"
"io/ioutil"
"net" "net"
"strconv"
"strings"
"sync" "sync"
"time" "time"
@@ -33,6 +36,7 @@ import (
"github.com/fatedier/golib/errors" "github.com/fatedier/golib/errors"
frpIo "github.com/fatedier/golib/io" frpIo "github.com/fatedier/golib/io"
"github.com/fatedier/golib/pool" "github.com/fatedier/golib/pool"
fmux "github.com/hashicorp/yamux"
) )
// Proxy defines how to handle work connections for different proxy type. // Proxy defines how to handle work connections for different proxy type.
@@ -53,32 +57,32 @@ func NewProxy(pxyConf config.ProxyConf) (pxy Proxy) {
switch cfg := pxyConf.(type) { switch cfg := pxyConf.(type) {
case *config.TcpProxyConf: case *config.TcpProxyConf:
pxy = &TcpProxy{ pxy = &TcpProxy{
BaseProxy: baseProxy, BaseProxy: &baseProxy,
cfg: cfg, cfg: cfg,
} }
case *config.UdpProxyConf: case *config.UdpProxyConf:
pxy = &UdpProxy{ pxy = &UdpProxy{
BaseProxy: baseProxy, BaseProxy: &baseProxy,
cfg: cfg, cfg: cfg,
} }
case *config.HttpProxyConf: case *config.HttpProxyConf:
pxy = &HttpProxy{ pxy = &HttpProxy{
BaseProxy: baseProxy, BaseProxy: &baseProxy,
cfg: cfg, cfg: cfg,
} }
case *config.HttpsProxyConf: case *config.HttpsProxyConf:
pxy = &HttpsProxy{ pxy = &HttpsProxy{
BaseProxy: baseProxy, BaseProxy: &baseProxy,
cfg: cfg, cfg: cfg,
} }
case *config.StcpProxyConf: case *config.StcpProxyConf:
pxy = &StcpProxy{ pxy = &StcpProxy{
BaseProxy: baseProxy, BaseProxy: &baseProxy,
cfg: cfg, cfg: cfg,
} }
case *config.XtcpProxyConf: case *config.XtcpProxyConf:
pxy = &XtcpProxy{ pxy = &XtcpProxy{
BaseProxy: baseProxy, BaseProxy: &baseProxy,
cfg: cfg, cfg: cfg,
} }
} }
@@ -93,7 +97,7 @@ type BaseProxy struct {
// TCP // TCP
type TcpProxy struct { type TcpProxy struct {
BaseProxy *BaseProxy
cfg *config.TcpProxyConf cfg *config.TcpProxyConf
proxyPlugin plugin.Plugin proxyPlugin plugin.Plugin
@@ -122,7 +126,7 @@ func (pxy *TcpProxy) InWorkConn(conn frpNet.Conn) {
// HTTP // HTTP
type HttpProxy struct { type HttpProxy struct {
BaseProxy *BaseProxy
cfg *config.HttpProxyConf cfg *config.HttpProxyConf
proxyPlugin plugin.Plugin proxyPlugin plugin.Plugin
@@ -151,7 +155,7 @@ func (pxy *HttpProxy) InWorkConn(conn frpNet.Conn) {
// HTTPS // HTTPS
type HttpsProxy struct { type HttpsProxy struct {
BaseProxy *BaseProxy
cfg *config.HttpsProxyConf cfg *config.HttpsProxyConf
proxyPlugin plugin.Plugin proxyPlugin plugin.Plugin
@@ -180,7 +184,7 @@ func (pxy *HttpsProxy) InWorkConn(conn frpNet.Conn) {
// STCP // STCP
type StcpProxy struct { type StcpProxy struct {
BaseProxy *BaseProxy
cfg *config.StcpProxyConf cfg *config.StcpProxyConf
proxyPlugin plugin.Plugin proxyPlugin plugin.Plugin
@@ -209,7 +213,7 @@ func (pxy *StcpProxy) InWorkConn(conn frpNet.Conn) {
// XTCP // XTCP
type XtcpProxy struct { type XtcpProxy struct {
BaseProxy *BaseProxy
cfg *config.XtcpProxyConf cfg *config.XtcpProxyConf
proxyPlugin plugin.Plugin proxyPlugin plugin.Plugin
@@ -278,37 +282,102 @@ func (pxy *XtcpProxy) InWorkConn(conn frpNet.Conn) {
return return
} }
pxy.Trace("get natHoleRespMsg, sid [%s], client address [%s]", natHoleRespMsg.Sid, natHoleRespMsg.ClientAddr) pxy.Trace("get natHoleRespMsg, sid [%s], client address [%s] visitor address [%s]", natHoleRespMsg.Sid, natHoleRespMsg.ClientAddr, natHoleRespMsg.VisitorAddr)
// Send sid to visitor udp address. // Send detect message
time.Sleep(time.Second) array := strings.Split(natHoleRespMsg.VisitorAddr, ":")
if len(array) <= 1 {
pxy.Error("get NatHoleResp visitor address error: %v", natHoleRespMsg.VisitorAddr)
}
laddr, _ := net.ResolveUDPAddr("udp", clientConn.LocalAddr().String()) laddr, _ := net.ResolveUDPAddr("udp", clientConn.LocalAddr().String())
daddr, err := net.ResolveUDPAddr("udp", natHoleRespMsg.VisitorAddr) /*
for i := 1000; i < 65000; i++ {
pxy.sendDetectMsg(array[0], int64(i), laddr, "a")
}
*/
port, err := strconv.ParseInt(array[1], 10, 64)
if err != nil { if err != nil {
pxy.Error("resolve visitor udp address error: %v", err) pxy.Error("get natHoleResp visitor address error: %v", natHoleRespMsg.VisitorAddr)
return return
} }
pxy.sendDetectMsg(array[0], int(port), laddr, []byte(natHoleRespMsg.Sid))
pxy.Trace("send all detect msg done")
lConn, err := net.DialUDP("udp", laddr, daddr) msg.WriteMsg(conn, &msg.NatHoleClientDetectOK{})
// Listen for clientConn's address and wait for visitor connection
lConn, err := net.ListenUDP("udp", laddr)
if err != nil { if err != nil {
pxy.Error("dial visitor udp address error: %v", err) pxy.Error("listen on visitorConn's local adress error: %v", err)
return return
} }
lConn.Write([]byte(natHoleRespMsg.Sid)) defer lConn.Close()
kcpConn, err := frpNet.NewKcpConnFromUdp(lConn, true, natHoleRespMsg.VisitorAddr) lConn.SetReadDeadline(time.Now().Add(8 * time.Second))
sidBuf := pool.GetBuf(1024)
var uAddr *net.UDPAddr
n, uAddr, err = lConn.ReadFromUDP(sidBuf)
if err != nil {
pxy.Warn("get sid from visitor error: %v", err)
return
}
lConn.SetReadDeadline(time.Time{})
if string(sidBuf[:n]) != natHoleRespMsg.Sid {
pxy.Warn("incorrect sid from visitor")
return
}
pool.PutBuf(sidBuf)
pxy.Info("nat hole connection make success, sid [%s]", natHoleRespMsg.Sid)
lConn.WriteToUDP(sidBuf[:n], uAddr)
kcpConn, err := frpNet.NewKcpConnFromUdp(lConn, false, natHoleRespMsg.VisitorAddr)
if err != nil { if err != nil {
pxy.Error("create kcp connection from udp connection error: %v", err) pxy.Error("create kcp connection from udp connection error: %v", err)
return return
} }
fmuxCfg := fmux.DefaultConfig()
fmuxCfg.KeepAliveInterval = 5 * time.Second
fmuxCfg.LogOutput = ioutil.Discard
sess, err := fmux.Server(kcpConn, fmuxCfg)
if err != nil {
pxy.Error("create yamux server from kcp connection error: %v", err)
return
}
defer sess.Close()
muxConn, err := sess.Accept()
if err != nil {
pxy.Error("accept for yamux connection error: %v", err)
return
}
HandleTcpWorkConnection(&pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, HandleTcpWorkConnection(&pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf,
frpNet.WrapConn(kcpConn), []byte(pxy.cfg.Sk)) frpNet.WrapConn(muxConn), []byte(pxy.cfg.Sk))
}
func (pxy *XtcpProxy) sendDetectMsg(addr string, port int, laddr *net.UDPAddr, content []byte) (err error) {
daddr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", addr, port))
if err != nil {
return err
}
tConn, err := net.DialUDP("udp", laddr, daddr)
if err != nil {
return err
}
//uConn := ipv4.NewConn(tConn)
//uConn.SetTTL(3)
tConn.Write(content)
tConn.Close()
return nil
} }
// UDP // UDP
type UdpProxy struct { type UdpProxy struct {
BaseProxy *BaseProxy
cfg *config.UdpProxyConf cfg *config.UdpProxyConf

View File

@@ -15,6 +15,7 @@
package client package client
import ( import (
"crypto/tls"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"runtime" "runtime"
@@ -22,6 +23,7 @@ import (
"sync/atomic" "sync/atomic"
"time" "time"
"github.com/fatedier/frp/assets"
"github.com/fatedier/frp/g" "github.com/fatedier/frp/g"
"github.com/fatedier/frp/models/config" "github.com/fatedier/frp/models/config"
"github.com/fatedier/frp/models/msg" "github.com/fatedier/frp/models/msg"
@@ -49,7 +51,14 @@ type Service struct {
closedCh chan int closedCh chan int
} }
func NewService(pxyCfgs map[string]config.ProxyConf, visitorCfgs map[string]config.VisitorConf) (svr *Service) { func NewService(pxyCfgs map[string]config.ProxyConf, visitorCfgs map[string]config.VisitorConf) (svr *Service, err error) {
// Init assets
err = assets.Load("")
if err != nil {
err = fmt.Errorf("Load assets error: %v", err)
return
}
svr = &Service{ svr = &Service{
pxyCfgs: pxyCfgs, pxyCfgs: pxyCfgs,
visitorCfgs: visitorCfgs, visitorCfgs: visitorCfgs,
@@ -143,8 +152,14 @@ func (svr *Service) keepControllerWorking() {
// conn: control connection // conn: control connection
// session: if it's not nil, using tcp mux // session: if it's not nil, using tcp mux
func (svr *Service) login() (conn frpNet.Conn, session *fmux.Session, err error) { func (svr *Service) login() (conn frpNet.Conn, session *fmux.Session, err error) {
conn, err = frpNet.ConnectServerByProxy(g.GlbClientCfg.HttpProxy, g.GlbClientCfg.Protocol, var tlsConfig *tls.Config
fmt.Sprintf("%s:%d", g.GlbClientCfg.ServerAddr, g.GlbClientCfg.ServerPort)) if g.GlbClientCfg.TLSEnable {
tlsConfig = &tls.Config{
InsecureSkipVerify: true,
}
}
conn, err = frpNet.ConnectServerByProxyWithTLS(g.GlbClientCfg.HttpProxy, g.GlbClientCfg.Protocol,
fmt.Sprintf("%s:%d", g.GlbClientCfg.ServerAddr, g.GlbClientCfg.ServerPort), tlsConfig)
if err != nil { if err != nil {
return return
} }

View File

@@ -18,14 +18,11 @@ import (
"bytes" "bytes"
"fmt" "fmt"
"io" "io"
"io/ioutil"
"net" "net"
"strconv"
"strings"
"sync" "sync"
"time" "time"
"golang.org/x/net/ipv4"
"github.com/fatedier/frp/g" "github.com/fatedier/frp/g"
"github.com/fatedier/frp/models/config" "github.com/fatedier/frp/models/config"
"github.com/fatedier/frp/models/msg" "github.com/fatedier/frp/models/msg"
@@ -35,6 +32,7 @@ import (
frpIo "github.com/fatedier/golib/io" frpIo "github.com/fatedier/golib/io"
"github.com/fatedier/golib/pool" "github.com/fatedier/golib/pool"
fmux "github.com/hashicorp/yamux"
) )
// Visitor is used for forward traffics from local port tot remote service. // Visitor is used for forward traffics from local port tot remote service.
@@ -52,12 +50,12 @@ func NewVisitor(ctl *Control, cfg config.VisitorConf) (visitor Visitor) {
switch cfg := cfg.(type) { switch cfg := cfg.(type) {
case *config.StcpVisitorConf: case *config.StcpVisitorConf:
visitor = &StcpVisitor{ visitor = &StcpVisitor{
BaseVisitor: baseVisitor, BaseVisitor: &baseVisitor,
cfg: cfg, cfg: cfg,
} }
case *config.XtcpVisitorConf: case *config.XtcpVisitorConf:
visitor = &XtcpVisitor{ visitor = &XtcpVisitor{
BaseVisitor: baseVisitor, BaseVisitor: &baseVisitor,
cfg: cfg, cfg: cfg,
} }
} }
@@ -73,7 +71,7 @@ type BaseVisitor struct {
} }
type StcpVisitor struct { type StcpVisitor struct {
BaseVisitor *BaseVisitor
cfg *config.StcpVisitorConf cfg *config.StcpVisitorConf
} }
@@ -160,7 +158,7 @@ func (sv *StcpVisitor) handleConn(userConn frpNet.Conn) {
} }
type XtcpVisitor struct { type XtcpVisitor struct {
BaseVisitor *BaseVisitor
cfg *config.XtcpVisitorConf cfg *config.XtcpVisitorConf
} }
@@ -249,40 +247,31 @@ func (sv *XtcpVisitor) handleConn(userConn frpNet.Conn) {
return return
} }
sv.Trace("get natHoleRespMsg, sid [%s], client address [%s]", natHoleRespMsg.Sid, natHoleRespMsg.ClientAddr) sv.Trace("get natHoleRespMsg, sid [%s], client address [%s], visitor address [%s]", natHoleRespMsg.Sid, natHoleRespMsg.ClientAddr, natHoleRespMsg.VisitorAddr)
// Close visitorConn, so we can use it's local address. // Close visitorConn, so we can use it's local address.
visitorConn.Close() visitorConn.Close()
// Send detect message. // send sid message to client
array := strings.Split(natHoleRespMsg.ClientAddr, ":")
if len(array) <= 1 {
sv.Error("get natHoleResp client address error: %s", natHoleRespMsg.ClientAddr)
return
}
laddr, _ := net.ResolveUDPAddr("udp", visitorConn.LocalAddr().String()) laddr, _ := net.ResolveUDPAddr("udp", visitorConn.LocalAddr().String())
/* daddr, err := net.ResolveUDPAddr("udp", natHoleRespMsg.ClientAddr)
for i := 1000; i < 65000; i++ {
sv.sendDetectMsg(array[0], int64(i), laddr, "a")
}
*/
port, err := strconv.ParseInt(array[1], 10, 64)
if err != nil { if err != nil {
sv.Error("get natHoleResp client address error: %s", natHoleRespMsg.ClientAddr) sv.Error("resolve client udp address error: %v", err)
return return
} }
sv.sendDetectMsg(array[0], int(port), laddr, []byte(natHoleRespMsg.Sid)) lConn, err := net.DialUDP("udp", laddr, daddr)
sv.Trace("send all detect msg done") if err != nil {
sv.Error("dial client udp address error: %v", err)
return
}
defer lConn.Close()
// Listen for visitorConn's address and wait for client connection. lConn.Write([]byte(natHoleRespMsg.Sid))
lConn, err := net.ListenUDP("udp", laddr)
if err != nil { // read ack sid from client
sv.Error("listen on visitorConn's local adress error: %v", err)
return
}
lConn.SetReadDeadline(time.Now().Add(5 * time.Second))
sidBuf := pool.GetBuf(1024) sidBuf := pool.GetBuf(1024)
n, _, err = lConn.ReadFromUDP(sidBuf) lConn.SetReadDeadline(time.Now().Add(8 * time.Second))
n, err = lConn.Read(sidBuf)
if err != nil { if err != nil {
sv.Warn("get sid from client error: %v", err) sv.Warn("get sid from client error: %v", err)
return return
@@ -292,11 +281,13 @@ func (sv *XtcpVisitor) handleConn(userConn frpNet.Conn) {
sv.Warn("incorrect sid from client") sv.Warn("incorrect sid from client")
return return
} }
sv.Info("nat hole connection make success, sid [%s]", string(sidBuf[:n]))
pool.PutBuf(sidBuf) pool.PutBuf(sidBuf)
sv.Info("nat hole connection make success, sid [%s]", natHoleRespMsg.Sid)
// wrap kcp connection
var remote io.ReadWriteCloser var remote io.ReadWriteCloser
remote, err = frpNet.NewKcpConnFromUdp(lConn, false, natHoleRespMsg.ClientAddr) remote, err = frpNet.NewKcpConnFromUdp(lConn, true, natHoleRespMsg.ClientAddr)
if err != nil { if err != nil {
sv.Error("create kcp connection from udp connection error: %v", err) sv.Error("create kcp connection from udp connection error: %v", err)
return return
@@ -314,25 +305,21 @@ func (sv *XtcpVisitor) handleConn(userConn frpNet.Conn) {
remote = frpIo.WithCompression(remote) remote = frpIo.WithCompression(remote)
} }
frpIo.Join(userConn, remote) fmuxCfg := fmux.DefaultConfig()
fmuxCfg.KeepAliveInterval = 5 * time.Second
fmuxCfg.LogOutput = ioutil.Discard
sess, err := fmux.Client(remote, fmuxCfg)
if err != nil {
sv.Error("create yamux session error: %v", err)
return
}
defer sess.Close()
muxConn, err := sess.Open()
if err != nil {
sv.Error("open yamux stream error: %v", err)
return
}
frpIo.Join(userConn, muxConn)
sv.Debug("join connections closed") sv.Debug("join connections closed")
} }
func (sv *XtcpVisitor) sendDetectMsg(addr string, port int, laddr *net.UDPAddr, content []byte) (err error) {
daddr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", addr, port))
if err != nil {
return err
}
tConn, err := net.DialUDP("udp", laddr, daddr)
if err != nil {
return err
}
uConn := ipv4.NewConn(tConn)
uConn.SetTTL(3)
tConn.Write(content)
tConn.Close()
return nil
}

View File

@@ -12,9 +12,10 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
package main // "github.com/fatedier/frp/cmd/frpc" package main
import ( import (
_ "github.com/fatedier/frp/assets/frpc/statik"
"github.com/fatedier/frp/cmd/frpc/sub" "github.com/fatedier/frp/cmd/frpc/sub"
"github.com/fatedier/golib/crypto" "github.com/fatedier/golib/crypto"

View File

@@ -16,7 +16,6 @@ package sub
import ( import (
"encoding/base64" "encoding/base64"
"encoding/json"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"net/http" "net/http"
@@ -25,7 +24,6 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/fatedier/frp/client"
"github.com/fatedier/frp/g" "github.com/fatedier/frp/g"
"github.com/fatedier/frp/models/config" "github.com/fatedier/frp/models/config"
) )
@@ -79,21 +77,16 @@ func reload() error {
if err != nil { if err != nil {
return err return err
} else { } else {
if resp.StatusCode != 200 { if resp.StatusCode == 200 {
return fmt.Errorf("admin api status code [%d]", resp.StatusCode) return nil
} }
defer resp.Body.Close() defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body) body, err := ioutil.ReadAll(resp.Body)
if err != nil { if err != nil {
return err return err
} }
res := &client.GeneralResponse{} return fmt.Errorf("code [%d], %s", resp.StatusCode, strings.TrimSpace(string(body)))
err = json.Unmarshal(body, &res)
if err != nil {
return fmt.Errorf("unmarshal http response error: %s", strings.TrimSpace(string(body)))
} else if res.Code != 0 {
return fmt.Errorf(res.Msg)
}
} }
return nil return nil
} }

View File

@@ -205,7 +205,11 @@ func startService(pxyCfgs map[string]config.ProxyConf, visitorCfgs map[string]co
}, },
} }
} }
svr := client.NewService(pxyCfgs, visitorCfgs) svr, errRet := client.NewService(pxyCfgs, visitorCfgs)
if errRet != nil {
err = errRet
return
}
// Capture the exit signal if we use kcp. // Capture the exit signal if we use kcp.
if g.GlbClientCfg.Protocol == "kcp" { if g.GlbClientCfg.Protocol == "kcp" {

View File

@@ -68,7 +68,7 @@ var xtcpCmd = &cobra.Command{
if role == "server" { if role == "server" {
cfg := &config.XtcpProxyConf{} cfg := &config.XtcpProxyConf{}
cfg.ProxyName = prefix + proxyName cfg.ProxyName = prefix + proxyName
cfg.ProxyType = consts.StcpProxy cfg.ProxyType = consts.XtcpProxy
cfg.UseEncryption = useEncryption cfg.UseEncryption = useEncryption
cfg.UseCompression = useCompression cfg.UseCompression = useCompression
cfg.Role = role cfg.Role = role
@@ -84,7 +84,7 @@ var xtcpCmd = &cobra.Command{
} else if role == "visitor" { } else if role == "visitor" {
cfg := &config.XtcpVisitorConf{} cfg := &config.XtcpVisitorConf{}
cfg.ProxyName = prefix + proxyName cfg.ProxyName = prefix + proxyName
cfg.ProxyType = consts.StcpProxy cfg.ProxyType = consts.XtcpProxy
cfg.UseEncryption = useEncryption cfg.UseEncryption = useEncryption
cfg.UseCompression = useCompression cfg.UseCompression = useCompression
cfg.Role = role cfg.Role = role

View File

@@ -12,10 +12,12 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
package main // "github.com/fatedier/frp/cmd/frps" package main
import ( import (
"github.com/fatedier/golib/crypto" "github.com/fatedier/golib/crypto"
_ "github.com/fatedier/frp/assets/frps/statik"
) )
func main() { func main() {

View File

@@ -44,6 +44,9 @@ login_fail_exit = true
# now it supports tcp and kcp and websocket, default is tcp # now it supports tcp and kcp and websocket, default is tcp
protocol = tcp protocol = tcp
# if tls_enable is true, frpc will connect frps by tls
tls_enable = true
# specify a dns server, so frpc will use this instead of default one # specify a dns server, so frpc will use this instead of default one
# dns_server = 8.8.8.8 # dns_server = 8.8.8.8

2
go.mod
View File

@@ -12,7 +12,7 @@ require (
github.com/gorilla/context v1.1.1 // indirect github.com/gorilla/context v1.1.1 // indirect
github.com/gorilla/mux v1.6.2 github.com/gorilla/mux v1.6.2
github.com/gorilla/websocket v1.2.0 github.com/gorilla/websocket v1.2.0
github.com/hashicorp/yamux v0.0.0-20180314200745-2658be15c5f0 github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d
github.com/inconshreveable/mousetrap v1.0.0 // indirect github.com/inconshreveable/mousetrap v1.0.0 // indirect
github.com/mattn/go-runewidth v0.0.4 // indirect github.com/mattn/go-runewidth v0.0.4 // indirect
github.com/pkg/errors v0.8.0 // indirect github.com/pkg/errors v0.8.0 // indirect

3
go.sum
View File

@@ -9,7 +9,8 @@ github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/websocket v1.2.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.2.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/hashicorp/yamux v0.0.0-20180314200745-2658be15c5f0/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d h1:kJCB4vdITiW1eC1vq2e6IsrXKrZit1bv/TDYFGMp4BQ=
github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM=
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/mattn/go-runewidth v0.0.4 h1:2BvfKmzob6Bmd4YsL0zygOqfdFnK7GR4QL06Do4/p7Y= github.com/mattn/go-runewidth v0.0.4 h1:2BvfKmzob6Bmd4YsL0zygOqfdFnK7GR4QL06Do4/p7Y=

View File

@@ -44,6 +44,7 @@ type ClientCommonConf struct {
LoginFailExit bool `json:"login_fail_exit"` LoginFailExit bool `json:"login_fail_exit"`
Start map[string]struct{} `json:"start"` Start map[string]struct{} `json:"start"`
Protocol string `json:"protocol"` Protocol string `json:"protocol"`
TLSEnable bool `json:"tls_enable"`
HeartBeatInterval int64 `json:"heartbeat_interval"` HeartBeatInterval int64 `json:"heartbeat_interval"`
HeartBeatTimeout int64 `json:"heartbeat_timeout"` HeartBeatTimeout int64 `json:"heartbeat_timeout"`
} }
@@ -69,6 +70,7 @@ func GetDefaultClientConf() *ClientCommonConf {
LoginFailExit: true, LoginFailExit: true,
Start: make(map[string]struct{}), Start: make(map[string]struct{}),
Protocol: "tcp", Protocol: "tcp",
TLSEnable: false,
HeartBeatInterval: 30, HeartBeatInterval: 30,
HeartBeatTimeout: 90, HeartBeatTimeout: 90,
} }
@@ -194,6 +196,12 @@ func UnmarshalClientConfFromIni(defaultCfg *ClientCommonConf, content string) (c
cfg.Protocol = tmpStr cfg.Protocol = tmpStr
} }
if tmpStr, ok = conf.Get("common", "tls_enable"); ok && tmpStr == "true" {
cfg.TLSEnable = true
} else {
cfg.TLSEnable = false
}
if tmpStr, ok = conf.Get("common", "heartbeat_timeout"); ok { if tmpStr, ok = conf.Get("common", "heartbeat_timeout"); ok {
if v, err = strconv.ParseInt(tmpStr, 10, 64); err != nil { if v, err = strconv.ParseInt(tmpStr, 10, 64); err != nil {
err = fmt.Errorf("Parse conf error: invalid heartbeat_timeout") err = fmt.Errorf("Parse conf error: invalid heartbeat_timeout")

View File

@@ -51,7 +51,7 @@ type ServerCommonConf struct {
VhostHttpPort int `json:"vhost_http_port"` VhostHttpPort int `json:"vhost_http_port"`
// if VhostHttpsPort equals 0, don't listen a public port for https protocol // if VhostHttpsPort equals 0, don't listen a public port for https protocol
VhostHttpsPort int `json:"vhost_http_port"` VhostHttpsPort int `json:"vhost_https_port"`
VhostHttpTimeout int64 `json:"vhost_http_timeout"` VhostHttpTimeout int64 `json:"vhost_http_timeout"`

View File

@@ -17,44 +17,46 @@ package msg
import "net" import "net"
const ( const (
TypeLogin = 'o' TypeLogin = 'o'
TypeLoginResp = '1' TypeLoginResp = '1'
TypeNewProxy = 'p' TypeNewProxy = 'p'
TypeNewProxyResp = '2' TypeNewProxyResp = '2'
TypeCloseProxy = 'c' TypeCloseProxy = 'c'
TypeNewWorkConn = 'w' TypeNewWorkConn = 'w'
TypeReqWorkConn = 'r' TypeReqWorkConn = 'r'
TypeStartWorkConn = 's' TypeStartWorkConn = 's'
TypeNewVisitorConn = 'v' TypeNewVisitorConn = 'v'
TypeNewVisitorConnResp = '3' TypeNewVisitorConnResp = '3'
TypePing = 'h' TypePing = 'h'
TypePong = '4' TypePong = '4'
TypeUdpPacket = 'u' TypeUdpPacket = 'u'
TypeNatHoleVisitor = 'i' TypeNatHoleVisitor = 'i'
TypeNatHoleClient = 'n' TypeNatHoleClient = 'n'
TypeNatHoleResp = 'm' TypeNatHoleResp = 'm'
TypeNatHoleSid = '5' TypeNatHoleClientDetectOK = 'd'
TypeNatHoleSid = '5'
) )
var ( var (
msgTypeMap = map[byte]interface{}{ msgTypeMap = map[byte]interface{}{
TypeLogin: Login{}, TypeLogin: Login{},
TypeLoginResp: LoginResp{}, TypeLoginResp: LoginResp{},
TypeNewProxy: NewProxy{}, TypeNewProxy: NewProxy{},
TypeNewProxyResp: NewProxyResp{}, TypeNewProxyResp: NewProxyResp{},
TypeCloseProxy: CloseProxy{}, TypeCloseProxy: CloseProxy{},
TypeNewWorkConn: NewWorkConn{}, TypeNewWorkConn: NewWorkConn{},
TypeReqWorkConn: ReqWorkConn{}, TypeReqWorkConn: ReqWorkConn{},
TypeStartWorkConn: StartWorkConn{}, TypeStartWorkConn: StartWorkConn{},
TypeNewVisitorConn: NewVisitorConn{}, TypeNewVisitorConn: NewVisitorConn{},
TypeNewVisitorConnResp: NewVisitorConnResp{}, TypeNewVisitorConnResp: NewVisitorConnResp{},
TypePing: Ping{}, TypePing: Ping{},
TypePong: Pong{}, TypePong: Pong{},
TypeUdpPacket: UdpPacket{}, TypeUdpPacket: UdpPacket{},
TypeNatHoleVisitor: NatHoleVisitor{}, TypeNatHoleVisitor: NatHoleVisitor{},
TypeNatHoleClient: NatHoleClient{}, TypeNatHoleClient: NatHoleClient{},
TypeNatHoleResp: NatHoleResp{}, TypeNatHoleResp: NatHoleResp{},
TypeNatHoleSid: NatHoleSid{}, TypeNatHoleClientDetectOK: NatHoleClientDetectOK{},
TypeNatHoleSid: NatHoleSid{},
} }
) )
@@ -169,6 +171,9 @@ type NatHoleResp struct {
Error string `json:"error"` Error string `json:"error"`
} }
type NatHoleClientDetectOK struct {
}
type NatHoleSid struct { type NatHoleSid struct {
Sid string `json:"sid"` Sid string `json:"sid"`
} }

View File

@@ -18,6 +18,11 @@ import (
// Timeout seconds. // Timeout seconds.
var NatHoleTimeout int64 = 10 var NatHoleTimeout int64 = 10
type SidRequest struct {
Sid string
NotifyCh chan struct{}
}
type NatHoleController struct { type NatHoleController struct {
listener *net.UDPConn listener *net.UDPConn
@@ -44,11 +49,11 @@ func NewNatHoleController(udpBindAddr string) (nc *NatHoleController, err error)
return nc, nil return nc, nil
} }
func (nc *NatHoleController) ListenClient(name string, sk string) (sidCh chan string) { func (nc *NatHoleController) ListenClient(name string, sk string) (sidCh chan *SidRequest) {
clientCfg := &NatHoleClientCfg{ clientCfg := &NatHoleClientCfg{
Name: name, Name: name,
Sk: sk, Sk: sk,
SidCh: make(chan string), SidCh: make(chan *SidRequest),
} }
nc.mu.Lock() nc.mu.Lock()
nc.clientCfgs[name] = clientCfg nc.clientCfgs[name] = clientCfg
@@ -132,7 +137,10 @@ func (nc *NatHoleController) HandleVisitor(m *msg.NatHoleVisitor, raddr *net.UDP
}() }()
err := errors.PanicToError(func() { err := errors.PanicToError(func() {
clientCfg.SidCh <- sid clientCfg.SidCh <- &SidRequest{
Sid: sid,
NotifyCh: session.NotifyCh,
}
}) })
if err != nil { if err != nil {
return return
@@ -158,7 +166,6 @@ func (nc *NatHoleController) HandleClient(m *msg.NatHoleClient, raddr *net.UDPAd
} }
log.Trace("handle client message, sid [%s]", session.Sid) log.Trace("handle client message, sid [%s]", session.Sid)
session.ClientAddr = raddr session.ClientAddr = raddr
session.NotifyCh <- struct{}{}
resp := nc.GenNatHoleResponse(session, "") resp := nc.GenNatHoleResponse(session, "")
log.Trace("send nat hole response to client") log.Trace("send nat hole response to client")
@@ -201,5 +208,5 @@ type NatHoleSession struct {
type NatHoleClientCfg struct { type NatHoleClientCfg struct {
Name string Name string
Sk string Sk string
SidCh chan string SidCh chan *SidRequest
} }

View File

@@ -67,7 +67,6 @@ func ForwardUserConn(udpConn *net.UDPConn, readCh <-chan *msg.UdpPacket, sendCh
default: default:
} }
} }
return
} }
func Forwarder(dstAddr *net.UDPAddr, readCh <-chan *msg.UdpPacket, sendCh chan<- msg.Message) { func Forwarder(dstAddr *net.UDPAddr, readCh <-chan *msg.UdpPacket, sendCh chan<- msg.Message) {

View File

@@ -28,13 +28,11 @@ import (
) )
type GeneralResponse struct { type GeneralResponse struct {
Code int64 `json:"code"` Code int
Msg string `json:"msg"` Msg string
} }
type ServerInfoResp struct { type ServerInfoResp struct {
GeneralResponse
Version string `json:"version"` Version string `json:"version"`
BindPort int `json:"bind_port"` BindPort int `json:"bind_port"`
BindUdpPort int `json:"bind_udp_port"` BindUdpPort int `json:"bind_udp_port"`
@@ -55,18 +53,19 @@ type ServerInfoResp struct {
// api/serverinfo // api/serverinfo
func (svr *Service) ApiServerInfo(w http.ResponseWriter, r *http.Request) { func (svr *Service) ApiServerInfo(w http.ResponseWriter, r *http.Request) {
var ( res := GeneralResponse{Code: 200}
buf []byte
res ServerInfoResp
)
defer func() { defer func() {
log.Info("Http response [%s]: code [%d]", r.URL.Path, res.Code) log.Info("Http response [%s]: code [%d]", r.URL.Path, res.Code)
w.WriteHeader(res.Code)
if len(res.Msg) > 0 {
w.Write([]byte(res.Msg))
}
}() }()
log.Info("Http request: [%s]", r.URL.Path) log.Info("Http request: [%s]", r.URL.Path)
cfg := &g.GlbServerCfg.ServerCommonConf cfg := &g.GlbServerCfg.ServerCommonConf
serverStats := svr.statsCollector.GetServer() serverStats := svr.statsCollector.GetServer()
res = ServerInfoResp{ svrResp := ServerInfoResp{
Version: version.Full(), Version: version.Full(),
BindPort: cfg.BindPort, BindPort: cfg.BindPort,
BindUdpPort: cfg.BindUdpPort, BindUdpPort: cfg.BindUdpPort,
@@ -85,8 +84,8 @@ func (svr *Service) ApiServerInfo(w http.ResponseWriter, r *http.Request) {
ProxyTypeCounts: serverStats.ProxyTypeCounts, ProxyTypeCounts: serverStats.ProxyTypeCounts,
} }
buf, _ = json.Marshal(&res) buf, _ := json.Marshal(&svrResp)
w.Write(buf) res.Msg = string(buf)
} }
type BaseOutConf struct { type BaseOutConf struct {
@@ -155,31 +154,29 @@ type ProxyStatsInfo struct {
} }
type GetProxyInfoResp struct { type GetProxyInfoResp struct {
GeneralResponse
Proxies []*ProxyStatsInfo `json:"proxies"` Proxies []*ProxyStatsInfo `json:"proxies"`
} }
// api/proxy/:type // api/proxy/:type
func (svr *Service) ApiProxyByType(w http.ResponseWriter, r *http.Request) { func (svr *Service) ApiProxyByType(w http.ResponseWriter, r *http.Request) {
var ( res := GeneralResponse{Code: 200}
buf []byte
res GetProxyInfoResp
)
params := mux.Vars(r) params := mux.Vars(r)
proxyType := params["type"] proxyType := params["type"]
defer func() { defer func() {
log.Info("Http response [%s]: code [%d]", r.URL.Path, res.Code) log.Info("Http response [%s]: code [%d]", r.URL.Path, res.Code)
log.Info(r.URL.Path) w.WriteHeader(res.Code)
log.Info(r.URL.RawPath) if len(res.Msg) > 0 {
w.Write([]byte(res.Msg))
}
}() }()
log.Info("Http request: [%s]", r.URL.Path) log.Info("Http request: [%s]", r.URL.Path)
res.Proxies = svr.getProxyStatsByType(proxyType) proxyInfoResp := GetProxyInfoResp{}
proxyInfoResp.Proxies = svr.getProxyStatsByType(proxyType)
buf, _ = json.Marshal(&res)
w.Write(buf)
buf, _ := json.Marshal(&proxyInfoResp)
res.Msg = string(buf)
} }
func (svr *Service) getProxyStatsByType(proxyType string) (proxyInfos []*ProxyStatsInfo) { func (svr *Service) getProxyStatsByType(proxyType string) (proxyInfos []*ProxyStatsInfo) {
@@ -215,8 +212,6 @@ func (svr *Service) getProxyStatsByType(proxyType string) (proxyInfos []*ProxySt
// Get proxy info by name. // Get proxy info by name.
type GetProxyStatsResp struct { type GetProxyStatsResp struct {
GeneralResponse
Name string `json:"name"` Name string `json:"name"`
Conf interface{} `json:"conf"` Conf interface{} `json:"conf"`
TodayTrafficIn int64 `json:"today_traffic_in"` TodayTrafficIn int64 `json:"today_traffic_in"`
@@ -229,45 +224,50 @@ type GetProxyStatsResp struct {
// api/proxy/:type/:name // api/proxy/:type/:name
func (svr *Service) ApiProxyByTypeAndName(w http.ResponseWriter, r *http.Request) { func (svr *Service) ApiProxyByTypeAndName(w http.ResponseWriter, r *http.Request) {
var ( res := GeneralResponse{Code: 200}
buf []byte
res GetProxyStatsResp
)
params := mux.Vars(r) params := mux.Vars(r)
proxyType := params["type"] proxyType := params["type"]
name := params["name"] name := params["name"]
defer func() { defer func() {
log.Info("Http response [%s]: code [%d]", r.URL.Path, res.Code) log.Info("Http response [%s]: code [%d]", r.URL.Path, res.Code)
w.WriteHeader(res.Code)
if len(res.Msg) > 0 {
w.Write([]byte(res.Msg))
}
}() }()
log.Info("Http request: [%s]", r.URL.Path) log.Info("Http request: [%s]", r.URL.Path)
res = svr.getProxyStatsByTypeAndName(proxyType, name) proxyStatsResp := GetProxyStatsResp{}
proxyStatsResp, res.Code, res.Msg = svr.getProxyStatsByTypeAndName(proxyType, name)
if res.Code != 200 {
return
}
buf, _ = json.Marshal(&res) buf, _ := json.Marshal(&proxyStatsResp)
w.Write(buf) res.Msg = string(buf)
} }
func (svr *Service) getProxyStatsByTypeAndName(proxyType string, proxyName string) (proxyInfo GetProxyStatsResp) { func (svr *Service) getProxyStatsByTypeAndName(proxyType string, proxyName string) (proxyInfo GetProxyStatsResp, code int, msg string) {
proxyInfo.Name = proxyName proxyInfo.Name = proxyName
ps := svr.statsCollector.GetProxiesByTypeAndName(proxyType, proxyName) ps := svr.statsCollector.GetProxiesByTypeAndName(proxyType, proxyName)
if ps == nil { if ps == nil {
proxyInfo.Code = 1 code = 404
proxyInfo.Msg = "no proxy info found" msg = "no proxy info found"
} else { } else {
if pxy, ok := svr.pxyManager.GetByName(proxyName); ok { if pxy, ok := svr.pxyManager.GetByName(proxyName); ok {
content, err := json.Marshal(pxy.GetConf()) content, err := json.Marshal(pxy.GetConf())
if err != nil { if err != nil {
log.Warn("marshal proxy [%s] conf info error: %v", ps.Name, err) log.Warn("marshal proxy [%s] conf info error: %v", ps.Name, err)
proxyInfo.Code = 2 code = 400
proxyInfo.Msg = "parse conf error" msg = "parse conf error"
return return
} }
proxyInfo.Conf = getConfByType(ps.Type) proxyInfo.Conf = getConfByType(ps.Type)
if err = json.Unmarshal(content, &proxyInfo.Conf); err != nil { if err = json.Unmarshal(content, &proxyInfo.Conf); err != nil {
log.Warn("unmarshal proxy [%s] conf info error: %v", ps.Name, err) log.Warn("unmarshal proxy [%s] conf info error: %v", ps.Name, err)
proxyInfo.Code = 2 code = 400
proxyInfo.Msg = "parse conf error" msg = "parse conf error"
return return
} }
proxyInfo.Status = consts.Online proxyInfo.Status = consts.Online
@@ -286,36 +286,38 @@ func (svr *Service) getProxyStatsByTypeAndName(proxyType string, proxyName strin
// api/traffic/:name // api/traffic/:name
type GetProxyTrafficResp struct { type GetProxyTrafficResp struct {
GeneralResponse
Name string `json:"name"` Name string `json:"name"`
TrafficIn []int64 `json:"traffic_in"` TrafficIn []int64 `json:"traffic_in"`
TrafficOut []int64 `json:"traffic_out"` TrafficOut []int64 `json:"traffic_out"`
} }
func (svr *Service) ApiProxyTraffic(w http.ResponseWriter, r *http.Request) { func (svr *Service) ApiProxyTraffic(w http.ResponseWriter, r *http.Request) {
var ( res := GeneralResponse{Code: 200}
buf []byte
res GetProxyTrafficResp
)
params := mux.Vars(r) params := mux.Vars(r)
name := params["name"] name := params["name"]
defer func() { defer func() {
log.Info("Http response [%s]: code [%d]", r.URL.Path, res.Code) log.Info("Http response [%s]: code [%d]", r.URL.Path, res.Code)
w.WriteHeader(res.Code)
if len(res.Msg) > 0 {
w.Write([]byte(res.Msg))
}
}() }()
log.Info("Http request: [%s]", r.URL.Path) log.Info("Http request: [%s]", r.URL.Path)
res.Name = name trafficResp := GetProxyTrafficResp{}
trafficResp.Name = name
proxyTrafficInfo := svr.statsCollector.GetProxyTraffic(name) proxyTrafficInfo := svr.statsCollector.GetProxyTraffic(name)
if proxyTrafficInfo == nil { if proxyTrafficInfo == nil {
res.Code = 1 res.Code = 404
res.Msg = "no proxy info found" res.Msg = "no proxy info found"
return
} else { } else {
res.TrafficIn = proxyTrafficInfo.TrafficIn trafficResp.TrafficIn = proxyTrafficInfo.TrafficIn
res.TrafficOut = proxyTrafficInfo.TrafficOut trafficResp.TrafficOut = proxyTrafficInfo.TrafficOut
} }
buf, _ = json.Marshal(&res) buf, _ := json.Marshal(&trafficResp)
w.Write(buf) res.Msg = string(buf)
} }

View File

@@ -29,7 +29,7 @@ import (
) )
type HttpProxy struct { type HttpProxy struct {
BaseProxy *BaseProxy
cfg *config.HttpProxyConf cfg *config.HttpProxyConf
closeFuncs []func() closeFuncs []func()

View File

@@ -24,7 +24,7 @@ import (
) )
type HttpsProxy struct { type HttpsProxy struct {
BaseProxy *BaseProxy
cfg *config.HttpsProxyConf cfg *config.HttpsProxyConf
} }

View File

@@ -135,33 +135,33 @@ func NewProxy(runId string, rc *controller.ResourceController, statsCollector st
case *config.TcpProxyConf: case *config.TcpProxyConf:
basePxy.usedPortsNum = 1 basePxy.usedPortsNum = 1
pxy = &TcpProxy{ pxy = &TcpProxy{
BaseProxy: basePxy, BaseProxy: &basePxy,
cfg: cfg, cfg: cfg,
} }
case *config.HttpProxyConf: case *config.HttpProxyConf:
pxy = &HttpProxy{ pxy = &HttpProxy{
BaseProxy: basePxy, BaseProxy: &basePxy,
cfg: cfg, cfg: cfg,
} }
case *config.HttpsProxyConf: case *config.HttpsProxyConf:
pxy = &HttpsProxy{ pxy = &HttpsProxy{
BaseProxy: basePxy, BaseProxy: &basePxy,
cfg: cfg, cfg: cfg,
} }
case *config.UdpProxyConf: case *config.UdpProxyConf:
basePxy.usedPortsNum = 1 basePxy.usedPortsNum = 1
pxy = &UdpProxy{ pxy = &UdpProxy{
BaseProxy: basePxy, BaseProxy: &basePxy,
cfg: cfg, cfg: cfg,
} }
case *config.StcpProxyConf: case *config.StcpProxyConf:
pxy = &StcpProxy{ pxy = &StcpProxy{
BaseProxy: basePxy, BaseProxy: &basePxy,
cfg: cfg, cfg: cfg,
} }
case *config.XtcpProxyConf: case *config.XtcpProxyConf:
pxy = &XtcpProxy{ pxy = &XtcpProxy{
BaseProxy: basePxy, BaseProxy: &basePxy,
cfg: cfg, cfg: cfg,
} }
default: default:

View File

@@ -19,7 +19,7 @@ import (
) )
type StcpProxy struct { type StcpProxy struct {
BaseProxy *BaseProxy
cfg *config.StcpProxyConf cfg *config.StcpProxyConf
} }

View File

@@ -23,7 +23,7 @@ import (
) )
type TcpProxy struct { type TcpProxy struct {
BaseProxy *BaseProxy
cfg *config.TcpProxyConf cfg *config.TcpProxyConf
realPort int realPort int

View File

@@ -30,7 +30,7 @@ import (
) )
type UdpProxy struct { type UdpProxy struct {
BaseProxy *BaseProxy
cfg *config.UdpProxyConf cfg *config.UdpProxyConf
realPort int realPort int

View File

@@ -24,7 +24,7 @@ import (
) )
type XtcpProxy struct { type XtcpProxy struct {
BaseProxy *BaseProxy
cfg *config.XtcpProxyConf cfg *config.XtcpProxyConf
closeCh chan struct{} closeCh chan struct{}
@@ -42,18 +42,40 @@ func (pxy *XtcpProxy) Run() (remoteAddr string, err error) {
select { select {
case <-pxy.closeCh: case <-pxy.closeCh:
break break
case sid := <-sidCh: case sidRequest := <-sidCh:
sr := sidRequest
workConn, errRet := pxy.GetWorkConnFromPool() workConn, errRet := pxy.GetWorkConnFromPool()
if errRet != nil { if errRet != nil {
continue continue
} }
m := &msg.NatHoleSid{ m := &msg.NatHoleSid{
Sid: sid, Sid: sr.Sid,
} }
errRet = msg.WriteMsg(workConn, m) errRet = msg.WriteMsg(workConn, m)
if errRet != nil { if errRet != nil {
pxy.Warn("write nat hole sid package error, %v", errRet) pxy.Warn("write nat hole sid package error, %v", errRet)
workConn.Close()
break
} }
go func() {
raw, errRet := msg.ReadMsg(workConn)
if errRet != nil {
pxy.Warn("read nat hole client ok package error: %v", errRet)
workConn.Close()
return
}
if _, ok := raw.(*msg.NatHoleClientDetectOK); !ok {
pxy.Warn("read nat hole client ok package format error")
workConn.Close()
return
}
select {
case sr.NotifyCh <- struct{}{}:
default:
}
}()
} }
} }
}() }()

View File

@@ -16,8 +16,14 @@ package server
import ( import (
"bytes" "bytes"
"crypto/rand"
"crypto/rsa"
"crypto/tls"
"crypto/x509"
"encoding/pem"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"math/big"
"net" "net"
"net/http" "net/http"
"time" "time"
@@ -61,6 +67,9 @@ type Service struct {
// Accept connections using websocket // Accept connections using websocket
websocketListener frpNet.Listener websocketListener frpNet.Listener
// Accept frp tls connections
tlsListener frpNet.Listener
// Manage all controllers // Manage all controllers
ctlManager *ControlManager ctlManager *ControlManager
@@ -72,6 +81,8 @@ type Service struct {
// stats collector to store server and proxies stats info // stats collector to store server and proxies stats info
statsCollector stats.Collector statsCollector stats.Collector
tlsConfig *tls.Config
} }
func NewService() (svr *Service, err error) { func NewService() (svr *Service, err error) {
@@ -84,12 +95,13 @@ func NewService() (svr *Service, err error) {
TcpPortManager: ports.NewPortManager("tcp", cfg.ProxyBindAddr, cfg.AllowPorts), TcpPortManager: ports.NewPortManager("tcp", cfg.ProxyBindAddr, cfg.AllowPorts),
UdpPortManager: ports.NewPortManager("udp", cfg.ProxyBindAddr, cfg.AllowPorts), UdpPortManager: ports.NewPortManager("udp", cfg.ProxyBindAddr, cfg.AllowPorts),
}, },
tlsConfig: generateTLSConfig(),
} }
// Init group controller // Init group controller
svr.rc.TcpGroupCtl = group.NewTcpGroupCtl(svr.rc.TcpPortManager) svr.rc.TcpGroupCtl = group.NewTcpGroupCtl(svr.rc.TcpPortManager)
// Init assets. // Init assets
err = assets.Load(cfg.AssetsDir) err = assets.Load(cfg.AssetsDir)
if err != nil { if err != nil {
err = fmt.Errorf("Load assets error: %v", err) err = fmt.Errorf("Load assets error: %v", err)
@@ -187,6 +199,12 @@ func NewService() (svr *Service, err error) {
log.Info("https service listen on %s:%d", cfg.ProxyBindAddr, cfg.VhostHttpsPort) log.Info("https service listen on %s:%d", cfg.ProxyBindAddr, cfg.VhostHttpsPort)
} }
// frp tls listener
tlsListener := svr.muxer.Listen(1, 1, func(data []byte) bool {
return int(data[0]) == frpNet.FRP_TLS_HEAD_BYTE
})
svr.tlsListener = frpNet.WrapLogListener(tlsListener)
// Create nat hole controller. // Create nat hole controller.
if cfg.BindUdpPort > 0 { if cfg.BindUdpPort > 0 {
var nc *nathole.NatHoleController var nc *nathole.NatHoleController
@@ -225,6 +243,7 @@ func (svr *Service) Run() {
} }
go svr.HandleListener(svr.websocketListener) go svr.HandleListener(svr.websocketListener)
go svr.HandleListener(svr.tlsListener)
svr.HandleListener(svr.listener) svr.HandleListener(svr.listener)
} }
@@ -237,6 +256,7 @@ func (svr *Service) HandleListener(l frpNet.Listener) {
log.Warn("Listener for incoming connections from client closed") log.Warn("Listener for incoming connections from client closed")
return return
} }
c = frpNet.CheckAndEnableTLSServerConn(c, svr.tlsConfig)
// Start a new goroutine for dealing connections. // Start a new goroutine for dealing connections.
go func(frpConn frpNet.Conn) { go func(frpConn frpNet.Conn) {
@@ -373,3 +393,24 @@ func (svr *Service) RegisterVisitorConn(visitorConn frpNet.Conn, newMsg *msg.New
return svr.rc.VisitorManager.NewConn(newMsg.ProxyName, visitorConn, newMsg.Timestamp, newMsg.SignKey, return svr.rc.VisitorManager.NewConn(newMsg.ProxyName, visitorConn, newMsg.Timestamp, newMsg.SignKey,
newMsg.UseEncryption, newMsg.UseCompression) newMsg.UseEncryption, newMsg.UseCompression)
} }
// Setup a bare-bones TLS config for the server
func generateTLSConfig() *tls.Config {
key, err := rsa.GenerateKey(rand.Reader, 1024)
if err != nil {
panic(err)
}
template := x509.Certificate{SerialNumber: big.NewInt(1)}
certDER, err := x509.CreateCertificate(rand.Reader, &template, &template, &key.PublicKey, key)
if err != nil {
panic(err)
}
keyPEM := pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(key)})
certPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certDER})
tlsCert, err := tls.X509KeyPair(certPEM, keyPEM)
if err != nil {
panic(err)
}
return &tls.Config{Certificates: []tls.Certificate{tlsCert}}
}

View File

@@ -19,7 +19,7 @@ func TestCmdTcp(t *testing.T) {
if assert.NoError(err) { if assert.NoError(err) {
defer s.Stop() defer s.Stop()
} }
time.Sleep(100 * time.Millisecond) time.Sleep(200 * time.Millisecond)
c := util.NewProcess(consts.FRPC_BIN_PATH, []string{"tcp", "-s", "127.0.0.1:20000", "-t", "123", "-u", "test", c := util.NewProcess(consts.FRPC_BIN_PATH, []string{"tcp", "-s", "127.0.0.1:20000", "-t", "123", "-u", "test",
"-l", "10701", "-r", "20801", "-n", "tcp_test"}) "-l", "10701", "-r", "20801", "-n", "tcp_test"})
@@ -27,7 +27,7 @@ func TestCmdTcp(t *testing.T) {
if assert.NoError(err) { if assert.NoError(err) {
defer c.Stop() defer c.Stop()
} }
time.Sleep(250 * time.Millisecond) time.Sleep(500 * time.Millisecond)
res, err := util.SendTcpMsg("127.0.0.1:20801", consts.TEST_TCP_ECHO_STR) res, err := util.SendTcpMsg("127.0.0.1:20801", consts.TEST_TCP_ECHO_STR)
assert.NoError(err) assert.NoError(err)
@@ -43,7 +43,7 @@ func TestCmdUdp(t *testing.T) {
if assert.NoError(err) { if assert.NoError(err) {
defer s.Stop() defer s.Stop()
} }
time.Sleep(100 * time.Millisecond) time.Sleep(200 * time.Millisecond)
c := util.NewProcess(consts.FRPC_BIN_PATH, []string{"udp", "-s", "127.0.0.1:20000", "-t", "123", "-u", "test", c := util.NewProcess(consts.FRPC_BIN_PATH, []string{"udp", "-s", "127.0.0.1:20000", "-t", "123", "-u", "test",
"-l", "10702", "-r", "20802", "-n", "udp_test"}) "-l", "10702", "-r", "20802", "-n", "udp_test"})
@@ -51,7 +51,7 @@ func TestCmdUdp(t *testing.T) {
if assert.NoError(err) { if assert.NoError(err) {
defer c.Stop() defer c.Stop()
} }
time.Sleep(250 * time.Millisecond) time.Sleep(500 * time.Millisecond)
res, err := util.SendUdpMsg("127.0.0.1:20802", consts.TEST_UDP_ECHO_STR) res, err := util.SendUdpMsg("127.0.0.1:20802", consts.TEST_UDP_ECHO_STR)
assert.NoError(err) assert.NoError(err)
@@ -67,7 +67,7 @@ func TestCmdHttp(t *testing.T) {
if assert.NoError(err) { if assert.NoError(err) {
defer s.Stop() defer s.Stop()
} }
time.Sleep(100 * time.Millisecond) time.Sleep(200 * time.Millisecond)
c := util.NewProcess(consts.FRPC_BIN_PATH, []string{"http", "-s", "127.0.0.1:20000", "-t", "123", "-u", "test", c := util.NewProcess(consts.FRPC_BIN_PATH, []string{"http", "-s", "127.0.0.1:20000", "-t", "123", "-u", "test",
"-n", "udp_test", "-l", "10704", "--custom_domain", "127.0.0.1"}) "-n", "udp_test", "-l", "10704", "--custom_domain", "127.0.0.1"})
@@ -75,7 +75,7 @@ func TestCmdHttp(t *testing.T) {
if assert.NoError(err) { if assert.NoError(err) {
defer c.Stop() defer c.Stop()
} }
time.Sleep(250 * time.Millisecond) time.Sleep(500 * time.Millisecond)
code, body, _, err := util.SendHttpMsg("GET", "http://127.0.0.1:20001", "", nil, "") code, body, _, err := util.SendHttpMsg("GET", "http://127.0.0.1:20001", "", nil, "")
if assert.NoError(err) { if assert.NoError(err) {

View File

@@ -56,14 +56,14 @@ func TestReconnect(t *testing.T) {
defer frpsProcess.Stop() defer frpsProcess.Stop()
} }
time.Sleep(100 * time.Millisecond) time.Sleep(200 * time.Millisecond)
frpcProcess := util.NewProcess(consts.FRPC_BIN_PATH, []string{"-c", frpcCfgPath}) frpcProcess := util.NewProcess(consts.FRPC_BIN_PATH, []string{"-c", frpcCfgPath})
err = frpcProcess.Start() err = frpcProcess.Start()
if assert.NoError(err) { if assert.NoError(err) {
defer frpcProcess.Stop() defer frpcProcess.Stop()
} }
time.Sleep(250 * time.Millisecond) time.Sleep(500 * time.Millisecond)
// test tcp // test tcp
res, err := util.SendTcpMsg("127.0.0.1:20801", consts.TEST_TCP_ECHO_STR) res, err := util.SendTcpMsg("127.0.0.1:20801", consts.TEST_TCP_ECHO_STR)
@@ -72,7 +72,7 @@ func TestReconnect(t *testing.T) {
// stop frpc // stop frpc
frpcProcess.Stop() frpcProcess.Stop()
time.Sleep(100 * time.Millisecond) time.Sleep(200 * time.Millisecond)
// test tcp, expect failed // test tcp, expect failed
_, err = util.SendTcpMsg("127.0.0.1:20801", consts.TEST_TCP_ECHO_STR) _, err = util.SendTcpMsg("127.0.0.1:20801", consts.TEST_TCP_ECHO_STR)
@@ -84,7 +84,7 @@ func TestReconnect(t *testing.T) {
if assert.NoError(err) { if assert.NoError(err) {
defer newFrpcProcess.Stop() defer newFrpcProcess.Stop()
} }
time.Sleep(250 * time.Millisecond) time.Sleep(500 * time.Millisecond)
// test tcp // test tcp
res, err = util.SendTcpMsg("127.0.0.1:20801", consts.TEST_TCP_ECHO_STR) res, err = util.SendTcpMsg("127.0.0.1:20801", consts.TEST_TCP_ECHO_STR)
@@ -93,7 +93,7 @@ func TestReconnect(t *testing.T) {
// stop frps // stop frps
frpsProcess.Stop() frpsProcess.Stop()
time.Sleep(100 * time.Millisecond) time.Sleep(200 * time.Millisecond)
// test tcp, expect failed // test tcp, expect failed
_, err = util.SendTcpMsg("127.0.0.1:20801", consts.TEST_TCP_ECHO_STR) _, err = util.SendTcpMsg("127.0.0.1:20801", consts.TEST_TCP_ECHO_STR)

View File

@@ -94,7 +94,7 @@ func TestReload(t *testing.T) {
defer frpsProcess.Stop() defer frpsProcess.Stop()
} }
time.Sleep(100 * time.Millisecond) time.Sleep(200 * time.Millisecond)
frpcProcess := util.NewProcess(consts.FRPC_BIN_PATH, []string{"-c", frpcCfgPath}) frpcProcess := util.NewProcess(consts.FRPC_BIN_PATH, []string{"-c", frpcCfgPath})
err = frpcProcess.Start() err = frpcProcess.Start()
@@ -102,7 +102,7 @@ func TestReload(t *testing.T) {
defer frpcProcess.Stop() defer frpcProcess.Stop()
} }
time.Sleep(250 * time.Millisecond) time.Sleep(500 * time.Millisecond)
// test tcp1 // test tcp1
res, err := util.SendTcpMsg("127.0.0.1:20801", consts.TEST_TCP_ECHO_STR) res, err := util.SendTcpMsg("127.0.0.1:20801", consts.TEST_TCP_ECHO_STR)

View File

@@ -55,7 +55,7 @@ func TestConfTemplate(t *testing.T) {
defer frpsProcess.Stop() defer frpsProcess.Stop()
} }
time.Sleep(100 * time.Millisecond) time.Sleep(200 * time.Millisecond)
frpcProcess := util.NewProcess("env", []string{"FRP_TOKEN=123456", "TCP_REMOTE_PORT=20801", consts.FRPC_BIN_PATH, "-c", frpcCfgPath}) frpcProcess := util.NewProcess("env", []string{"FRP_TOKEN=123456", "TCP_REMOTE_PORT=20801", consts.FRPC_BIN_PATH, "-c", frpcCfgPath})
err = frpcProcess.Start() err = frpcProcess.Start()
@@ -63,7 +63,7 @@ func TestConfTemplate(t *testing.T) {
defer frpcProcess.Stop() defer frpcProcess.Stop()
} }
time.Sleep(250 * time.Millisecond) time.Sleep(500 * time.Millisecond)
// test tcp1 // test tcp1
res, err := util.SendTcpMsg("127.0.0.1:20801", consts.TEST_TCP_ECHO_STR) res, err := util.SendTcpMsg("127.0.0.1:20801", consts.TEST_TCP_ECHO_STR)

188
tests/ci/tls_test.go Normal file
View File

@@ -0,0 +1,188 @@
package ci
import (
"os"
"testing"
"time"
"github.com/fatedier/frp/tests/config"
"github.com/fatedier/frp/tests/consts"
"github.com/fatedier/frp/tests/util"
"github.com/stretchr/testify/assert"
)
const FRPS_TLS_TCP_CONF = `
[common]
bind_addr = 0.0.0.0
bind_port = 20000
log_file = console
log_level = debug
token = 123456
`
const FRPC_TLS_TCP_CONF = `
[common]
server_addr = 127.0.0.1
server_port = 20000
log_file = console
log_level = debug
token = 123456
protocol = tcp
tls_enable = true
[tcp]
type = tcp
local_port = 10701
remote_port = 20801
`
func TestTlsOverTCP(t *testing.T) {
assert := assert.New(t)
frpsCfgPath, err := config.GenerateConfigFile(consts.FRPS_NORMAL_CONFIG, FRPS_TLS_TCP_CONF)
if assert.NoError(err) {
defer os.Remove(frpsCfgPath)
}
frpcCfgPath, err := config.GenerateConfigFile(consts.FRPC_NORMAL_CONFIG, FRPC_TLS_TCP_CONF)
if assert.NoError(err) {
defer os.Remove(frpcCfgPath)
}
frpsProcess := util.NewProcess(consts.FRPS_BIN_PATH, []string{"-c", frpsCfgPath})
err = frpsProcess.Start()
if assert.NoError(err) {
defer frpsProcess.Stop()
}
time.Sleep(200 * time.Millisecond)
frpcProcess := util.NewProcess(consts.FRPC_BIN_PATH, []string{"-c", frpcCfgPath})
err = frpcProcess.Start()
if assert.NoError(err) {
defer frpcProcess.Stop()
}
time.Sleep(500 * time.Millisecond)
// test tcp
res, err := util.SendTcpMsg("127.0.0.1:20801", consts.TEST_TCP_ECHO_STR)
assert.NoError(err)
assert.Equal(consts.TEST_TCP_ECHO_STR, res)
}
const FRPS_TLS_KCP_CONF = `
[common]
bind_addr = 0.0.0.0
bind_port = 20000
kcp_bind_port = 20000
log_file = console
log_level = debug
token = 123456
`
const FRPC_TLS_KCP_CONF = `
[common]
server_addr = 127.0.0.1
server_port = 20000
log_file = console
log_level = debug
token = 123456
protocol = kcp
tls_enable = true
[tcp]
type = tcp
local_port = 10701
remote_port = 20801
`
func TestTLSOverKCP(t *testing.T) {
assert := assert.New(t)
frpsCfgPath, err := config.GenerateConfigFile(consts.FRPS_NORMAL_CONFIG, FRPS_TLS_KCP_CONF)
if assert.NoError(err) {
defer os.Remove(frpsCfgPath)
}
frpcCfgPath, err := config.GenerateConfigFile(consts.FRPC_NORMAL_CONFIG, FRPC_TLS_KCP_CONF)
if assert.NoError(err) {
defer os.Remove(frpcCfgPath)
}
frpsProcess := util.NewProcess(consts.FRPS_BIN_PATH, []string{"-c", frpsCfgPath})
err = frpsProcess.Start()
if assert.NoError(err) {
defer frpsProcess.Stop()
}
time.Sleep(200 * time.Millisecond)
frpcProcess := util.NewProcess(consts.FRPC_BIN_PATH, []string{"-c", frpcCfgPath})
err = frpcProcess.Start()
if assert.NoError(err) {
defer frpcProcess.Stop()
}
time.Sleep(500 * time.Millisecond)
// test tcp
res, err := util.SendTcpMsg("127.0.0.1:20801", consts.TEST_TCP_ECHO_STR)
assert.NoError(err)
assert.Equal(consts.TEST_TCP_ECHO_STR, res)
}
const FRPS_TLS_WS_CONF = `
[common]
bind_addr = 0.0.0.0
bind_port = 20000
log_file = console
log_level = debug
token = 123456
`
const FRPC_TLS_WS_CONF = `
[common]
server_addr = 127.0.0.1
server_port = 20000
log_file = console
log_level = debug
token = 123456
protocol = websocket
tls_enable = true
[tcp]
type = tcp
local_port = 10701
remote_port = 20801
`
func TestTLSOverWebsocket(t *testing.T) {
assert := assert.New(t)
frpsCfgPath, err := config.GenerateConfigFile(consts.FRPS_NORMAL_CONFIG, FRPS_TLS_WS_CONF)
if assert.NoError(err) {
defer os.Remove(frpsCfgPath)
}
frpcCfgPath, err := config.GenerateConfigFile(consts.FRPC_NORMAL_CONFIG, FRPC_TLS_WS_CONF)
if assert.NoError(err) {
defer os.Remove(frpcCfgPath)
}
frpsProcess := util.NewProcess(consts.FRPS_BIN_PATH, []string{"-c", frpsCfgPath})
err = frpsProcess.Start()
if assert.NoError(err) {
defer frpsProcess.Stop()
}
time.Sleep(200 * time.Millisecond)
frpcProcess := util.NewProcess(consts.FRPC_BIN_PATH, []string{"-c", frpcCfgPath})
err = frpcProcess.Start()
if assert.NoError(err) {
defer frpcProcess.Stop()
}
time.Sleep(500 * time.Millisecond)
// test tcp
res, err := util.SendTcpMsg("127.0.0.1:20801", consts.TEST_TCP_ECHO_STR)
assert.NoError(err)
assert.Equal(consts.TEST_TCP_ECHO_STR, res)
}

View File

@@ -20,6 +20,7 @@ import (
"github.com/fatedier/beego/logs" "github.com/fatedier/beego/logs"
) )
// Log is the under log object
var Log *logs.BeeLogger var Log *logs.BeeLogger
func init() { func init() {
@@ -33,6 +34,7 @@ func InitLog(logWay string, logFile string, logLevel string, maxdays int64) {
SetLogLevel(logLevel) SetLogLevel(logLevel)
} }
// SetLogFile to configure log params
// logWay: file or console // logWay: file or console
func SetLogFile(logWay string, logFile string, maxdays int64) { func SetLogFile(logWay string, logFile string, maxdays int64) {
if logWay == "console" { if logWay == "console" {
@@ -43,6 +45,7 @@ func SetLogFile(logWay string, logFile string, maxdays int64) {
} }
} }
// SetLogLevel set log level, default is warning
// value: error, warning, info, debug, trace // value: error, warning, info, debug, trace
func SetLogLevel(logLevel string) { func SetLogLevel(logLevel string) {
level := 4 // warning level := 4 // warning
@@ -85,7 +88,7 @@ func Trace(format string, v ...interface{}) {
Log.Trace(format, v...) Log.Trace(format, v...)
} }
// Logger // Logger is the log interface
type Logger interface { type Logger interface {
AddLogPrefix(string) AddLogPrefix(string)
GetPrefixStr() string GetPrefixStr() string

View File

@@ -15,6 +15,7 @@
package net package net
import ( import (
"crypto/tls"
"errors" "errors"
"fmt" "fmt"
"io" "io"
@@ -207,3 +208,13 @@ func ConnectServerByProxy(proxyUrl string, protocol string, addr string) (c Conn
return nil, fmt.Errorf("unsupport protocol: %s", protocol) return nil, fmt.Errorf("unsupport protocol: %s", protocol)
} }
} }
func ConnectServerByProxyWithTLS(proxyUrl string, protocol string, addr string, tlsConfig *tls.Config) (c Conn, err error) {
c, err = ConnectServerByProxy(proxyUrl, protocol, addr)
if tlsConfig == nil {
return
}
c = WrapTLSClientConn(c, tlsConfig)
return
}

44
utils/net/tls.go Normal file
View File

@@ -0,0 +1,44 @@
// Copyright 2019 fatedier, fatedier@gmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package net
import (
"crypto/tls"
"net"
gnet "github.com/fatedier/golib/net"
)
var (
FRP_TLS_HEAD_BYTE = 0x17
)
func WrapTLSClientConn(c net.Conn, tlsConfig *tls.Config) (out Conn) {
c.Write([]byte{byte(FRP_TLS_HEAD_BYTE)})
out = WrapConn(tls.Client(c, tlsConfig))
return
}
func CheckAndEnableTLSServerConn(c net.Conn, tlsConfig *tls.Config) (out Conn) {
sc, r := gnet.NewSharedConnSize(c, 1)
buf := make([]byte, 1)
n, _ := r.Read(buf)
if n == 1 && int(buf[0]) == FRP_TLS_HEAD_BYTE {
out = WrapConn(tls.Server(c, tlsConfig))
} else {
out = WrapConn(sc)
}
return
}

View File

@@ -31,6 +31,7 @@ type WebsocketListener struct {
httpMutex *http.ServeMux httpMutex *http.ServeMux
} }
// NewWebsocketListener to handle websocket connections
// ln: tcp listener for websocket connections // ln: tcp listener for websocket connections
func NewWebsocketListener(ln net.Listener) (wl *WebsocketListener) { func NewWebsocketListener(ln net.Listener) (wl *WebsocketListener) {
wl = &WebsocketListener{ wl = &WebsocketListener{

View File

@@ -19,7 +19,7 @@ import (
"strings" "strings"
) )
var version string = "0.23.2" var version string = "0.25.0"
func Full() string { func Full() string {
return version return version

1
vendor/github.com/hashicorp/yamux/go.mod generated vendored Normal file
View File

@@ -0,0 +1 @@
module github.com/hashicorp/yamux

View File

@@ -3,6 +3,7 @@ package yamux
import ( import (
"fmt" "fmt"
"io" "io"
"log"
"os" "os"
"time" "time"
) )
@@ -30,8 +31,13 @@ type Config struct {
// window size that we allow for a stream. // window size that we allow for a stream.
MaxStreamWindowSize uint32 MaxStreamWindowSize uint32
// LogOutput is used to control the log destination // LogOutput is used to control the log destination. Either Logger or
// LogOutput can be set, not both.
LogOutput io.Writer LogOutput io.Writer
// Logger is used to pass in the logger to be used. Either Logger or
// LogOutput can be set, not both.
Logger *log.Logger
} }
// DefaultConfig is used to return a default configuration // DefaultConfig is used to return a default configuration
@@ -57,6 +63,11 @@ func VerifyConfig(config *Config) error {
if config.MaxStreamWindowSize < initialStreamWindow { if config.MaxStreamWindowSize < initialStreamWindow {
return fmt.Errorf("MaxStreamWindowSize must be larger than %d", initialStreamWindow) return fmt.Errorf("MaxStreamWindowSize must be larger than %d", initialStreamWindow)
} }
if config.LogOutput != nil && config.Logger != nil {
return fmt.Errorf("both Logger and LogOutput may not be set, select one")
} else if config.LogOutput == nil && config.Logger == nil {
return fmt.Errorf("one of Logger or LogOutput must be set, select one")
}
return nil return nil
} }

View File

@@ -86,9 +86,14 @@ type sendReady struct {
// newSession is used to construct a new session // newSession is used to construct a new session
func newSession(config *Config, conn io.ReadWriteCloser, client bool) *Session { func newSession(config *Config, conn io.ReadWriteCloser, client bool) *Session {
logger := config.Logger
if logger == nil {
logger = log.New(config.LogOutput, "", log.LstdFlags)
}
s := &Session{ s := &Session{
config: config, config: config,
logger: log.New(config.LogOutput, "", log.LstdFlags), logger: logger,
conn: conn, conn: conn,
bufRead: bufio.NewReader(conn), bufRead: bufio.NewReader(conn),
pings: make(map[uint32]chan struct{}), pings: make(map[uint32]chan struct{}),
@@ -309,8 +314,10 @@ func (s *Session) keepalive() {
case <-time.After(s.config.KeepAliveInterval): case <-time.After(s.config.KeepAliveInterval):
_, err := s.Ping() _, err := s.Ping()
if err != nil { if err != nil {
s.logger.Printf("[ERR] yamux: keepalive failed: %v", err) if err != ErrSessionShutdown {
s.exitErr(ErrKeepAliveTimeout) s.logger.Printf("[ERR] yamux: keepalive failed: %v", err)
s.exitErr(ErrKeepAliveTimeout)
}
return return
} }
case <-s.shutdownCh: case <-s.shutdownCh:

10
vendor/modules.txt vendored
View File

@@ -23,7 +23,7 @@ github.com/gorilla/context
github.com/gorilla/mux github.com/gorilla/mux
# github.com/gorilla/websocket v1.2.0 # github.com/gorilla/websocket v1.2.0
github.com/gorilla/websocket github.com/gorilla/websocket
# github.com/hashicorp/yamux v0.0.0-20180314200745-2658be15c5f0 # github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d
github.com/hashicorp/yamux github.com/hashicorp/yamux
# github.com/inconshreveable/mousetrap v1.0.0 # github.com/inconshreveable/mousetrap v1.0.0
github.com/inconshreveable/mousetrap github.com/inconshreveable/mousetrap
@@ -61,11 +61,11 @@ golang.org/x/crypto/twofish
golang.org/x/crypto/xtea golang.org/x/crypto/xtea
golang.org/x/crypto/salsa20/salsa golang.org/x/crypto/salsa20/salsa
# golang.org/x/net v0.0.0-20180524181706-dfa909b99c79 # golang.org/x/net v0.0.0-20180524181706-dfa909b99c79
golang.org/x/net/ipv4
golang.org/x/net/websocket golang.org/x/net/websocket
golang.org/x/net/context
golang.org/x/net/proxy
golang.org/x/net/ipv4
golang.org/x/net/internal/socks
golang.org/x/net/bpf golang.org/x/net/bpf
golang.org/x/net/internal/iana golang.org/x/net/internal/iana
golang.org/x/net/internal/socket golang.org/x/net/internal/socket
golang.org/x/net/context
golang.org/x/net/proxy
golang.org/x/net/internal/socks

14
web/frpc/.babelrc Normal file
View File

@@ -0,0 +1,14 @@
{
"presets": [
["es2015", { "modules": false }]
],
"plugins": [
[
"component",
{
"libraryName": "element-ui",
"styleLibraryName": "theme-chalk"
}
]
]
}

6
web/frpc/.gitignore vendored Normal file
View File

@@ -0,0 +1,6 @@
.DS_Store
node_modules/
dist/
npm-debug.log
.idea
.vscode/settings.json

6
web/frpc/Makefile Normal file
View File

@@ -0,0 +1,6 @@
.PHONY: dist build
build:
@npm run build
dev:
@npm run dev

9334
web/frpc/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

46
web/frpc/package.json Normal file
View File

@@ -0,0 +1,46 @@
{
"name": "frpc-web",
"description": "An admin web ui for frp client.",
"author": "fatedier",
"private": true,
"scripts": {
"dev": "webpack-dev-server -d --inline --hot --env.dev",
"build": "rimraf dist && webpack -p --progress --hide-modules"
},
"dependencies": {
"element-ui": "^2.5.3",
"vue": "^2.5.22",
"vue-resource": "^1.5.1",
"vue-router": "^3.0.2",
"whatwg-fetch": "^3.0.0"
},
"engines": {
"node": ">=6"
},
"devDependencies": {
"autoprefixer": "^9.4.7",
"babel-core": "^6.26.3",
"babel-eslint": "^10.0.1",
"babel-loader": "^7.1.5",
"babel-plugin-component": "^1.1.1",
"babel-preset-es2015": "^6.24.1",
"css-loader": "^2.1.0",
"eslint": "^5.12.1",
"eslint-config-enough": "^0.3.4",
"eslint-loader": "^2.1.1",
"file-loader": "^3.0.1",
"html-loader": "^0.5.5",
"html-webpack-plugin": "^2.24.1",
"less": "^3.9.0",
"less-loader": "^4.1.0",
"postcss-loader": "^3.0.0",
"rimraf": "^2.6.3",
"style-loader": "^0.23.1",
"url-loader": "^1.1.2",
"vue-loader": "^15.6.2",
"vue-template-compiler": "^2.5.22",
"webpack": "^2.7.0",
"webpack-cli": "^3.2.1",
"webpack-dev-server": "^3.1.14"
}
}

View File

@@ -0,0 +1,5 @@
module.exports = {
plugins: [
require('autoprefixer')()
]
}

73
web/frpc/src/App.vue Normal file
View File

@@ -0,0 +1,73 @@
<template>
<div id="app">
<header class="grid-content header-color">
<el-row>
<a class="brand" href="#">frp client</a>
</el-row>
</header>
<section>
<el-row :gutter="20">
<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-menu-item index="/configure">Configure</el-menu-item>
<el-menu-item index="">Help</el-menu-item>
</el-menu>
</el-col>
<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 {
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;
}
</style>

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

View File

@@ -0,0 +1,93 @@
<template>
<div>
<el-row id="head">
<el-button type="primary" @click="fetchData">Refresh</el-button>
<el-button type="primary" @click="uploadConfig">Upload</el-button>
</el-row>
<el-input type="textarea" autosize v-model="textarea" placeholder="frpc configrue file, can not be empty..."></el-input>
</div>
</template>
<script>
export default {
data() {
return {
textarea: ''
}
},
created() {
this.fetchData()
},
watch: {
'$route': 'fetchData'
},
methods: {
fetchData() {
fetch('/api/config', {credentials: 'include'})
.then(res => {
return res.text()
}).then(text => {
this.textarea= text
}).catch( err => {
this.$message({
showClose: true,
message: 'Get configure content from frpc failed!',
type: 'warning'
})
})
},
uploadConfig() {
this.$confirm('This operation will upload your frpc configure file content and hot reload it, do you want to continue?', 'Notice', {
confirmButtonText: 'Yes',
cancelButtonText: 'No',
type: 'warning'
}).then(() => {
if (this.textarea == "") {
this.$message({
type: 'warning',
message: 'Configure content can not be empty!'
})
return
}
fetch('/api/config', {
credentials: 'include',
method: 'PUT',
body: this.textarea,
}).then(() => {
fetch('/api/reload', {credentials: 'include'})
.then(() => {
this.$message({
type: 'success',
message: 'Success'
})
}).catch(err => {
this.$message({
showClose: true,
message: 'Reload frpc configure file error, ' + err,
type: 'warning'
})
})
}).catch(err => {
this.$message({
showClose: true,
message: 'Put config to frpc and hot reload failed!',
type: 'warning'
})
})
}).catch(() => {
this.$message({
type: 'info',
message: 'Canceled'
})
})
}
}
}
</script>
<style>
#head {
margin-bottom: 30px;
}
</style>

View File

@@ -0,0 +1,72 @@
<template>
<div>
<el-row>
<el-col :md="24">
<div>
<el-table :data="status" stripe style="width: 100%" :default-sort="{prop: 'type', order: 'ascending'}">
<el-table-column prop="name" label="name"></el-table-column>
<el-table-column prop="type" label="type" width="150"></el-table-column>
<el-table-column prop="local_addr" label="local address" width="200"></el-table-column>
<el-table-column prop="plugin" label="plugin" width="200"></el-table-column>
<el-table-column prop="remote_addr" label="remote address"></el-table-column>
<el-table-column prop="status" label="status" width="150"></el-table-column>
<el-table-column prop="err" label="info"></el-table-column>
</el-table>
</div>
</el-col>
</el-row>
</div>
</template>
<script>
export default {
data() {
return {
status: null
}
},
created() {
this.fetchData()
},
watch: {
'$route': 'fetchData'
},
methods: {
fetchData() {
fetch('/api/status', {credentials: 'include'})
.then(res => {
return res.json()
}).then(json => {
this.status = new Array()
for (let s of json.tcp) {
this.status.push(s)
}
for (let s of json.udp) {
this.status.push(s)
}
for (let s of json.http) {
this.status.push(s)
}
for (let s of json.https) {
this.status.push(s)
}
for (let s of json.stcp) {
this.status.push(s)
}
for (let s of json.xtcp) {
this.status.push(s)
}
}).catch( err => {
this.$message({
showClose: true,
message: 'Get status info from frpc failed!',
type: 'warning'
})
})
}
}
}
</script>
<style>
</style>

15
web/frpc/src/index.html Normal file
View File

@@ -0,0 +1,15 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>frp client admin UI</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>

52
web/frpc/src/main.js Normal file
View File

@@ -0,0 +1,52 @@
import Vue from 'vue'
// import ElementUI from 'element-ui'
import {
Button,
Form,
FormItem,
Row,
Col,
Table,
TableColumn,
Menu,
MenuItem,
MessageBox,
Message,
Input
} 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'
import './utils/less/custom.less'
import App from './App.vue'
import router from './router'
import 'whatwg-fetch'
locale.use(lang)
Vue.use(Button)
Vue.use(Form)
Vue.use(FormItem)
Vue.use(Row)
Vue.use(Col)
Vue.use(Table)
Vue.use(TableColumn)
Vue.use(Menu)
Vue.use(MenuItem)
Vue.use(Input)
Vue.prototype.$msgbox = MessageBox;
Vue.prototype.$confirm = MessageBox.confirm
Vue.prototype.$message = Message
//Vue.use(ElementUI)
Vue.config.productionTip = false
new Vue({
el: '#app',
router,
template: '<App/>',
components: { App }
})

View File

@@ -0,0 +1,18 @@
import Vue from 'vue'
import Router from 'vue-router'
import Overview from '../components/Overview.vue'
import Configure from '../components/Configure.vue'
Vue.use(Router)
export default new Router({
routes: [{
path: '/',
name: 'Overview',
component: Overview
},{
path: '/configure',
name: 'Configure',
component: Configure,
}]
})

View File

@@ -0,0 +1,22 @@
@color: red;
.el-form-item {
span {
margin-left: 15px;
}
}
.demo-table-expand {
font-size: 0;
label {
width: 90px;
color: #99a9bf;
}
.el-form-item {
margin-right: 0;
margin-bottom: 0;
width: 50%;
}
}

View File

@@ -0,0 +1,13 @@
class ProxyStatus {
constructor(status) {
this.name = status.name
this.type = status.type
this.status = status.status
this.err = status.err
this.local_addr = status.local_addr
this.plugin = status.plugin
this.remote_addr = status.remote_addr
}
}
export {ProxyStatus}

107
web/frpc/webpack.config.js Normal file
View File

@@ -0,0 +1,107 @@
const path = require('path')
var webpack = require('webpack')
var HtmlWebpackPlugin = require('html-webpack-plugin')
var VueLoaderPlugin = require('vue-loader/lib/plugin')
var url = require('url')
var publicPath = ''
module.exports = (options = {}) => ({
entry: {
vendor: './src/main'
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: options.dev ? '[name].js' : '[name].js?[chunkhash]',
chunkFilename: '[id].js?[chunkhash]',
publicPath: options.dev ? '/assets/' : publicPath
},
resolve: {
extensions: ['.js', '.vue', '.json'],
alias: {
'vue$': 'vue/dist/vue.esm.js',
'@': path.resolve(__dirname, 'src'),
}
},
module: {
rules: [{
test: /\.vue$/,
loader: 'vue-loader'
}, {
test: /\.js$/,
use: ['babel-loader'],
exclude: /node_modules/
}, {
test: /\.html$/,
use: [{
loader: 'html-loader',
options: {
root: path.resolve(__dirname, 'src'),
attrs: ['img:src', 'link:href']
}
}]
}, {
test: /\.less$/,
loader: 'style-loader!css-loader!postcss-loader!less-loader'
}, {
test: /\.css$/,
use: ['style-loader', 'css-loader', 'postcss-loader']
}, {
test: /favicon\.png$/,
use: [{
loader: 'file-loader',
options: {
name: '[name].[ext]?[hash]'
}
}]
}, {
test: /\.(png|jpg|jpeg|gif|eot|ttf|woff|woff2|svg|svgz)(\?.+)?$/,
exclude: /favicon\.png$/,
use: [{
loader: 'url-loader',
options: {
limit: 10000
}
}]
}]
},
plugins: [
new webpack.optimize.CommonsChunkPlugin({
names: ['vendor', 'manifest']
}),
new HtmlWebpackPlugin({
favicon: 'src/assets/favicon.ico',
template: 'src/index.html'
}),
new webpack.NormalModuleReplacementPlugin(/element-ui[\/\\]lib[\/\\]locale[\/\\]lang[\/\\]zh-CN/, 'element-ui/lib/locale/lang/en'),
new webpack.DefinePlugin({
'process.env': {
NODE_ENV: '"production"'
}
}),
new webpack.optimize.UglifyJsPlugin({
sourceMap: false,
comments: false,
compress: {
warnings: false
}
}),
new VueLoaderPlugin()
],
devServer: {
host: '127.0.0.1',
port: 8010,
proxy: {
'/api/': {
target: 'http://127.0.0.1:8080',
changeOrigin: true,
pathRewrite: {
'^/api': ''
}
}
},
historyApiFallback: {
index: url.parse(options.dev ? '/assets/' : publicPath).pathname
}
}//,
//devtool: options.dev ? '#eval-source-map' : '#source-map'
})

6236
web/frpc/yarn.lock Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,9 +1,7 @@
.PHONY: dist build .PHONY: dist build
install:
@npm install
dev: install
@npm run dev
build: build:
@npm run build @npm run build
dev: install
@npm run dev