forked from Mxmilu666/frp
Compare commits
72 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e62d9a5242 | ||
|
|
94212ac8b8 | ||
|
|
e9e86fccf0 | ||
|
|
58745992ef | ||
|
|
234d634bfe | ||
|
|
fdc6902a90 | ||
|
|
d8d587fd93 | ||
|
|
92791260a7 | ||
|
|
4dfd851c46 | ||
|
|
bc4df74b5e | ||
|
|
666f122a72 | ||
|
|
f999c8a87e | ||
|
|
90a32ab75d | ||
|
|
0713fd28da | ||
|
|
f5b33e6de8 | ||
|
|
fc6043bb4d | ||
|
|
bc46e3330a | ||
|
|
5fc7b3ceb5 | ||
|
|
6277af4790 | ||
|
|
00bd0a8af4 | ||
|
|
a415573e45 | ||
|
|
e68012858e | ||
|
|
ca8a5b753c | ||
|
|
d1f4ac0f2d | ||
|
|
ff357882ac | ||
|
|
934ac2b836 | ||
|
|
1ad50d5982 | ||
|
|
388b016842 | ||
|
|
134a46c00b | ||
|
|
50796643fb | ||
|
|
b1838b1d5e | ||
|
|
757b3613fe | ||
|
|
ae08811636 | ||
|
|
b657c0fe09 | ||
|
|
84df71047c | ||
|
|
abc6d720d0 | ||
|
|
80154639e3 | ||
|
|
f2117d8331 | ||
|
|
261be6a7b7 | ||
|
|
b53a2c1ed9 | ||
|
|
ee0df07a3c | ||
|
|
4e363eca2b | ||
|
|
4277405c0e | ||
|
|
6a99f0caf7 | ||
|
|
394af08561 | ||
|
|
6451583e60 | ||
|
|
30cb0a3ab0 | ||
|
|
5680a88267 | ||
|
|
6b089858db | ||
|
|
b3ed863021 | ||
|
|
5796c27ed5 | ||
|
|
310e8dd768 | ||
|
|
0b40ac2dbc | ||
|
|
f22c8e0882 | ||
|
|
a388bb2c95 | ||
|
|
e611c44dea | ||
|
|
8e36e2bb67 | ||
|
|
541ad8d899 | ||
|
|
17cc0735d1 | ||
|
|
fd336a5503 | ||
|
|
802d1c1861 | ||
|
|
65fe0a1179 | ||
|
|
2d24879fa3 | ||
|
|
75383a95b3 | ||
|
|
95444ea46b | ||
|
|
9f9c01b520 | ||
|
|
285d1eba0d | ||
|
|
0dfd3a421c | ||
|
|
6a1f15b25e | ||
|
|
9f47c324b7 | ||
|
|
f0df6084af | ||
|
|
879ca47590 |
2
.github/ISSUE_TEMPLATE
vendored
2
.github/ISSUE_TEMPLATE
vendored
@@ -1,5 +1,7 @@
|
||||
Issue is only used for submiting bug report and documents typo. If there are same issues or answers can be found in documents, we will close it directly.
|
||||
(为了节约时间,提高处理问题的效率,不按照格式填写的 issue 将会直接关闭。)
|
||||
(请不要在 issue 评论中出现无意义的 **加1**,**我也是** 等内容,将会被直接删除。)
|
||||
(由于个人精力有限,和系统环境,网络环境等相关的求助问题请转至其他论坛或社交平台。)
|
||||
|
||||
Use the commands below to provide key information from your environment:
|
||||
You do NOT have to include this information if this is a FEATURE REQUEST
|
||||
|
||||
@@ -2,7 +2,6 @@ sudo: false
|
||||
language: go
|
||||
|
||||
go:
|
||||
- 1.11.x
|
||||
- 1.12.x
|
||||
|
||||
install:
|
||||
|
||||
10
README.md
10
README.md
@@ -66,7 +66,7 @@ Now it also try to support p2p connect.
|
||||
|
||||
frp is under development and you can try it with latest release version. Master branch for releasing stable version when dev branch for developing.
|
||||
|
||||
**We may change any protocol and can't promise backward compatible. Please check the release log when upgrading.**
|
||||
**We may change any protocol and can't promise backward compatibility. Please check the release log when upgrading.**
|
||||
|
||||
## Architecture
|
||||
|
||||
@@ -265,6 +265,7 @@ Configure frps same as above.
|
||||
plugin_crt_path = ./server.crt
|
||||
plugin_key_path = ./server.key
|
||||
plugin_host_header_rewrite = 127.0.0.1
|
||||
plugin_header_X-From-Where = frp
|
||||
```
|
||||
|
||||
2. Visit `https://test.yourdomain.com`.
|
||||
@@ -560,7 +561,8 @@ This feature is fit for a large number of short connections.
|
||||
### Load balancing
|
||||
|
||||
Load balancing is supported by `group`.
|
||||
This feature is available only for type `tcp` now.
|
||||
|
||||
This feature is available only for type `tcp` and `http` now.
|
||||
|
||||
```ini
|
||||
# frpc.ini
|
||||
@@ -583,6 +585,10 @@ group_key = 123
|
||||
|
||||
Proxies in same group will accept connections from port 80 randomly.
|
||||
|
||||
For `tcp` type, `remote_port` in one group shoud be same.
|
||||
|
||||
For `http` type, `custom_domains, subdomain, locations` shoud be same.
|
||||
|
||||
### Health Check
|
||||
|
||||
Health check feature can help you achieve high availability with load balancing.
|
||||
|
||||
13
README_zh.md
13
README_zh.md
@@ -16,7 +16,7 @@ frp 是一个可用于内网穿透的高性能的反向代理应用,支持 tcp
|
||||
* [通过 ssh 访问公司内网机器](#通过-ssh-访问公司内网机器)
|
||||
* [通过自定义域名访问部署于内网的 web 服务](#通过自定义域名访问部署于内网的-web-服务)
|
||||
* [转发 DNS 查询请求](#转发-dns-查询请求)
|
||||
* [转发 Unix域套接字](#转发-unix域套接字)
|
||||
* [转发 Unix 域套接字](#转发-unix-域套接字)
|
||||
* [对外提供简单的文件访问服务](#对外提供简单的文件访问服务)
|
||||
* [为本地 HTTP 服务启用 HTTPS](#为本地-http-服务启用-https)
|
||||
* [安全地暴露内网服务](#安全地暴露内网服务)
|
||||
@@ -129,7 +129,7 @@ master 分支用于发布稳定版本,dev 分支用于开发,您可以尝试
|
||||
vhost_http_port = 8080
|
||||
```
|
||||
|
||||
2. 启动 frps;
|
||||
2. 启动 frps:
|
||||
|
||||
`./frps -c ./frps.ini`
|
||||
|
||||
@@ -194,7 +194,7 @@ DNS 查询请求通常使用 UDP 协议,frp 支持对内网 UDP 服务的穿
|
||||
|
||||
`dig @x.x.x.x -p 6000 www.google.com`
|
||||
|
||||
### 转发 Unix域套接字
|
||||
### 转发 Unix 域套接字
|
||||
|
||||
通过 tcp 端口访问内网的 unix域套接字(例如和 docker daemon 通信)。
|
||||
|
||||
@@ -270,6 +270,7 @@ frps 的部署步骤同上。
|
||||
plugin_crt_path = ./server.crt
|
||||
plugin_key_path = ./server.key
|
||||
plugin_host_header_rewrite = 127.0.0.1
|
||||
plugin_header_X-From-Where = frp
|
||||
```
|
||||
|
||||
2. 通过浏览器访问 `https://test.yourdomain.com` 即可。
|
||||
@@ -597,7 +598,7 @@ tcp_mux = false
|
||||
|
||||
可以将多个相同类型的 proxy 加入到同一个 group 中,从而实现负载均衡的功能。
|
||||
|
||||
目前只支持 tcp 类型的 proxy。
|
||||
目前只支持 TCP 和 HTTP 类型的 proxy。
|
||||
|
||||
```ini
|
||||
# frpc.ini
|
||||
@@ -618,7 +619,9 @@ group_key = 123
|
||||
|
||||
用户连接 frps 服务器的 80 端口,frps 会将接收到的用户连接随机分发给其中一个存活的 proxy。这样可以在一台 frpc 机器挂掉后仍然有其他节点能够提供服务。
|
||||
|
||||
要求 `group_key` 相同,做权限验证,且 `remote_port` 相同。
|
||||
TCP 类型代理要求 `group_key` 相同,做权限验证,且 `remote_port` 相同。
|
||||
|
||||
HTTP 类型代理要求 `group_key, custom_domains 或 subdomain 和 locations` 相同。
|
||||
|
||||
### 健康检查
|
||||
|
||||
|
||||
@@ -21,7 +21,6 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/fatedier/frp/assets"
|
||||
"github.com/fatedier/frp/g"
|
||||
frpNet "github.com/fatedier/frp/utils/net"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
@@ -36,7 +35,7 @@ func (svr *Service) RunAdminServer(addr string, port int) (err error) {
|
||||
// url router
|
||||
router := mux.NewRouter()
|
||||
|
||||
user, passwd := g.GlbClientCfg.AdminUser, g.GlbClientCfg.AdminPwd
|
||||
user, passwd := svr.cfg.AdminUser, svr.cfg.AdminPwd
|
||||
router.Use(frpNet.NewHttpAuthMiddleware(user, passwd).Middleware)
|
||||
|
||||
// api, see dashboard_api.go
|
||||
|
||||
@@ -23,7 +23,6 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/fatedier/frp/client/proxy"
|
||||
"github.com/fatedier/frp/g"
|
||||
"github.com/fatedier/frp/models/config"
|
||||
"github.com/fatedier/frp/utils/log"
|
||||
)
|
||||
@@ -47,7 +46,7 @@ func (svr *Service) apiReload(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}()
|
||||
|
||||
content, err := config.GetRenderedConfFromFile(g.GlbClientCfg.CfgFile)
|
||||
content, err := config.GetRenderedConfFromFile(svr.cfgFile)
|
||||
if err != nil {
|
||||
res.Code = 400
|
||||
res.Msg = err.Error()
|
||||
@@ -55,7 +54,7 @@ func (svr *Service) apiReload(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
newCommonCfg, err := config.UnmarshalClientConfFromIni(nil, content)
|
||||
newCommonCfg, err := config.UnmarshalClientConfFromIni(content)
|
||||
if err != nil {
|
||||
res.Code = 400
|
||||
res.Msg = err.Error()
|
||||
@@ -63,7 +62,7 @@ func (svr *Service) apiReload(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
pxyCfgs, visitorCfgs, err := config.LoadAllConfFromIni(g.GlbClientCfg.User, content, newCommonCfg.Start)
|
||||
pxyCfgs, visitorCfgs, err := config.LoadAllConfFromIni(svr.cfg.User, content, newCommonCfg.Start)
|
||||
if err != nil {
|
||||
res.Code = 400
|
||||
res.Msg = err.Error()
|
||||
@@ -107,7 +106,7 @@ func (a ByProxyStatusResp) Len() int { return len(a) }
|
||||
func (a ByProxyStatusResp) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||
func (a ByProxyStatusResp) Less(i, j int) bool { return strings.Compare(a[i].Name, a[j].Name) < 0 }
|
||||
|
||||
func NewProxyStatusResp(status *proxy.ProxyStatus) ProxyStatusResp {
|
||||
func NewProxyStatusResp(status *proxy.ProxyStatus, serverAddr string) ProxyStatusResp {
|
||||
psr := ProxyStatusResp{
|
||||
Name: status.Name,
|
||||
Type: status.Type,
|
||||
@@ -121,18 +120,18 @@ func NewProxyStatusResp(status *proxy.ProxyStatus) ProxyStatusResp {
|
||||
}
|
||||
psr.Plugin = cfg.Plugin
|
||||
if status.Err != "" {
|
||||
psr.RemoteAddr = fmt.Sprintf("%s:%d", g.GlbClientCfg.ServerAddr, cfg.RemotePort)
|
||||
psr.RemoteAddr = fmt.Sprintf("%s:%d", serverAddr, cfg.RemotePort)
|
||||
} else {
|
||||
psr.RemoteAddr = g.GlbClientCfg.ServerAddr + status.RemoteAddr
|
||||
psr.RemoteAddr = serverAddr + status.RemoteAddr
|
||||
}
|
||||
case *config.UdpProxyConf:
|
||||
if cfg.LocalPort != 0 {
|
||||
psr.LocalAddr = fmt.Sprintf("%s:%d", cfg.LocalIp, cfg.LocalPort)
|
||||
}
|
||||
if status.Err != "" {
|
||||
psr.RemoteAddr = fmt.Sprintf("%s:%d", g.GlbClientCfg.ServerAddr, cfg.RemotePort)
|
||||
psr.RemoteAddr = fmt.Sprintf("%s:%d", serverAddr, cfg.RemotePort)
|
||||
} else {
|
||||
psr.RemoteAddr = g.GlbClientCfg.ServerAddr + status.RemoteAddr
|
||||
psr.RemoteAddr = serverAddr + status.RemoteAddr
|
||||
}
|
||||
case *config.HttpProxyConf:
|
||||
if cfg.LocalPort != 0 {
|
||||
@@ -184,17 +183,17 @@ func (svr *Service) apiStatus(w http.ResponseWriter, r *http.Request) {
|
||||
for _, status := range ps {
|
||||
switch status.Type {
|
||||
case "tcp":
|
||||
res.Tcp = append(res.Tcp, NewProxyStatusResp(status))
|
||||
res.Tcp = append(res.Tcp, NewProxyStatusResp(status, svr.cfg.ServerAddr))
|
||||
case "udp":
|
||||
res.Udp = append(res.Udp, NewProxyStatusResp(status))
|
||||
res.Udp = append(res.Udp, NewProxyStatusResp(status, svr.cfg.ServerAddr))
|
||||
case "http":
|
||||
res.Http = append(res.Http, NewProxyStatusResp(status))
|
||||
res.Http = append(res.Http, NewProxyStatusResp(status, svr.cfg.ServerAddr))
|
||||
case "https":
|
||||
res.Https = append(res.Https, NewProxyStatusResp(status))
|
||||
res.Https = append(res.Https, NewProxyStatusResp(status, svr.cfg.ServerAddr))
|
||||
case "stcp":
|
||||
res.Stcp = append(res.Stcp, NewProxyStatusResp(status))
|
||||
res.Stcp = append(res.Stcp, NewProxyStatusResp(status, svr.cfg.ServerAddr))
|
||||
case "xtcp":
|
||||
res.Xtcp = append(res.Xtcp, NewProxyStatusResp(status))
|
||||
res.Xtcp = append(res.Xtcp, NewProxyStatusResp(status, svr.cfg.ServerAddr))
|
||||
}
|
||||
}
|
||||
sort.Sort(ByProxyStatusResp(res.Tcp))
|
||||
@@ -219,14 +218,14 @@ func (svr *Service) apiGetConfig(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}()
|
||||
|
||||
if g.GlbClientCfg.CfgFile == "" {
|
||||
if svr.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)
|
||||
content, err := config.GetRenderedConfFromFile(svr.cfgFile)
|
||||
if err != nil {
|
||||
res.Code = 400
|
||||
res.Msg = err.Error()
|
||||
@@ -277,7 +276,7 @@ func (svr *Service) apiPutConfig(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
// get token from origin content
|
||||
token := ""
|
||||
b, err := ioutil.ReadFile(g.GlbClientCfg.CfgFile)
|
||||
b, err := ioutil.ReadFile(svr.cfgFile)
|
||||
if err != nil {
|
||||
res.Code = 400
|
||||
res.Msg = err.Error()
|
||||
@@ -316,7 +315,7 @@ func (svr *Service) apiPutConfig(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
content = strings.Join(newRows, "\n")
|
||||
|
||||
err = ioutil.WriteFile(g.GlbClientCfg.CfgFile, []byte(content), 0644)
|
||||
err = ioutil.WriteFile(svr.cfgFile, []byte(content), 0644)
|
||||
if err != nil {
|
||||
res.Code = 500
|
||||
res.Msg = fmt.Sprintf("write content to frpc config file error: %v", err)
|
||||
|
||||
@@ -23,7 +23,6 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/fatedier/frp/client/proxy"
|
||||
"github.com/fatedier/frp/g"
|
||||
"github.com/fatedier/frp/models/config"
|
||||
"github.com/fatedier/frp/models/msg"
|
||||
"github.com/fatedier/frp/utils/log"
|
||||
@@ -65,16 +64,24 @@ type Control struct {
|
||||
// last time got the Pong message
|
||||
lastPong time.Time
|
||||
|
||||
// The client configuration
|
||||
clientCfg config.ClientCommonConf
|
||||
|
||||
readerShutdown *shutdown.Shutdown
|
||||
writerShutdown *shutdown.Shutdown
|
||||
msgHandlerShutdown *shutdown.Shutdown
|
||||
|
||||
// The UDP port that the server is listening on
|
||||
serverUDPPort int
|
||||
|
||||
mu sync.RWMutex
|
||||
|
||||
log.Logger
|
||||
}
|
||||
|
||||
func NewControl(runId string, conn frpNet.Conn, session *fmux.Session, pxyCfgs map[string]config.ProxyConf, visitorCfgs map[string]config.VisitorConf) *Control {
|
||||
func NewControl(runId string, conn frpNet.Conn, session *fmux.Session, clientCfg config.ClientCommonConf,
|
||||
pxyCfgs map[string]config.ProxyConf, visitorCfgs map[string]config.VisitorConf, serverUDPPort int) *Control {
|
||||
|
||||
ctl := &Control{
|
||||
runId: runId,
|
||||
conn: conn,
|
||||
@@ -84,12 +91,14 @@ func NewControl(runId string, conn frpNet.Conn, session *fmux.Session, pxyCfgs m
|
||||
readCh: make(chan msg.Message, 100),
|
||||
closedCh: make(chan struct{}),
|
||||
closedDoneCh: make(chan struct{}),
|
||||
clientCfg: clientCfg,
|
||||
readerShutdown: shutdown.New(),
|
||||
writerShutdown: shutdown.New(),
|
||||
msgHandlerShutdown: shutdown.New(),
|
||||
serverUDPPort: serverUDPPort,
|
||||
Logger: log.NewPrefixLogger(""),
|
||||
}
|
||||
ctl.pm = proxy.NewProxyManager(ctl.sendCh, runId)
|
||||
ctl.pm = proxy.NewProxyManager(ctl.sendCh, runId, clientCfg, serverUDPPort)
|
||||
|
||||
ctl.vm = NewVisitorManager(ctl)
|
||||
ctl.vm.Reload(visitorCfgs)
|
||||
@@ -161,7 +170,7 @@ func (ctl *Control) ClosedDoneCh() <-chan struct{} {
|
||||
|
||||
// connectServer return a new connection to frps
|
||||
func (ctl *Control) connectServer() (conn frpNet.Conn, err error) {
|
||||
if g.GlbClientCfg.TcpMux {
|
||||
if ctl.clientCfg.TcpMux {
|
||||
stream, errRet := ctl.session.OpenStream()
|
||||
if errRet != nil {
|
||||
err = errRet
|
||||
@@ -171,13 +180,13 @@ func (ctl *Control) connectServer() (conn frpNet.Conn, err error) {
|
||||
conn = frpNet.WrapConn(stream)
|
||||
} else {
|
||||
var tlsConfig *tls.Config
|
||||
if g.GlbClientCfg.TLSEnable {
|
||||
if ctl.clientCfg.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)
|
||||
conn, err = frpNet.ConnectServerByProxyWithTLS(ctl.clientCfg.HttpProxy, ctl.clientCfg.Protocol,
|
||||
fmt.Sprintf("%s:%d", ctl.clientCfg.ServerAddr, ctl.clientCfg.ServerPort), tlsConfig)
|
||||
if err != nil {
|
||||
ctl.Warn("start new connection to server error: %v", err)
|
||||
return
|
||||
@@ -197,7 +206,7 @@ func (ctl *Control) reader() {
|
||||
defer ctl.readerShutdown.Done()
|
||||
defer close(ctl.closedCh)
|
||||
|
||||
encReader := crypto.NewReader(ctl.conn, []byte(g.GlbClientCfg.Token))
|
||||
encReader := crypto.NewReader(ctl.conn, []byte(ctl.clientCfg.Token))
|
||||
for {
|
||||
if m, err := msg.ReadMsg(encReader); err != nil {
|
||||
if err == io.EOF {
|
||||
@@ -217,7 +226,7 @@ func (ctl *Control) reader() {
|
||||
// writer writes messages got from sendCh to frps
|
||||
func (ctl *Control) writer() {
|
||||
defer ctl.writerShutdown.Done()
|
||||
encWriter, err := crypto.NewWriter(ctl.conn, []byte(g.GlbClientCfg.Token))
|
||||
encWriter, err := crypto.NewWriter(ctl.conn, []byte(ctl.clientCfg.Token))
|
||||
if err != nil {
|
||||
ctl.conn.Error("crypto new writer error: %v", err)
|
||||
ctl.conn.Close()
|
||||
@@ -246,7 +255,7 @@ func (ctl *Control) msgHandler() {
|
||||
}()
|
||||
defer ctl.msgHandlerShutdown.Done()
|
||||
|
||||
hbSend := time.NewTicker(time.Duration(g.GlbClientCfg.HeartBeatInterval) * time.Second)
|
||||
hbSend := time.NewTicker(time.Duration(ctl.clientCfg.HeartBeatInterval) * time.Second)
|
||||
defer hbSend.Stop()
|
||||
hbCheck := time.NewTicker(time.Second)
|
||||
defer hbCheck.Stop()
|
||||
@@ -260,7 +269,7 @@ func (ctl *Control) msgHandler() {
|
||||
ctl.Debug("send heartbeat to server")
|
||||
ctl.sendCh <- &msg.Ping{}
|
||||
case <-hbCheck.C:
|
||||
if time.Since(ctl.lastPong) > time.Duration(g.GlbClientCfg.HeartBeatTimeout)*time.Second {
|
||||
if time.Since(ctl.lastPong) > time.Duration(ctl.clientCfg.HeartBeatTimeout)*time.Second {
|
||||
ctl.Warn("heartbeat timeout")
|
||||
// let reader() stop
|
||||
ctl.conn.Close()
|
||||
|
||||
@@ -96,12 +96,12 @@ func (monitor *HealthCheckMonitor) Stop() {
|
||||
|
||||
func (monitor *HealthCheckMonitor) checkWorker() {
|
||||
for {
|
||||
ctx, cancel := context.WithDeadline(monitor.ctx, time.Now().Add(monitor.timeout))
|
||||
err := monitor.doCheck(ctx)
|
||||
doCtx, cancel := context.WithDeadline(monitor.ctx, time.Now().Add(monitor.timeout))
|
||||
err := monitor.doCheck(doCtx)
|
||||
|
||||
// check if this monitor has been closed
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
case <-monitor.ctx.Done():
|
||||
cancel()
|
||||
return
|
||||
default:
|
||||
|
||||
@@ -25,7 +25,6 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/fatedier/frp/g"
|
||||
"github.com/fatedier/frp/models/config"
|
||||
"github.com/fatedier/frp/models/msg"
|
||||
"github.com/fatedier/frp/models/plugin"
|
||||
@@ -51,9 +50,11 @@ type Proxy interface {
|
||||
log.Logger
|
||||
}
|
||||
|
||||
func NewProxy(pxyConf config.ProxyConf) (pxy Proxy) {
|
||||
func NewProxy(pxyConf config.ProxyConf, clientCfg config.ClientCommonConf, serverUDPPort int) (pxy Proxy) {
|
||||
baseProxy := BaseProxy{
|
||||
Logger: log.NewPrefixLogger(pxyConf.GetBaseInfo().ProxyName),
|
||||
Logger: log.NewPrefixLogger(pxyConf.GetBaseInfo().ProxyName),
|
||||
clientCfg: clientCfg,
|
||||
serverUDPPort: serverUDPPort,
|
||||
}
|
||||
switch cfg := pxyConf.(type) {
|
||||
case *config.TcpProxyConf:
|
||||
@@ -91,8 +92,10 @@ func NewProxy(pxyConf config.ProxyConf) (pxy Proxy) {
|
||||
}
|
||||
|
||||
type BaseProxy struct {
|
||||
closed bool
|
||||
mu sync.RWMutex
|
||||
closed bool
|
||||
mu sync.RWMutex
|
||||
clientCfg config.ClientCommonConf
|
||||
serverUDPPort int
|
||||
log.Logger
|
||||
}
|
||||
|
||||
@@ -122,7 +125,7 @@ func (pxy *TcpProxy) Close() {
|
||||
|
||||
func (pxy *TcpProxy) InWorkConn(conn frpNet.Conn, m *msg.StartWorkConn) {
|
||||
HandleTcpWorkConnection(&pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, conn,
|
||||
[]byte(g.GlbClientCfg.Token), m)
|
||||
[]byte(pxy.clientCfg.Token), m)
|
||||
}
|
||||
|
||||
// HTTP
|
||||
@@ -151,7 +154,7 @@ func (pxy *HttpProxy) Close() {
|
||||
|
||||
func (pxy *HttpProxy) InWorkConn(conn frpNet.Conn, m *msg.StartWorkConn) {
|
||||
HandleTcpWorkConnection(&pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, conn,
|
||||
[]byte(g.GlbClientCfg.Token), m)
|
||||
[]byte(pxy.clientCfg.Token), m)
|
||||
}
|
||||
|
||||
// HTTPS
|
||||
@@ -180,7 +183,7 @@ func (pxy *HttpsProxy) Close() {
|
||||
|
||||
func (pxy *HttpsProxy) InWorkConn(conn frpNet.Conn, m *msg.StartWorkConn) {
|
||||
HandleTcpWorkConnection(&pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, conn,
|
||||
[]byte(g.GlbClientCfg.Token), m)
|
||||
[]byte(pxy.clientCfg.Token), m)
|
||||
}
|
||||
|
||||
// STCP
|
||||
@@ -209,7 +212,7 @@ func (pxy *StcpProxy) Close() {
|
||||
|
||||
func (pxy *StcpProxy) InWorkConn(conn frpNet.Conn, m *msg.StartWorkConn) {
|
||||
HandleTcpWorkConnection(&pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, conn,
|
||||
[]byte(g.GlbClientCfg.Token), m)
|
||||
[]byte(pxy.clientCfg.Token), m)
|
||||
}
|
||||
|
||||
// XTCP
|
||||
@@ -250,7 +253,7 @@ func (pxy *XtcpProxy) InWorkConn(conn frpNet.Conn, m *msg.StartWorkConn) {
|
||||
Sid: natHoleSidMsg.Sid,
|
||||
}
|
||||
raddr, _ := net.ResolveUDPAddr("udp",
|
||||
fmt.Sprintf("%s:%d", g.GlbClientCfg.ServerAddr, g.GlbClientCfg.ServerUdpPort))
|
||||
fmt.Sprintf("%s:%d", pxy.clientCfg.ServerAddr, pxy.serverUDPPort))
|
||||
clientConn, err := net.DialUDP("udp", nil, raddr)
|
||||
defer clientConn.Close()
|
||||
|
||||
@@ -503,10 +506,43 @@ func HandleTcpWorkConnection(localInfo *config.LocalSvrConf, proxyPlugin plugin.
|
||||
remote = frpIo.WithCompression(remote)
|
||||
}
|
||||
|
||||
// check if we need to send proxy protocol info
|
||||
var extraInfo []byte
|
||||
if baseInfo.ProxyProtocolVersion != "" {
|
||||
if m.SrcAddr != "" && m.SrcPort != 0 {
|
||||
if m.DstAddr == "" {
|
||||
m.DstAddr = "127.0.0.1"
|
||||
}
|
||||
h := &pp.Header{
|
||||
Command: pp.PROXY,
|
||||
SourceAddress: net.ParseIP(m.SrcAddr),
|
||||
SourcePort: m.SrcPort,
|
||||
DestinationAddress: net.ParseIP(m.DstAddr),
|
||||
DestinationPort: m.DstPort,
|
||||
}
|
||||
|
||||
if strings.Contains(m.SrcAddr, ".") {
|
||||
h.TransportProtocol = pp.TCPv4
|
||||
} else {
|
||||
h.TransportProtocol = pp.TCPv6
|
||||
}
|
||||
|
||||
if baseInfo.ProxyProtocolVersion == "v1" {
|
||||
h.Version = 1
|
||||
} else if baseInfo.ProxyProtocolVersion == "v2" {
|
||||
h.Version = 2
|
||||
}
|
||||
|
||||
buf := bytes.NewBuffer(nil)
|
||||
h.WriteTo(buf)
|
||||
extraInfo = buf.Bytes()
|
||||
}
|
||||
}
|
||||
|
||||
if proxyPlugin != nil {
|
||||
// if plugin is set, let plugin handle connections first
|
||||
workConn.Debug("handle by plugin: %s", proxyPlugin.Name())
|
||||
proxyPlugin.Handle(remote, workConn)
|
||||
proxyPlugin.Handle(remote, workConn, extraInfo)
|
||||
workConn.Debug("handle by plugin finished")
|
||||
return
|
||||
} else {
|
||||
@@ -520,34 +556,8 @@ func HandleTcpWorkConnection(localInfo *config.LocalSvrConf, proxyPlugin plugin.
|
||||
workConn.Debug("join connections, localConn(l[%s] r[%s]) workConn(l[%s] r[%s])", localConn.LocalAddr().String(),
|
||||
localConn.RemoteAddr().String(), workConn.LocalAddr().String(), workConn.RemoteAddr().String())
|
||||
|
||||
// check if we need to send proxy protocol info
|
||||
if baseInfo.ProxyProtocolVersion != "" {
|
||||
if m.SrcAddr != "" && m.SrcPort != 0 {
|
||||
if m.DstAddr == "" {
|
||||
m.DstAddr = "127.0.0.1"
|
||||
}
|
||||
h := &pp.Header{
|
||||
Command: pp.PROXY,
|
||||
SourceAddress: net.ParseIP(m.SrcAddr),
|
||||
SourcePort: m.SrcPort,
|
||||
DestinationAddress: net.ParseIP(m.DstAddr),
|
||||
DestinationPort: m.DstPort,
|
||||
}
|
||||
|
||||
if h.SourceAddress.To16() == nil {
|
||||
h.TransportProtocol = pp.TCPv4
|
||||
} else {
|
||||
h.TransportProtocol = pp.TCPv6
|
||||
}
|
||||
|
||||
if baseInfo.ProxyProtocolVersion == "v1" {
|
||||
h.Version = 1
|
||||
} else if baseInfo.ProxyProtocolVersion == "v2" {
|
||||
h.Version = 2
|
||||
}
|
||||
|
||||
h.WriteTo(localConn)
|
||||
}
|
||||
if len(extraInfo) > 0 {
|
||||
localConn.Write(extraInfo)
|
||||
}
|
||||
|
||||
frpIo.Join(localConn, remote)
|
||||
|
||||
@@ -20,17 +20,24 @@ type ProxyManager struct {
|
||||
closed bool
|
||||
mu sync.RWMutex
|
||||
|
||||
clientCfg config.ClientCommonConf
|
||||
|
||||
// The UDP port that the server is listening on
|
||||
serverUDPPort int
|
||||
|
||||
logPrefix string
|
||||
log.Logger
|
||||
}
|
||||
|
||||
func NewProxyManager(msgSendCh chan (msg.Message), logPrefix string) *ProxyManager {
|
||||
func NewProxyManager(msgSendCh chan (msg.Message), logPrefix string, clientCfg config.ClientCommonConf, serverUDPPort int) *ProxyManager {
|
||||
return &ProxyManager{
|
||||
proxies: make(map[string]*ProxyWrapper),
|
||||
sendCh: msgSendCh,
|
||||
closed: false,
|
||||
logPrefix: logPrefix,
|
||||
Logger: log.NewPrefixLogger(logPrefix),
|
||||
proxies: make(map[string]*ProxyWrapper),
|
||||
sendCh: msgSendCh,
|
||||
closed: false,
|
||||
clientCfg: clientCfg,
|
||||
serverUDPPort: serverUDPPort,
|
||||
logPrefix: logPrefix,
|
||||
Logger: log.NewPrefixLogger(logPrefix),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -126,7 +133,7 @@ func (pm *ProxyManager) Reload(pxyCfgs map[string]config.ProxyConf) {
|
||||
addPxyNames := make([]string, 0)
|
||||
for name, cfg := range pxyCfgs {
|
||||
if _, ok := pm.proxies[name]; !ok {
|
||||
pxy := NewProxyWrapper(cfg, pm.HandleEvent, pm.logPrefix)
|
||||
pxy := NewProxyWrapper(cfg, pm.clientCfg, pm.HandleEvent, pm.logPrefix, pm.serverUDPPort)
|
||||
pm.proxies[name] = pxy
|
||||
addPxyNames = append(addPxyNames, name)
|
||||
|
||||
|
||||
@@ -65,7 +65,7 @@ type ProxyWrapper struct {
|
||||
log.Logger
|
||||
}
|
||||
|
||||
func NewProxyWrapper(cfg config.ProxyConf, eventHandler event.EventHandler, logPrefix string) *ProxyWrapper {
|
||||
func NewProxyWrapper(cfg config.ProxyConf, clientCfg config.ClientCommonConf, eventHandler event.EventHandler, logPrefix string, serverUDPPort int) *ProxyWrapper {
|
||||
baseInfo := cfg.GetBaseInfo()
|
||||
pw := &ProxyWrapper{
|
||||
ProxyStatus: ProxyStatus{
|
||||
@@ -90,7 +90,7 @@ func NewProxyWrapper(cfg config.ProxyConf, eventHandler event.EventHandler, logP
|
||||
pw.Trace("enable health check monitor")
|
||||
}
|
||||
|
||||
pw.pxy = NewProxy(pw.Cfg)
|
||||
pw.pxy = NewProxy(pw.Cfg, clientCfg, serverUDPPort)
|
||||
return pw
|
||||
}
|
||||
|
||||
|
||||
@@ -24,7 +24,6 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/fatedier/frp/assets"
|
||||
"github.com/fatedier/frp/g"
|
||||
"github.com/fatedier/frp/models/config"
|
||||
"github.com/fatedier/frp/models/msg"
|
||||
"github.com/fatedier/frp/utils/log"
|
||||
@@ -35,6 +34,7 @@ import (
|
||||
fmux "github.com/hashicorp/yamux"
|
||||
)
|
||||
|
||||
// Service is a client service.
|
||||
type Service struct {
|
||||
// uniq id got from frps, attach it in loginMsg
|
||||
runId string
|
||||
@@ -43,23 +43,27 @@ type Service struct {
|
||||
ctl *Control
|
||||
ctlMu sync.RWMutex
|
||||
|
||||
cfg config.ClientCommonConf
|
||||
pxyCfgs map[string]config.ProxyConf
|
||||
visitorCfgs map[string]config.VisitorConf
|
||||
cfgMu sync.RWMutex
|
||||
|
||||
// The configuration file used to initialize this client, or an empty
|
||||
// string if no configuration file was used.
|
||||
cfgFile string
|
||||
|
||||
// This is configured by the login response from frps
|
||||
serverUDPPort int
|
||||
|
||||
exit uint32 // 0 means not exit
|
||||
closedCh chan int
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// NewService creates a new client service with the given configuration.
|
||||
func NewService(cfg config.ClientCommonConf, pxyCfgs map[string]config.ProxyConf, visitorCfgs map[string]config.VisitorConf, cfgFile string) (svr *Service, err error) {
|
||||
svr = &Service{
|
||||
cfg: cfg,
|
||||
cfgFile: cfgFile,
|
||||
pxyCfgs: pxyCfgs,
|
||||
visitorCfgs: visitorCfgs,
|
||||
exit: 0,
|
||||
@@ -83,14 +87,14 @@ func (svr *Service) Run() error {
|
||||
|
||||
// if login_fail_exit is true, just exit this program
|
||||
// otherwise sleep a while and try again to connect to server
|
||||
if g.GlbClientCfg.LoginFailExit {
|
||||
if svr.cfg.LoginFailExit {
|
||||
return err
|
||||
} else {
|
||||
time.Sleep(10 * time.Second)
|
||||
}
|
||||
} else {
|
||||
// login success
|
||||
ctl := NewControl(svr.runId, conn, session, svr.pxyCfgs, svr.visitorCfgs)
|
||||
ctl := NewControl(svr.runId, conn, session, svr.cfg, svr.pxyCfgs, svr.visitorCfgs, svr.serverUDPPort)
|
||||
ctl.Run()
|
||||
svr.ctlMu.Lock()
|
||||
svr.ctl = ctl
|
||||
@@ -101,12 +105,18 @@ func (svr *Service) Run() error {
|
||||
|
||||
go svr.keepControllerWorking()
|
||||
|
||||
if g.GlbClientCfg.AdminPort != 0 {
|
||||
err := svr.RunAdminServer(g.GlbClientCfg.AdminAddr, g.GlbClientCfg.AdminPort)
|
||||
if svr.cfg.AdminPort != 0 {
|
||||
// Init admin server assets
|
||||
err := assets.Load(svr.cfg.AssetsDir)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Load assets error: %v", err)
|
||||
}
|
||||
|
||||
err = svr.RunAdminServer(svr.cfg.AdminAddr, svr.cfg.AdminPort)
|
||||
if err != nil {
|
||||
log.Warn("run admin server error: %v", err)
|
||||
}
|
||||
log.Info("admin server listen on %s:%d", g.GlbClientCfg.AdminAddr, g.GlbClientCfg.AdminPort)
|
||||
log.Info("admin server listen on %s:%d", svr.cfg.AdminAddr, svr.cfg.AdminPort)
|
||||
}
|
||||
|
||||
<-svr.closedCh
|
||||
@@ -138,7 +148,7 @@ func (svr *Service) keepControllerWorking() {
|
||||
// reconnect success, init delayTime
|
||||
delayTime = time.Second
|
||||
|
||||
ctl := NewControl(svr.runId, conn, session, svr.pxyCfgs, svr.visitorCfgs)
|
||||
ctl := NewControl(svr.runId, conn, session, svr.cfg, svr.pxyCfgs, svr.visitorCfgs, svr.serverUDPPort)
|
||||
ctl.Run()
|
||||
svr.ctlMu.Lock()
|
||||
svr.ctl = ctl
|
||||
@@ -153,13 +163,13 @@ func (svr *Service) keepControllerWorking() {
|
||||
// session: if it's not nil, using tcp mux
|
||||
func (svr *Service) login() (conn frpNet.Conn, session *fmux.Session, err error) {
|
||||
var tlsConfig *tls.Config
|
||||
if g.GlbClientCfg.TLSEnable {
|
||||
if svr.cfg.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)
|
||||
conn, err = frpNet.ConnectServerByProxyWithTLS(svr.cfg.HttpProxy, svr.cfg.Protocol,
|
||||
fmt.Sprintf("%s:%d", svr.cfg.ServerAddr, svr.cfg.ServerPort), tlsConfig)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -167,10 +177,13 @@ func (svr *Service) login() (conn frpNet.Conn, session *fmux.Session, err error)
|
||||
defer func() {
|
||||
if err != nil {
|
||||
conn.Close()
|
||||
if session != nil {
|
||||
session.Close()
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
if g.GlbClientCfg.TcpMux {
|
||||
if svr.cfg.TcpMux {
|
||||
fmuxCfg := fmux.DefaultConfig()
|
||||
fmuxCfg.KeepAliveInterval = 20 * time.Second
|
||||
fmuxCfg.LogOutput = ioutil.Discard
|
||||
@@ -191,10 +204,10 @@ func (svr *Service) login() (conn frpNet.Conn, session *fmux.Session, err error)
|
||||
loginMsg := &msg.Login{
|
||||
Arch: runtime.GOARCH,
|
||||
Os: runtime.GOOS,
|
||||
PoolCount: g.GlbClientCfg.PoolCount,
|
||||
User: g.GlbClientCfg.User,
|
||||
PoolCount: svr.cfg.PoolCount,
|
||||
User: svr.cfg.User,
|
||||
Version: version.Full(),
|
||||
PrivilegeKey: util.GetAuthKey(g.GlbClientCfg.Token, now),
|
||||
PrivilegeKey: util.GetAuthKey(svr.cfg.Token, now),
|
||||
Timestamp: now,
|
||||
RunId: svr.runId,
|
||||
}
|
||||
@@ -217,7 +230,7 @@ func (svr *Service) login() (conn frpNet.Conn, session *fmux.Session, err error)
|
||||
}
|
||||
|
||||
svr.runId = loginRespMsg.RunId
|
||||
g.GlbClientCfg.ServerUdpPort = loginRespMsg.ServerUdpPort
|
||||
svr.serverUDPPort = loginRespMsg.ServerUdpPort
|
||||
log.Info("login to server success, get run id [%s], server udp port [%d]", loginRespMsg.RunId, loginRespMsg.ServerUdpPort)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -23,7 +23,6 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/fatedier/frp/g"
|
||||
"github.com/fatedier/frp/models/config"
|
||||
"github.com/fatedier/frp/models/msg"
|
||||
"github.com/fatedier/frp/utils/log"
|
||||
@@ -193,13 +192,13 @@ func (sv *XtcpVisitor) handleConn(userConn frpNet.Conn) {
|
||||
defer userConn.Close()
|
||||
|
||||
sv.Debug("get a new xtcp user connection")
|
||||
if g.GlbClientCfg.ServerUdpPort == 0 {
|
||||
if sv.ctl.serverUDPPort == 0 {
|
||||
sv.Error("xtcp is not supported by server")
|
||||
return
|
||||
}
|
||||
|
||||
raddr, err := net.ResolveUDPAddr("udp",
|
||||
fmt.Sprintf("%s:%d", g.GlbClientCfg.ServerAddr, g.GlbClientCfg.ServerUdpPort))
|
||||
fmt.Sprintf("%s:%d", sv.ctl.clientCfg.ServerAddr, sv.ctl.serverUDPPort))
|
||||
if err != nil {
|
||||
sv.Error("resolve server UDP addr error")
|
||||
return
|
||||
|
||||
@@ -15,6 +15,9 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"time"
|
||||
|
||||
_ "github.com/fatedier/frp/assets/frpc/statik"
|
||||
"github.com/fatedier/frp/cmd/frpc/sub"
|
||||
|
||||
@@ -23,6 +26,7 @@ import (
|
||||
|
||||
func main() {
|
||||
crypto.DefaultSalt = "frp"
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
|
||||
sub.Execute()
|
||||
}
|
||||
|
||||
@@ -33,6 +33,7 @@ func init() {
|
||||
httpCmd.PersistentFlags().StringVarP(&logLevel, "log_level", "", "info", "log level")
|
||||
httpCmd.PersistentFlags().StringVarP(&logFile, "log_file", "", "console", "console or file path")
|
||||
httpCmd.PersistentFlags().IntVarP(&logMaxDays, "log_max_days", "", 3, "log file reversed days")
|
||||
httpCmd.PersistentFlags().BoolVarP(&disableLogColor, "disable_log_color", "", false, "disable log color in console")
|
||||
|
||||
httpCmd.PersistentFlags().StringVarP(&proxyName, "proxy_name", "n", "", "proxy name")
|
||||
httpCmd.PersistentFlags().StringVarP(&localIp, "local_ip", "i", "127.0.0.1", "local ip")
|
||||
@@ -53,7 +54,7 @@ var httpCmd = &cobra.Command{
|
||||
Use: "http",
|
||||
Short: "Run frpc with a single http proxy",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
err := parseClientCommonCfg(CfgFileTypeCmd, "")
|
||||
clientCfg, err := parseClientCommonCfg(CfgFileTypeCmd, "")
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
@@ -86,7 +87,7 @@ var httpCmd = &cobra.Command{
|
||||
proxyConfs := map[string]config.ProxyConf{
|
||||
cfg.ProxyName: cfg,
|
||||
}
|
||||
err = startService(proxyConfs, nil)
|
||||
err = startService(clientCfg, proxyConfs, nil, "")
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
|
||||
@@ -33,6 +33,7 @@ func init() {
|
||||
httpsCmd.PersistentFlags().StringVarP(&logLevel, "log_level", "", "info", "log level")
|
||||
httpsCmd.PersistentFlags().StringVarP(&logFile, "log_file", "", "console", "console or file path")
|
||||
httpsCmd.PersistentFlags().IntVarP(&logMaxDays, "log_max_days", "", 3, "log file reversed days")
|
||||
httpsCmd.PersistentFlags().BoolVarP(&disableLogColor, "disable_log_color", "", false, "disable log color in console")
|
||||
|
||||
httpsCmd.PersistentFlags().StringVarP(&proxyName, "proxy_name", "n", "", "proxy name")
|
||||
httpsCmd.PersistentFlags().StringVarP(&localIp, "local_ip", "i", "127.0.0.1", "local ip")
|
||||
@@ -49,7 +50,7 @@ var httpsCmd = &cobra.Command{
|
||||
Use: "https",
|
||||
Short: "Run frpc with a single https proxy",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
err := parseClientCommonCfg(CfgFileTypeCmd, "")
|
||||
clientCfg, err := parseClientCommonCfg(CfgFileTypeCmd, "")
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
@@ -78,7 +79,7 @@ var httpsCmd = &cobra.Command{
|
||||
proxyConfs := map[string]config.ProxyConf{
|
||||
cfg.ProxyName: cfg,
|
||||
}
|
||||
err = startService(proxyConfs, nil)
|
||||
err = startService(clientCfg, proxyConfs, nil, "")
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
|
||||
@@ -24,7 +24,6 @@ import (
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/fatedier/frp/g"
|
||||
"github.com/fatedier/frp/models/config"
|
||||
)
|
||||
|
||||
@@ -42,13 +41,13 @@ var reloadCmd = &cobra.Command{
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
err = parseClientCommonCfg(CfgFileTypeIni, iniContent)
|
||||
clientCfg, err := parseClientCommonCfg(CfgFileTypeIni, iniContent)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
err = reload()
|
||||
err = reload(clientCfg)
|
||||
if err != nil {
|
||||
fmt.Printf("frpc reload error: %v\n", err)
|
||||
os.Exit(1)
|
||||
@@ -58,35 +57,34 @@ var reloadCmd = &cobra.Command{
|
||||
},
|
||||
}
|
||||
|
||||
func reload() error {
|
||||
if g.GlbClientCfg.AdminPort == 0 {
|
||||
func reload(clientCfg config.ClientCommonConf) error {
|
||||
if clientCfg.AdminPort == 0 {
|
||||
return fmt.Errorf("admin_port shoud be set if you want to use reload feature")
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("GET", "http://"+
|
||||
g.GlbClientCfg.AdminAddr+":"+fmt.Sprintf("%d", g.GlbClientCfg.AdminPort)+"/api/reload", nil)
|
||||
clientCfg.AdminAddr+":"+fmt.Sprintf("%d", clientCfg.AdminPort)+"/api/reload", nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
authStr := "Basic " + base64.StdEncoding.EncodeToString([]byte(g.GlbClientCfg.AdminUser+":"+
|
||||
g.GlbClientCfg.AdminPwd))
|
||||
authStr := "Basic " + base64.StdEncoding.EncodeToString([]byte(clientCfg.AdminUser+":"+
|
||||
clientCfg.AdminPwd))
|
||||
|
||||
req.Header.Add("Authorization", authStr)
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
} else {
|
||||
if resp.StatusCode == 200 {
|
||||
return nil
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return fmt.Errorf("code [%d], %s", resp.StatusCode, strings.TrimSpace(string(body)))
|
||||
}
|
||||
return nil
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode == 200 {
|
||||
return nil
|
||||
}
|
||||
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return fmt.Errorf("code [%d], %s", resp.StatusCode, strings.TrimSpace(string(body)))
|
||||
}
|
||||
|
||||
@@ -28,7 +28,6 @@ import (
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/fatedier/frp/client"
|
||||
"github.com/fatedier/frp/g"
|
||||
"github.com/fatedier/frp/models/config"
|
||||
"github.com/fatedier/frp/utils/log"
|
||||
"github.com/fatedier/frp/utils/version"
|
||||
@@ -43,13 +42,14 @@ var (
|
||||
cfgFile string
|
||||
showVersion bool
|
||||
|
||||
serverAddr string
|
||||
user string
|
||||
protocol string
|
||||
token string
|
||||
logLevel string
|
||||
logFile string
|
||||
logMaxDays int
|
||||
serverAddr string
|
||||
user string
|
||||
protocol string
|
||||
token string
|
||||
logLevel string
|
||||
logFile string
|
||||
logMaxDays int
|
||||
disableLogColor bool
|
||||
|
||||
proxyName string
|
||||
localIp string
|
||||
@@ -113,59 +113,62 @@ func handleSignal(svr *client.Service) {
|
||||
close(kcpDoneCh)
|
||||
}
|
||||
|
||||
func parseClientCommonCfg(fileType int, content string) (err error) {
|
||||
func parseClientCommonCfg(fileType int, content string) (cfg config.ClientCommonConf, err error) {
|
||||
if fileType == CfgFileTypeIni {
|
||||
err = parseClientCommonCfgFromIni(content)
|
||||
cfg, err = parseClientCommonCfgFromIni(content)
|
||||
} else if fileType == CfgFileTypeCmd {
|
||||
err = parseClientCommonCfgFromCmd()
|
||||
cfg, err = parseClientCommonCfgFromCmd()
|
||||
}
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = g.GlbClientCfg.ClientCommonConf.Check()
|
||||
err = cfg.Check()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func parseClientCommonCfgFromIni(content string) (err error) {
|
||||
cfg, err := config.UnmarshalClientConfFromIni(&g.GlbClientCfg.ClientCommonConf, content)
|
||||
func parseClientCommonCfgFromIni(content string) (config.ClientCommonConf, error) {
|
||||
cfg, err := config.UnmarshalClientConfFromIni(content)
|
||||
if err != nil {
|
||||
return err
|
||||
return config.ClientCommonConf{}, err
|
||||
}
|
||||
g.GlbClientCfg.ClientCommonConf = *cfg
|
||||
return
|
||||
return cfg, err
|
||||
}
|
||||
|
||||
func parseClientCommonCfgFromCmd() (err error) {
|
||||
func parseClientCommonCfgFromCmd() (cfg config.ClientCommonConf, err error) {
|
||||
cfg = config.GetDefaultClientConf()
|
||||
|
||||
strs := strings.Split(serverAddr, ":")
|
||||
if len(strs) < 2 {
|
||||
err = fmt.Errorf("invalid server_addr")
|
||||
return
|
||||
}
|
||||
if strs[0] != "" {
|
||||
g.GlbClientCfg.ServerAddr = strs[0]
|
||||
cfg.ServerAddr = strs[0]
|
||||
}
|
||||
g.GlbClientCfg.ServerPort, err = strconv.Atoi(strs[1])
|
||||
cfg.ServerPort, err = strconv.Atoi(strs[1])
|
||||
if err != nil {
|
||||
err = fmt.Errorf("invalid server_addr")
|
||||
return
|
||||
}
|
||||
|
||||
g.GlbClientCfg.User = user
|
||||
g.GlbClientCfg.Protocol = protocol
|
||||
g.GlbClientCfg.Token = token
|
||||
g.GlbClientCfg.LogLevel = logLevel
|
||||
g.GlbClientCfg.LogFile = logFile
|
||||
g.GlbClientCfg.LogMaxDays = int64(logMaxDays)
|
||||
cfg.User = user
|
||||
cfg.Protocol = protocol
|
||||
cfg.Token = token
|
||||
cfg.LogLevel = logLevel
|
||||
cfg.LogFile = logFile
|
||||
cfg.LogMaxDays = int64(logMaxDays)
|
||||
if logFile == "console" {
|
||||
g.GlbClientCfg.LogWay = "console"
|
||||
cfg.LogWay = "console"
|
||||
} else {
|
||||
g.GlbClientCfg.LogWay = "file"
|
||||
cfg.LogWay = "file"
|
||||
}
|
||||
return nil
|
||||
cfg.DisableLogColor = disableLogColor
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func runClient(cfgFilePath string) (err error) {
|
||||
@@ -174,26 +177,27 @@ func runClient(cfgFilePath string) (err error) {
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
g.GlbClientCfg.CfgFile = cfgFilePath
|
||||
|
||||
err = parseClientCommonCfg(CfgFileTypeIni, content)
|
||||
cfg, err := parseClientCommonCfg(CfgFileTypeIni, content)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
pxyCfgs, visitorCfgs, err := config.LoadAllConfFromIni(g.GlbClientCfg.User, content, g.GlbClientCfg.Start)
|
||||
pxyCfgs, visitorCfgs, err := config.LoadAllConfFromIni(cfg.User, content, cfg.Start)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = startService(pxyCfgs, visitorCfgs)
|
||||
err = startService(cfg, pxyCfgs, visitorCfgs, cfgFilePath)
|
||||
return
|
||||
}
|
||||
|
||||
func startService(pxyCfgs map[string]config.ProxyConf, visitorCfgs map[string]config.VisitorConf) (err error) {
|
||||
log.InitLog(g.GlbClientCfg.LogWay, g.GlbClientCfg.LogFile, g.GlbClientCfg.LogLevel, g.GlbClientCfg.LogMaxDays)
|
||||
if g.GlbClientCfg.DnsServer != "" {
|
||||
s := g.GlbClientCfg.DnsServer
|
||||
func startService(cfg config.ClientCommonConf, pxyCfgs map[string]config.ProxyConf, visitorCfgs map[string]config.VisitorConf, cfgFile string) (err error) {
|
||||
log.InitLog(cfg.LogWay, cfg.LogFile, cfg.LogLevel,
|
||||
cfg.LogMaxDays, cfg.DisableLogColor)
|
||||
|
||||
if cfg.DnsServer != "" {
|
||||
s := cfg.DnsServer
|
||||
if !strings.Contains(s, ":") {
|
||||
s += ":53"
|
||||
}
|
||||
@@ -205,19 +209,19 @@ func startService(pxyCfgs map[string]config.ProxyConf, visitorCfgs map[string]co
|
||||
},
|
||||
}
|
||||
}
|
||||
svr, errRet := client.NewService(pxyCfgs, visitorCfgs)
|
||||
svr, errRet := client.NewService(cfg, pxyCfgs, visitorCfgs, cfgFile)
|
||||
if errRet != nil {
|
||||
err = errRet
|
||||
return
|
||||
}
|
||||
|
||||
// Capture the exit signal if we use kcp.
|
||||
if g.GlbClientCfg.Protocol == "kcp" {
|
||||
if cfg.Protocol == "kcp" {
|
||||
go handleSignal(svr)
|
||||
}
|
||||
|
||||
err = svr.Run()
|
||||
if g.GlbClientCfg.Protocol == "kcp" {
|
||||
if cfg.Protocol == "kcp" {
|
||||
<-kcpDoneCh
|
||||
}
|
||||
return
|
||||
|
||||
@@ -27,7 +27,6 @@ import (
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/fatedier/frp/client"
|
||||
"github.com/fatedier/frp/g"
|
||||
"github.com/fatedier/frp/models/config"
|
||||
)
|
||||
|
||||
@@ -45,13 +44,13 @@ var statusCmd = &cobra.Command{
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
err = parseClientCommonCfg(CfgFileTypeIni, iniContent)
|
||||
clientCfg, err := parseClientCommonCfg(CfgFileTypeIni, iniContent)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
err = status()
|
||||
err = status(clientCfg)
|
||||
if err != nil {
|
||||
fmt.Printf("frpc get status error: %v\n", err)
|
||||
os.Exit(1)
|
||||
@@ -60,94 +59,96 @@ var statusCmd = &cobra.Command{
|
||||
},
|
||||
}
|
||||
|
||||
func status() error {
|
||||
if g.GlbClientCfg.AdminPort == 0 {
|
||||
func status(clientCfg config.ClientCommonConf) error {
|
||||
if clientCfg.AdminPort == 0 {
|
||||
return fmt.Errorf("admin_port shoud be set if you want to get proxy status")
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("GET", "http://"+
|
||||
g.GlbClientCfg.AdminAddr+":"+fmt.Sprintf("%d", g.GlbClientCfg.AdminPort)+"/api/status", nil)
|
||||
clientCfg.AdminAddr+":"+fmt.Sprintf("%d", clientCfg.AdminPort)+"/api/status", nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
authStr := "Basic " + base64.StdEncoding.EncodeToString([]byte(g.GlbClientCfg.AdminUser+":"+
|
||||
g.GlbClientCfg.AdminPwd))
|
||||
authStr := "Basic " + base64.StdEncoding.EncodeToString([]byte(clientCfg.AdminUser+":"+
|
||||
clientCfg.AdminPwd))
|
||||
|
||||
req.Header.Add("Authorization", authStr)
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
} else {
|
||||
if resp.StatusCode != 200 {
|
||||
return fmt.Errorf("admin api status code [%d]", resp.StatusCode)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
res := &client.StatusResp{}
|
||||
err = json.Unmarshal(body, &res)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unmarshal http response error: %s", strings.TrimSpace(string(body)))
|
||||
}
|
||||
|
||||
fmt.Println("Proxy Status...")
|
||||
if len(res.Tcp) > 0 {
|
||||
fmt.Printf("TCP")
|
||||
tbl := table.New("Name", "Status", "LocalAddr", "Plugin", "RemoteAddr", "Error")
|
||||
for _, ps := range res.Tcp {
|
||||
tbl.AddRow(ps.Name, ps.Status, ps.LocalAddr, ps.Plugin, ps.RemoteAddr, ps.Err)
|
||||
}
|
||||
tbl.Print()
|
||||
fmt.Println("")
|
||||
}
|
||||
if len(res.Udp) > 0 {
|
||||
fmt.Printf("UDP")
|
||||
tbl := table.New("Name", "Status", "LocalAddr", "Plugin", "RemoteAddr", "Error")
|
||||
for _, ps := range res.Udp {
|
||||
tbl.AddRow(ps.Name, ps.Status, ps.LocalAddr, ps.Plugin, ps.RemoteAddr, ps.Err)
|
||||
}
|
||||
tbl.Print()
|
||||
fmt.Println("")
|
||||
}
|
||||
if len(res.Http) > 0 {
|
||||
fmt.Printf("HTTP")
|
||||
tbl := table.New("Name", "Status", "LocalAddr", "Plugin", "RemoteAddr", "Error")
|
||||
for _, ps := range res.Http {
|
||||
tbl.AddRow(ps.Name, ps.Status, ps.LocalAddr, ps.Plugin, ps.RemoteAddr, ps.Err)
|
||||
}
|
||||
tbl.Print()
|
||||
fmt.Println("")
|
||||
}
|
||||
if len(res.Https) > 0 {
|
||||
fmt.Printf("HTTPS")
|
||||
tbl := table.New("Name", "Status", "LocalAddr", "Plugin", "RemoteAddr", "Error")
|
||||
for _, ps := range res.Https {
|
||||
tbl.AddRow(ps.Name, ps.Status, ps.LocalAddr, ps.Plugin, ps.RemoteAddr, ps.Err)
|
||||
}
|
||||
tbl.Print()
|
||||
fmt.Println("")
|
||||
}
|
||||
if len(res.Stcp) > 0 {
|
||||
fmt.Printf("STCP")
|
||||
tbl := table.New("Name", "Status", "LocalAddr", "Plugin", "RemoteAddr", "Error")
|
||||
for _, ps := range res.Stcp {
|
||||
tbl.AddRow(ps.Name, ps.Status, ps.LocalAddr, ps.Plugin, ps.RemoteAddr, ps.Err)
|
||||
}
|
||||
tbl.Print()
|
||||
fmt.Println("")
|
||||
}
|
||||
if len(res.Xtcp) > 0 {
|
||||
fmt.Printf("XTCP")
|
||||
tbl := table.New("Name", "Status", "LocalAddr", "Plugin", "RemoteAddr", "Error")
|
||||
for _, ps := range res.Xtcp {
|
||||
tbl.AddRow(ps.Name, ps.Status, ps.LocalAddr, ps.Plugin, ps.RemoteAddr, ps.Err)
|
||||
}
|
||||
tbl.Print()
|
||||
fmt.Println("")
|
||||
}
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != 200 {
|
||||
return fmt.Errorf("admin api status code [%d]", resp.StatusCode)
|
||||
}
|
||||
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
res := &client.StatusResp{}
|
||||
err = json.Unmarshal(body, &res)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unmarshal http response error: %s", strings.TrimSpace(string(body)))
|
||||
}
|
||||
|
||||
fmt.Println("Proxy Status...")
|
||||
if len(res.Tcp) > 0 {
|
||||
fmt.Printf("TCP")
|
||||
tbl := table.New("Name", "Status", "LocalAddr", "Plugin", "RemoteAddr", "Error")
|
||||
for _, ps := range res.Tcp {
|
||||
tbl.AddRow(ps.Name, ps.Status, ps.LocalAddr, ps.Plugin, ps.RemoteAddr, ps.Err)
|
||||
}
|
||||
tbl.Print()
|
||||
fmt.Println("")
|
||||
}
|
||||
if len(res.Udp) > 0 {
|
||||
fmt.Printf("UDP")
|
||||
tbl := table.New("Name", "Status", "LocalAddr", "Plugin", "RemoteAddr", "Error")
|
||||
for _, ps := range res.Udp {
|
||||
tbl.AddRow(ps.Name, ps.Status, ps.LocalAddr, ps.Plugin, ps.RemoteAddr, ps.Err)
|
||||
}
|
||||
tbl.Print()
|
||||
fmt.Println("")
|
||||
}
|
||||
if len(res.Http) > 0 {
|
||||
fmt.Printf("HTTP")
|
||||
tbl := table.New("Name", "Status", "LocalAddr", "Plugin", "RemoteAddr", "Error")
|
||||
for _, ps := range res.Http {
|
||||
tbl.AddRow(ps.Name, ps.Status, ps.LocalAddr, ps.Plugin, ps.RemoteAddr, ps.Err)
|
||||
}
|
||||
tbl.Print()
|
||||
fmt.Println("")
|
||||
}
|
||||
if len(res.Https) > 0 {
|
||||
fmt.Printf("HTTPS")
|
||||
tbl := table.New("Name", "Status", "LocalAddr", "Plugin", "RemoteAddr", "Error")
|
||||
for _, ps := range res.Https {
|
||||
tbl.AddRow(ps.Name, ps.Status, ps.LocalAddr, ps.Plugin, ps.RemoteAddr, ps.Err)
|
||||
}
|
||||
tbl.Print()
|
||||
fmt.Println("")
|
||||
}
|
||||
if len(res.Stcp) > 0 {
|
||||
fmt.Printf("STCP")
|
||||
tbl := table.New("Name", "Status", "LocalAddr", "Plugin", "RemoteAddr", "Error")
|
||||
for _, ps := range res.Stcp {
|
||||
tbl.AddRow(ps.Name, ps.Status, ps.LocalAddr, ps.Plugin, ps.RemoteAddr, ps.Err)
|
||||
}
|
||||
tbl.Print()
|
||||
fmt.Println("")
|
||||
}
|
||||
if len(res.Xtcp) > 0 {
|
||||
fmt.Printf("XTCP")
|
||||
tbl := table.New("Name", "Status", "LocalAddr", "Plugin", "RemoteAddr", "Error")
|
||||
for _, ps := range res.Xtcp {
|
||||
tbl.AddRow(ps.Name, ps.Status, ps.LocalAddr, ps.Plugin, ps.RemoteAddr, ps.Err)
|
||||
}
|
||||
tbl.Print()
|
||||
fmt.Println("")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -32,6 +32,7 @@ func init() {
|
||||
stcpCmd.PersistentFlags().StringVarP(&logLevel, "log_level", "", "info", "log level")
|
||||
stcpCmd.PersistentFlags().StringVarP(&logFile, "log_file", "", "console", "console or file path")
|
||||
stcpCmd.PersistentFlags().IntVarP(&logMaxDays, "log_max_days", "", 3, "log file reversed days")
|
||||
stcpCmd.PersistentFlags().BoolVarP(&disableLogColor, "disable_log_color", "", false, "disable log color in console")
|
||||
|
||||
stcpCmd.PersistentFlags().StringVarP(&proxyName, "proxy_name", "n", "", "proxy name")
|
||||
stcpCmd.PersistentFlags().StringVarP(&role, "role", "", "server", "role")
|
||||
@@ -51,7 +52,7 @@ var stcpCmd = &cobra.Command{
|
||||
Use: "stcp",
|
||||
Short: "Run frpc with a single stcp proxy",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
err := parseClientCommonCfg(CfgFileTypeCmd, "")
|
||||
clientCfg, err := parseClientCommonCfg(CfgFileTypeCmd, "")
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
@@ -103,7 +104,7 @@ var stcpCmd = &cobra.Command{
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
err = startService(proxyConfs, visitorConfs)
|
||||
err = startService(clientCfg, proxyConfs, visitorConfs, "")
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
|
||||
@@ -32,6 +32,7 @@ func init() {
|
||||
tcpCmd.PersistentFlags().StringVarP(&logLevel, "log_level", "", "info", "log level")
|
||||
tcpCmd.PersistentFlags().StringVarP(&logFile, "log_file", "", "console", "console or file path")
|
||||
tcpCmd.PersistentFlags().IntVarP(&logMaxDays, "log_max_days", "", 3, "log file reversed days")
|
||||
tcpCmd.PersistentFlags().BoolVarP(&disableLogColor, "disable_log_color", "", false, "disable log color in console")
|
||||
|
||||
tcpCmd.PersistentFlags().StringVarP(&proxyName, "proxy_name", "n", "", "proxy name")
|
||||
tcpCmd.PersistentFlags().StringVarP(&localIp, "local_ip", "i", "127.0.0.1", "local ip")
|
||||
@@ -47,7 +48,7 @@ var tcpCmd = &cobra.Command{
|
||||
Use: "tcp",
|
||||
Short: "Run frpc with a single tcp proxy",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
err := parseClientCommonCfg(CfgFileTypeCmd, "")
|
||||
clientCfg, err := parseClientCommonCfg(CfgFileTypeCmd, "")
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
@@ -75,7 +76,7 @@ var tcpCmd = &cobra.Command{
|
||||
proxyConfs := map[string]config.ProxyConf{
|
||||
cfg.ProxyName: cfg,
|
||||
}
|
||||
err = startService(proxyConfs, nil)
|
||||
err = startService(clientCfg, proxyConfs, nil, "")
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
|
||||
@@ -32,6 +32,7 @@ func init() {
|
||||
udpCmd.PersistentFlags().StringVarP(&logLevel, "log_level", "", "info", "log level")
|
||||
udpCmd.PersistentFlags().StringVarP(&logFile, "log_file", "", "console", "console or file path")
|
||||
udpCmd.PersistentFlags().IntVarP(&logMaxDays, "log_max_days", "", 3, "log file reversed days")
|
||||
udpCmd.PersistentFlags().BoolVarP(&disableLogColor, "disable_log_color", "", false, "disable log color in console")
|
||||
|
||||
udpCmd.PersistentFlags().StringVarP(&proxyName, "proxy_name", "n", "", "proxy name")
|
||||
udpCmd.PersistentFlags().StringVarP(&localIp, "local_ip", "i", "127.0.0.1", "local ip")
|
||||
@@ -47,7 +48,7 @@ var udpCmd = &cobra.Command{
|
||||
Use: "udp",
|
||||
Short: "Run frpc with a single udp proxy",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
err := parseClientCommonCfg(CfgFileTypeCmd, "")
|
||||
clientCfg, err := parseClientCommonCfg(CfgFileTypeCmd, "")
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
@@ -75,7 +76,7 @@ var udpCmd = &cobra.Command{
|
||||
proxyConfs := map[string]config.ProxyConf{
|
||||
cfg.ProxyName: cfg,
|
||||
}
|
||||
err = startService(proxyConfs, nil)
|
||||
err = startService(clientCfg, proxyConfs, nil, "")
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
|
||||
@@ -32,6 +32,7 @@ func init() {
|
||||
xtcpCmd.PersistentFlags().StringVarP(&logLevel, "log_level", "", "info", "log level")
|
||||
xtcpCmd.PersistentFlags().StringVarP(&logFile, "log_file", "", "console", "console or file path")
|
||||
xtcpCmd.PersistentFlags().IntVarP(&logMaxDays, "log_max_days", "", 3, "log file reversed days")
|
||||
xtcpCmd.PersistentFlags().BoolVarP(&disableLogColor, "disable_log_color", "", false, "disable log color in console")
|
||||
|
||||
xtcpCmd.PersistentFlags().StringVarP(&proxyName, "proxy_name", "n", "", "proxy name")
|
||||
xtcpCmd.PersistentFlags().StringVarP(&role, "role", "", "server", "role")
|
||||
@@ -51,7 +52,7 @@ var xtcpCmd = &cobra.Command{
|
||||
Use: "xtcp",
|
||||
Short: "Run frpc with a single xtcp proxy",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
err := parseClientCommonCfg(CfgFileTypeCmd, "")
|
||||
clientCfg, err := parseClientCommonCfg(CfgFileTypeCmd, "")
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
@@ -103,7 +104,7 @@ var xtcpCmd = &cobra.Command{
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
err = startService(proxyConfs, visitorConfs)
|
||||
err = startService(clientCfg, proxyConfs, visitorConfs, "")
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
|
||||
@@ -15,6 +15,9 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"time"
|
||||
|
||||
"github.com/fatedier/golib/crypto"
|
||||
|
||||
_ "github.com/fatedier/frp/assets/frps/statik"
|
||||
@@ -22,6 +25,7 @@ import (
|
||||
|
||||
func main() {
|
||||
crypto.DefaultSalt = "frp"
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
|
||||
Execute()
|
||||
}
|
||||
|
||||
@@ -20,7 +20,6 @@ import (
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/fatedier/frp/g"
|
||||
"github.com/fatedier/frp/models/config"
|
||||
"github.com/fatedier/frp/server"
|
||||
"github.com/fatedier/frp/utils/log"
|
||||
@@ -53,6 +52,7 @@ var (
|
||||
logFile string
|
||||
logLevel string
|
||||
logMaxDays int64
|
||||
disableLogColor bool
|
||||
token string
|
||||
subDomainHost string
|
||||
tcpMux bool
|
||||
@@ -79,7 +79,9 @@ func init() {
|
||||
rootCmd.PersistentFlags().StringVarP(&dashboardPwd, "dashboard_pwd", "", "admin", "dashboard password")
|
||||
rootCmd.PersistentFlags().StringVarP(&logFile, "log_file", "", "console", "log file")
|
||||
rootCmd.PersistentFlags().StringVarP(&logLevel, "log_level", "", "info", "log level")
|
||||
rootCmd.PersistentFlags().Int64VarP(&logMaxDays, "log_max_days", "", 3, "log_max_days")
|
||||
rootCmd.PersistentFlags().Int64VarP(&logMaxDays, "log_max_days", "", 3, "log max days")
|
||||
rootCmd.PersistentFlags().BoolVarP(&disableLogColor, "disable_log_color", "", false, "disable log color in console")
|
||||
|
||||
rootCmd.PersistentFlags().StringVarP(&token, "token", "t", "", "auth token")
|
||||
rootCmd.PersistentFlags().StringVarP(&subDomainHost, "subdomain_host", "", "", "subdomain host")
|
||||
rootCmd.PersistentFlags().StringVarP(&allowPorts, "allow_ports", "", "", "allow ports")
|
||||
@@ -95,6 +97,7 @@ var rootCmd = &cobra.Command{
|
||||
return nil
|
||||
}
|
||||
|
||||
var cfg config.ServerCommonConf
|
||||
var err error
|
||||
if cfgFile != "" {
|
||||
var content string
|
||||
@@ -102,16 +105,15 @@ var rootCmd = &cobra.Command{
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
g.GlbServerCfg.CfgFile = cfgFile
|
||||
err = parseServerCommonCfg(CfgFileTypeIni, content)
|
||||
cfg, err = parseServerCommonCfg(CfgFileTypeIni, content)
|
||||
} else {
|
||||
err = parseServerCommonCfg(CfgFileTypeCmd, "")
|
||||
cfg, err = parseServerCommonCfg(CfgFileTypeCmd, "")
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = runServer()
|
||||
err = runServer(cfg)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
@@ -126,52 +128,51 @@ func Execute() {
|
||||
}
|
||||
}
|
||||
|
||||
func parseServerCommonCfg(fileType int, content string) (err error) {
|
||||
func parseServerCommonCfg(fileType int, content string) (cfg config.ServerCommonConf, err error) {
|
||||
if fileType == CfgFileTypeIni {
|
||||
err = parseServerCommonCfgFromIni(content)
|
||||
cfg, err = parseServerCommonCfgFromIni(content)
|
||||
} else if fileType == CfgFileTypeCmd {
|
||||
err = parseServerCommonCfgFromCmd()
|
||||
cfg, err = parseServerCommonCfgFromCmd()
|
||||
}
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = g.GlbServerCfg.ServerCommonConf.Check()
|
||||
err = cfg.Check()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
config.InitServerCfg(&g.GlbServerCfg.ServerCommonConf)
|
||||
return
|
||||
}
|
||||
|
||||
func parseServerCommonCfgFromIni(content string) (err error) {
|
||||
cfg, err := config.UnmarshalServerConfFromIni(&g.GlbServerCfg.ServerCommonConf, content)
|
||||
func parseServerCommonCfgFromIni(content string) (config.ServerCommonConf, error) {
|
||||
cfg, err := config.UnmarshalServerConfFromIni(content)
|
||||
if err != nil {
|
||||
return err
|
||||
return config.ServerCommonConf{}, err
|
||||
}
|
||||
g.GlbServerCfg.ServerCommonConf = *cfg
|
||||
return
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
func parseServerCommonCfgFromCmd() (err error) {
|
||||
g.GlbServerCfg.BindAddr = bindAddr
|
||||
g.GlbServerCfg.BindPort = bindPort
|
||||
g.GlbServerCfg.BindUdpPort = bindUdpPort
|
||||
g.GlbServerCfg.KcpBindPort = kcpBindPort
|
||||
g.GlbServerCfg.ProxyBindAddr = proxyBindAddr
|
||||
g.GlbServerCfg.VhostHttpPort = vhostHttpPort
|
||||
g.GlbServerCfg.VhostHttpsPort = vhostHttpsPort
|
||||
g.GlbServerCfg.VhostHttpTimeout = vhostHttpTimeout
|
||||
g.GlbServerCfg.DashboardAddr = dashboardAddr
|
||||
g.GlbServerCfg.DashboardPort = dashboardPort
|
||||
g.GlbServerCfg.DashboardUser = dashboardUser
|
||||
g.GlbServerCfg.DashboardPwd = dashboardPwd
|
||||
g.GlbServerCfg.LogFile = logFile
|
||||
g.GlbServerCfg.LogLevel = logLevel
|
||||
g.GlbServerCfg.LogMaxDays = logMaxDays
|
||||
g.GlbServerCfg.Token = token
|
||||
g.GlbServerCfg.SubDomainHost = subDomainHost
|
||||
func parseServerCommonCfgFromCmd() (cfg config.ServerCommonConf, err error) {
|
||||
cfg = config.GetDefaultServerConf()
|
||||
|
||||
cfg.BindAddr = bindAddr
|
||||
cfg.BindPort = bindPort
|
||||
cfg.BindUdpPort = bindUdpPort
|
||||
cfg.KcpBindPort = kcpBindPort
|
||||
cfg.ProxyBindAddr = proxyBindAddr
|
||||
cfg.VhostHttpPort = vhostHttpPort
|
||||
cfg.VhostHttpsPort = vhostHttpsPort
|
||||
cfg.VhostHttpTimeout = vhostHttpTimeout
|
||||
cfg.DashboardAddr = dashboardAddr
|
||||
cfg.DashboardPort = dashboardPort
|
||||
cfg.DashboardUser = dashboardUser
|
||||
cfg.DashboardPwd = dashboardPwd
|
||||
cfg.LogFile = logFile
|
||||
cfg.LogLevel = logLevel
|
||||
cfg.LogMaxDays = logMaxDays
|
||||
cfg.Token = token
|
||||
cfg.SubDomainHost = subDomainHost
|
||||
if len(allowPorts) > 0 {
|
||||
// e.g. 1000-2000,2001,2002,3000-4000
|
||||
ports, errRet := util.ParseRangeNumbers(allowPorts)
|
||||
@@ -181,28 +182,27 @@ func parseServerCommonCfgFromCmd() (err error) {
|
||||
}
|
||||
|
||||
for _, port := range ports {
|
||||
g.GlbServerCfg.AllowPorts[int(port)] = struct{}{}
|
||||
cfg.AllowPorts[int(port)] = struct{}{}
|
||||
}
|
||||
}
|
||||
g.GlbServerCfg.MaxPortsPerClient = maxPortsPerClient
|
||||
cfg.MaxPortsPerClient = maxPortsPerClient
|
||||
|
||||
if logFile == "console" {
|
||||
g.GlbServerCfg.LogWay = "console"
|
||||
cfg.LogWay = "console"
|
||||
} else {
|
||||
g.GlbServerCfg.LogWay = "file"
|
||||
cfg.LogWay = "file"
|
||||
}
|
||||
cfg.DisableLogColor = disableLogColor
|
||||
return
|
||||
}
|
||||
|
||||
func runServer() (err error) {
|
||||
log.InitLog(g.GlbServerCfg.LogWay, g.GlbServerCfg.LogFile, g.GlbServerCfg.LogLevel,
|
||||
g.GlbServerCfg.LogMaxDays)
|
||||
svr, err := server.NewService()
|
||||
func runServer(cfg config.ServerCommonConf) (err error) {
|
||||
log.InitLog(cfg.LogWay, cfg.LogFile, cfg.LogLevel, cfg.LogMaxDays, cfg.DisableLogColor)
|
||||
svr, err := server.NewService(cfg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Info("Start frps success")
|
||||
server.ServerService = svr
|
||||
svr.Run()
|
||||
return
|
||||
}
|
||||
|
||||
@@ -18,6 +18,9 @@ log_level = info
|
||||
|
||||
log_max_days = 3
|
||||
|
||||
# disable log colors when log_file is console, default is false
|
||||
disable_log_color = false
|
||||
|
||||
# for authentication
|
||||
token = 12345678
|
||||
|
||||
@@ -26,6 +29,8 @@ admin_addr = 127.0.0.1
|
||||
admin_port = 7400
|
||||
admin_user = admin
|
||||
admin_pwd = admin
|
||||
# Admin assets directory. By default, these assets are bundled with frpc.
|
||||
# assets_dir = ./static
|
||||
|
||||
# connections will be established in advance, default value is zero
|
||||
pool_count = 5
|
||||
@@ -50,7 +55,7 @@ tls_enable = true
|
||||
# specify a dns server, so frpc will use this instead of default one
|
||||
# dns_server = 8.8.8.8
|
||||
|
||||
# proxy names you want to start divided by ','
|
||||
# proxy names you want to start seperated by ','
|
||||
# default is empty, means all proxies
|
||||
# start = ssh,dns
|
||||
|
||||
@@ -198,6 +203,7 @@ plugin_local_addr = 127.0.0.1:80
|
||||
plugin_crt_path = ./server.crt
|
||||
plugin_key_path = ./server.key
|
||||
plugin_host_header_rewrite = 127.0.0.1
|
||||
plugin_header_X-From-Where = frp
|
||||
|
||||
[secret_tcp]
|
||||
# If the type is secret tcp, remote_port is useless
|
||||
|
||||
@@ -43,6 +43,9 @@ log_level = info
|
||||
|
||||
log_max_days = 3
|
||||
|
||||
# disable log colors when log_file is console, default is false
|
||||
disable_log_color = false
|
||||
|
||||
# auth token
|
||||
token = 12345678
|
||||
|
||||
@@ -65,3 +68,6 @@ subdomain_host = frps.com
|
||||
|
||||
# if tcp stream multiplexing is used, default is true
|
||||
tcp_mux = true
|
||||
|
||||
# custom 404 page for HTTP requests
|
||||
# custom_404_page = /path/to/404.html
|
||||
|
||||
32
g/g.go
32
g/g.go
@@ -1,32 +0,0 @@
|
||||
package g
|
||||
|
||||
import (
|
||||
"github.com/fatedier/frp/models/config"
|
||||
)
|
||||
|
||||
var (
|
||||
GlbClientCfg *ClientCfg
|
||||
GlbServerCfg *ServerCfg
|
||||
)
|
||||
|
||||
func init() {
|
||||
GlbClientCfg = &ClientCfg{
|
||||
ClientCommonConf: *config.GetDefaultClientConf(),
|
||||
}
|
||||
GlbServerCfg = &ServerCfg{
|
||||
ServerCommonConf: *config.GetDefaultServerConf(),
|
||||
}
|
||||
}
|
||||
|
||||
type ClientCfg struct {
|
||||
config.ClientCommonConf
|
||||
|
||||
CfgFile string
|
||||
ServerUdpPort int // this is configured by login response from frps
|
||||
}
|
||||
|
||||
type ServerCfg struct {
|
||||
config.ServerCommonConf
|
||||
|
||||
CfgFile string
|
||||
}
|
||||
16
go.mod
16
go.mod
@@ -4,14 +4,12 @@ go 1.12
|
||||
|
||||
require (
|
||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5
|
||||
github.com/davecgh/go-spew v1.1.0 // indirect
|
||||
github.com/fatedier/beego v0.0.0-20171024143340-6c6a4f5bd5eb
|
||||
github.com/fatedier/golib v0.0.0-20181107124048-ff8cd814b049
|
||||
github.com/fatedier/kcp-go v2.0.4-0.20190317085623-2063a803e6fe+incompatible
|
||||
github.com/fatedier/kcp-go v2.0.4-0.20190803094908-fe8645b0a904+incompatible
|
||||
github.com/golang/snappy v0.0.0-20170215233205-553a64147049 // indirect
|
||||
github.com/gorilla/context v1.1.1 // indirect
|
||||
github.com/gorilla/mux v1.6.2
|
||||
github.com/gorilla/websocket v1.2.0
|
||||
github.com/gorilla/mux v1.7.3
|
||||
github.com/gorilla/websocket v1.4.0
|
||||
github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d
|
||||
github.com/inconshreveable/mousetrap v1.0.0 // indirect
|
||||
github.com/klauspost/cpuid v1.2.0 // indirect
|
||||
@@ -19,16 +17,16 @@ require (
|
||||
github.com/mattn/go-runewidth v0.0.4 // indirect
|
||||
github.com/pires/go-proxyproto v0.0.0-20190111085350-4d51b51e3bfc
|
||||
github.com/pkg/errors v0.8.0 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/rakyll/statik v0.1.1
|
||||
github.com/rodaine/table v1.0.0
|
||||
github.com/spf13/cobra v0.0.3
|
||||
github.com/spf13/pflag v1.0.1 // indirect
|
||||
github.com/stretchr/testify v1.2.1
|
||||
github.com/stretchr/testify v1.3.0
|
||||
github.com/templexxx/cpufeat v0.0.0-20170927014610-3794dfbfb047 // indirect
|
||||
github.com/templexxx/xor v0.0.0-20170926022130-0af8e873c554 // indirect
|
||||
github.com/tjfoc/gmsm v0.0.0-20171124023159-98aa888b79d8 // indirect
|
||||
github.com/vaughan0/go-ini v0.0.0-20130923145212-a98ad7ee00ec
|
||||
golang.org/x/crypto v0.0.0-20180505025534-4ec37c66abab // indirect
|
||||
golang.org/x/net v0.0.0-20180524181706-dfa909b99c79
|
||||
github.com/xtaci/lossyconn v0.0.0-20190602105132-8df528c0c9ae // indirect
|
||||
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80
|
||||
golang.org/x/text v0.3.2 // indirect
|
||||
)
|
||||
|
||||
44
go.sum
44
go.sum
@@ -1,15 +1,19 @@
|
||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
|
||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
|
||||
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/fatedier/beego v0.0.0-20171024143340-6c6a4f5bd5eb h1:wCrNShQidLmvVWn/0PikGmpdP0vtQmnvyRg3ZBEhczw=
|
||||
github.com/fatedier/beego v0.0.0-20171024143340-6c6a4f5bd5eb/go.mod h1:wx3gB6dbIfBRcucp94PI9Bt3I0F2c/MyNEWuhzpWiwk=
|
||||
github.com/fatedier/golib v0.0.0-20181107124048-ff8cd814b049 h1:teH578mf2ii42NHhIp3PhgvjU5bv+NFMq9fSQR8NaG8=
|
||||
github.com/fatedier/golib v0.0.0-20181107124048-ff8cd814b049/go.mod h1:DqIrnl0rp3Zybg9zbJmozTy1n8fYJoX+QoAj9slIkKM=
|
||||
github.com/fatedier/kcp-go v2.0.4-0.20190317085623-2063a803e6fe+incompatible h1:pNNeBKz1jtMDupiwvtEGFTujA3J86xoEXGSkwVeYFsw=
|
||||
github.com/fatedier/kcp-go v2.0.4-0.20190317085623-2063a803e6fe+incompatible/go.mod h1:YpCOaxj7vvMThhIQ9AfTOPW2sfztQR5WDfs7AflSy4s=
|
||||
github.com/fatedier/kcp-go v2.0.4-0.20190803094908-fe8645b0a904+incompatible h1:ssXat9YXFvigNge/IkkZvFMn8yeYKFX+uI6wn2mLJ74=
|
||||
github.com/fatedier/kcp-go v2.0.4-0.20190803094908-fe8645b0a904+incompatible/go.mod h1:YpCOaxj7vvMThhIQ9AfTOPW2sfztQR5WDfs7AflSy4s=
|
||||
github.com/golang/snappy v0.0.0-20170215233205-553a64147049 h1:K9KHZbXKpGydfDN0aZrsoHpLJlZsBrGMFWbgLDGnPZk=
|
||||
github.com/golang/snappy v0.0.0-20170215233205-553a64147049/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8=
|
||||
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/websocket v1.2.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
|
||||
github.com/gorilla/mux v1.7.3 h1:gnP5JzjVOuiZD07fKKToCAOjS0yOpj/qPETTXCCS6hw=
|
||||
github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
||||
github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q=
|
||||
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
|
||||
github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d h1:kJCB4vdITiW1eC1vq2e6IsrXKrZit1bv/TDYFGMp4BQ=
|
||||
github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM=
|
||||
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
|
||||
@@ -22,16 +26,38 @@ github.com/mattn/go-runewidth v0.0.4 h1:2BvfKmzob6Bmd4YsL0zygOqfdFnK7GR4QL06Do4/
|
||||
github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
||||
github.com/pires/go-proxyproto v0.0.0-20190111085350-4d51b51e3bfc h1:lNOt1SMsgHXTdpuGw+RpnJtzUcCb/oRKZP65pBy9pr8=
|
||||
github.com/pires/go-proxyproto v0.0.0-20190111085350-4d51b51e3bfc/go.mod h1:6/gX3+E/IYGa0wMORlSMla999awQFdbaeQCHjSMKIzY=
|
||||
github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw=
|
||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/rakyll/statik v0.1.1 h1:fCLHsIMajHqD5RKigbFXpvX3dN7c80Pm12+NCrI3kvg=
|
||||
github.com/rakyll/statik v0.1.1/go.mod h1:OEi9wJV/fMUAGx1eNjq75DKDsJVuEv1U0oYdX6GX8Zs=
|
||||
github.com/rodaine/table v1.0.0 h1:UaCJG5Axc/cNXVGXqnCrffm1KxP0OfYLe1HuJLf5sFY=
|
||||
github.com/rodaine/table v1.0.0/go.mod h1:YAUzwPOji0DUJNEvggdxyQcUAl4g3hDRcFlyjnnR51I=
|
||||
github.com/spf13/cobra v0.0.3 h1:ZlrZ4XsMRm04Fr5pSFxBgfND2EBVa1nLpiy1stUsX/8=
|
||||
github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
|
||||
github.com/spf13/pflag v1.0.1 h1:aCvUg6QPl3ibpQUxyLkrEkCHtPqYJL4x9AuhqVqFis4=
|
||||
github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||
github.com/stretchr/testify v1.2.1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/templexxx/cpufeat v0.0.0-20170927014610-3794dfbfb047 h1:K+jtWCOuZgCra7eXZ/VWn2FbJmrA/D058mTXhh2rq+8=
|
||||
github.com/templexxx/cpufeat v0.0.0-20170927014610-3794dfbfb047/go.mod h1:wM7WEvslTq+iOEAMDLSzhVuOt5BRZ05WirO+b09GHQU=
|
||||
github.com/templexxx/xor v0.0.0-20170926022130-0af8e873c554 h1:pexgSe+JCFuxG+uoMZLO+ce8KHtdHGhst4cs6rw3gmk=
|
||||
github.com/templexxx/xor v0.0.0-20170926022130-0af8e873c554/go.mod h1:5XA7W9S6mni3h5uvOC75dA3m9CCCaS83lltmc0ukdi4=
|
||||
github.com/tjfoc/gmsm v0.0.0-20171124023159-98aa888b79d8 h1:6CNSDqI1wiE+JqyOy5Qt/yo/DoNI2/QmmOZeiCid2Nw=
|
||||
github.com/tjfoc/gmsm v0.0.0-20171124023159-98aa888b79d8/go.mod h1:XxO4hdhhrzAd+G4CjDqaOkd0hUzmtPR/d3EiBBMn/wc=
|
||||
github.com/vaughan0/go-ini v0.0.0-20130923145212-a98ad7ee00ec h1:DGmKwyZwEB8dI7tbLt/I/gQuP559o/0FrAkHKlQM/Ks=
|
||||
github.com/vaughan0/go-ini v0.0.0-20130923145212-a98ad7ee00ec/go.mod h1:owBmyHYMLkxyrugmfwE/DLJyW8Ro9mkphwuVErQ0iUw=
|
||||
golang.org/x/crypto v0.0.0-20180505025534-4ec37c66abab/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/net v0.0.0-20180524181706-dfa909b99c79/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
github.com/xtaci/lossyconn v0.0.0-20190602105132-8df528c0c9ae h1:J0GxkO96kL4WF+AIT3M4mfUVinOCPgf2uUWYFUzN0sM=
|
||||
github.com/xtaci/lossyconn v0.0.0-20190602105132-8df528c0c9ae/go.mod h1:gXtu8J62kEgmN++bm9BVICuT/e8yiLI2KFobd/TRFsE=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80 h1:Ao/3l156eZf2AW5wK8a7/smtodRU+gha3+BeqJ69lRk=
|
||||
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
|
||||
@@ -23,34 +23,103 @@ import (
|
||||
ini "github.com/vaughan0/go-ini"
|
||||
)
|
||||
|
||||
// client common config
|
||||
// ClientCommonConf contains information for a client service. It is
|
||||
// recommended to use GetDefaultClientConf instead of creating this object
|
||||
// directly, so that all unspecified fields have reasonable default values.
|
||||
type ClientCommonConf struct {
|
||||
ServerAddr string `json:"server_addr"`
|
||||
ServerPort int `json:"server_port"`
|
||||
HttpProxy string `json:"http_proxy"`
|
||||
LogFile string `json:"log_file"`
|
||||
LogWay string `json:"log_way"`
|
||||
LogLevel string `json:"log_level"`
|
||||
LogMaxDays int64 `json:"log_max_days"`
|
||||
Token string `json:"token"`
|
||||
AdminAddr string `json:"admin_addr"`
|
||||
AdminPort int `json:"admin_port"`
|
||||
AdminUser string `json:"admin_user"`
|
||||
AdminPwd string `json:"admin_pwd"`
|
||||
PoolCount int `json:"pool_count"`
|
||||
TcpMux bool `json:"tcp_mux"`
|
||||
User string `json:"user"`
|
||||
DnsServer string `json:"dns_server"`
|
||||
LoginFailExit bool `json:"login_fail_exit"`
|
||||
Start map[string]struct{} `json:"start"`
|
||||
Protocol string `json:"protocol"`
|
||||
TLSEnable bool `json:"tls_enable"`
|
||||
HeartBeatInterval int64 `json:"heartbeat_interval"`
|
||||
HeartBeatTimeout int64 `json:"heartbeat_timeout"`
|
||||
// ServerAddr specifies the address of the server to connect to. By
|
||||
// default, this value is "0.0.0.0".
|
||||
ServerAddr string `json:"server_addr"`
|
||||
// ServerPort specifies the port to connect to the server on. By default,
|
||||
// this value is 7000.
|
||||
ServerPort int `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"`
|
||||
// LogFile specifies a file where logs will be written to. This value will
|
||||
// only be used if LogWay is set appropriately. By default, this value is
|
||||
// "console".
|
||||
LogFile string `json:"log_file"`
|
||||
// LogWay specifies the way logging is managed. Valid values are "console"
|
||||
// or "file". If "console" is used, logs will be printed to stdout. If
|
||||
// "file" is used, logs will be printed to LogFile. By default, this value
|
||||
// is "console".
|
||||
LogWay string `json:"log_way"`
|
||||
// LogLevel specifies the minimum log level. Valid values are "trace",
|
||||
// "debug", "info", "warn", and "error". By default, this value is "info".
|
||||
LogLevel string `json:"log_level"`
|
||||
// LogMaxDays specifies the maximum number of days to store log information
|
||||
// before deletion. This is only used if LogWay == "file". By default, this
|
||||
// value is 0.
|
||||
LogMaxDays int64 `json:"log_max_days"`
|
||||
// DisableLogColor disables log colors when LogWay == "console" when set to
|
||||
// true. By default, this value is false.
|
||||
DisableLogColor bool `json:"disable_log_color"`
|
||||
// Token specifies the authorization token used to create keys to be sent
|
||||
// to the server. The server must have a matching token for authorization
|
||||
// to succeed. By default, this value is "".
|
||||
Token string `json:"token"`
|
||||
// AdminAddr specifies the address that the admin server binds to. By
|
||||
// default, this value is "127.0.0.1".
|
||||
AdminAddr string `json:"admin_addr"`
|
||||
// AdminPort specifies the port for the admin server to listen on. If this
|
||||
// value is 0, the admin server will not be started. By default, this value
|
||||
// is 0.
|
||||
AdminPort int `json:"admin_port"`
|
||||
// AdminUser specifies the username that the admin server will use for
|
||||
// login. By default, this value is "admin".
|
||||
AdminUser string `json:"admin_user"`
|
||||
// AdminPwd specifies the password that the admin server will use for
|
||||
// login. By default, this value is "admin".
|
||||
AdminPwd string `json:"admin_pwd"`
|
||||
// AssetsDir specifies the local directory that the admin server will load
|
||||
// resources from. If this value is "", assets will be loaded from the
|
||||
// bundled executable using statik. By default, this value is "".
|
||||
AssetsDir string `json:"assets_dir"`
|
||||
// PoolCount specifies the number of connections the client will make to
|
||||
// the server in advance. By default, this value is 0.
|
||||
PoolCount int `json:"pool_count"`
|
||||
// TcpMux toggles TCP stream multiplexing. This allows multiple requests
|
||||
// from a client to share a single TCP connection. If this value is true,
|
||||
// the server must have TCP multiplexing enabled as well. By default, this
|
||||
// value is true.
|
||||
TcpMux bool `json:"tcp_mux"`
|
||||
// User specifies a prefix for proxy names to distinguish them from other
|
||||
// clients. If this value is not "", proxy names will automatically be
|
||||
// changed to "{user}.{proxy_name}". By default, this value is "".
|
||||
User string `json:"user"`
|
||||
// DnsServer specifies a DNS server address for FRPC to use. If this value
|
||||
// is "", the default DNS will be used. By default, this value is "".
|
||||
DnsServer string `json:"dns_server"`
|
||||
// LoginFailExit controls whether or not the client should exit after a
|
||||
// failed login attempt. If false, the client will retry until a login
|
||||
// attempt succeeds. By default, this value is true.
|
||||
LoginFailExit bool `json:"login_fail_exit"`
|
||||
// Start specifies a set of enabled proxies by name. If this set is empty,
|
||||
// all supplied proxies are enabled. By default, this value is an empty
|
||||
// set.
|
||||
Start map[string]struct{} `json:"start"`
|
||||
// Protocol specifies the protocol to use when interacting with the server.
|
||||
// Valid values are "tcp", "kcp", and "websocket". By default, this value
|
||||
// is "tcp".
|
||||
Protocol string `json:"protocol"`
|
||||
// TLSEnable specifies whether or not TLS should be used when communicating
|
||||
// with the server.
|
||||
TLSEnable bool `json:"tls_enable"`
|
||||
// HeartBeatInterval specifies at what interval heartbeats are sent to the
|
||||
// server, in seconds. It is not recommended to change this value. By
|
||||
// default, this value is 30.
|
||||
HeartBeatInterval int64 `json:"heartbeat_interval"`
|
||||
// HeartBeatTimeout specifies the maximum allowed heartbeat response delay
|
||||
// before the connection is terminated, in seconds. It is not recommended
|
||||
// to change this value. By default, this value is 90.
|
||||
HeartBeatTimeout int64 `json:"heartbeat_timeout"`
|
||||
}
|
||||
|
||||
func GetDefaultClientConf() *ClientCommonConf {
|
||||
return &ClientCommonConf{
|
||||
// GetDefaultClientConf returns a client configuration with default values.
|
||||
func GetDefaultClientConf() ClientCommonConf {
|
||||
return ClientCommonConf{
|
||||
ServerAddr: "0.0.0.0",
|
||||
ServerPort: 7000,
|
||||
HttpProxy: os.Getenv("http_proxy"),
|
||||
@@ -58,11 +127,13 @@ func GetDefaultClientConf() *ClientCommonConf {
|
||||
LogWay: "console",
|
||||
LogLevel: "info",
|
||||
LogMaxDays: 3,
|
||||
DisableLogColor: false,
|
||||
Token: "",
|
||||
AdminAddr: "127.0.0.1",
|
||||
AdminPort: 0,
|
||||
AdminUser: "",
|
||||
AdminPwd: "",
|
||||
AssetsDir: "",
|
||||
PoolCount: 1,
|
||||
TcpMux: true,
|
||||
User: "",
|
||||
@@ -76,16 +147,12 @@ func GetDefaultClientConf() *ClientCommonConf {
|
||||
}
|
||||
}
|
||||
|
||||
func UnmarshalClientConfFromIni(defaultCfg *ClientCommonConf, content string) (cfg *ClientCommonConf, err error) {
|
||||
cfg = defaultCfg
|
||||
if cfg == nil {
|
||||
cfg = GetDefaultClientConf()
|
||||
}
|
||||
func UnmarshalClientConfFromIni(content string) (cfg ClientCommonConf, err error) {
|
||||
cfg = GetDefaultClientConf()
|
||||
|
||||
conf, err := ini.Load(strings.NewReader(content))
|
||||
if err != nil {
|
||||
err = fmt.Errorf("parse ini conf file error: %v", err)
|
||||
return nil, err
|
||||
return ClientCommonConf{}, fmt.Errorf("parse ini conf file error: %v", err)
|
||||
}
|
||||
|
||||
var (
|
||||
@@ -106,6 +173,10 @@ func UnmarshalClientConfFromIni(defaultCfg *ClientCommonConf, content string) (c
|
||||
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
|
||||
}
|
||||
@@ -154,6 +225,10 @@ func UnmarshalClientConfFromIni(defaultCfg *ClientCommonConf, content string) (c
|
||||
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)
|
||||
|
||||
@@ -58,11 +58,11 @@ type ProxyConf interface {
|
||||
UnmarshalFromIni(prefix string, name string, conf ini.Section) error
|
||||
MarshalToMsg(pMsg *msg.NewProxy)
|
||||
CheckForCli() error
|
||||
CheckForSvr() error
|
||||
CheckForSvr(serverCfg ServerCommonConf) error
|
||||
Compare(conf ProxyConf) bool
|
||||
}
|
||||
|
||||
func NewProxyConfFromMsg(pMsg *msg.NewProxy) (cfg ProxyConf, err error) {
|
||||
func NewProxyConfFromMsg(pMsg *msg.NewProxy, serverCfg ServerCommonConf) (cfg ProxyConf, err error) {
|
||||
if pMsg.ProxyType == "" {
|
||||
pMsg.ProxyType = consts.TcpProxy
|
||||
}
|
||||
@@ -73,7 +73,7 @@ func NewProxyConfFromMsg(pMsg *msg.NewProxy) (cfg ProxyConf, err error) {
|
||||
return
|
||||
}
|
||||
cfg.UnmarshalFromMsg(pMsg)
|
||||
err = cfg.CheckForSvr()
|
||||
err = cfg.CheckForSvr(serverCfg)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -97,17 +97,33 @@ func NewProxyConfFromIni(prefix string, name string, section ini.Section) (cfg P
|
||||
return
|
||||
}
|
||||
|
||||
// BaseProxy info
|
||||
// BaseProxyConf provides configuration info that is common to all proxy types.
|
||||
type BaseProxyConf struct {
|
||||
// ProxyName is the name of this proxy.
|
||||
ProxyName string `json:"proxy_name"`
|
||||
// ProxyType specifies the type of this proxy. Valid values include "tcp",
|
||||
// "udp", "http", "https", "stcp", and "xtcp". By default, this value is
|
||||
// "tcp".
|
||||
ProxyType string `json:"proxy_type"`
|
||||
|
||||
UseEncryption bool `json:"use_encryption"`
|
||||
UseCompression bool `json:"use_compression"`
|
||||
Group string `json:"group"`
|
||||
GroupKey string `json:"group_key"`
|
||||
// UseEncryption controls whether or not communication with the server will
|
||||
// be encrypted. Encryption is done using the tokens supplied in the server
|
||||
// and client configuration. By default, this value is false.
|
||||
UseEncryption bool `json:"use_encryption"`
|
||||
// UseCompression controls whether or not communication with the server
|
||||
// will be compressed. By default, this value is false.
|
||||
UseCompression bool `json:"use_compression"`
|
||||
// Group specifies which group the proxy is a part of. The server will use
|
||||
// this information to load balance proxies in the same group. If the value
|
||||
// is "", this proxy will not be in a group. By default, this value is "".
|
||||
Group string `json:"group"`
|
||||
// GroupKey specifies a group key, which should be the same among proxies
|
||||
// of the same group. By default, this value is "".
|
||||
GroupKey string `json:"group_key"`
|
||||
|
||||
// only used for client
|
||||
// ProxyProtocolVersion specifies which protocol version to use. Valid
|
||||
// values include "v1", "v2", and "". If the value is "", a protocol
|
||||
// version will be automatically selected. By default, this value is "".
|
||||
ProxyProtocolVersion string `json:"proxy_protocol_version"`
|
||||
LocalSvrConf
|
||||
HealthCheckConf
|
||||
@@ -308,21 +324,21 @@ func (cfg *DomainConf) checkForCli() (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func (cfg *DomainConf) checkForSvr() (err error) {
|
||||
func (cfg *DomainConf) checkForSvr(serverCfg ServerCommonConf) (err error) {
|
||||
if err = cfg.check(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
for _, domain := range cfg.CustomDomains {
|
||||
if subDomainHost != "" && len(strings.Split(subDomainHost, ".")) < len(strings.Split(domain, ".")) {
|
||||
if strings.Contains(domain, subDomainHost) {
|
||||
return fmt.Errorf("custom domain [%s] should not belong to subdomain_host [%s]", domain, subDomainHost)
|
||||
if serverCfg.SubDomainHost != "" && len(strings.Split(serverCfg.SubDomainHost, ".")) < len(strings.Split(domain, ".")) {
|
||||
if strings.Contains(domain, serverCfg.SubDomainHost) {
|
||||
return fmt.Errorf("custom domain [%s] should not belong to subdomain_host [%s]", domain, serverCfg.SubDomainHost)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if cfg.SubDomain != "" {
|
||||
if subDomainHost == "" {
|
||||
if serverCfg.SubDomainHost == "" {
|
||||
return fmt.Errorf("subdomain is not supported because this feature is not enabled in remote frps")
|
||||
}
|
||||
if strings.Contains(cfg.SubDomain, ".") || strings.Contains(cfg.SubDomain, "*") {
|
||||
@@ -332,12 +348,20 @@ func (cfg *DomainConf) checkForSvr() (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
// Local service info
|
||||
// LocalSvrConf configures what location the client will proxy to, or what
|
||||
// plugin will be used.
|
||||
type LocalSvrConf struct {
|
||||
LocalIp string `json:"local_ip"`
|
||||
LocalPort int `json:"local_port"`
|
||||
// LocalIp specifies the IP address or host name to proxy to.
|
||||
LocalIp string `json:"local_ip"`
|
||||
// LocalPort specifies the port to proxy to.
|
||||
LocalPort int `json:"local_port"`
|
||||
|
||||
Plugin string `json:"plugin"`
|
||||
// Plugin specifies what plugin should be used for proxying. If this value
|
||||
// is set, the LocalIp and LocalPort values will be ignored. By default,
|
||||
// this value is "".
|
||||
Plugin string `json:"plugin"`
|
||||
// PluginParams specify parameters to be passed to the plugin, if one is
|
||||
// being used. By default, this value is an empty map.
|
||||
PluginParams map[string]string `json:"plugin_params"`
|
||||
}
|
||||
|
||||
@@ -399,15 +423,35 @@ func (cfg *LocalSvrConf) checkForCli() (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
// Health check info
|
||||
// HealthCheckConf configures health checking. This can be useful for load
|
||||
// balancing purposes to detect and remove proxies to failing services.
|
||||
type HealthCheckConf struct {
|
||||
HealthCheckType string `json:"health_check_type"` // tcp | http
|
||||
HealthCheckTimeoutS int `json:"health_check_timeout_s"`
|
||||
HealthCheckMaxFailed int `json:"health_check_max_failed"`
|
||||
HealthCheckIntervalS int `json:"health_check_interval_s"`
|
||||
HealthCheckUrl string `json:"health_check_url"`
|
||||
|
||||
// local_ip + local_port
|
||||
// HealthCheckType specifies what protocol to use for health checking.
|
||||
// Valid values include "tcp", "http", and "". If this value is "", health
|
||||
// checking will not be performed. By default, this value is "".
|
||||
//
|
||||
// If the type is "tcp", a connection will be attempted to the target
|
||||
// server. If a connection cannot be established, the health check fails.
|
||||
//
|
||||
// If the type is "http", a GET request will be made to the endpoint
|
||||
// specified by HealthCheckUrl. If the response is not a 200, the health
|
||||
// check fails.
|
||||
HealthCheckType string `json:"health_check_type"` // tcp | http
|
||||
// HealthCheckTimeoutS specifies the number of seconds to wait for a health
|
||||
// check attempt to connect. If the timeout is reached, this counts as a
|
||||
// health check failure. By default, this value is 3.
|
||||
HealthCheckTimeoutS int `json:"health_check_timeout_s"`
|
||||
// HealthCheckMaxFailed specifies the number of allowed failures before the
|
||||
// proxy is stopped. By default, this value is 1.
|
||||
HealthCheckMaxFailed int `json:"health_check_max_failed"`
|
||||
// HealthCheckIntervalS specifies the time in seconds between health
|
||||
// checks. By default, this value is 10.
|
||||
HealthCheckIntervalS int `json:"health_check_interval_s"`
|
||||
// HealthCheckUrl specifies the address to send health checks to if the
|
||||
// health check type is "http".
|
||||
HealthCheckUrl string `json:"health_check_url"`
|
||||
// HealthCheckAddr specifies the address to connect to if the health check
|
||||
// type is "tcp".
|
||||
HealthCheckAddr string `json:"-"`
|
||||
}
|
||||
|
||||
@@ -504,7 +548,7 @@ func (cfg *TcpProxyConf) CheckForCli() (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func (cfg *TcpProxyConf) CheckForSvr() error { return nil }
|
||||
func (cfg *TcpProxyConf) CheckForSvr(serverCfg ServerCommonConf) error { return nil }
|
||||
|
||||
// UDP
|
||||
type UdpProxyConf struct {
|
||||
@@ -552,7 +596,7 @@ func (cfg *UdpProxyConf) CheckForCli() (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func (cfg *UdpProxyConf) CheckForSvr() error { return nil }
|
||||
func (cfg *UdpProxyConf) CheckForSvr(serverCfg ServerCommonConf) error { return nil }
|
||||
|
||||
// HTTP
|
||||
type HttpProxyConf struct {
|
||||
@@ -657,11 +701,11 @@ func (cfg *HttpProxyConf) CheckForCli() (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func (cfg *HttpProxyConf) CheckForSvr() (err error) {
|
||||
if vhostHttpPort == 0 {
|
||||
func (cfg *HttpProxyConf) CheckForSvr(serverCfg ServerCommonConf) (err error) {
|
||||
if serverCfg.VhostHttpPort == 0 {
|
||||
return fmt.Errorf("type [http] not support when vhost_http_port is not set")
|
||||
}
|
||||
if err = cfg.DomainConf.checkForSvr(); err != nil {
|
||||
if err = cfg.DomainConf.checkForSvr(serverCfg); err != nil {
|
||||
err = fmt.Errorf("proxy [%s] domain conf check error: %v", cfg.ProxyName, err)
|
||||
return
|
||||
}
|
||||
@@ -717,11 +761,11 @@ func (cfg *HttpsProxyConf) CheckForCli() (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func (cfg *HttpsProxyConf) CheckForSvr() (err error) {
|
||||
if vhostHttpsPort == 0 {
|
||||
func (cfg *HttpsProxyConf) CheckForSvr(serverCfg ServerCommonConf) (err error) {
|
||||
if serverCfg.VhostHttpsPort == 0 {
|
||||
return fmt.Errorf("type [https] not support when vhost_https_port is not set")
|
||||
}
|
||||
if err = cfg.DomainConf.checkForSvr(); err != nil {
|
||||
if err = cfg.DomainConf.checkForSvr(serverCfg); err != nil {
|
||||
err = fmt.Errorf("proxy [%s] domain conf check error: %v", cfg.ProxyName, err)
|
||||
return
|
||||
}
|
||||
@@ -790,7 +834,7 @@ func (cfg *StcpProxyConf) CheckForCli() (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func (cfg *StcpProxyConf) CheckForSvr() (err error) {
|
||||
func (cfg *StcpProxyConf) CheckForSvr(serverCfg ServerCommonConf) (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -857,7 +901,7 @@ func (cfg *XtcpProxyConf) CheckForCli() (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func (cfg *XtcpProxyConf) CheckForSvr() (err error) {
|
||||
func (cfg *XtcpProxyConf) CheckForSvr(serverCfg ServerCommonConf) (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -24,61 +24,122 @@ import (
|
||||
"github.com/fatedier/frp/utils/util"
|
||||
)
|
||||
|
||||
var (
|
||||
// server global configure used for generate proxy conf used in frps
|
||||
proxyBindAddr string
|
||||
subDomainHost string
|
||||
vhostHttpPort int
|
||||
vhostHttpsPort int
|
||||
)
|
||||
|
||||
func InitServerCfg(cfg *ServerCommonConf) {
|
||||
proxyBindAddr = cfg.ProxyBindAddr
|
||||
subDomainHost = cfg.SubDomainHost
|
||||
vhostHttpPort = cfg.VhostHttpPort
|
||||
vhostHttpsPort = cfg.VhostHttpsPort
|
||||
}
|
||||
|
||||
// common config
|
||||
// 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 {
|
||||
BindAddr string `json:"bind_addr"`
|
||||
BindPort int `json:"bind_port"`
|
||||
BindUdpPort int `json:"bind_udp_port"`
|
||||
KcpBindPort int `json:"kcp_bind_port"`
|
||||
// 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"`
|
||||
// BindKcpPort specifies the KCP port that the server listens on. If this
|
||||
// value is 0, the server will not listen for KCP connections. By default,
|
||||
// this value is 0.
|
||||
KcpBindPort int `json:"kcp_bind_port"`
|
||||
// ProxyBindAddr specifies the address that the proxy binds to. This value
|
||||
// may be the same as BindAddr. By default, this value is "0.0.0.0".
|
||||
ProxyBindAddr string `json:"proxy_bind_addr"`
|
||||
|
||||
// If VhostHttpPort equals 0, don't listen a public port for http protocol.
|
||||
// VhostHttpPort specifies the port that the server listens for HTTP Vhost
|
||||
// requests. If this value is 0, the server will not listen for HTTP
|
||||
// requests. By default, this value is 0.
|
||||
VhostHttpPort int `json:"vhost_http_port"`
|
||||
|
||||
// if VhostHttpsPort equals 0, don't listen a public port for https protocol
|
||||
// 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"`
|
||||
|
||||
// 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"`
|
||||
|
||||
// if DashboardPort equals 0, dashboard is not available
|
||||
DashboardPort int `json:"dashboard_port"`
|
||||
// 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"`
|
||||
DashboardPwd string `json:"dashboard_pwd"`
|
||||
AssetsDir string `json:"asserts_dir"`
|
||||
LogFile string `json:"log_file"`
|
||||
LogWay string `json:"log_way"` // console or file
|
||||
LogLevel string `json:"log_level"`
|
||||
LogMaxDays int64 `json:"log_max_days"`
|
||||
Token string `json:"token"`
|
||||
// DashboardUser specifies the password that the dashboard will use for
|
||||
// login. By default, this value is "admin".
|
||||
DashboardPwd string `json:"dashboard_pwd"`
|
||||
// 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:"asserts_dir"`
|
||||
// LogFile specifies a file where logs will be written to. This value will
|
||||
// only be used if LogWay is set appropriately. By default, this value is
|
||||
// "console".
|
||||
LogFile string `json:"log_file"`
|
||||
// LogWay specifies the way logging is managed. Valid values are "console"
|
||||
// or "file". If "console" is used, logs will be printed to stdout. If
|
||||
// "file" is used, logs will be printed to LogFile. By default, this value
|
||||
// is "console".
|
||||
LogWay string `json:"log_way"`
|
||||
// LogLevel specifies the minimum log level. Valid values are "trace",
|
||||
// "debug", "info", "warn", and "error". By default, this value is "info".
|
||||
LogLevel string `json:"log_level"`
|
||||
// LogMaxDays specifies the maximum number of days to store log information
|
||||
// before deletion. This is only used if LogWay == "file". By default, this
|
||||
// value is 0.
|
||||
LogMaxDays int64 `json:"log_max_days"`
|
||||
// DisableLogColor disables log colors when LogWay == "console" when set to
|
||||
// true. By default, this value is false.
|
||||
DisableLogColor bool `json:"disable_log_color"`
|
||||
// Token specifies the authorization token used to authenticate keys
|
||||
// received from clients. Clients must have a matching token to be
|
||||
// authorized to use the server. By default, this value is "".
|
||||
Token string `json:"token"`
|
||||
// SubDomainHost specifies the domain that will be attached to sub-domains
|
||||
// requested by the client when using Vhost proxying. For example, if this
|
||||
// value is set to "frps.com" and the client requested the subdomain
|
||||
// "test", the resulting URL would be "test.frps.com". By default, this
|
||||
// value is "".
|
||||
SubDomainHost string `json:"subdomain_host"`
|
||||
TcpMux bool `json:"tcp_mux"`
|
||||
// TcpMux toggles TCP stream multiplexing. This allows multiple requests
|
||||
// from a client to share a single TCP connection. By default, this value
|
||||
// is true.
|
||||
TcpMux bool `json:"tcp_mux"`
|
||||
// Custom404Page specifies a path to a custom 404 page to display. If this
|
||||
// value is "", a default page will be displayed. By default, this value is
|
||||
// "".
|
||||
Custom404Page string `json:"custom_404_page"`
|
||||
|
||||
AllowPorts map[int]struct{}
|
||||
MaxPoolCount int64 `json:"max_pool_count"`
|
||||
// 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"`
|
||||
HeartBeatTimeout int64 `json:"heart_beat_timeout"`
|
||||
UserConnTimeout int64 `json:"user_conn_timeout"`
|
||||
// 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:"heart_beat_timeout"`
|
||||
// UserConnTimeout specifies the maximum time to wait for a work
|
||||
// connection. By default, this value is 10.
|
||||
UserConnTimeout int64 `json:"user_conn_timeout"`
|
||||
}
|
||||
|
||||
func GetDefaultServerConf() *ServerCommonConf {
|
||||
return &ServerCommonConf{
|
||||
// GetDefaultServerConf returns a server configuration with reasonable
|
||||
// defaults.
|
||||
func GetDefaultServerConf() ServerCommonConf {
|
||||
return ServerCommonConf{
|
||||
BindAddr: "0.0.0.0",
|
||||
BindPort: 7000,
|
||||
BindUdpPort: 0,
|
||||
@@ -96,6 +157,7 @@ func GetDefaultServerConf() *ServerCommonConf {
|
||||
LogWay: "console",
|
||||
LogLevel: "info",
|
||||
LogMaxDays: 3,
|
||||
DisableLogColor: false,
|
||||
Token: "",
|
||||
SubDomainHost: "",
|
||||
TcpMux: true,
|
||||
@@ -104,19 +166,19 @@ func GetDefaultServerConf() *ServerCommonConf {
|
||||
MaxPortsPerClient: 0,
|
||||
HeartBeatTimeout: 90,
|
||||
UserConnTimeout: 10,
|
||||
Custom404Page: "",
|
||||
}
|
||||
}
|
||||
|
||||
func UnmarshalServerConfFromIni(defaultCfg *ServerCommonConf, content string) (cfg *ServerCommonConf, err error) {
|
||||
cfg = defaultCfg
|
||||
if cfg == nil {
|
||||
cfg = GetDefaultServerConf()
|
||||
}
|
||||
// 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 nil, err
|
||||
return ServerCommonConf{}, err
|
||||
}
|
||||
|
||||
var (
|
||||
@@ -242,6 +304,10 @@ func UnmarshalServerConfFromIni(defaultCfg *ServerCommonConf, content string) (c
|
||||
}
|
||||
}
|
||||
|
||||
if tmpStr, ok = conf.Get("common", "disable_log_color"); ok && tmpStr == "true" {
|
||||
cfg.DisableLogColor = true
|
||||
}
|
||||
|
||||
cfg.Token, _ = conf.Get("common", "token")
|
||||
|
||||
if allowPortsStr, ok := conf.Get("common", "allow_ports"); ok {
|
||||
@@ -293,6 +359,10 @@ func UnmarshalServerConfFromIni(defaultCfg *ServerCommonConf, content string) (c
|
||||
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 {
|
||||
|
||||
@@ -64,7 +64,7 @@ func (hp *HttpProxy) Name() string {
|
||||
return PluginHttpProxy
|
||||
}
|
||||
|
||||
func (hp *HttpProxy) Handle(conn io.ReadWriteCloser, realConn frpNet.Conn) {
|
||||
func (hp *HttpProxy) Handle(conn io.ReadWriteCloser, realConn frpNet.Conn, extraBufToLocal []byte) {
|
||||
wrapConn := frpNet.WrapReadWriteCloserToConn(conn, realConn)
|
||||
|
||||
sc, rd := gnet.NewSharedConn(wrapConn)
|
||||
|
||||
@@ -20,6 +20,7 @@ import (
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"strings"
|
||||
|
||||
frpNet "github.com/fatedier/frp/utils/net"
|
||||
)
|
||||
@@ -35,6 +36,7 @@ type HTTPS2HTTPPlugin struct {
|
||||
keyPath string
|
||||
hostHeaderRewrite string
|
||||
localAddr string
|
||||
headers map[string]string
|
||||
|
||||
l *Listener
|
||||
s *http.Server
|
||||
@@ -45,6 +47,15 @@ func NewHTTPS2HTTPPlugin(params map[string]string) (Plugin, error) {
|
||||
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")
|
||||
@@ -63,6 +74,7 @@ func NewHTTPS2HTTPPlugin(params map[string]string) (Plugin, error) {
|
||||
keyPath: keyPath,
|
||||
localAddr: localAddr,
|
||||
hostHeaderRewrite: hostHeaderRewrite,
|
||||
headers: headers,
|
||||
l: listener,
|
||||
}
|
||||
|
||||
@@ -73,6 +85,9 @@ func NewHTTPS2HTTPPlugin(params map[string]string) (Plugin, error) {
|
||||
if p.hostHeaderRewrite != "" {
|
||||
req.Host = p.hostHeaderRewrite
|
||||
}
|
||||
for k, v := range p.headers {
|
||||
req.Header.Set(k, v)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
@@ -100,16 +115,11 @@ func (p *HTTPS2HTTPPlugin) genTLSConfig() (*tls.Config, error) {
|
||||
return config, nil
|
||||
}
|
||||
|
||||
func (p *HTTPS2HTTPPlugin) Handle(conn io.ReadWriteCloser, realConn frpNet.Conn) {
|
||||
func (p *HTTPS2HTTPPlugin) Handle(conn io.ReadWriteCloser, realConn frpNet.Conn, extraBufToLocal []byte) {
|
||||
wrapConn := frpNet.WrapReadWriteCloserToConn(conn, realConn)
|
||||
p.l.PutConn(wrapConn)
|
||||
}
|
||||
|
||||
func (p *HTTPS2HTTPPlugin) handleRequest(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write([]byte("hello"))
|
||||
return
|
||||
}
|
||||
|
||||
func (p *HTTPS2HTTPPlugin) Name() string {
|
||||
return PluginHTTPS2HTTP
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@ func Create(name string, params map[string]string) (p Plugin, err error) {
|
||||
|
||||
type Plugin interface {
|
||||
Name() string
|
||||
Handle(conn io.ReadWriteCloser, realConn frpNet.Conn)
|
||||
Handle(conn io.ReadWriteCloser, realConn frpNet.Conn, extraBufToLocal []byte)
|
||||
Close() error
|
||||
}
|
||||
|
||||
|
||||
@@ -53,7 +53,7 @@ func NewSocks5Plugin(params map[string]string) (p Plugin, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func (sp *Socks5Plugin) Handle(conn io.ReadWriteCloser, realConn frpNet.Conn) {
|
||||
func (sp *Socks5Plugin) Handle(conn io.ReadWriteCloser, realConn frpNet.Conn, extraBufToLocal []byte) {
|
||||
defer conn.Close()
|
||||
wrapConn := frpNet.WrapReadWriteCloserToConn(conn, realConn)
|
||||
sp.Server.ServeConn(wrapConn)
|
||||
|
||||
@@ -72,7 +72,7 @@ func NewStaticFilePlugin(params map[string]string) (Plugin, error) {
|
||||
return sp, nil
|
||||
}
|
||||
|
||||
func (sp *StaticFilePlugin) Handle(conn io.ReadWriteCloser, realConn frpNet.Conn) {
|
||||
func (sp *StaticFilePlugin) Handle(conn io.ReadWriteCloser, realConn frpNet.Conn, extraBufToLocal []byte) {
|
||||
wrapConn := frpNet.WrapReadWriteCloserToConn(conn, realConn)
|
||||
sp.l.PutConn(wrapConn)
|
||||
}
|
||||
|
||||
@@ -53,11 +53,14 @@ func NewUnixDomainSocketPlugin(params map[string]string) (p Plugin, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func (uds *UnixDomainSocketPlugin) Handle(conn io.ReadWriteCloser, realConn frpNet.Conn) {
|
||||
func (uds *UnixDomainSocketPlugin) Handle(conn io.ReadWriteCloser, realConn frpNet.Conn, extraBufToLocal []byte) {
|
||||
localConn, err := net.DialUnix("unix", nil, uds.UnixAddr)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if len(extraBufToLocal) > 0 {
|
||||
localConn.Write(extraBufToLocal)
|
||||
}
|
||||
|
||||
frpIo.Join(localConn, conn)
|
||||
}
|
||||
|
||||
@@ -21,7 +21,6 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/fatedier/frp/g"
|
||||
"github.com/fatedier/frp/models/config"
|
||||
"github.com/fatedier/frp/models/consts"
|
||||
frpErr "github.com/fatedier/frp/models/errors"
|
||||
@@ -129,11 +128,19 @@ type Control struct {
|
||||
allShutdown *shutdown.Shutdown
|
||||
|
||||
mu sync.RWMutex
|
||||
|
||||
// Server configuration information
|
||||
serverCfg config.ServerCommonConf
|
||||
}
|
||||
|
||||
func NewControl(rc *controller.ResourceController, pxyManager *proxy.ProxyManager,
|
||||
statsCollector stats.Collector, ctlConn net.Conn, loginMsg *msg.Login) *Control {
|
||||
statsCollector stats.Collector, ctlConn net.Conn, loginMsg *msg.Login,
|
||||
serverCfg config.ServerCommonConf) *Control {
|
||||
|
||||
poolCount := loginMsg.PoolCount
|
||||
if poolCount > int(serverCfg.MaxPoolCount) {
|
||||
poolCount = int(serverCfg.MaxPoolCount)
|
||||
}
|
||||
return &Control{
|
||||
rc: rc,
|
||||
pxyManager: pxyManager,
|
||||
@@ -142,9 +149,9 @@ func NewControl(rc *controller.ResourceController, pxyManager *proxy.ProxyManage
|
||||
loginMsg: loginMsg,
|
||||
sendCh: make(chan msg.Message, 10),
|
||||
readCh: make(chan msg.Message, 10),
|
||||
workConnCh: make(chan net.Conn, loginMsg.PoolCount+10),
|
||||
workConnCh: make(chan net.Conn, poolCount+10),
|
||||
proxies: make(map[string]proxy.Proxy),
|
||||
poolCount: loginMsg.PoolCount,
|
||||
poolCount: poolCount,
|
||||
portsUsedNum: 0,
|
||||
lastPing: time.Now(),
|
||||
runId: loginMsg.RunId,
|
||||
@@ -153,6 +160,7 @@ func NewControl(rc *controller.ResourceController, pxyManager *proxy.ProxyManage
|
||||
writerShutdown: shutdown.New(),
|
||||
managerShutdown: shutdown.New(),
|
||||
allShutdown: shutdown.New(),
|
||||
serverCfg: serverCfg,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -161,7 +169,7 @@ func (ctl *Control) Start() {
|
||||
loginRespMsg := &msg.LoginResp{
|
||||
Version: version.Full(),
|
||||
RunId: ctl.runId,
|
||||
ServerUdpPort: g.GlbServerCfg.BindUdpPort,
|
||||
ServerUdpPort: ctl.serverCfg.BindUdpPort,
|
||||
Error: "",
|
||||
}
|
||||
msg.WriteMsg(ctl.conn, loginRespMsg)
|
||||
@@ -232,7 +240,7 @@ func (ctl *Control) GetWorkConn() (workConn net.Conn, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
case <-time.After(time.Duration(g.GlbServerCfg.UserConnTimeout) * time.Second):
|
||||
case <-time.After(time.Duration(ctl.serverCfg.UserConnTimeout) * time.Second):
|
||||
err = fmt.Errorf("timeout trying to get work connection")
|
||||
ctl.conn.Warn("%v", err)
|
||||
return
|
||||
@@ -263,7 +271,7 @@ func (ctl *Control) writer() {
|
||||
defer ctl.allShutdown.Start()
|
||||
defer ctl.writerShutdown.Done()
|
||||
|
||||
encWriter, err := crypto.NewWriter(ctl.conn, []byte(g.GlbServerCfg.Token))
|
||||
encWriter, err := crypto.NewWriter(ctl.conn, []byte(ctl.serverCfg.Token))
|
||||
if err != nil {
|
||||
ctl.conn.Error("crypto new writer error: %v", err)
|
||||
ctl.allShutdown.Start()
|
||||
@@ -293,7 +301,7 @@ func (ctl *Control) reader() {
|
||||
defer ctl.allShutdown.Start()
|
||||
defer ctl.readerShutdown.Done()
|
||||
|
||||
encReader := crypto.NewReader(ctl.conn, []byte(g.GlbServerCfg.Token))
|
||||
encReader := crypto.NewReader(ctl.conn, []byte(ctl.serverCfg.Token))
|
||||
for {
|
||||
if m, err := msg.ReadMsg(encReader); err != nil {
|
||||
if err == io.EOF {
|
||||
@@ -374,7 +382,7 @@ func (ctl *Control) manager() {
|
||||
for {
|
||||
select {
|
||||
case <-heartbeat.C:
|
||||
if time.Since(ctl.lastPing) > time.Duration(g.GlbServerCfg.HeartBeatTimeout)*time.Second {
|
||||
if time.Since(ctl.lastPing) > time.Duration(ctl.serverCfg.HeartBeatTimeout)*time.Second {
|
||||
ctl.conn.Warn("heartbeat timeout")
|
||||
return
|
||||
}
|
||||
@@ -417,22 +425,22 @@ func (ctl *Control) manager() {
|
||||
func (ctl *Control) RegisterProxy(pxyMsg *msg.NewProxy) (remoteAddr string, err error) {
|
||||
var pxyConf config.ProxyConf
|
||||
// Load configures from NewProxy message and check.
|
||||
pxyConf, err = config.NewProxyConfFromMsg(pxyMsg)
|
||||
pxyConf, err = config.NewProxyConfFromMsg(pxyMsg, ctl.serverCfg)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// NewProxy will return a interface Proxy.
|
||||
// In fact it create different proxies by different proxy type, we just call run() here.
|
||||
pxy, err := proxy.NewProxy(ctl.runId, ctl.rc, ctl.statsCollector, ctl.poolCount, ctl.GetWorkConn, pxyConf)
|
||||
pxy, err := proxy.NewProxy(ctl.runId, ctl.rc, ctl.statsCollector, ctl.poolCount, ctl.GetWorkConn, pxyConf, ctl.serverCfg)
|
||||
if err != nil {
|
||||
return remoteAddr, err
|
||||
}
|
||||
|
||||
// Check ports used number in each client
|
||||
if g.GlbServerCfg.MaxPortsPerClient > 0 {
|
||||
if ctl.serverCfg.MaxPortsPerClient > 0 {
|
||||
ctl.mu.Lock()
|
||||
if ctl.portsUsedNum+pxy.GetUsedPortsNum() > int(g.GlbServerCfg.MaxPortsPerClient) {
|
||||
if ctl.portsUsedNum+pxy.GetUsedPortsNum() > int(ctl.serverCfg.MaxPortsPerClient) {
|
||||
ctl.mu.Unlock()
|
||||
err = fmt.Errorf("exceed the max_ports_per_client")
|
||||
return
|
||||
@@ -478,7 +486,7 @@ func (ctl *Control) CloseProxy(closeMsg *msg.CloseProxy) (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
if g.GlbServerCfg.MaxPortsPerClient > 0 {
|
||||
if ctl.serverCfg.MaxPortsPerClient > 0 {
|
||||
ctl.portsUsedNum = ctl.portsUsedNum - pxy.GetUsedPortsNum()
|
||||
}
|
||||
pxy.Close()
|
||||
|
||||
@@ -29,6 +29,9 @@ type ResourceController struct {
|
||||
// Tcp Group Controller
|
||||
TcpGroupCtl *group.TcpGroupCtl
|
||||
|
||||
// HTTP Group Controller
|
||||
HTTPGroupCtl *group.HTTPGroupController
|
||||
|
||||
// Manage all tcp ports
|
||||
TcpPortManager *ports.PortManager
|
||||
|
||||
@@ -38,7 +41,7 @@ type ResourceController struct {
|
||||
// For http proxies, forwarding http requests
|
||||
HttpReverseProxy *vhost.HttpReverseProxy
|
||||
|
||||
// For https proxies, route requests to different clients by hostname and other infomation
|
||||
// For https proxies, route requests to different clients by hostname and other information
|
||||
VhostHttpsMuxer *vhost.HttpsMuxer
|
||||
|
||||
// Controller for nat hole connections
|
||||
|
||||
@@ -21,7 +21,6 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/fatedier/frp/assets"
|
||||
"github.com/fatedier/frp/g"
|
||||
frpNet "github.com/fatedier/frp/utils/net"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
@@ -36,7 +35,7 @@ func (svr *Service) RunDashboardServer(addr string, port int) (err error) {
|
||||
// url router
|
||||
router := mux.NewRouter()
|
||||
|
||||
user, passwd := g.GlbServerCfg.DashboardUser, g.GlbServerCfg.DashboardPwd
|
||||
user, passwd := svr.cfg.DashboardUser, svr.cfg.DashboardPwd
|
||||
router.Use(frpNet.NewHttpAuthMiddleware(user, passwd).Middleware)
|
||||
|
||||
// api, see dashboard_api.go
|
||||
|
||||
@@ -18,7 +18,6 @@ import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
|
||||
"github.com/fatedier/frp/g"
|
||||
"github.com/fatedier/frp/models/config"
|
||||
"github.com/fatedier/frp/models/consts"
|
||||
"github.com/fatedier/frp/utils/log"
|
||||
@@ -63,19 +62,18 @@ func (svr *Service) ApiServerInfo(w http.ResponseWriter, r *http.Request) {
|
||||
}()
|
||||
|
||||
log.Info("Http request: [%s]", r.URL.Path)
|
||||
cfg := &g.GlbServerCfg.ServerCommonConf
|
||||
serverStats := svr.statsCollector.GetServer()
|
||||
svrResp := ServerInfoResp{
|
||||
Version: version.Full(),
|
||||
BindPort: cfg.BindPort,
|
||||
BindUdpPort: cfg.BindUdpPort,
|
||||
VhostHttpPort: cfg.VhostHttpPort,
|
||||
VhostHttpsPort: cfg.VhostHttpsPort,
|
||||
KcpBindPort: cfg.KcpBindPort,
|
||||
SubdomainHost: cfg.SubDomainHost,
|
||||
MaxPoolCount: cfg.MaxPoolCount,
|
||||
MaxPortsPerClient: cfg.MaxPortsPerClient,
|
||||
HeartBeatTimeout: cfg.HeartBeatTimeout,
|
||||
BindPort: svr.cfg.BindPort,
|
||||
BindUdpPort: svr.cfg.BindUdpPort,
|
||||
VhostHttpPort: svr.cfg.VhostHttpPort,
|
||||
VhostHttpsPort: svr.cfg.VhostHttpsPort,
|
||||
KcpBindPort: svr.cfg.KcpBindPort,
|
||||
SubdomainHost: svr.cfg.SubDomainHost,
|
||||
MaxPoolCount: svr.cfg.MaxPoolCount,
|
||||
MaxPortsPerClient: svr.cfg.MaxPortsPerClient,
|
||||
HeartBeatTimeout: svr.cfg.HeartBeatTimeout,
|
||||
|
||||
TotalTrafficIn: serverStats.TotalTrafficIn,
|
||||
TotalTrafficOut: serverStats.TotalTrafficOut,
|
||||
@@ -279,6 +277,7 @@ func (svr *Service) getProxyStatsByTypeAndName(proxyType string, proxyName strin
|
||||
proxyInfo.CurConns = ps.CurConns
|
||||
proxyInfo.LastStartTime = ps.LastStartTime
|
||||
proxyInfo.LastCloseTime = ps.LastCloseTime
|
||||
code = 200
|
||||
}
|
||||
|
||||
return
|
||||
|
||||
@@ -23,4 +23,5 @@ var (
|
||||
ErrGroupParamsInvalid = errors.New("group params invalid")
|
||||
ErrListenerClosed = errors.New("group listener closed")
|
||||
ErrGroupDifferentPort = errors.New("group should have same remote port")
|
||||
ErrProxyRepeated = errors.New("group proxy repeated")
|
||||
)
|
||||
|
||||
157
server/group/http.go
Normal file
157
server/group/http.go
Normal file
@@ -0,0 +1,157 @@
|
||||
package group
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
frpNet "github.com/fatedier/frp/utils/net"
|
||||
|
||||
"github.com/fatedier/frp/utils/vhost"
|
||||
)
|
||||
|
||||
type HTTPGroupController struct {
|
||||
groups map[string]*HTTPGroup
|
||||
|
||||
vhostRouter *vhost.VhostRouters
|
||||
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
func NewHTTPGroupController(vhostRouter *vhost.VhostRouters) *HTTPGroupController {
|
||||
return &HTTPGroupController{
|
||||
groups: make(map[string]*HTTPGroup),
|
||||
vhostRouter: vhostRouter,
|
||||
}
|
||||
}
|
||||
|
||||
func (ctl *HTTPGroupController) Register(proxyName, group, groupKey string,
|
||||
routeConfig vhost.VhostRouteConfig) (err error) {
|
||||
|
||||
indexKey := httpGroupIndex(group, routeConfig.Domain, routeConfig.Location)
|
||||
ctl.mu.Lock()
|
||||
g, ok := ctl.groups[indexKey]
|
||||
if !ok {
|
||||
g = NewHTTPGroup(ctl)
|
||||
ctl.groups[indexKey] = g
|
||||
}
|
||||
ctl.mu.Unlock()
|
||||
|
||||
return g.Register(proxyName, group, groupKey, routeConfig)
|
||||
}
|
||||
|
||||
func (ctl *HTTPGroupController) UnRegister(proxyName, group, domain, location string) {
|
||||
indexKey := httpGroupIndex(group, domain, location)
|
||||
ctl.mu.Lock()
|
||||
defer ctl.mu.Unlock()
|
||||
g, ok := ctl.groups[indexKey]
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
isEmpty := g.UnRegister(proxyName)
|
||||
if isEmpty {
|
||||
delete(ctl.groups, indexKey)
|
||||
}
|
||||
}
|
||||
|
||||
type HTTPGroup struct {
|
||||
group string
|
||||
groupKey string
|
||||
domain string
|
||||
location string
|
||||
|
||||
createFuncs map[string]vhost.CreateConnFunc
|
||||
pxyNames []string
|
||||
index uint64
|
||||
ctl *HTTPGroupController
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
func NewHTTPGroup(ctl *HTTPGroupController) *HTTPGroup {
|
||||
return &HTTPGroup{
|
||||
createFuncs: make(map[string]vhost.CreateConnFunc),
|
||||
pxyNames: make([]string, 0),
|
||||
ctl: ctl,
|
||||
}
|
||||
}
|
||||
|
||||
func (g *HTTPGroup) Register(proxyName, group, groupKey string,
|
||||
routeConfig vhost.VhostRouteConfig) (err error) {
|
||||
|
||||
g.mu.Lock()
|
||||
defer g.mu.Unlock()
|
||||
if len(g.createFuncs) == 0 {
|
||||
// the first proxy in this group
|
||||
tmp := routeConfig // copy object
|
||||
tmp.CreateConnFn = g.createConn
|
||||
err = g.ctl.vhostRouter.Add(routeConfig.Domain, routeConfig.Location, &tmp)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
g.group = group
|
||||
g.groupKey = groupKey
|
||||
g.domain = routeConfig.Domain
|
||||
g.location = routeConfig.Location
|
||||
} else {
|
||||
if g.group != group || g.domain != routeConfig.Domain || g.location != routeConfig.Location {
|
||||
err = ErrGroupParamsInvalid
|
||||
return
|
||||
}
|
||||
if g.groupKey != groupKey {
|
||||
err = ErrGroupAuthFailed
|
||||
return
|
||||
}
|
||||
}
|
||||
if _, ok := g.createFuncs[proxyName]; ok {
|
||||
err = ErrProxyRepeated
|
||||
return
|
||||
}
|
||||
g.createFuncs[proxyName] = routeConfig.CreateConnFn
|
||||
g.pxyNames = append(g.pxyNames, proxyName)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (g *HTTPGroup) UnRegister(proxyName string) (isEmpty bool) {
|
||||
g.mu.Lock()
|
||||
defer g.mu.Unlock()
|
||||
delete(g.createFuncs, proxyName)
|
||||
for i, name := range g.pxyNames {
|
||||
if name == proxyName {
|
||||
g.pxyNames = append(g.pxyNames[:i], g.pxyNames[i+1:]...)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if len(g.createFuncs) == 0 {
|
||||
isEmpty = true
|
||||
g.ctl.vhostRouter.Del(g.domain, g.location)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (g *HTTPGroup) createConn(remoteAddr string) (frpNet.Conn, error) {
|
||||
var f vhost.CreateConnFunc
|
||||
newIndex := atomic.AddUint64(&g.index, 1)
|
||||
|
||||
g.mu.RLock()
|
||||
group := g.group
|
||||
domain := g.domain
|
||||
location := g.location
|
||||
if len(g.pxyNames) > 0 {
|
||||
name := g.pxyNames[int(newIndex)%len(g.pxyNames)]
|
||||
f, _ = g.createFuncs[name]
|
||||
}
|
||||
g.mu.RUnlock()
|
||||
|
||||
if f == nil {
|
||||
return nil, fmt.Errorf("no CreateConnFunc for http group [%s], domain [%s], location [%s]", group, domain, location)
|
||||
}
|
||||
|
||||
return f(remoteAddr)
|
||||
}
|
||||
|
||||
func httpGroupIndex(group, domain, location string) string {
|
||||
return fmt.Sprintf("%s_%s_%s", group, domain, location)
|
||||
}
|
||||
@@ -24,46 +24,47 @@ import (
|
||||
gerr "github.com/fatedier/golib/errors"
|
||||
)
|
||||
|
||||
type TcpGroupListener struct {
|
||||
groupName string
|
||||
group *TcpGroup
|
||||
// TcpGroupCtl manage all TcpGroups
|
||||
type TcpGroupCtl struct {
|
||||
groups map[string]*TcpGroup
|
||||
|
||||
addr net.Addr
|
||||
closeCh chan struct{}
|
||||
// portManager is used to manage port
|
||||
portManager *ports.PortManager
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
func newTcpGroupListener(name string, group *TcpGroup, addr net.Addr) *TcpGroupListener {
|
||||
return &TcpGroupListener{
|
||||
groupName: name,
|
||||
group: group,
|
||||
addr: addr,
|
||||
closeCh: make(chan struct{}),
|
||||
// NewTcpGroupCtl return a new TcpGroupCtl
|
||||
func NewTcpGroupCtl(portManager *ports.PortManager) *TcpGroupCtl {
|
||||
return &TcpGroupCtl{
|
||||
groups: make(map[string]*TcpGroup),
|
||||
portManager: portManager,
|
||||
}
|
||||
}
|
||||
|
||||
func (ln *TcpGroupListener) Accept() (c net.Conn, err error) {
|
||||
var ok bool
|
||||
select {
|
||||
case <-ln.closeCh:
|
||||
return nil, ErrListenerClosed
|
||||
case c, ok = <-ln.group.Accept():
|
||||
if !ok {
|
||||
return nil, ErrListenerClosed
|
||||
}
|
||||
return c, nil
|
||||
// Listen is the wrapper for TcpGroup's Listen
|
||||
// If there are no group, we will create one here
|
||||
func (tgc *TcpGroupCtl) Listen(proxyName string, group string, groupKey string,
|
||||
addr string, port int) (l net.Listener, realPort int, err error) {
|
||||
|
||||
tgc.mu.Lock()
|
||||
tcpGroup, ok := tgc.groups[group]
|
||||
if !ok {
|
||||
tcpGroup = NewTcpGroup(tgc)
|
||||
tgc.groups[group] = tcpGroup
|
||||
}
|
||||
tgc.mu.Unlock()
|
||||
|
||||
return tcpGroup.Listen(proxyName, group, groupKey, addr, port)
|
||||
}
|
||||
|
||||
func (ln *TcpGroupListener) Addr() net.Addr {
|
||||
return ln.addr
|
||||
}
|
||||
|
||||
func (ln *TcpGroupListener) Close() (err error) {
|
||||
close(ln.closeCh)
|
||||
ln.group.CloseListener(ln)
|
||||
return
|
||||
// RemoveGroup remove TcpGroup from controller
|
||||
func (tgc *TcpGroupCtl) RemoveGroup(group string) {
|
||||
tgc.mu.Lock()
|
||||
defer tgc.mu.Unlock()
|
||||
delete(tgc.groups, group)
|
||||
}
|
||||
|
||||
// TcpGroup route connections to different proxies
|
||||
type TcpGroup struct {
|
||||
group string
|
||||
groupKey string
|
||||
@@ -79,6 +80,7 @@ type TcpGroup struct {
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
// NewTcpGroup return a new TcpGroup
|
||||
func NewTcpGroup(ctl *TcpGroupCtl) *TcpGroup {
|
||||
return &TcpGroup{
|
||||
lns: make([]*TcpGroupListener, 0),
|
||||
@@ -87,10 +89,14 @@ func NewTcpGroup(ctl *TcpGroupCtl) *TcpGroup {
|
||||
}
|
||||
}
|
||||
|
||||
// Listen will return a new TcpGroupListener
|
||||
// if TcpGroup already has a listener, just add a new TcpGroupListener to the queues
|
||||
// otherwise, listen on the real address
|
||||
func (tg *TcpGroup) Listen(proxyName string, group string, groupKey string, addr string, port int) (ln *TcpGroupListener, realPort int, err error) {
|
||||
tg.mu.Lock()
|
||||
defer tg.mu.Unlock()
|
||||
if len(tg.lns) == 0 {
|
||||
// the first listener, listen on the real address
|
||||
realPort, err = tg.ctl.portManager.Acquire(proxyName, port)
|
||||
if err != nil {
|
||||
return
|
||||
@@ -114,6 +120,7 @@ func (tg *TcpGroup) Listen(proxyName string, group string, groupKey string, addr
|
||||
}
|
||||
go tg.worker()
|
||||
} else {
|
||||
// address and port in the same group must be equal
|
||||
if tg.group != group || tg.addr != addr {
|
||||
err = ErrGroupParamsInvalid
|
||||
return
|
||||
@@ -133,6 +140,7 @@ func (tg *TcpGroup) Listen(proxyName string, group string, groupKey string, addr
|
||||
return
|
||||
}
|
||||
|
||||
// worker is called when the real tcp listener has been created
|
||||
func (tg *TcpGroup) worker() {
|
||||
for {
|
||||
c, err := tg.tcpLn.Accept()
|
||||
@@ -152,6 +160,7 @@ func (tg *TcpGroup) Accept() <-chan net.Conn {
|
||||
return tg.acceptCh
|
||||
}
|
||||
|
||||
// CloseListener remove the TcpGroupListener from the TcpGroup
|
||||
func (tg *TcpGroup) CloseListener(ln *TcpGroupListener) {
|
||||
tg.mu.Lock()
|
||||
defer tg.mu.Unlock()
|
||||
@@ -169,36 +178,47 @@ func (tg *TcpGroup) CloseListener(ln *TcpGroupListener) {
|
||||
}
|
||||
}
|
||||
|
||||
type TcpGroupCtl struct {
|
||||
groups map[string]*TcpGroup
|
||||
// TcpGroupListener
|
||||
type TcpGroupListener struct {
|
||||
groupName string
|
||||
group *TcpGroup
|
||||
|
||||
portManager *ports.PortManager
|
||||
mu sync.Mutex
|
||||
addr net.Addr
|
||||
closeCh chan struct{}
|
||||
}
|
||||
|
||||
func NewTcpGroupCtl(portManager *ports.PortManager) *TcpGroupCtl {
|
||||
return &TcpGroupCtl{
|
||||
groups: make(map[string]*TcpGroup),
|
||||
portManager: portManager,
|
||||
func newTcpGroupListener(name string, group *TcpGroup, addr net.Addr) *TcpGroupListener {
|
||||
return &TcpGroupListener{
|
||||
groupName: name,
|
||||
group: group,
|
||||
addr: addr,
|
||||
closeCh: make(chan struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
func (tgc *TcpGroupCtl) Listen(proxyNanme string, group string, groupKey string,
|
||||
addr string, port int) (l net.Listener, realPort int, err error) {
|
||||
|
||||
tgc.mu.Lock()
|
||||
defer tgc.mu.Unlock()
|
||||
if tcpGroup, ok := tgc.groups[group]; ok {
|
||||
return tcpGroup.Listen(proxyNanme, group, groupKey, addr, port)
|
||||
} else {
|
||||
tcpGroup = NewTcpGroup(tgc)
|
||||
tgc.groups[group] = tcpGroup
|
||||
return tcpGroup.Listen(proxyNanme, group, groupKey, addr, port)
|
||||
// Accept will accept connections from TcpGroup
|
||||
func (ln *TcpGroupListener) Accept() (c net.Conn, err error) {
|
||||
var ok bool
|
||||
select {
|
||||
case <-ln.closeCh:
|
||||
return nil, ErrListenerClosed
|
||||
case c, ok = <-ln.group.Accept():
|
||||
if !ok {
|
||||
return nil, ErrListenerClosed
|
||||
}
|
||||
return c, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (tgc *TcpGroupCtl) RemoveGroup(group string) {
|
||||
tgc.mu.Lock()
|
||||
defer tgc.mu.Unlock()
|
||||
delete(tgc.groups, group)
|
||||
func (ln *TcpGroupListener) Addr() net.Addr {
|
||||
return ln.addr
|
||||
}
|
||||
|
||||
// Close close the listener
|
||||
func (ln *TcpGroupListener) Close() (err error) {
|
||||
close(ln.closeCh)
|
||||
|
||||
// remove self from TcpGroup
|
||||
ln.group.CloseListener(ln)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -19,7 +19,6 @@ import (
|
||||
"net"
|
||||
"strings"
|
||||
|
||||
"github.com/fatedier/frp/g"
|
||||
"github.com/fatedier/frp/models/config"
|
||||
"github.com/fatedier/frp/server/stats"
|
||||
frpNet "github.com/fatedier/frp/utils/net"
|
||||
@@ -50,6 +49,12 @@ func (pxy *HttpProxy) Run() (remoteAddr string, err error) {
|
||||
locations = []string{""}
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if err != nil {
|
||||
pxy.Close()
|
||||
}
|
||||
}()
|
||||
|
||||
addrs := make([]string, 0)
|
||||
for _, domain := range pxy.cfg.CustomDomains {
|
||||
if domain == "" {
|
||||
@@ -59,35 +64,63 @@ func (pxy *HttpProxy) Run() (remoteAddr string, err error) {
|
||||
routeConfig.Domain = domain
|
||||
for _, location := range locations {
|
||||
routeConfig.Location = location
|
||||
err = pxy.rc.HttpReverseProxy.Register(routeConfig)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
tmpDomain := routeConfig.Domain
|
||||
tmpLocation := routeConfig.Location
|
||||
addrs = append(addrs, util.CanonicalAddr(tmpDomain, int(g.GlbServerCfg.VhostHttpPort)))
|
||||
pxy.closeFuncs = append(pxy.closeFuncs, func() {
|
||||
pxy.rc.HttpReverseProxy.UnRegister(tmpDomain, tmpLocation)
|
||||
})
|
||||
pxy.Info("http proxy listen for host [%s] location [%s]", routeConfig.Domain, routeConfig.Location)
|
||||
|
||||
// handle group
|
||||
if pxy.cfg.Group != "" {
|
||||
err = pxy.rc.HTTPGroupCtl.Register(pxy.name, pxy.cfg.Group, pxy.cfg.GroupKey, routeConfig)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
pxy.closeFuncs = append(pxy.closeFuncs, func() {
|
||||
pxy.rc.HTTPGroupCtl.UnRegister(pxy.name, pxy.cfg.Group, tmpDomain, tmpLocation)
|
||||
})
|
||||
} else {
|
||||
// no group
|
||||
err = pxy.rc.HttpReverseProxy.Register(routeConfig)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
pxy.closeFuncs = append(pxy.closeFuncs, func() {
|
||||
pxy.rc.HttpReverseProxy.UnRegister(tmpDomain, tmpLocation)
|
||||
})
|
||||
}
|
||||
addrs = append(addrs, util.CanonicalAddr(routeConfig.Domain, int(pxy.serverCfg.VhostHttpPort)))
|
||||
pxy.Info("http proxy listen for host [%s] location [%s] group [%s]", routeConfig.Domain, routeConfig.Location, pxy.cfg.Group)
|
||||
}
|
||||
}
|
||||
|
||||
if pxy.cfg.SubDomain != "" {
|
||||
routeConfig.Domain = pxy.cfg.SubDomain + "." + g.GlbServerCfg.SubDomainHost
|
||||
routeConfig.Domain = pxy.cfg.SubDomain + "." + pxy.serverCfg.SubDomainHost
|
||||
for _, location := range locations {
|
||||
routeConfig.Location = location
|
||||
err = pxy.rc.HttpReverseProxy.Register(routeConfig)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
tmpDomain := routeConfig.Domain
|
||||
tmpLocation := routeConfig.Location
|
||||
addrs = append(addrs, util.CanonicalAddr(tmpDomain, g.GlbServerCfg.VhostHttpPort))
|
||||
pxy.closeFuncs = append(pxy.closeFuncs, func() {
|
||||
pxy.rc.HttpReverseProxy.UnRegister(tmpDomain, tmpLocation)
|
||||
})
|
||||
pxy.Info("http proxy listen for host [%s] location [%s]", routeConfig.Domain, routeConfig.Location)
|
||||
|
||||
// handle group
|
||||
if pxy.cfg.Group != "" {
|
||||
err = pxy.rc.HTTPGroupCtl.Register(pxy.name, pxy.cfg.Group, pxy.cfg.GroupKey, routeConfig)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
pxy.closeFuncs = append(pxy.closeFuncs, func() {
|
||||
pxy.rc.HTTPGroupCtl.UnRegister(pxy.name, pxy.cfg.Group, tmpDomain, tmpLocation)
|
||||
})
|
||||
} else {
|
||||
err = pxy.rc.HttpReverseProxy.Register(routeConfig)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
pxy.closeFuncs = append(pxy.closeFuncs, func() {
|
||||
pxy.rc.HttpReverseProxy.UnRegister(tmpDomain, tmpLocation)
|
||||
})
|
||||
}
|
||||
addrs = append(addrs, util.CanonicalAddr(tmpDomain, pxy.serverCfg.VhostHttpPort))
|
||||
|
||||
pxy.Info("http proxy listen for host [%s] location [%s] group [%s]", routeConfig.Domain, routeConfig.Location, pxy.cfg.Group)
|
||||
}
|
||||
}
|
||||
remoteAddr = strings.Join(addrs, ",")
|
||||
@@ -113,7 +146,7 @@ func (pxy *HttpProxy) GetRealConn(remoteAddr string) (workConn frpNet.Conn, err
|
||||
|
||||
var rwc io.ReadWriteCloser = tmpConn
|
||||
if pxy.cfg.UseEncryption {
|
||||
rwc, err = frpIo.WithEncryption(rwc, []byte(g.GlbServerCfg.Token))
|
||||
rwc, err = frpIo.WithEncryption(rwc, []byte(pxy.serverCfg.Token))
|
||||
if err != nil {
|
||||
pxy.Error("create encryption stream error: %v", err)
|
||||
return
|
||||
|
||||
@@ -17,7 +17,6 @@ package proxy
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/fatedier/frp/g"
|
||||
"github.com/fatedier/frp/models/config"
|
||||
"github.com/fatedier/frp/utils/util"
|
||||
"github.com/fatedier/frp/utils/vhost"
|
||||
@@ -31,6 +30,11 @@ type HttpsProxy struct {
|
||||
func (pxy *HttpsProxy) Run() (remoteAddr string, err error) {
|
||||
routeConfig := &vhost.VhostRouteConfig{}
|
||||
|
||||
defer func() {
|
||||
if err != nil {
|
||||
pxy.Close()
|
||||
}
|
||||
}()
|
||||
addrs := make([]string, 0)
|
||||
for _, domain := range pxy.cfg.CustomDomains {
|
||||
if domain == "" {
|
||||
@@ -46,11 +50,11 @@ func (pxy *HttpsProxy) Run() (remoteAddr string, err error) {
|
||||
l.AddLogPrefix(pxy.name)
|
||||
pxy.Info("https proxy listen for host [%s]", routeConfig.Domain)
|
||||
pxy.listeners = append(pxy.listeners, l)
|
||||
addrs = append(addrs, util.CanonicalAddr(routeConfig.Domain, g.GlbServerCfg.VhostHttpsPort))
|
||||
addrs = append(addrs, util.CanonicalAddr(routeConfig.Domain, pxy.serverCfg.VhostHttpsPort))
|
||||
}
|
||||
|
||||
if pxy.cfg.SubDomain != "" {
|
||||
routeConfig.Domain = pxy.cfg.SubDomain + "." + g.GlbServerCfg.SubDomainHost
|
||||
routeConfig.Domain = pxy.cfg.SubDomain + "." + pxy.serverCfg.SubDomainHost
|
||||
l, errRet := pxy.rc.VhostHttpsMuxer.Listen(routeConfig)
|
||||
if errRet != nil {
|
||||
err = errRet
|
||||
@@ -59,7 +63,7 @@ func (pxy *HttpsProxy) Run() (remoteAddr string, err error) {
|
||||
l.AddLogPrefix(pxy.name)
|
||||
pxy.Info("https proxy listen for host [%s]", routeConfig.Domain)
|
||||
pxy.listeners = append(pxy.listeners, l)
|
||||
addrs = append(addrs, util.CanonicalAddr(routeConfig.Domain, int(g.GlbServerCfg.VhostHttpsPort)))
|
||||
addrs = append(addrs, util.CanonicalAddr(routeConfig.Domain, int(pxy.serverCfg.VhostHttpsPort)))
|
||||
}
|
||||
|
||||
pxy.startListenHandler(pxy, HandleUserTcpConnection)
|
||||
|
||||
@@ -21,7 +21,6 @@ import (
|
||||
"strconv"
|
||||
"sync"
|
||||
|
||||
"github.com/fatedier/frp/g"
|
||||
"github.com/fatedier/frp/models/config"
|
||||
"github.com/fatedier/frp/models/msg"
|
||||
"github.com/fatedier/frp/server/controller"
|
||||
@@ -52,6 +51,7 @@ type BaseProxy struct {
|
||||
usedPortsNum int
|
||||
poolCount int
|
||||
getWorkConnFn GetWorkConnFn
|
||||
serverCfg config.ServerCommonConf
|
||||
|
||||
mu sync.RWMutex
|
||||
log.Logger
|
||||
@@ -72,6 +72,8 @@ func (pxy *BaseProxy) Close() {
|
||||
}
|
||||
}
|
||||
|
||||
// GetWorkConnFromPool try to get a new work connections from pool
|
||||
// for quickly response, we immediately send the StartWorkConn message to frpc after take out one from pool
|
||||
func (pxy *BaseProxy) GetWorkConnFromPool(src, dst net.Addr) (workConn frpNet.Conn, err error) {
|
||||
// try all connections from the pool
|
||||
for i := 0; i < pxy.poolCount+1; i++ {
|
||||
@@ -124,7 +126,7 @@ func (pxy *BaseProxy) GetWorkConnFromPool(src, dst net.Addr) (workConn frpNet.Co
|
||||
// startListenHandler start a goroutine handler for each listener.
|
||||
// p: p will just be passed to handler(Proxy, frpNet.Conn).
|
||||
// handler: each proxy type can set different handler function to deal with connections accepted from listeners.
|
||||
func (pxy *BaseProxy) startListenHandler(p Proxy, handler func(Proxy, frpNet.Conn, stats.Collector)) {
|
||||
func (pxy *BaseProxy) startListenHandler(p Proxy, handler func(Proxy, frpNet.Conn, stats.Collector, config.ServerCommonConf)) {
|
||||
for _, listener := range pxy.listeners {
|
||||
go func(l frpNet.Listener) {
|
||||
for {
|
||||
@@ -136,14 +138,14 @@ func (pxy *BaseProxy) startListenHandler(p Proxy, handler func(Proxy, frpNet.Con
|
||||
return
|
||||
}
|
||||
pxy.Debug("get a user connection [%s]", c.RemoteAddr().String())
|
||||
go handler(p, c, pxy.statsCollector)
|
||||
go handler(p, c, pxy.statsCollector, pxy.serverCfg)
|
||||
}
|
||||
}(listener)
|
||||
}
|
||||
}
|
||||
|
||||
func NewProxy(runId string, rc *controller.ResourceController, statsCollector stats.Collector, poolCount int,
|
||||
getWorkConnFn GetWorkConnFn, pxyConf config.ProxyConf) (pxy Proxy, err error) {
|
||||
getWorkConnFn GetWorkConnFn, pxyConf config.ProxyConf, serverCfg config.ServerCommonConf) (pxy Proxy, err error) {
|
||||
|
||||
basePxy := BaseProxy{
|
||||
name: pxyConf.GetBaseInfo().ProxyName,
|
||||
@@ -153,6 +155,7 @@ func NewProxy(runId string, rc *controller.ResourceController, statsCollector st
|
||||
poolCount: poolCount,
|
||||
getWorkConnFn: getWorkConnFn,
|
||||
Logger: log.NewPrefixLogger(runId),
|
||||
serverCfg: serverCfg,
|
||||
}
|
||||
switch cfg := pxyConf.(type) {
|
||||
case *config.TcpProxyConf:
|
||||
@@ -196,7 +199,7 @@ func NewProxy(runId string, rc *controller.ResourceController, statsCollector st
|
||||
|
||||
// HandleUserTcpConnection is used for incoming tcp user connections.
|
||||
// It can be used for tcp, http, https type.
|
||||
func HandleUserTcpConnection(pxy Proxy, userConn frpNet.Conn, statsCollector stats.Collector) {
|
||||
func HandleUserTcpConnection(pxy Proxy, userConn frpNet.Conn, statsCollector stats.Collector, serverCfg config.ServerCommonConf) {
|
||||
defer userConn.Close()
|
||||
|
||||
// try all connections from the pool
|
||||
@@ -209,7 +212,7 @@ func HandleUserTcpConnection(pxy Proxy, userConn frpNet.Conn, statsCollector sta
|
||||
var local io.ReadWriteCloser = workConn
|
||||
cfg := pxy.GetConf().GetBaseInfo()
|
||||
if cfg.UseEncryption {
|
||||
local, err = frpIo.WithEncryption(local, []byte(g.GlbServerCfg.Token))
|
||||
local, err = frpIo.WithEncryption(local, []byte(serverCfg.Token))
|
||||
if err != nil {
|
||||
pxy.Error("create encryption stream error: %v", err)
|
||||
return
|
||||
|
||||
@@ -17,7 +17,6 @@ package proxy
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/fatedier/frp/g"
|
||||
"github.com/fatedier/frp/models/config"
|
||||
frpNet "github.com/fatedier/frp/utils/net"
|
||||
)
|
||||
@@ -31,7 +30,7 @@ type TcpProxy struct {
|
||||
|
||||
func (pxy *TcpProxy) Run() (remoteAddr string, err error) {
|
||||
if pxy.cfg.Group != "" {
|
||||
l, realPort, errRet := pxy.rc.TcpGroupCtl.Listen(pxy.name, pxy.cfg.Group, pxy.cfg.GroupKey, g.GlbServerCfg.ProxyBindAddr, pxy.cfg.RemotePort)
|
||||
l, realPort, errRet := pxy.rc.TcpGroupCtl.Listen(pxy.name, pxy.cfg.Group, pxy.cfg.GroupKey, pxy.serverCfg.ProxyBindAddr, pxy.cfg.RemotePort)
|
||||
if errRet != nil {
|
||||
err = errRet
|
||||
return
|
||||
@@ -56,7 +55,7 @@ func (pxy *TcpProxy) Run() (remoteAddr string, err error) {
|
||||
pxy.rc.TcpPortManager.Release(pxy.realPort)
|
||||
}
|
||||
}()
|
||||
listener, errRet := frpNet.ListenTcp(g.GlbServerCfg.ProxyBindAddr, pxy.realPort)
|
||||
listener, errRet := frpNet.ListenTcp(pxy.serverCfg.ProxyBindAddr, pxy.realPort)
|
||||
if errRet != nil {
|
||||
err = errRet
|
||||
return
|
||||
|
||||
@@ -20,7 +20,6 @@ import (
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/fatedier/frp/g"
|
||||
"github.com/fatedier/frp/models/config"
|
||||
"github.com/fatedier/frp/models/msg"
|
||||
"github.com/fatedier/frp/models/proto/udp"
|
||||
@@ -67,7 +66,7 @@ func (pxy *UdpProxy) Run() (remoteAddr string, err error) {
|
||||
|
||||
remoteAddr = fmt.Sprintf(":%d", pxy.realPort)
|
||||
pxy.cfg.RemotePort = pxy.realPort
|
||||
addr, errRet := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", g.GlbServerCfg.ProxyBindAddr, pxy.realPort))
|
||||
addr, errRet := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", pxy.serverCfg.ProxyBindAddr, pxy.realPort))
|
||||
if errRet != nil {
|
||||
err = errRet
|
||||
return
|
||||
|
||||
@@ -29,7 +29,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/fatedier/frp/assets"
|
||||
"github.com/fatedier/frp/g"
|
||||
"github.com/fatedier/frp/models/config"
|
||||
"github.com/fatedier/frp/models/msg"
|
||||
"github.com/fatedier/frp/models/nathole"
|
||||
"github.com/fatedier/frp/server/controller"
|
||||
@@ -51,8 +51,6 @@ const (
|
||||
connReadTimeout time.Duration = 10 * time.Second
|
||||
)
|
||||
|
||||
var ServerService *Service
|
||||
|
||||
// Server service
|
||||
type Service struct {
|
||||
// Dispatch connections to different handlers listen on same port
|
||||
@@ -76,6 +74,9 @@ type Service struct {
|
||||
// Manage all proxies
|
||||
pxyManager *proxy.ProxyManager
|
||||
|
||||
// HTTP vhost router
|
||||
httpVhostRouter *vhost.VhostRouters
|
||||
|
||||
// All resource managers and controllers
|
||||
rc *controller.ResourceController
|
||||
|
||||
@@ -83,10 +84,11 @@ type Service struct {
|
||||
statsCollector stats.Collector
|
||||
|
||||
tlsConfig *tls.Config
|
||||
|
||||
cfg config.ServerCommonConf
|
||||
}
|
||||
|
||||
func NewService() (svr *Service, err error) {
|
||||
cfg := &g.GlbServerCfg.ServerCommonConf
|
||||
func NewService(cfg config.ServerCommonConf) (svr *Service, err error) {
|
||||
svr = &Service{
|
||||
ctlManager: NewControlManager(),
|
||||
pxyManager: proxy.NewProxyManager(),
|
||||
@@ -95,18 +97,19 @@ func NewService() (svr *Service, err error) {
|
||||
TcpPortManager: ports.NewPortManager("tcp", cfg.ProxyBindAddr, cfg.AllowPorts),
|
||||
UdpPortManager: ports.NewPortManager("udp", cfg.ProxyBindAddr, cfg.AllowPorts),
|
||||
},
|
||||
tlsConfig: generateTLSConfig(),
|
||||
httpVhostRouter: vhost.NewVhostRouters(),
|
||||
tlsConfig: generateTLSConfig(),
|
||||
cfg: cfg,
|
||||
}
|
||||
|
||||
// Init group controller
|
||||
svr.rc.TcpGroupCtl = group.NewTcpGroupCtl(svr.rc.TcpPortManager)
|
||||
|
||||
// Init assets
|
||||
err = assets.Load(cfg.AssetsDir)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("Load assets error: %v", err)
|
||||
return
|
||||
}
|
||||
// Init HTTP group controller
|
||||
svr.rc.HTTPGroupCtl = group.NewHTTPGroupController(svr.httpVhostRouter)
|
||||
|
||||
// Init 404 not found page
|
||||
vhost.NotFoundPagePath = cfg.Custom404Page
|
||||
|
||||
var (
|
||||
httpMuxOn bool
|
||||
@@ -156,7 +159,7 @@ func NewService() (svr *Service, err error) {
|
||||
if cfg.VhostHttpPort > 0 {
|
||||
rp := vhost.NewHttpReverseProxy(vhost.HttpReverseProxyOptions{
|
||||
ResponseHeaderTimeoutS: cfg.VhostHttpTimeout,
|
||||
})
|
||||
}, svr.httpVhostRouter)
|
||||
svr.rc.HttpReverseProxy = rp
|
||||
|
||||
address := fmt.Sprintf("%s:%d", cfg.ProxyBindAddr, cfg.VhostHttpPort)
|
||||
@@ -221,6 +224,13 @@ func NewService() (svr *Service, err error) {
|
||||
var statsEnable bool
|
||||
// Create dashboard web server.
|
||||
if cfg.DashboardPort > 0 {
|
||||
// Init dashboard assets
|
||||
err = assets.Load(cfg.AssetsDir)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("Load assets error: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
err = svr.RunDashboardServer(cfg.DashboardAddr, cfg.DashboardPort)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("Create dashboard web server error, %v", err)
|
||||
@@ -238,7 +248,7 @@ func (svr *Service) Run() {
|
||||
if svr.rc.NatHoleController != nil {
|
||||
go svr.rc.NatHoleController.Run()
|
||||
}
|
||||
if g.GlbServerCfg.KcpBindPort > 0 {
|
||||
if svr.cfg.KcpBindPort > 0 {
|
||||
go svr.HandleListener(svr.kcpListener)
|
||||
}
|
||||
|
||||
@@ -256,7 +266,16 @@ func (svr *Service) HandleListener(l frpNet.Listener) {
|
||||
log.Warn("Listener for incoming connections from client closed")
|
||||
return
|
||||
}
|
||||
c = frpNet.CheckAndEnableTLSServerConn(c, svr.tlsConfig)
|
||||
|
||||
log.Trace("start check TLS connection...")
|
||||
originConn := c
|
||||
c, err = frpNet.CheckAndEnableTLSServerConnWithTimeout(c, svr.tlsConfig, connReadTimeout)
|
||||
if err != nil {
|
||||
log.Warn("CheckAndEnableTLSServerConnWithTimeout error: %v", err)
|
||||
originConn.Close()
|
||||
continue
|
||||
}
|
||||
log.Trace("success check TLS connection")
|
||||
|
||||
// Start a new goroutine for dealing connections.
|
||||
go func(frpConn frpNet.Conn) {
|
||||
@@ -305,7 +324,7 @@ func (svr *Service) HandleListener(l frpNet.Listener) {
|
||||
}
|
||||
}
|
||||
|
||||
if g.GlbServerCfg.TcpMux {
|
||||
if svr.cfg.TcpMux {
|
||||
fmuxCfg := fmux.DefaultConfig()
|
||||
fmuxCfg.KeepAliveInterval = 20 * time.Second
|
||||
fmuxCfg.LogOutput = ioutil.Discard
|
||||
@@ -344,7 +363,7 @@ func (svr *Service) RegisterControl(ctlConn frpNet.Conn, loginMsg *msg.Login) (e
|
||||
}
|
||||
|
||||
// Check auth.
|
||||
if util.GetAuthKey(g.GlbServerCfg.Token, loginMsg.Timestamp) != loginMsg.PrivilegeKey {
|
||||
if util.GetAuthKey(svr.cfg.Token, loginMsg.Timestamp) != loginMsg.PrivilegeKey {
|
||||
err = fmt.Errorf("authorization failed")
|
||||
return
|
||||
}
|
||||
@@ -358,7 +377,7 @@ func (svr *Service) RegisterControl(ctlConn frpNet.Conn, loginMsg *msg.Login) (e
|
||||
}
|
||||
}
|
||||
|
||||
ctl := NewControl(svr.rc, svr.pxyManager, svr.statsCollector, ctlConn, loginMsg)
|
||||
ctl := NewControl(svr.rc, svr.pxyManager, svr.statsCollector, ctlConn, loginMsg, svr.cfg)
|
||||
|
||||
if oldCtl := svr.ctlManager.Add(loginMsg.RunId, ctl); oldCtl != nil {
|
||||
oldCtl.allShutdown.WaitDone()
|
||||
|
||||
@@ -67,12 +67,26 @@ custom_domains = test2.com
|
||||
health_check_type = http
|
||||
health_check_interval_s = 1
|
||||
health_check_url = /health
|
||||
|
||||
[http3]
|
||||
type = http
|
||||
local_port = 15005
|
||||
custom_domains = test.balancing.com
|
||||
group = test-balancing
|
||||
group_key = 123
|
||||
|
||||
[http4]
|
||||
type = http
|
||||
local_port = 15006
|
||||
custom_domains = test.balancing.com
|
||||
group = test-balancing
|
||||
group_key = 123
|
||||
`
|
||||
|
||||
func TestHealthCheck(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
// ****** start backgroud services ******
|
||||
// ****** start background services ******
|
||||
echoSvc1 := mock.NewEchoServer(15001, 1, "echo1")
|
||||
err := echoSvc1.Start()
|
||||
if assert.NoError(err) {
|
||||
@@ -124,6 +138,22 @@ func TestHealthCheck(t *testing.T) {
|
||||
defer httpSvc2.Stop()
|
||||
}
|
||||
|
||||
httpSvc3 := mock.NewHttpServer(15005, func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write([]byte("http3"))
|
||||
})
|
||||
err = httpSvc3.Start()
|
||||
if assert.NoError(err) {
|
||||
defer httpSvc3.Stop()
|
||||
}
|
||||
|
||||
httpSvc4 := mock.NewHttpServer(15006, func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write([]byte("http4"))
|
||||
})
|
||||
err = httpSvc4.Start()
|
||||
if assert.NoError(err) {
|
||||
defer httpSvc4.Stop()
|
||||
}
|
||||
|
||||
time.Sleep(200 * time.Millisecond)
|
||||
|
||||
// ****** start frps and frpc ******
|
||||
@@ -244,4 +274,20 @@ func TestHealthCheck(t *testing.T) {
|
||||
assert.NoError(err)
|
||||
assert.Equal(200, code)
|
||||
assert.Equal("http2", body)
|
||||
|
||||
// ****** load balancing type http ******
|
||||
result = make([]string, 0)
|
||||
|
||||
code, body, _, err = util.SendHttpMsg("GET", "http://127.0.0.1:14000/xxx", "test.balancing.com", nil, "")
|
||||
assert.NoError(err)
|
||||
assert.Equal(200, code)
|
||||
result = append(result, body)
|
||||
|
||||
code, body, _, err = util.SendHttpMsg("GET", "http://127.0.0.1:14000/xxx", "test.balancing.com", nil, "")
|
||||
assert.NoError(err)
|
||||
assert.Equal(200, code)
|
||||
result = append(result, body)
|
||||
|
||||
assert.Contains(result, "http3")
|
||||
assert.Contains(result, "http4")
|
||||
}
|
||||
|
||||
@@ -28,51 +28,51 @@ func GetProxyStatus(statusAddr string, user string, passwd string, name string)
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return status, err
|
||||
} else {
|
||||
if resp.StatusCode != 200 {
|
||||
return status, fmt.Errorf("admin api status code [%d]", resp.StatusCode)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return status, err
|
||||
}
|
||||
allStatus := &client.StatusResp{}
|
||||
err = json.Unmarshal(body, &allStatus)
|
||||
if err != nil {
|
||||
return status, fmt.Errorf("unmarshal http response error: %s", strings.TrimSpace(string(body)))
|
||||
}
|
||||
for _, s := range allStatus.Tcp {
|
||||
if s.Name == name {
|
||||
return &s, nil
|
||||
}
|
||||
}
|
||||
for _, s := range allStatus.Udp {
|
||||
if s.Name == name {
|
||||
return &s, nil
|
||||
}
|
||||
}
|
||||
for _, s := range allStatus.Http {
|
||||
if s.Name == name {
|
||||
return &s, nil
|
||||
}
|
||||
}
|
||||
for _, s := range allStatus.Https {
|
||||
if s.Name == name {
|
||||
return &s, nil
|
||||
}
|
||||
}
|
||||
for _, s := range allStatus.Stcp {
|
||||
if s.Name == name {
|
||||
return &s, nil
|
||||
}
|
||||
}
|
||||
for _, s := range allStatus.Xtcp {
|
||||
if s.Name == name {
|
||||
return &s, nil
|
||||
}
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode != 200 {
|
||||
return status, fmt.Errorf("admin api status code [%d]", resp.StatusCode)
|
||||
}
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return status, err
|
||||
}
|
||||
allStatus := &client.StatusResp{}
|
||||
err = json.Unmarshal(body, &allStatus)
|
||||
if err != nil {
|
||||
return status, fmt.Errorf("unmarshal http response error: %s", strings.TrimSpace(string(body)))
|
||||
}
|
||||
for _, s := range allStatus.Tcp {
|
||||
if s.Name == name {
|
||||
return &s, nil
|
||||
}
|
||||
}
|
||||
for _, s := range allStatus.Udp {
|
||||
if s.Name == name {
|
||||
return &s, nil
|
||||
}
|
||||
}
|
||||
for _, s := range allStatus.Http {
|
||||
if s.Name == name {
|
||||
return &s, nil
|
||||
}
|
||||
}
|
||||
for _, s := range allStatus.Https {
|
||||
if s.Name == name {
|
||||
return &s, nil
|
||||
}
|
||||
}
|
||||
for _, s := range allStatus.Stcp {
|
||||
if s.Name == name {
|
||||
return &s, nil
|
||||
}
|
||||
}
|
||||
for _, s := range allStatus.Xtcp {
|
||||
if s.Name == name {
|
||||
return &s, nil
|
||||
}
|
||||
}
|
||||
|
||||
return status, errors.New("no proxy status found")
|
||||
}
|
||||
|
||||
@@ -87,13 +87,13 @@ func ReloadConf(reloadAddr string, user string, passwd string) error {
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
} else {
|
||||
if resp.StatusCode != 200 {
|
||||
return fmt.Errorf("admin api status code [%d]", resp.StatusCode)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
io.Copy(ioutil.Discard, resp.Body)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != 200 {
|
||||
return fmt.Errorf("admin api status code [%d]", resp.StatusCode)
|
||||
}
|
||||
io.Copy(ioutil.Discard, resp.Body)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -29,16 +29,20 @@ func init() {
|
||||
Log.SetLogFuncCallDepth(Log.GetLogFuncCallDepth() + 1)
|
||||
}
|
||||
|
||||
func InitLog(logWay string, logFile string, logLevel string, maxdays int64) {
|
||||
SetLogFile(logWay, logFile, maxdays)
|
||||
func InitLog(logWay string, logFile string, logLevel string, maxdays int64, disableLogColor bool) {
|
||||
SetLogFile(logWay, logFile, maxdays, disableLogColor)
|
||||
SetLogLevel(logLevel)
|
||||
}
|
||||
|
||||
// SetLogFile to configure log params
|
||||
// logWay: file or console
|
||||
func SetLogFile(logWay string, logFile string, maxdays int64) {
|
||||
func SetLogFile(logWay string, logFile string, maxdays int64, disableLogColor bool) {
|
||||
if logWay == "console" {
|
||||
Log.SetLogger("console", "")
|
||||
params := ""
|
||||
if disableLogColor {
|
||||
params = fmt.Sprintf(`{"color": false}`)
|
||||
}
|
||||
Log.SetLogger("console", params)
|
||||
} else {
|
||||
params := fmt.Sprintf(`{"filename": "%s", "maxdays": %d}`, logFile, maxdays)
|
||||
Log.SetLogger("file", params)
|
||||
|
||||
@@ -17,6 +17,7 @@ package net
|
||||
import (
|
||||
"crypto/tls"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
gnet "github.com/fatedier/golib/net"
|
||||
)
|
||||
@@ -31,10 +32,17 @@ func WrapTLSClientConn(c net.Conn, tlsConfig *tls.Config) (out Conn) {
|
||||
return
|
||||
}
|
||||
|
||||
func CheckAndEnableTLSServerConn(c net.Conn, tlsConfig *tls.Config) (out Conn) {
|
||||
sc, r := gnet.NewSharedConnSize(c, 1)
|
||||
func CheckAndEnableTLSServerConnWithTimeout(c net.Conn, tlsConfig *tls.Config, timeout time.Duration) (out Conn, err error) {
|
||||
sc, r := gnet.NewSharedConnSize(c, 2)
|
||||
buf := make([]byte, 1)
|
||||
n, _ := r.Read(buf)
|
||||
var n int
|
||||
c.SetReadDeadline(time.Now().Add(timeout))
|
||||
n, err = r.Read(buf)
|
||||
c.SetReadDeadline(time.Time{})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if n == 1 && int(buf[0]) == FRP_TLS_HEAD_BYTE {
|
||||
out = WrapConn(tls.Server(c, tlsConfig))
|
||||
} else {
|
||||
|
||||
@@ -19,7 +19,7 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
var version string = "0.26.0"
|
||||
var version string = "0.29.0"
|
||||
|
||||
func Full() string {
|
||||
return version
|
||||
|
||||
@@ -23,7 +23,6 @@ import (
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
frpLog "github.com/fatedier/frp/utils/log"
|
||||
@@ -32,8 +31,7 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
ErrRouterConfigConflict = errors.New("router config conflict")
|
||||
ErrNoDomain = errors.New("no such domain")
|
||||
ErrNoDomain = errors.New("no such domain")
|
||||
)
|
||||
|
||||
func getHostFromAddr(addr string) (host string) {
|
||||
@@ -51,21 +49,19 @@ type HttpReverseProxyOptions struct {
|
||||
}
|
||||
|
||||
type HttpReverseProxy struct {
|
||||
proxy *ReverseProxy
|
||||
|
||||
proxy *ReverseProxy
|
||||
vhostRouter *VhostRouters
|
||||
|
||||
responseHeaderTimeout time.Duration
|
||||
cfgMu sync.RWMutex
|
||||
}
|
||||
|
||||
func NewHttpReverseProxy(option HttpReverseProxyOptions) *HttpReverseProxy {
|
||||
func NewHttpReverseProxy(option HttpReverseProxyOptions, vhostRouter *VhostRouters) *HttpReverseProxy {
|
||||
if option.ResponseHeaderTimeoutS <= 0 {
|
||||
option.ResponseHeaderTimeoutS = 60
|
||||
}
|
||||
rp := &HttpReverseProxy{
|
||||
responseHeaderTimeout: time.Duration(option.ResponseHeaderTimeoutS) * time.Second,
|
||||
vhostRouter: NewVhostRouters(),
|
||||
vhostRouter: vhostRouter,
|
||||
}
|
||||
proxy := &ReverseProxy{
|
||||
Director: func(req *http.Request) {
|
||||
@@ -93,34 +89,30 @@ func NewHttpReverseProxy(option HttpReverseProxyOptions) *HttpReverseProxy {
|
||||
return rp.CreateConnection(host, url, remote)
|
||||
},
|
||||
},
|
||||
WebSocketDialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||
url := ctx.Value("url").(string)
|
||||
host := getHostFromAddr(ctx.Value("host").(string))
|
||||
remote := ctx.Value("remote").(string)
|
||||
return rp.CreateConnection(host, url, remote)
|
||||
},
|
||||
BufferPool: newWrapPool(),
|
||||
ErrorLog: log.New(newWrapLogger(), "", 0),
|
||||
ErrorHandler: func(rw http.ResponseWriter, req *http.Request, err error) {
|
||||
frpLog.Warn("do http proxy request error: %v", err)
|
||||
rw.WriteHeader(http.StatusNotFound)
|
||||
rw.Write(getNotFoundPageContent())
|
||||
},
|
||||
}
|
||||
rp.proxy = proxy
|
||||
return rp
|
||||
}
|
||||
|
||||
// Register register the route config to reverse proxy
|
||||
// reverse proxy will use CreateConnFn from routeCfg to create a connection to the remote service
|
||||
func (rp *HttpReverseProxy) Register(routeCfg VhostRouteConfig) error {
|
||||
rp.cfgMu.Lock()
|
||||
defer rp.cfgMu.Unlock()
|
||||
_, ok := rp.vhostRouter.Exist(routeCfg.Domain, routeCfg.Location)
|
||||
if ok {
|
||||
return ErrRouterConfigConflict
|
||||
} else {
|
||||
rp.vhostRouter.Add(routeCfg.Domain, routeCfg.Location, &routeCfg)
|
||||
err := rp.vhostRouter.Add(routeCfg.Domain, routeCfg.Location, &routeCfg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// UnRegister unregister route config by domain and location
|
||||
func (rp *HttpReverseProxy) UnRegister(domain string, location string) {
|
||||
rp.cfgMu.Lock()
|
||||
defer rp.cfgMu.Unlock()
|
||||
rp.vhostRouter.Del(domain, location)
|
||||
}
|
||||
|
||||
@@ -140,6 +132,7 @@ func (rp *HttpReverseProxy) GetHeaders(domain string, location string) (headers
|
||||
return
|
||||
}
|
||||
|
||||
// CreateConnection create a new connection by route config
|
||||
func (rp *HttpReverseProxy) CreateConnection(domain string, location string, remoteAddr string) (net.Conn, error) {
|
||||
vr, ok := rp.getVhost(domain, location)
|
||||
if ok {
|
||||
@@ -163,10 +156,8 @@ func (rp *HttpReverseProxy) CheckAuth(domain, location, user, passwd string) boo
|
||||
return true
|
||||
}
|
||||
|
||||
// getVhost get vhost router by domain and location
|
||||
func (rp *HttpReverseProxy) getVhost(domain string, location string) (vr *VhostRouter, ok bool) {
|
||||
rp.cfgMu.RLock()
|
||||
defer rp.cfgMu.RUnlock()
|
||||
|
||||
// first we check the full hostname
|
||||
// if not exist, then check the wildcard_domain such as *.example.com
|
||||
vr, ok = rp.vhostRouter.Get(domain, location)
|
||||
|
||||
@@ -15,13 +15,18 @@
|
||||
package vhost
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
frpLog "github.com/fatedier/frp/utils/log"
|
||||
"github.com/fatedier/frp/utils/version"
|
||||
)
|
||||
|
||||
var (
|
||||
NotFoundPagePath = ""
|
||||
)
|
||||
|
||||
const (
|
||||
NotFound = `<!DOCTYPE html>
|
||||
<html>
|
||||
@@ -46,10 +51,28 @@ Please try again later.</p>
|
||||
`
|
||||
)
|
||||
|
||||
func getNotFoundPageContent() []byte {
|
||||
var (
|
||||
buf []byte
|
||||
err error
|
||||
)
|
||||
if NotFoundPagePath != "" {
|
||||
buf, err = ioutil.ReadFile(NotFoundPagePath)
|
||||
if err != nil {
|
||||
frpLog.Warn("read custom 404 page error: %v", err)
|
||||
buf = []byte(NotFound)
|
||||
}
|
||||
} else {
|
||||
buf = []byte(NotFound)
|
||||
}
|
||||
return buf
|
||||
}
|
||||
|
||||
func notFoundResponse() *http.Response {
|
||||
header := make(http.Header)
|
||||
header.Set("server", "frp/"+version.Full())
|
||||
header.Set("Content-Type", "text/html")
|
||||
|
||||
res := &http.Response{
|
||||
Status: "Not Found",
|
||||
StatusCode: 404,
|
||||
@@ -57,7 +80,7 @@ func notFoundResponse() *http.Response {
|
||||
ProtoMajor: 1,
|
||||
ProtoMinor: 0,
|
||||
Header: header,
|
||||
Body: ioutil.NopCloser(strings.NewReader(NotFound)),
|
||||
Body: ioutil.NopCloser(bytes.NewReader(getNotFoundPageContent())),
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ package vhost
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net"
|
||||
@@ -17,13 +18,9 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
frpIo "github.com/fatedier/golib/io"
|
||||
"golang.org/x/net/http/httpguts"
|
||||
)
|
||||
|
||||
// onExitFlushLoop is a callback set by tests to detect the state of the
|
||||
// flushLoop() goroutine.
|
||||
var onExitFlushLoop func()
|
||||
|
||||
// ReverseProxy is an HTTP Handler that takes an incoming request and
|
||||
// sends it to another server, proxying the response back to the
|
||||
// client.
|
||||
@@ -44,12 +41,17 @@ type ReverseProxy struct {
|
||||
// to flush to the client while copying the
|
||||
// response body.
|
||||
// If zero, no periodic flushing is done.
|
||||
// A negative value means to flush immediately
|
||||
// after each write to the client.
|
||||
// The FlushInterval is ignored when ReverseProxy
|
||||
// recognizes a response as a streaming response;
|
||||
// for such responses, writes are flushed to the client
|
||||
// immediately.
|
||||
FlushInterval time.Duration
|
||||
|
||||
// ErrorLog specifies an optional logger for errors
|
||||
// that occur when attempting to proxy the request.
|
||||
// If nil, logging goes to os.Stderr via the log package's
|
||||
// standard logger.
|
||||
// If nil, logging is done via the log package's standard logger.
|
||||
ErrorLog *log.Logger
|
||||
|
||||
// BufferPool optionally specifies a buffer pool to
|
||||
@@ -57,12 +59,23 @@ type ReverseProxy struct {
|
||||
// copying HTTP response bodies.
|
||||
BufferPool BufferPool
|
||||
|
||||
// ModifyResponse is an optional function that
|
||||
// modifies the Response from the backend.
|
||||
// If it returns an error, the proxy returns a StatusBadGateway error.
|
||||
// ModifyResponse is an optional function that modifies the
|
||||
// Response from the backend. It is called if the backend
|
||||
// returns a response at all, with any HTTP status code.
|
||||
// If the backend is unreachable, the optional ErrorHandler is
|
||||
// called without any call to ModifyResponse.
|
||||
//
|
||||
// If ModifyResponse returns an error, ErrorHandler is called
|
||||
// with its error value. If ErrorHandler is nil, its default
|
||||
// implementation is used.
|
||||
ModifyResponse func(*http.Response) error
|
||||
|
||||
WebSocketDialContext func(ctx context.Context, network, addr string) (net.Conn, error)
|
||||
// ErrorHandler is an optional function that handles errors
|
||||
// reaching the backend or errors from ModifyResponse.
|
||||
//
|
||||
// If nil, the default is to log the provided error and return
|
||||
// a 502 Status Bad Gateway response.
|
||||
ErrorHandler func(http.ResponseWriter, *http.Request, error)
|
||||
}
|
||||
|
||||
// A BufferPool is an interface for getting and returning temporary
|
||||
@@ -118,18 +131,11 @@ func copyHeader(dst, src http.Header) {
|
||||
}
|
||||
}
|
||||
|
||||
func cloneHeader(h http.Header) http.Header {
|
||||
h2 := make(http.Header, len(h))
|
||||
for k, vv := range h {
|
||||
vv2 := make([]string, len(vv))
|
||||
copy(vv2, vv)
|
||||
h2[k] = vv2
|
||||
}
|
||||
return h2
|
||||
}
|
||||
|
||||
// Hop-by-hop headers. These are removed when sent to the backend.
|
||||
// http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html
|
||||
// As of RFC 7230, hop-by-hop headers are required to appear in the
|
||||
// Connection header field. These are the headers defined by the
|
||||
// obsoleted RFC 2616 (section 13.5.1) and are used for backward
|
||||
// compatibility.
|
||||
var hopHeaders = []string{
|
||||
"Connection",
|
||||
"Proxy-Connection", // non-standard but still sent by libcurl and rejected by e.g. google
|
||||
@@ -137,55 +143,38 @@ var hopHeaders = []string{
|
||||
"Proxy-Authenticate",
|
||||
"Proxy-Authorization",
|
||||
"Te", // canonicalized version of "TE"
|
||||
"Trailer", // not Trailers per URL above; http://www.rfc-editor.org/errata_search.php?eid=4522
|
||||
"Trailer", // not Trailers per URL above; https://www.rfc-editor.org/errata_search.php?eid=4522
|
||||
"Transfer-Encoding",
|
||||
"Upgrade",
|
||||
}
|
||||
|
||||
func (p *ReverseProxy) defaultErrorHandler(rw http.ResponseWriter, req *http.Request, err error) {
|
||||
p.logf("http: proxy error: %v", err)
|
||||
rw.WriteHeader(http.StatusBadGateway)
|
||||
}
|
||||
|
||||
func (p *ReverseProxy) getErrorHandler() func(http.ResponseWriter, *http.Request, error) {
|
||||
if p.ErrorHandler != nil {
|
||||
return p.ErrorHandler
|
||||
}
|
||||
return p.defaultErrorHandler
|
||||
}
|
||||
|
||||
// modifyResponse conditionally runs the optional ModifyResponse hook
|
||||
// and reports whether the request should proceed.
|
||||
func (p *ReverseProxy) modifyResponse(rw http.ResponseWriter, res *http.Response, req *http.Request) bool {
|
||||
if p.ModifyResponse == nil {
|
||||
return true
|
||||
}
|
||||
if err := p.ModifyResponse(res); err != nil {
|
||||
res.Body.Close()
|
||||
p.getErrorHandler()(rw, req, err)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (p *ReverseProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||
if IsWebsocketRequest(req) {
|
||||
p.serveWebSocket(rw, req)
|
||||
} else {
|
||||
p.serveHTTP(rw, req)
|
||||
}
|
||||
}
|
||||
|
||||
func (p *ReverseProxy) serveWebSocket(rw http.ResponseWriter, req *http.Request) {
|
||||
if p.WebSocketDialContext == nil {
|
||||
rw.WriteHeader(500)
|
||||
return
|
||||
}
|
||||
|
||||
req = req.WithContext(context.WithValue(req.Context(), "url", req.URL.Path))
|
||||
req = req.WithContext(context.WithValue(req.Context(), "host", req.Host))
|
||||
req = req.WithContext(context.WithValue(req.Context(), "remote", req.RemoteAddr))
|
||||
|
||||
targetConn, err := p.WebSocketDialContext(req.Context(), "tcp", "")
|
||||
if err != nil {
|
||||
rw.WriteHeader(501)
|
||||
return
|
||||
}
|
||||
defer targetConn.Close()
|
||||
|
||||
p.Director(req)
|
||||
|
||||
hijacker, ok := rw.(http.Hijacker)
|
||||
if !ok {
|
||||
rw.WriteHeader(500)
|
||||
return
|
||||
}
|
||||
conn, _, errHijack := hijacker.Hijack()
|
||||
if errHijack != nil {
|
||||
rw.WriteHeader(500)
|
||||
return
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
req.Write(targetConn)
|
||||
frpIo.Join(conn, targetConn)
|
||||
}
|
||||
|
||||
func (p *ReverseProxy) serveHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||
transport := p.Transport
|
||||
if transport == nil {
|
||||
transport = http.DefaultTransport
|
||||
@@ -206,38 +195,49 @@ func (p *ReverseProxy) serveHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||
}()
|
||||
}
|
||||
|
||||
outreq := req.WithContext(ctx) // includes shallow copies of maps, but okay
|
||||
outreq := req.WithContext(ctx)
|
||||
if req.ContentLength == 0 {
|
||||
outreq.Body = nil // Issue 16036: nil Body for http.Transport retries
|
||||
}
|
||||
|
||||
outreq.Header = cloneHeader(req.Header)
|
||||
|
||||
// Modify for frp
|
||||
// =============================
|
||||
// Modified for frp
|
||||
outreq = outreq.WithContext(context.WithValue(outreq.Context(), "url", req.URL.Path))
|
||||
outreq = outreq.WithContext(context.WithValue(outreq.Context(), "host", req.Host))
|
||||
outreq = outreq.WithContext(context.WithValue(outreq.Context(), "remote", req.RemoteAddr))
|
||||
// =============================
|
||||
|
||||
p.Director(outreq)
|
||||
outreq.Close = false
|
||||
|
||||
// Remove hop-by-hop headers listed in the "Connection" header.
|
||||
// See RFC 2616, section 14.10.
|
||||
if c := outreq.Header.Get("Connection"); c != "" {
|
||||
for _, f := range strings.Split(c, ",") {
|
||||
if f = strings.TrimSpace(f); f != "" {
|
||||
outreq.Header.Del(f)
|
||||
}
|
||||
}
|
||||
}
|
||||
reqUpType := upgradeType(outreq.Header)
|
||||
removeConnectionHeaders(outreq.Header)
|
||||
|
||||
// Remove hop-by-hop headers to the backend. Especially
|
||||
// important is "Connection" because we want a persistent
|
||||
// connection, regardless of what the client sent to us.
|
||||
for _, h := range hopHeaders {
|
||||
if outreq.Header.Get(h) != "" {
|
||||
outreq.Header.Del(h)
|
||||
hv := outreq.Header.Get(h)
|
||||
if hv == "" {
|
||||
continue
|
||||
}
|
||||
if h == "Te" && hv == "trailers" {
|
||||
// Issue 21096: tell backend applications that
|
||||
// care about trailer support that we support
|
||||
// trailers. (We do, but we don't go out of
|
||||
// our way to advertise that unless the
|
||||
// incoming client request thought it was
|
||||
// worth mentioning)
|
||||
continue
|
||||
}
|
||||
outreq.Header.Del(h)
|
||||
}
|
||||
|
||||
// After stripping all the hop-by-hop connection headers above, add back any
|
||||
// necessary for protocol upgrades, such as for websockets.
|
||||
if reqUpType != "" {
|
||||
outreq.Header.Set("Connection", "Upgrade")
|
||||
outreq.Header.Set("Upgrade", reqUpType)
|
||||
}
|
||||
|
||||
if clientIP, _, err := net.SplitHostPort(req.RemoteAddr); err == nil {
|
||||
@@ -252,32 +252,27 @@ func (p *ReverseProxy) serveHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||
|
||||
res, err := transport.RoundTrip(outreq)
|
||||
if err != nil {
|
||||
p.logf("http: proxy error: %v", err)
|
||||
rw.WriteHeader(http.StatusNotFound)
|
||||
rw.Write([]byte(NotFound))
|
||||
p.getErrorHandler()(rw, outreq, err)
|
||||
return
|
||||
}
|
||||
|
||||
// Remove hop-by-hop headers listed in the
|
||||
// "Connection" header of the response.
|
||||
if c := res.Header.Get("Connection"); c != "" {
|
||||
for _, f := range strings.Split(c, ",") {
|
||||
if f = strings.TrimSpace(f); f != "" {
|
||||
res.Header.Del(f)
|
||||
}
|
||||
// Deal with 101 Switching Protocols responses: (WebSocket, h2c, etc)
|
||||
if res.StatusCode == http.StatusSwitchingProtocols {
|
||||
if !p.modifyResponse(rw, res, outreq) {
|
||||
return
|
||||
}
|
||||
p.handleUpgradeResponse(rw, outreq, res)
|
||||
return
|
||||
}
|
||||
|
||||
removeConnectionHeaders(res.Header)
|
||||
|
||||
for _, h := range hopHeaders {
|
||||
res.Header.Del(h)
|
||||
}
|
||||
|
||||
if p.ModifyResponse != nil {
|
||||
if err := p.ModifyResponse(res); err != nil {
|
||||
p.logf("http: proxy error: %v", err)
|
||||
rw.WriteHeader(http.StatusBadGateway)
|
||||
return
|
||||
}
|
||||
if !p.modifyResponse(rw, res, outreq) {
|
||||
return
|
||||
}
|
||||
|
||||
copyHeader(rw.Header(), res.Header)
|
||||
@@ -294,6 +289,21 @@ func (p *ReverseProxy) serveHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||
}
|
||||
|
||||
rw.WriteHeader(res.StatusCode)
|
||||
|
||||
err = p.copyResponse(rw, res.Body, p.flushInterval(req, res))
|
||||
if err != nil {
|
||||
defer res.Body.Close()
|
||||
// Since we're streaming the response, if we run into an error all we can do
|
||||
// is abort the request. Issue 23643: ReverseProxy should use ErrAbortHandler
|
||||
// on read error while copying body.
|
||||
if !shouldPanicOnCopyError(req) {
|
||||
p.logf("suppressing panic for copyResponse error in test; copy error: %v", err)
|
||||
return
|
||||
}
|
||||
panic(http.ErrAbortHandler)
|
||||
}
|
||||
res.Body.Close() // close now, instead of defer, to populate res.Trailer
|
||||
|
||||
if len(res.Trailer) > 0 {
|
||||
// Force chunking if we saw a response trailer.
|
||||
// This prevents net/http from calculating the length for short
|
||||
@@ -302,8 +312,6 @@ func (p *ReverseProxy) serveHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||
fl.Flush()
|
||||
}
|
||||
}
|
||||
p.copyResponse(rw, res.Body)
|
||||
res.Body.Close() // close now, instead of defer, to populate res.Trailer
|
||||
|
||||
if len(res.Trailer) == announcedTrailers {
|
||||
copyHeader(rw.Header(), res.Trailer)
|
||||
@@ -318,16 +326,68 @@ func (p *ReverseProxy) serveHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
func (p *ReverseProxy) copyResponse(dst io.Writer, src io.Reader) {
|
||||
if p.FlushInterval != 0 {
|
||||
var inOurTests bool // whether we're in our own tests
|
||||
|
||||
// shouldPanicOnCopyError reports whether the reverse proxy should
|
||||
// panic with http.ErrAbortHandler. This is the right thing to do by
|
||||
// default, but Go 1.10 and earlier did not, so existing unit tests
|
||||
// weren't expecting panics. Only panic in our own tests, or when
|
||||
// running under the HTTP server.
|
||||
func shouldPanicOnCopyError(req *http.Request) bool {
|
||||
if inOurTests {
|
||||
// Our tests know to handle this panic.
|
||||
return true
|
||||
}
|
||||
if req.Context().Value(http.ServerContextKey) != nil {
|
||||
// We seem to be running under an HTTP server, so
|
||||
// it'll recover the panic.
|
||||
return true
|
||||
}
|
||||
// Otherwise act like Go 1.10 and earlier to not break
|
||||
// existing tests.
|
||||
return false
|
||||
}
|
||||
|
||||
// removeConnectionHeaders removes hop-by-hop headers listed in the "Connection" header of h.
|
||||
// See RFC 7230, section 6.1
|
||||
func removeConnectionHeaders(h http.Header) {
|
||||
for _, f := range h["Connection"] {
|
||||
for _, sf := range strings.Split(f, ",") {
|
||||
if sf = strings.TrimSpace(sf); sf != "" {
|
||||
h.Del(sf)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// flushInterval returns the p.FlushInterval value, conditionally
|
||||
// overriding its value for a specific request/response.
|
||||
func (p *ReverseProxy) flushInterval(req *http.Request, res *http.Response) time.Duration {
|
||||
resCT := res.Header.Get("Content-Type")
|
||||
|
||||
// For Server-Sent Events responses, flush immediately.
|
||||
// The MIME type is defined in https://www.w3.org/TR/eventsource/#text-event-stream
|
||||
if resCT == "text/event-stream" {
|
||||
return -1 // negative means immediately
|
||||
}
|
||||
|
||||
// TODO: more specific cases? e.g. res.ContentLength == -1?
|
||||
return p.FlushInterval
|
||||
}
|
||||
|
||||
func (p *ReverseProxy) copyResponse(dst io.Writer, src io.Reader, flushInterval time.Duration) error {
|
||||
if flushInterval != 0 {
|
||||
if wf, ok := dst.(writeFlusher); ok {
|
||||
mlw := &maxLatencyWriter{
|
||||
dst: wf,
|
||||
latency: p.FlushInterval,
|
||||
done: make(chan bool),
|
||||
latency: flushInterval,
|
||||
}
|
||||
go mlw.flushLoop()
|
||||
defer mlw.stop()
|
||||
|
||||
// set up initial timer so headers get flushed even if body writes are delayed
|
||||
mlw.flushPending = true
|
||||
mlw.t = time.AfterFunc(flushInterval, mlw.delayedFlush)
|
||||
|
||||
dst = mlw
|
||||
}
|
||||
}
|
||||
@@ -335,13 +395,14 @@ func (p *ReverseProxy) copyResponse(dst io.Writer, src io.Reader) {
|
||||
var buf []byte
|
||||
if p.BufferPool != nil {
|
||||
buf = p.BufferPool.Get()
|
||||
defer p.BufferPool.Put(buf)
|
||||
}
|
||||
p.copyBuffer(dst, src, buf)
|
||||
if p.BufferPool != nil {
|
||||
p.BufferPool.Put(buf)
|
||||
}
|
||||
_, err := p.copyBuffer(dst, src, buf)
|
||||
return err
|
||||
}
|
||||
|
||||
// copyBuffer returns any write errors or non-EOF read errors, and the amount
|
||||
// of bytes written.
|
||||
func (p *ReverseProxy) copyBuffer(dst io.Writer, src io.Reader, buf []byte) (int64, error) {
|
||||
if len(buf) == 0 {
|
||||
buf = make([]byte, 32*1024)
|
||||
@@ -365,6 +426,9 @@ func (p *ReverseProxy) copyBuffer(dst io.Writer, src io.Reader, buf []byte) (int
|
||||
}
|
||||
}
|
||||
if rerr != nil {
|
||||
if rerr == io.EOF {
|
||||
rerr = nil
|
||||
}
|
||||
return written, rerr
|
||||
}
|
||||
}
|
||||
@@ -385,47 +449,115 @@ type writeFlusher interface {
|
||||
|
||||
type maxLatencyWriter struct {
|
||||
dst writeFlusher
|
||||
latency time.Duration
|
||||
latency time.Duration // non-zero; negative means to flush immediately
|
||||
|
||||
mu sync.Mutex // protects Write + Flush
|
||||
done chan bool
|
||||
mu sync.Mutex // protects t, flushPending, and dst.Flush
|
||||
t *time.Timer
|
||||
flushPending bool
|
||||
}
|
||||
|
||||
func (m *maxLatencyWriter) Write(p []byte) (int, error) {
|
||||
func (m *maxLatencyWriter) Write(p []byte) (n int, err error) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
return m.dst.Write(p)
|
||||
n, err = m.dst.Write(p)
|
||||
if m.latency < 0 {
|
||||
m.dst.Flush()
|
||||
return
|
||||
}
|
||||
if m.flushPending {
|
||||
return
|
||||
}
|
||||
if m.t == nil {
|
||||
m.t = time.AfterFunc(m.latency, m.delayedFlush)
|
||||
} else {
|
||||
m.t.Reset(m.latency)
|
||||
}
|
||||
m.flushPending = true
|
||||
return
|
||||
}
|
||||
|
||||
func (m *maxLatencyWriter) flushLoop() {
|
||||
t := time.NewTicker(m.latency)
|
||||
defer t.Stop()
|
||||
for {
|
||||
select {
|
||||
case <-m.done:
|
||||
if onExitFlushLoop != nil {
|
||||
onExitFlushLoop()
|
||||
}
|
||||
return
|
||||
case <-t.C:
|
||||
m.mu.Lock()
|
||||
m.dst.Flush()
|
||||
m.mu.Unlock()
|
||||
}
|
||||
func (m *maxLatencyWriter) delayedFlush() {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
if !m.flushPending { // if stop was called but AfterFunc already started this goroutine
|
||||
return
|
||||
}
|
||||
m.dst.Flush()
|
||||
m.flushPending = false
|
||||
}
|
||||
|
||||
func (m *maxLatencyWriter) stop() {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
m.flushPending = false
|
||||
if m.t != nil {
|
||||
m.t.Stop()
|
||||
}
|
||||
}
|
||||
|
||||
func (m *maxLatencyWriter) stop() { m.done <- true }
|
||||
|
||||
func IsWebsocketRequest(req *http.Request) bool {
|
||||
containsHeader := func(name, value string) bool {
|
||||
items := strings.Split(req.Header.Get(name), ",")
|
||||
for _, item := range items {
|
||||
if value == strings.ToLower(strings.TrimSpace(item)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
func upgradeType(h http.Header) string {
|
||||
if !httpguts.HeaderValuesContainsToken(h["Connection"], "Upgrade") {
|
||||
return ""
|
||||
}
|
||||
return containsHeader("Connection", "upgrade") && containsHeader("Upgrade", "websocket")
|
||||
return strings.ToLower(h.Get("Upgrade"))
|
||||
}
|
||||
|
||||
func (p *ReverseProxy) handleUpgradeResponse(rw http.ResponseWriter, req *http.Request, res *http.Response) {
|
||||
reqUpType := upgradeType(req.Header)
|
||||
resUpType := upgradeType(res.Header)
|
||||
if reqUpType != resUpType {
|
||||
p.getErrorHandler()(rw, req, fmt.Errorf("backend tried to switch protocol %q when %q was requested", resUpType, reqUpType))
|
||||
return
|
||||
}
|
||||
|
||||
copyHeader(res.Header, rw.Header())
|
||||
|
||||
hj, ok := rw.(http.Hijacker)
|
||||
if !ok {
|
||||
p.getErrorHandler()(rw, req, fmt.Errorf("can't switch protocols using non-Hijacker ResponseWriter type %T", rw))
|
||||
return
|
||||
}
|
||||
backConn, ok := res.Body.(io.ReadWriteCloser)
|
||||
if !ok {
|
||||
p.getErrorHandler()(rw, req, fmt.Errorf("internal error: 101 switching protocols response with non-writable body"))
|
||||
return
|
||||
}
|
||||
defer backConn.Close()
|
||||
conn, brw, err := hj.Hijack()
|
||||
if err != nil {
|
||||
p.getErrorHandler()(rw, req, fmt.Errorf("Hijack failed on protocol switch: %v", err))
|
||||
return
|
||||
}
|
||||
defer conn.Close()
|
||||
res.Body = nil // so res.Write only writes the headers; we have res.Body in backConn above
|
||||
if err := res.Write(brw); err != nil {
|
||||
p.getErrorHandler()(rw, req, fmt.Errorf("response write: %v", err))
|
||||
return
|
||||
}
|
||||
if err := brw.Flush(); err != nil {
|
||||
p.getErrorHandler()(rw, req, fmt.Errorf("response flush: %v", err))
|
||||
return
|
||||
}
|
||||
errc := make(chan error, 1)
|
||||
spc := switchProtocolCopier{user: conn, backend: backConn}
|
||||
go spc.copyToBackend(errc)
|
||||
go spc.copyFromBackend(errc)
|
||||
<-errc
|
||||
return
|
||||
}
|
||||
|
||||
// switchProtocolCopier exists so goroutines proxying data back and
|
||||
// forth have nice names in stacks.
|
||||
type switchProtocolCopier struct {
|
||||
user, backend io.ReadWriter
|
||||
}
|
||||
|
||||
func (c switchProtocolCopier) copyFromBackend(errc chan<- error) {
|
||||
_, err := io.Copy(c.user, c.backend)
|
||||
errc <- err
|
||||
}
|
||||
|
||||
func (c switchProtocolCopier) copyToBackend(errc chan<- error) {
|
||||
_, err := io.Copy(c.backend, c.user)
|
||||
errc <- err
|
||||
}
|
||||
|
||||
@@ -1,11 +1,16 @@
|
||||
package vhost
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrRouterConfigConflict = errors.New("router config conflict")
|
||||
)
|
||||
|
||||
type VhostRouters struct {
|
||||
RouterByDomain map[string][]*VhostRouter
|
||||
mutex sync.RWMutex
|
||||
@@ -24,10 +29,14 @@ func NewVhostRouters() *VhostRouters {
|
||||
}
|
||||
}
|
||||
|
||||
func (r *VhostRouters) Add(domain, location string, payload interface{}) {
|
||||
func (r *VhostRouters) Add(domain, location string, payload interface{}) error {
|
||||
r.mutex.Lock()
|
||||
defer r.mutex.Unlock()
|
||||
|
||||
if _, exist := r.exist(domain, location); exist {
|
||||
return ErrRouterConfigConflict
|
||||
}
|
||||
|
||||
vrs, found := r.RouterByDomain[domain]
|
||||
if !found {
|
||||
vrs = make([]*VhostRouter, 0, 1)
|
||||
@@ -42,6 +51,7 @@ func (r *VhostRouters) Add(domain, location string, payload interface{}) {
|
||||
|
||||
sort.Sort(sort.Reverse(ByLocation(vrs)))
|
||||
r.RouterByDomain[domain] = vrs
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *VhostRouters) Del(domain, location string) {
|
||||
@@ -80,10 +90,7 @@ func (r *VhostRouters) Get(host, path string) (vr *VhostRouter, exist bool) {
|
||||
return
|
||||
}
|
||||
|
||||
func (r *VhostRouters) Exist(host, path string) (vr *VhostRouter, exist bool) {
|
||||
r.mutex.RLock()
|
||||
defer r.mutex.RUnlock()
|
||||
|
||||
func (r *VhostRouters) exist(host, path string) (vr *VhostRouter, exist bool) {
|
||||
vrs, found := r.RouterByDomain[host]
|
||||
if !found {
|
||||
return
|
||||
|
||||
@@ -15,7 +15,6 @@ package vhost
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/fatedier/frp/utils/log"
|
||||
@@ -35,7 +34,6 @@ type VhostMuxer struct {
|
||||
authFunc httpAuthFunc
|
||||
rewriteFunc hostRewriteFunc
|
||||
registryRouter *VhostRouters
|
||||
mutex sync.RWMutex
|
||||
}
|
||||
|
||||
func NewVhostMuxer(listener frpNet.Listener, vhostFunc muxFunc, authFunc httpAuthFunc, rewriteFunc hostRewriteFunc, timeout time.Duration) (mux *VhostMuxer, err error) {
|
||||
@@ -53,6 +51,7 @@ func NewVhostMuxer(listener frpNet.Listener, vhostFunc muxFunc, authFunc httpAut
|
||||
|
||||
type CreateConnFunc func(remoteAddr string) (frpNet.Conn, error)
|
||||
|
||||
// VhostRouteConfig is the params used to match HTTP requests
|
||||
type VhostRouteConfig struct {
|
||||
Domain string
|
||||
Location string
|
||||
@@ -67,14 +66,6 @@ type VhostRouteConfig struct {
|
||||
// listen for a new domain name, if rewriteHost is not empty and rewriteFunc is not nil
|
||||
// then rewrite the host header to rewriteHost
|
||||
func (v *VhostMuxer) Listen(cfg *VhostRouteConfig) (l *Listener, err error) {
|
||||
v.mutex.Lock()
|
||||
defer v.mutex.Unlock()
|
||||
|
||||
_, ok := v.registryRouter.Exist(cfg.Domain, cfg.Location)
|
||||
if ok {
|
||||
return nil, fmt.Errorf("hostname [%s] location [%s] is already registered", cfg.Domain, cfg.Location)
|
||||
}
|
||||
|
||||
l = &Listener{
|
||||
name: cfg.Domain,
|
||||
location: cfg.Location,
|
||||
@@ -85,14 +76,14 @@ func (v *VhostMuxer) Listen(cfg *VhostRouteConfig) (l *Listener, err error) {
|
||||
accept: make(chan frpNet.Conn),
|
||||
Logger: log.NewPrefixLogger(""),
|
||||
}
|
||||
v.registryRouter.Add(cfg.Domain, cfg.Location, l)
|
||||
err = v.registryRouter.Add(cfg.Domain, cfg.Location, l)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return l, nil
|
||||
}
|
||||
|
||||
func (v *VhostMuxer) getListener(name, path string) (l *Listener, exist bool) {
|
||||
v.mutex.RLock()
|
||||
defer v.mutex.RUnlock()
|
||||
|
||||
// first we check the full hostname
|
||||
// if not exist, then check the wildcard_domain such as *.example.com
|
||||
vr, found := v.registryRouter.Get(name, path)
|
||||
|
||||
56
vendor/github.com/fatedier/kcp-go/README.md
generated
vendored
56
vendor/github.com/fatedier/kcp-go/README.md
generated
vendored
@@ -34,6 +34,7 @@ This library intents to provide a **smooth, resilient, ordered, error-checked an
|
||||
1. Packet level encryption support with [AES](https://en.wikipedia.org/wiki/Advanced_Encryption_Standard), [TEA](https://en.wikipedia.org/wiki/Tiny_Encryption_Algorithm), [3DES](https://en.wikipedia.org/wiki/Triple_DES), [Blowfish](https://en.wikipedia.org/wiki/Blowfish_(cipher)), [Cast5](https://en.wikipedia.org/wiki/CAST-128), [Salsa20]( https://en.wikipedia.org/wiki/Salsa20), etc. in [CFB](https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Cipher_Feedback_.28CFB.29) mode, which generates completely anonymous packet.
|
||||
1. Only **A fixed number of goroutines** will be created for the entire server application, costs in **context switch** between goroutines have been taken into consideration.
|
||||
1. Compatible with [skywind3000's](https://github.com/skywind3000) C version with various improvements.
|
||||
1. Platform-dependent optimizations: [sendmmsg](http://man7.org/linux/man-pages/man2/sendmmsg.2.html) and [recvmmsg](http://man7.org/linux/man-pages/man2/recvmmsg.2.html) were expoloited for linux.
|
||||
|
||||
## Documentation
|
||||
|
||||
@@ -43,6 +44,24 @@ For complete documentation, see the associated [Godoc](https://godoc.org/github.
|
||||
|
||||
<img src="frame.png" alt="Frame Format" height="109px" />
|
||||
|
||||
```
|
||||
NONCE:
|
||||
16bytes cryptographically secure random number, nonce changes for every packet.
|
||||
|
||||
CRC32:
|
||||
CRC-32 checksum of data using the IEEE polynomial
|
||||
|
||||
FEC TYPE:
|
||||
typeData = 0xF1
|
||||
typeParity = 0xF2
|
||||
|
||||
FEC SEQID:
|
||||
monotonically increasing in range: [0, (0xffffffff/shardSize) * shardSize - 1]
|
||||
|
||||
SIZE:
|
||||
The size of KCP frame plus 2
|
||||
```
|
||||
|
||||
```
|
||||
+-----------------+
|
||||
| SESSION |
|
||||
@@ -65,16 +84,11 @@ For complete documentation, see the associated [Godoc](https://godoc.org/github.
|
||||
```
|
||||
|
||||
|
||||
## Usage
|
||||
## Examples
|
||||
|
||||
Client: [full demo](https://github.com/xtaci/kcptun/blob/master/client/main.go)
|
||||
```go
|
||||
kcpconn, err := kcp.DialWithOptions("192.168.0.1:10000", nil, 10, 3)
|
||||
```
|
||||
Server: [full demo](https://github.com/xtaci/kcptun/blob/master/server/main.go)
|
||||
```go
|
||||
lis, err := kcp.ListenWithOptions(":10000", nil, 10, 3)
|
||||
```
|
||||
1. [simple examples](https://github.com/xtaci/kcp-go/tree/master/examples)
|
||||
2. [kcptun client](https://github.com/xtaci/kcptun/blob/master/client/main.go)
|
||||
3. [kcptun server](https://github.com/xtaci/kcptun/blob/master/server/main.go)
|
||||
|
||||
## Benchmark
|
||||
```
|
||||
@@ -128,6 +142,10 @@ PASS
|
||||
ok github.com/xtaci/kcp-go 50.349s
|
||||
```
|
||||
|
||||
|
||||
## Typical Flame Graph
|
||||

|
||||
|
||||
## Key Design Considerations
|
||||
|
||||
1. slice vs. container/list
|
||||
@@ -159,6 +177,18 @@ BenchmarkNow-4 100000000 15.6 ns/op
|
||||
|
||||
In kcp-go, after each `kcp.output()` function call, current clock time will be updated upon return, and for a single `kcp.flush()` operation, current time will be queried from system once. For most of the time, 5000 connections costs 5000 * 15.6ns = 78us(a fixed cost while no packet needs to be sent), as for 10MB/s data transfering with 1400 MTU, `kcp.output()` will be called around 7500 times and costs 117us for `time.Now()` in **every second**.
|
||||
|
||||
3. Memory management
|
||||
|
||||
Primary memory allocation are done from a global buffer pool xmit.Buf, in kcp-go, when we need to allocate some bytes, we can get from that pool, and a fixed-capacity 1500 bytes(mtuLimit) will be returned, the rx queue, tx queue and fec queue all receive bytes from there, and they will return the bytes to the pool after using to prevent unnecessary zer0ing of bytes. The pool mechanism maintained a high watermark for slice objects, these in-flight objects from the pool will survive from the perodical garbage collection, meanwhile the pool kept the ability to return the memory to runtime if in idle.
|
||||
|
||||
4. Information security
|
||||
|
||||
kcp-go is shipped with builtin packet encryption powered by various block encryption algorithms and works in [Cipher Feedback Mode](https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Cipher_Feedback_(CFB)), for each packet to be sent, the encryption process will start from encrypting a [nonce](https://en.wikipedia.org/wiki/Cryptographic_nonce) from the [system entropy](https://en.wikipedia.org/wiki//dev/random), so encryption to same plaintexts never leads to a same ciphertexts thereafter.
|
||||
|
||||
The contents of the packets are completely anonymous with encryption, including the headers(FEC,KCP), checksums and contents. Note that, no matter which encryption method you choose on you upper layer, if you disable encryption, the transmit will be insecure somehow, since the header is ***PLAINTEXT*** to everyone it would be susceptible to header tampering, such as jamming the *sliding window size*, *round-trip time*, *FEC property* and *checksums*. ```AES-128``` is suggested for minimal encryption since modern CPUs are shipped with [AES-NI](https://en.wikipedia.org/wiki/AES_instruction_set) instructions and performs even better than `salsa20`(check the table above).
|
||||
|
||||
Other possible attacks to kcp-go includes: a) [traffic analysis](https://en.wikipedia.org/wiki/Traffic_analysis), dataflow on specific websites may have pattern while interchanging data, but this type of eavesdropping has been mitigated by adapting [smux](https://github.com/xtaci/smux) to mix data streams so as to introduce noises, perfect solution to this has not appeared yet, theroretically by shuffling/mixing messages on larger scale network may mitigate this problem. b) [replay attack](https://en.wikipedia.org/wiki/Replay_attack), since the asymmetrical encryption has not been introduced into kcp-go for some reason, capturing the packets and replay them on a different machine is possible, (notice: hijacking the session and decrypting the contents is still *impossible*), so upper layers should contain a asymmetrical encryption system to guarantee the authenticity of each message(to process message exactly once), such as HTTPS/OpenSSL/LibreSSL, only by signing the requests with private keys can eliminate this type of attack.
|
||||
|
||||
## Connection Termination
|
||||
|
||||
Control messages like **SYN/FIN/RST** in TCP **are not defined** in KCP, you need some **keepalive/heartbeat mechanism** in the application-level. A real world example is to use some **multiplexing** protocol over session, such as [smux](https://github.com/xtaci/smux)(with embedded keepalive mechanism), see [kcptun](https://github.com/xtaci/kcptun) for example.
|
||||
@@ -169,6 +199,14 @@ Q: I'm handling >5K connections on my server, the CPU utilization is so high.
|
||||
|
||||
A: A standalone `agent` or `gate` server for running kcp-go is suggested, not only for CPU utilization, but also important to the **precision** of RTT measurements(timing) which indirectly affects retransmission. By increasing update `interval` with `SetNoDelay` like `conn.SetNoDelay(1, 40, 1, 1)` will dramatically reduce system load, but lower the performance.
|
||||
|
||||
Q: When should I enable FEC?
|
||||
|
||||
A: Forward error correction is critical to long-distance transmission, because a packet loss will lead to a huge penalty in time. And for the complicated packet routing network in modern world, round-trip time based loss check will not always be efficient, the big deviation of RTT samples in the long way usually leads to a larger RTO value in typical rtt estimator, which in other words, slows down the transmission.
|
||||
|
||||
Q: Should I enable encryption?
|
||||
|
||||
A: Yes, for the safety of protocol, even if the upper layer has encrypted.
|
||||
|
||||
## Who is using this?
|
||||
|
||||
1. https://github.com/xtaci/kcptun -- A Secure Tunnel Based On KCP over UDP.
|
||||
|
||||
12
vendor/github.com/fatedier/kcp-go/batchconn.go
generated
vendored
Normal file
12
vendor/github.com/fatedier/kcp-go/batchconn.go
generated
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
package kcp
|
||||
|
||||
import "golang.org/x/net/ipv4"
|
||||
|
||||
const (
|
||||
batchSize = 16
|
||||
)
|
||||
|
||||
type batchConn interface {
|
||||
WriteBatch(ms []ipv4.Message, flags int) (int, error)
|
||||
ReadBatch(ms []ipv4.Message, flags int) (int, error)
|
||||
}
|
||||
115
vendor/github.com/fatedier/kcp-go/fec.go
generated
vendored
115
vendor/github.com/fatedier/kcp-go/fec.go
generated
vendored
@@ -11,36 +11,34 @@ const (
|
||||
fecHeaderSize = 6
|
||||
fecHeaderSizePlus2 = fecHeaderSize + 2 // plus 2B data size
|
||||
typeData = 0xf1
|
||||
typeFEC = 0xf2
|
||||
typeParity = 0xf2
|
||||
)
|
||||
|
||||
type (
|
||||
// fecPacket is a decoded FEC packet
|
||||
fecPacket struct {
|
||||
seqid uint32
|
||||
flag uint16
|
||||
data []byte
|
||||
}
|
||||
// fecPacket is a decoded FEC packet
|
||||
type fecPacket []byte
|
||||
|
||||
// fecDecoder for decoding incoming packets
|
||||
fecDecoder struct {
|
||||
rxlimit int // queue size limit
|
||||
dataShards int
|
||||
parityShards int
|
||||
shardSize int
|
||||
rx []fecPacket // ordered receive queue
|
||||
func (bts fecPacket) seqid() uint32 { return binary.LittleEndian.Uint32(bts) }
|
||||
func (bts fecPacket) flag() uint16 { return binary.LittleEndian.Uint16(bts[4:]) }
|
||||
func (bts fecPacket) data() []byte { return bts[6:] }
|
||||
|
||||
// caches
|
||||
decodeCache [][]byte
|
||||
flagCache []bool
|
||||
// fecDecoder for decoding incoming packets
|
||||
type fecDecoder struct {
|
||||
rxlimit int // queue size limit
|
||||
dataShards int
|
||||
parityShards int
|
||||
shardSize int
|
||||
rx []fecPacket // ordered receive queue
|
||||
|
||||
// zeros
|
||||
zeros []byte
|
||||
// caches
|
||||
decodeCache [][]byte
|
||||
flagCache []bool
|
||||
|
||||
// RS decoder
|
||||
codec reedsolomon.Encoder
|
||||
}
|
||||
)
|
||||
// zeros
|
||||
zeros []byte
|
||||
|
||||
// RS decoder
|
||||
codec reedsolomon.Encoder
|
||||
}
|
||||
|
||||
func newFECDecoder(rxlimit, dataShards, parityShards int) *fecDecoder {
|
||||
if dataShards <= 0 || parityShards <= 0 {
|
||||
@@ -66,33 +64,24 @@ func newFECDecoder(rxlimit, dataShards, parityShards int) *fecDecoder {
|
||||
return dec
|
||||
}
|
||||
|
||||
// decodeBytes a fec packet
|
||||
func (dec *fecDecoder) decodeBytes(data []byte) fecPacket {
|
||||
var pkt fecPacket
|
||||
pkt.seqid = binary.LittleEndian.Uint32(data)
|
||||
pkt.flag = binary.LittleEndian.Uint16(data[4:])
|
||||
// allocate memory & copy
|
||||
buf := xmitBuf.Get().([]byte)[:len(data)-6]
|
||||
copy(buf, data[6:])
|
||||
pkt.data = buf
|
||||
return pkt
|
||||
}
|
||||
|
||||
// decode a fec packet
|
||||
func (dec *fecDecoder) decode(pkt fecPacket) (recovered [][]byte) {
|
||||
func (dec *fecDecoder) decode(in fecPacket) (recovered [][]byte) {
|
||||
// insertion
|
||||
n := len(dec.rx) - 1
|
||||
insertIdx := 0
|
||||
for i := n; i >= 0; i-- {
|
||||
if pkt.seqid == dec.rx[i].seqid { // de-duplicate
|
||||
xmitBuf.Put(pkt.data)
|
||||
if in.seqid() == dec.rx[i].seqid() { // de-duplicate
|
||||
return nil
|
||||
} else if _itimediff(pkt.seqid, dec.rx[i].seqid) > 0 { // insertion
|
||||
} else if _itimediff(in.seqid(), dec.rx[i].seqid()) > 0 { // insertion
|
||||
insertIdx = i + 1
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// make a copy
|
||||
pkt := fecPacket(xmitBuf.Get().([]byte)[:len(in)])
|
||||
copy(pkt, in)
|
||||
|
||||
// insert into ordered rx queue
|
||||
if insertIdx == n+1 {
|
||||
dec.rx = append(dec.rx, pkt)
|
||||
@@ -103,11 +92,11 @@ func (dec *fecDecoder) decode(pkt fecPacket) (recovered [][]byte) {
|
||||
}
|
||||
|
||||
// shard range for current packet
|
||||
shardBegin := pkt.seqid - pkt.seqid%uint32(dec.shardSize)
|
||||
shardBegin := pkt.seqid() - pkt.seqid()%uint32(dec.shardSize)
|
||||
shardEnd := shardBegin + uint32(dec.shardSize) - 1
|
||||
|
||||
// max search range in ordered queue for current shard
|
||||
searchBegin := insertIdx - int(pkt.seqid%uint32(dec.shardSize))
|
||||
searchBegin := insertIdx - int(pkt.seqid()%uint32(dec.shardSize))
|
||||
if searchBegin < 0 {
|
||||
searchBegin = 0
|
||||
}
|
||||
@@ -130,21 +119,21 @@ func (dec *fecDecoder) decode(pkt fecPacket) (recovered [][]byte) {
|
||||
|
||||
// shard assembly
|
||||
for i := searchBegin; i <= searchEnd; i++ {
|
||||
seqid := dec.rx[i].seqid
|
||||
seqid := dec.rx[i].seqid()
|
||||
if _itimediff(seqid, shardEnd) > 0 {
|
||||
break
|
||||
} else if _itimediff(seqid, shardBegin) >= 0 {
|
||||
shards[seqid%uint32(dec.shardSize)] = dec.rx[i].data
|
||||
shards[seqid%uint32(dec.shardSize)] = dec.rx[i].data()
|
||||
shardsflag[seqid%uint32(dec.shardSize)] = true
|
||||
numshard++
|
||||
if dec.rx[i].flag == typeData {
|
||||
if dec.rx[i].flag() == typeData {
|
||||
numDataShard++
|
||||
}
|
||||
if numshard == 1 {
|
||||
first = i
|
||||
}
|
||||
if len(dec.rx[i].data) > maxlen {
|
||||
maxlen = len(dec.rx[i].data)
|
||||
if len(dec.rx[i].data()) > maxlen {
|
||||
maxlen = len(dec.rx[i].data())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -159,11 +148,14 @@ func (dec *fecDecoder) decode(pkt fecPacket) (recovered [][]byte) {
|
||||
dlen := len(shards[k])
|
||||
shards[k] = shards[k][:maxlen]
|
||||
copy(shards[k][dlen:], dec.zeros)
|
||||
} else {
|
||||
shards[k] = xmitBuf.Get().([]byte)[:0]
|
||||
}
|
||||
}
|
||||
if err := dec.codec.ReconstructData(shards); err == nil {
|
||||
for k := range shards[:dec.dataShards] {
|
||||
if !shardsflag[k] {
|
||||
// recovered data should be recycled
|
||||
recovered = append(recovered, shards[k])
|
||||
}
|
||||
}
|
||||
@@ -174,7 +166,7 @@ func (dec *fecDecoder) decode(pkt fecPacket) (recovered [][]byte) {
|
||||
|
||||
// keep rxlimit
|
||||
if len(dec.rx) > dec.rxlimit {
|
||||
if dec.rx[0].flag == typeData { // track the unrecoverable data
|
||||
if dec.rx[0].flag() == typeData { // track the unrecoverable data
|
||||
atomic.AddUint64(&DefaultSnmp.FECShortShards, 1)
|
||||
}
|
||||
dec.rx = dec.freeRange(0, 1, dec.rx)
|
||||
@@ -182,15 +174,16 @@ func (dec *fecDecoder) decode(pkt fecPacket) (recovered [][]byte) {
|
||||
return
|
||||
}
|
||||
|
||||
// free a range of fecPacket, and zero for GC recycling
|
||||
// free a range of fecPacket
|
||||
func (dec *fecDecoder) freeRange(first, n int, q []fecPacket) []fecPacket {
|
||||
for i := first; i < first+n; i++ { // recycle buffer
|
||||
xmitBuf.Put(q[i].data)
|
||||
xmitBuf.Put([]byte(q[i]))
|
||||
}
|
||||
|
||||
if first == 0 && n < cap(q)/2 {
|
||||
return q[n:]
|
||||
}
|
||||
copy(q[first:], q[first+n:])
|
||||
for i := 0; i < n; i++ { // dereference data
|
||||
q[len(q)-1-i].data = nil
|
||||
}
|
||||
return q[:len(q)-n]
|
||||
}
|
||||
|
||||
@@ -229,7 +222,7 @@ func newFECEncoder(dataShards, parityShards, offset int) *fecEncoder {
|
||||
enc.dataShards = dataShards
|
||||
enc.parityShards = parityShards
|
||||
enc.shardSize = dataShards + parityShards
|
||||
enc.paws = (0xffffffff/uint32(enc.shardSize) - 1) * uint32(enc.shardSize)
|
||||
enc.paws = 0xffffffff / uint32(enc.shardSize) * uint32(enc.shardSize)
|
||||
enc.headerOffset = offset
|
||||
enc.payloadOffset = enc.headerOffset + fecHeaderSize
|
||||
|
||||
@@ -252,13 +245,16 @@ func newFECEncoder(dataShards, parityShards, offset int) *fecEncoder {
|
||||
// encodes the packet, outputs parity shards if we have collected quorum datashards
|
||||
// notice: the contents of 'ps' will be re-written in successive calling
|
||||
func (enc *fecEncoder) encode(b []byte) (ps [][]byte) {
|
||||
// The header format:
|
||||
// | FEC SEQID(4B) | FEC TYPE(2B) | SIZE (2B) | PAYLOAD(SIZE-2) |
|
||||
// |<-headerOffset |<-payloadOffset
|
||||
enc.markData(b[enc.headerOffset:])
|
||||
binary.LittleEndian.PutUint16(b[enc.payloadOffset:], uint16(len(b[enc.payloadOffset:])))
|
||||
|
||||
// copy data to fec datashards
|
||||
// copy data from payloadOffset to fec shard cache
|
||||
sz := len(b)
|
||||
enc.shardCache[enc.shardCount] = enc.shardCache[enc.shardCount][:sz]
|
||||
copy(enc.shardCache[enc.shardCount], b)
|
||||
copy(enc.shardCache[enc.shardCount][enc.payloadOffset:], b[enc.payloadOffset:])
|
||||
enc.shardCount++
|
||||
|
||||
// track max datashard length
|
||||
@@ -285,7 +281,7 @@ func (enc *fecEncoder) encode(b []byte) (ps [][]byte) {
|
||||
if err := enc.codec.Encode(cache); err == nil {
|
||||
ps = enc.shardCache[enc.dataShards:]
|
||||
for k := range ps {
|
||||
enc.markFEC(ps[k][enc.headerOffset:])
|
||||
enc.markParity(ps[k][enc.headerOffset:])
|
||||
ps[k] = ps[k][:enc.maxSize]
|
||||
}
|
||||
}
|
||||
@@ -304,8 +300,9 @@ func (enc *fecEncoder) markData(data []byte) {
|
||||
enc.next++
|
||||
}
|
||||
|
||||
func (enc *fecEncoder) markFEC(data []byte) {
|
||||
func (enc *fecEncoder) markParity(data []byte) {
|
||||
binary.LittleEndian.PutUint32(data, enc.next)
|
||||
binary.LittleEndian.PutUint16(data[4:], typeFEC)
|
||||
binary.LittleEndian.PutUint16(data[4:], typeParity)
|
||||
// sequence wrap will only happen at parity shard
|
||||
enc.next = (enc.next + 1) % enc.paws
|
||||
}
|
||||
|
||||
BIN
vendor/github.com/fatedier/kcp-go/flame.png
generated
vendored
Normal file
BIN
vendor/github.com/fatedier/kcp-go/flame.png
generated
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 56 KiB |
147
vendor/github.com/fatedier/kcp-go/kcp.go
generated
vendored
147
vendor/github.com/fatedier/kcp-go/kcp.go
generated
vendored
@@ -1,9 +1,9 @@
|
||||
// Package kcp - A Fast and Reliable ARQ Protocol
|
||||
package kcp
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -30,6 +30,12 @@ const (
|
||||
IKCP_PROBE_LIMIT = 120000 // up to 120 secs to probe window
|
||||
)
|
||||
|
||||
// monotonic reference time point
|
||||
var refTime time.Time = time.Now()
|
||||
|
||||
// currentMs returns current elasped monotonic milliseconds since program startup
|
||||
func currentMs() uint32 { return uint32(time.Now().Sub(refTime) / time.Millisecond) }
|
||||
|
||||
// output_callback is a prototype which ought capture conn and call conn.Write
|
||||
type output_callback func(buf []byte, size int)
|
||||
|
||||
@@ -145,8 +151,9 @@ type KCP struct {
|
||||
|
||||
acklist []ackItem
|
||||
|
||||
buffer []byte
|
||||
output output_callback
|
||||
buffer []byte
|
||||
reserved int
|
||||
output output_callback
|
||||
}
|
||||
|
||||
type ackItem struct {
|
||||
@@ -154,8 +161,11 @@ type ackItem struct {
|
||||
ts uint32
|
||||
}
|
||||
|
||||
// NewKCP create a new kcp control object, 'conv' must equal in two endpoint
|
||||
// from the same connection.
|
||||
// NewKCP create a new kcp state machine
|
||||
//
|
||||
// 'conv' must be equal in the connection peers, or else data will be silently rejected.
|
||||
//
|
||||
// 'output' function will be called whenever these is data to be sent on wire.
|
||||
func NewKCP(conv uint32, output output_callback) *KCP {
|
||||
kcp := new(KCP)
|
||||
kcp.conv = conv
|
||||
@@ -164,7 +174,7 @@ func NewKCP(conv uint32, output output_callback) *KCP {
|
||||
kcp.rmt_wnd = IKCP_WND_RCV
|
||||
kcp.mtu = IKCP_MTU_DEF
|
||||
kcp.mss = kcp.mtu - IKCP_OVERHEAD
|
||||
kcp.buffer = make([]byte, (kcp.mtu+IKCP_OVERHEAD)*3)
|
||||
kcp.buffer = make([]byte, kcp.mtu)
|
||||
kcp.rx_rto = IKCP_RTO_DEF
|
||||
kcp.rx_minrto = IKCP_RTO_MIN
|
||||
kcp.interval = IKCP_INTERVAL
|
||||
@@ -189,6 +199,19 @@ func (kcp *KCP) delSegment(seg *segment) {
|
||||
}
|
||||
}
|
||||
|
||||
// ReserveBytes keeps n bytes untouched from the beginning of the buffer,
|
||||
// the output_callback function should be aware of this.
|
||||
//
|
||||
// Return false if n >= mss
|
||||
func (kcp *KCP) ReserveBytes(n int) bool {
|
||||
if n >= int(kcp.mtu-IKCP_OVERHEAD) || n < 0 {
|
||||
return false
|
||||
}
|
||||
kcp.reserved = n
|
||||
kcp.mss = kcp.mtu - IKCP_OVERHEAD - uint32(n)
|
||||
return true
|
||||
}
|
||||
|
||||
// PeekSize checks the size of next message in the recv queue
|
||||
func (kcp *KCP) PeekSize() (length int) {
|
||||
if len(kcp.rcv_queue) == 0 {
|
||||
@@ -214,19 +237,21 @@ func (kcp *KCP) PeekSize() (length int) {
|
||||
return
|
||||
}
|
||||
|
||||
// Recv is user/upper level recv: returns size, returns below zero for EAGAIN
|
||||
// Receive data from kcp state machine
|
||||
//
|
||||
// Return number of bytes read.
|
||||
//
|
||||
// Return -1 when there is no readable data.
|
||||
//
|
||||
// Return -2 if len(buffer) is smaller than kcp.PeekSize().
|
||||
func (kcp *KCP) Recv(buffer []byte) (n int) {
|
||||
if len(kcp.rcv_queue) == 0 {
|
||||
peeksize := kcp.PeekSize()
|
||||
if peeksize < 0 {
|
||||
return -1
|
||||
}
|
||||
|
||||
peeksize := kcp.PeekSize()
|
||||
if peeksize < 0 {
|
||||
return -2
|
||||
}
|
||||
|
||||
if peeksize > len(buffer) {
|
||||
return -3
|
||||
return -2
|
||||
}
|
||||
|
||||
var fast_recover bool
|
||||
@@ -255,7 +280,7 @@ func (kcp *KCP) Recv(buffer []byte) (n int) {
|
||||
count = 0
|
||||
for k := range kcp.rcv_buf {
|
||||
seg := &kcp.rcv_buf[k]
|
||||
if seg.sn == kcp.rcv_nxt && len(kcp.rcv_queue) < int(kcp.rcv_wnd) {
|
||||
if seg.sn == kcp.rcv_nxt && len(kcp.rcv_queue)+count < int(kcp.rcv_wnd) {
|
||||
kcp.rcv_nxt++
|
||||
count++
|
||||
} else {
|
||||
@@ -386,6 +411,10 @@ func (kcp *KCP) parse_ack(sn uint32) {
|
||||
for k := range kcp.snd_buf {
|
||||
seg := &kcp.snd_buf[k]
|
||||
if sn == seg.sn {
|
||||
// mark and free space, but leave the segment here,
|
||||
// and wait until `una` to delete this, then we don't
|
||||
// have to shift the segments behind forward,
|
||||
// which is an expensive operation for large window
|
||||
seg.acked = 1
|
||||
kcp.delSegment(seg)
|
||||
break
|
||||
@@ -474,7 +503,7 @@ func (kcp *KCP) parse_data(newseg segment) bool {
|
||||
count := 0
|
||||
for k := range kcp.rcv_buf {
|
||||
seg := &kcp.rcv_buf[k]
|
||||
if seg.sn == kcp.rcv_nxt && len(kcp.rcv_queue) < int(kcp.rcv_wnd) {
|
||||
if seg.sn == kcp.rcv_nxt && len(kcp.rcv_queue)+count < int(kcp.rcv_wnd) {
|
||||
kcp.rcv_nxt++
|
||||
count++
|
||||
} else {
|
||||
@@ -489,8 +518,12 @@ func (kcp *KCP) parse_data(newseg segment) bool {
|
||||
return repeat
|
||||
}
|
||||
|
||||
// Input when you received a low level packet (eg. UDP packet), call it
|
||||
// regular indicates a regular packet has received(not from FEC)
|
||||
// Input a packet into kcp state machine.
|
||||
//
|
||||
// 'regular' indicates it's a real data packet from remote, and it means it's not generated from ReedSolomon
|
||||
// codecs.
|
||||
//
|
||||
// 'ackNoDelay' will trigger immediate ACK, but surely it will not be efficient in bandwidth
|
||||
func (kcp *KCP) Input(data []byte, regular, ackNoDelay bool) int {
|
||||
snd_una := kcp.snd_una
|
||||
if len(data) < IKCP_OVERHEAD {
|
||||
@@ -634,14 +667,28 @@ func (kcp *KCP) flush(ackOnly bool) uint32 {
|
||||
seg.una = kcp.rcv_nxt
|
||||
|
||||
buffer := kcp.buffer
|
||||
// flush acknowledges
|
||||
ptr := buffer
|
||||
for i, ack := range kcp.acklist {
|
||||
ptr := buffer[kcp.reserved:] // keep n bytes untouched
|
||||
|
||||
// makeSpace makes room for writing
|
||||
makeSpace := func(space int) {
|
||||
size := len(buffer) - len(ptr)
|
||||
if size+IKCP_OVERHEAD > int(kcp.mtu) {
|
||||
if size+space > int(kcp.mtu) {
|
||||
kcp.output(buffer, size)
|
||||
ptr = buffer
|
||||
ptr = buffer[kcp.reserved:]
|
||||
}
|
||||
}
|
||||
|
||||
// flush bytes in buffer if there is any
|
||||
flushBuffer := func() {
|
||||
size := len(buffer) - len(ptr)
|
||||
if size > kcp.reserved {
|
||||
kcp.output(buffer, size)
|
||||
}
|
||||
}
|
||||
|
||||
// flush acknowledges
|
||||
for i, ack := range kcp.acklist {
|
||||
makeSpace(IKCP_OVERHEAD)
|
||||
// filter jitters caused by bufferbloat
|
||||
if ack.sn >= kcp.rcv_nxt || len(kcp.acklist)-1 == i {
|
||||
seg.sn, seg.ts = ack.sn, ack.ts
|
||||
@@ -651,10 +698,7 @@ func (kcp *KCP) flush(ackOnly bool) uint32 {
|
||||
kcp.acklist = kcp.acklist[0:0]
|
||||
|
||||
if ackOnly { // flash remain ack segments
|
||||
size := len(buffer) - len(ptr)
|
||||
if size > 0 {
|
||||
kcp.output(buffer, size)
|
||||
}
|
||||
flushBuffer()
|
||||
return kcp.interval
|
||||
}
|
||||
|
||||
@@ -685,22 +729,14 @@ func (kcp *KCP) flush(ackOnly bool) uint32 {
|
||||
// flush window probing commands
|
||||
if (kcp.probe & IKCP_ASK_SEND) != 0 {
|
||||
seg.cmd = IKCP_CMD_WASK
|
||||
size := len(buffer) - len(ptr)
|
||||
if size+IKCP_OVERHEAD > int(kcp.mtu) {
|
||||
kcp.output(buffer, size)
|
||||
ptr = buffer
|
||||
}
|
||||
makeSpace(IKCP_OVERHEAD)
|
||||
ptr = seg.encode(ptr)
|
||||
}
|
||||
|
||||
// flush window probing commands
|
||||
if (kcp.probe & IKCP_ASK_TELL) != 0 {
|
||||
seg.cmd = IKCP_CMD_WINS
|
||||
size := len(buffer) - len(ptr)
|
||||
if size+IKCP_OVERHEAD > int(kcp.mtu) {
|
||||
kcp.output(buffer, size)
|
||||
ptr = buffer
|
||||
}
|
||||
makeSpace(IKCP_OVERHEAD)
|
||||
ptr = seg.encode(ptr)
|
||||
}
|
||||
|
||||
@@ -779,20 +815,14 @@ func (kcp *KCP) flush(ackOnly bool) uint32 {
|
||||
}
|
||||
|
||||
if needsend {
|
||||
current = currentMs() // time update for a blocking call
|
||||
current = currentMs()
|
||||
segment.xmit++
|
||||
segment.ts = current
|
||||
segment.wnd = seg.wnd
|
||||
segment.una = seg.una
|
||||
|
||||
size := len(buffer) - len(ptr)
|
||||
need := IKCP_OVERHEAD + len(segment.data)
|
||||
|
||||
if size+need > int(kcp.mtu) {
|
||||
kcp.output(buffer, size)
|
||||
ptr = buffer
|
||||
}
|
||||
|
||||
makeSpace(need)
|
||||
ptr = segment.encode(ptr)
|
||||
copy(ptr, segment.data)
|
||||
ptr = ptr[len(segment.data):]
|
||||
@@ -809,10 +839,7 @@ func (kcp *KCP) flush(ackOnly bool) uint32 {
|
||||
}
|
||||
|
||||
// flash remain segments
|
||||
size := len(buffer) - len(ptr)
|
||||
if size > 0 {
|
||||
kcp.output(buffer, size)
|
||||
}
|
||||
flushBuffer()
|
||||
|
||||
// counter updates
|
||||
sum := lostSegs
|
||||
@@ -864,6 +891,8 @@ func (kcp *KCP) flush(ackOnly bool) uint32 {
|
||||
return uint32(minrto)
|
||||
}
|
||||
|
||||
// (deprecated)
|
||||
//
|
||||
// Update updates state (call it repeatedly, every 10ms-100ms), or you can ask
|
||||
// ikcp_check when to call it again (without ikcp_input/_send calling).
|
||||
// 'current' - current timestamp in millisec.
|
||||
@@ -892,6 +921,8 @@ func (kcp *KCP) Update() {
|
||||
}
|
||||
}
|
||||
|
||||
// (deprecated)
|
||||
//
|
||||
// Check determines when should you invoke ikcp_update:
|
||||
// returns when you should invoke ikcp_update in millisec, if there
|
||||
// is no ikcp_input/_send calling. you can call ikcp_update in that
|
||||
@@ -947,12 +978,16 @@ func (kcp *KCP) SetMtu(mtu int) int {
|
||||
if mtu < 50 || mtu < IKCP_OVERHEAD {
|
||||
return -1
|
||||
}
|
||||
buffer := make([]byte, (mtu+IKCP_OVERHEAD)*3)
|
||||
if kcp.reserved >= int(kcp.mtu-IKCP_OVERHEAD) || kcp.reserved < 0 {
|
||||
return -1
|
||||
}
|
||||
|
||||
buffer := make([]byte, mtu)
|
||||
if buffer == nil {
|
||||
return -2
|
||||
}
|
||||
kcp.mtu = uint32(mtu)
|
||||
kcp.mss = kcp.mtu - IKCP_OVERHEAD
|
||||
kcp.mss = kcp.mtu - IKCP_OVERHEAD - uint32(kcp.reserved)
|
||||
kcp.buffer = buffer
|
||||
return 0
|
||||
}
|
||||
@@ -1006,7 +1041,13 @@ func (kcp *KCP) WaitSnd() int {
|
||||
}
|
||||
|
||||
// remove front n elements from queue
|
||||
// if the number of elements to remove is more than half of the size.
|
||||
// just shift the rear elements to front, otherwise just reslice q to q[n:]
|
||||
// then the cost of runtime.growslice can always be less than n/2
|
||||
func (kcp *KCP) remove_front(q []segment, n int) []segment {
|
||||
newn := copy(q, q[n:])
|
||||
return q[:newn]
|
||||
if n > cap(q)/2 {
|
||||
newn := copy(q, q[n:])
|
||||
return q[:newn]
|
||||
}
|
||||
return q[n:]
|
||||
}
|
||||
|
||||
48
vendor/github.com/fatedier/kcp-go/readloop.go
generated
vendored
Normal file
48
vendor/github.com/fatedier/kcp-go/readloop.go
generated
vendored
Normal file
@@ -0,0 +1,48 @@
|
||||
package kcp
|
||||
|
||||
import (
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func (s *UDPSession) defaultReadLoop() {
|
||||
buf := make([]byte, mtuLimit)
|
||||
var src string
|
||||
for {
|
||||
if n, addr, err := s.conn.ReadFrom(buf); err == nil {
|
||||
// make sure the packet is from the same source
|
||||
if src == "" { // set source address
|
||||
src = addr.String()
|
||||
} else if addr.String() != src {
|
||||
atomic.AddUint64(&DefaultSnmp.InErrs, 1)
|
||||
continue
|
||||
}
|
||||
|
||||
if n >= s.headerSize+IKCP_OVERHEAD {
|
||||
s.packetInput(buf[:n])
|
||||
} else {
|
||||
atomic.AddUint64(&DefaultSnmp.InErrs, 1)
|
||||
}
|
||||
} else {
|
||||
s.notifyReadError(errors.WithStack(err))
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (l *Listener) defaultMonitor() {
|
||||
buf := make([]byte, mtuLimit)
|
||||
for {
|
||||
if n, from, err := l.conn.ReadFrom(buf); err == nil {
|
||||
if n >= l.headerSize+IKCP_OVERHEAD {
|
||||
l.packetInput(buf[:n], from)
|
||||
} else {
|
||||
atomic.AddUint64(&DefaultSnmp.InErrs, 1)
|
||||
}
|
||||
} else {
|
||||
l.notifyReadError(errors.WithStack(err))
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
11
vendor/github.com/fatedier/kcp-go/readloop_generic.go
generated
vendored
Normal file
11
vendor/github.com/fatedier/kcp-go/readloop_generic.go
generated
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
// +build !linux
|
||||
|
||||
package kcp
|
||||
|
||||
func (s *UDPSession) readLoop() {
|
||||
s.defaultReadLoop()
|
||||
}
|
||||
|
||||
func (l *Listener) monitor() {
|
||||
l.defaultMonitor()
|
||||
}
|
||||
120
vendor/github.com/fatedier/kcp-go/readloop_linux.go
generated
vendored
Normal file
120
vendor/github.com/fatedier/kcp-go/readloop_linux.go
generated
vendored
Normal file
@@ -0,0 +1,120 @@
|
||||
// +build linux
|
||||
|
||||
package kcp
|
||||
|
||||
import (
|
||||
"net"
|
||||
"os"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"golang.org/x/net/ipv4"
|
||||
"golang.org/x/net/ipv6"
|
||||
)
|
||||
|
||||
// the read loop for a client session
|
||||
func (s *UDPSession) readLoop() {
|
||||
// default version
|
||||
if s.xconn == nil {
|
||||
s.defaultReadLoop()
|
||||
return
|
||||
}
|
||||
|
||||
// x/net version
|
||||
var src string
|
||||
msgs := make([]ipv4.Message, batchSize)
|
||||
for k := range msgs {
|
||||
msgs[k].Buffers = [][]byte{make([]byte, mtuLimit)}
|
||||
}
|
||||
|
||||
for {
|
||||
if count, err := s.xconn.ReadBatch(msgs, 0); err == nil {
|
||||
for i := 0; i < count; i++ {
|
||||
msg := &msgs[i]
|
||||
// make sure the packet is from the same source
|
||||
if src == "" { // set source address if nil
|
||||
src = msg.Addr.String()
|
||||
} else if msg.Addr.String() != src {
|
||||
atomic.AddUint64(&DefaultSnmp.InErrs, 1)
|
||||
continue
|
||||
}
|
||||
|
||||
if msg.N < s.headerSize+IKCP_OVERHEAD {
|
||||
atomic.AddUint64(&DefaultSnmp.InErrs, 1)
|
||||
continue
|
||||
}
|
||||
|
||||
// source and size has validated
|
||||
s.packetInput(msg.Buffers[0][:msg.N])
|
||||
}
|
||||
} else {
|
||||
// compatibility issue:
|
||||
// for linux kernel<=2.6.32, support for sendmmsg is not available
|
||||
// an error of type os.SyscallError will be returned
|
||||
if operr, ok := err.(*net.OpError); ok {
|
||||
if se, ok := operr.Err.(*os.SyscallError); ok {
|
||||
if se.Syscall == "recvmmsg" {
|
||||
s.defaultReadLoop()
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
s.notifyReadError(errors.WithStack(err))
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// monitor incoming data for all connections of server
|
||||
func (l *Listener) monitor() {
|
||||
var xconn batchConn
|
||||
if _, ok := l.conn.(*net.UDPConn); ok {
|
||||
addr, err := net.ResolveUDPAddr("udp", l.conn.LocalAddr().String())
|
||||
if err == nil {
|
||||
if addr.IP.To4() != nil {
|
||||
xconn = ipv4.NewPacketConn(l.conn)
|
||||
} else {
|
||||
xconn = ipv6.NewPacketConn(l.conn)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// default version
|
||||
if xconn == nil {
|
||||
l.defaultMonitor()
|
||||
return
|
||||
}
|
||||
|
||||
// x/net version
|
||||
msgs := make([]ipv4.Message, batchSize)
|
||||
for k := range msgs {
|
||||
msgs[k].Buffers = [][]byte{make([]byte, mtuLimit)}
|
||||
}
|
||||
|
||||
for {
|
||||
if count, err := xconn.ReadBatch(msgs, 0); err == nil {
|
||||
for i := 0; i < count; i++ {
|
||||
msg := &msgs[i]
|
||||
if msg.N >= l.headerSize+IKCP_OVERHEAD {
|
||||
l.packetInput(msg.Buffers[0][:msg.N], msg.Addr)
|
||||
} else {
|
||||
atomic.AddUint64(&DefaultSnmp.InErrs, 1)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// compatibility issue:
|
||||
// for linux kernel<=2.6.32, support for sendmmsg is not available
|
||||
// an error of type os.SyscallError will be returned
|
||||
if operr, ok := err.(*net.OpError); ok {
|
||||
if se, ok := operr.Err.(*os.SyscallError); ok {
|
||||
if se.Syscall == "recvmmsg" {
|
||||
l.defaultMonitor()
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
l.notifyReadError(errors.WithStack(err))
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
602
vendor/github.com/fatedier/kcp-go/sess.go
generated
vendored
602
vendor/github.com/fatedier/kcp-go/sess.go
generated
vendored
@@ -1,9 +1,17 @@
|
||||
// Package kcp-go is a Reliable-UDP library for golang.
|
||||
//
|
||||
// This library intents to provide a smooth, resilient, ordered,
|
||||
// error-checked and anonymous delivery of streams over UDP packets.
|
||||
//
|
||||
// The interfaces of this package aims to be compatible with
|
||||
// net.Conn in standard library, but offers powerful features for advanced users.
|
||||
package kcp
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/binary"
|
||||
"hash/crc32"
|
||||
"io"
|
||||
"net"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
@@ -14,14 +22,6 @@ import (
|
||||
"golang.org/x/net/ipv6"
|
||||
)
|
||||
|
||||
type errTimeout struct {
|
||||
error
|
||||
}
|
||||
|
||||
func (errTimeout) Timeout() bool { return true }
|
||||
func (errTimeout) Temporary() bool { return true }
|
||||
func (errTimeout) Error() string { return "i/o timeout" }
|
||||
|
||||
const (
|
||||
// 16-bytes nonce for each packet
|
||||
nonceSize = 16
|
||||
@@ -42,9 +42,9 @@ const (
|
||||
acceptBacklog = 128
|
||||
)
|
||||
|
||||
const (
|
||||
errBrokenPipe = "broken pipe"
|
||||
errInvalidOperation = "invalid operation"
|
||||
var (
|
||||
errInvalidOperation = errors.New("invalid operation")
|
||||
errTimeout = errors.New("timeout")
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -72,8 +72,6 @@ type (
|
||||
// recvbuf turns packets into stream
|
||||
recvbuf []byte
|
||||
bufptr []byte
|
||||
// header extended output buffer, if has header
|
||||
ext []byte
|
||||
|
||||
// FEC codec
|
||||
fecDecoder *fecDecoder
|
||||
@@ -90,16 +88,27 @@ type (
|
||||
|
||||
// notifications
|
||||
die chan struct{} // notify current session has Closed
|
||||
dieOnce sync.Once
|
||||
chReadEvent chan struct{} // notify Read() can be called without blocking
|
||||
chWriteEvent chan struct{} // notify Write() can be called without blocking
|
||||
chReadError chan error // notify PacketConn.Read() have an error
|
||||
chWriteError chan error // notify PacketConn.Write() have an error
|
||||
|
||||
// socket error handling
|
||||
socketReadError atomic.Value
|
||||
socketWriteError atomic.Value
|
||||
chSocketReadError chan struct{}
|
||||
chSocketWriteError chan struct{}
|
||||
socketReadErrorOnce sync.Once
|
||||
socketWriteErrorOnce sync.Once
|
||||
|
||||
// nonce generator
|
||||
nonce Entropy
|
||||
|
||||
isClosed bool // flag the session has Closed
|
||||
mu sync.Mutex
|
||||
// packets waiting to be sent on wire
|
||||
txqueue []ipv4.Message
|
||||
xconn batchConn // for x/net
|
||||
xconnWriteError error
|
||||
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
setReadBuffer interface {
|
||||
@@ -119,14 +128,26 @@ func newUDPSession(conv uint32, dataShards, parityShards int, l *Listener, conn
|
||||
sess.nonce.Init()
|
||||
sess.chReadEvent = make(chan struct{}, 1)
|
||||
sess.chWriteEvent = make(chan struct{}, 1)
|
||||
sess.chReadError = make(chan error, 1)
|
||||
sess.chWriteError = make(chan error, 1)
|
||||
sess.chSocketReadError = make(chan struct{})
|
||||
sess.chSocketWriteError = make(chan struct{})
|
||||
sess.remote = remote
|
||||
sess.conn = conn
|
||||
sess.l = l
|
||||
sess.block = block
|
||||
sess.recvbuf = make([]byte, mtuLimit)
|
||||
|
||||
// cast to writebatch conn
|
||||
if _, ok := conn.(*net.UDPConn); ok {
|
||||
addr, err := net.ResolveUDPAddr("udp", conn.LocalAddr().String())
|
||||
if err == nil {
|
||||
if addr.IP.To4() != nil {
|
||||
sess.xconn = ipv4.NewPacketConn(conn)
|
||||
} else {
|
||||
sess.xconn = ipv6.NewPacketConn(conn)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// FEC codec initialization
|
||||
sess.fecDecoder = newFECDecoder(rxFECMulti*(dataShards+parityShards), dataShards, parityShards)
|
||||
if sess.block != nil {
|
||||
@@ -143,17 +164,12 @@ func newUDPSession(conv uint32, dataShards, parityShards int, l *Listener, conn
|
||||
sess.headerSize += fecHeaderSizePlus2
|
||||
}
|
||||
|
||||
// we only need to allocate extended packet buffer if we have the additional header
|
||||
if sess.headerSize > 0 {
|
||||
sess.ext = make([]byte, mtuLimit)
|
||||
}
|
||||
|
||||
sess.kcp = NewKCP(conv, func(buf []byte, size int) {
|
||||
if size >= IKCP_OVERHEAD {
|
||||
if size >= IKCP_OVERHEAD+sess.headerSize {
|
||||
sess.output(buf[:size])
|
||||
}
|
||||
})
|
||||
sess.kcp.SetMtu(IKCP_MTU_DEF - sess.headerSize)
|
||||
sess.kcp.ReserveBytes(sess.headerSize)
|
||||
|
||||
// register current session to the global updater,
|
||||
// which call sess.update() periodically.
|
||||
@@ -165,6 +181,7 @@ func newUDPSession(conv uint32, dataShards, parityShards int, l *Listener, conn
|
||||
} else {
|
||||
atomic.AddUint64(&DefaultSnmp.PassiveOpens, 1)
|
||||
}
|
||||
|
||||
currestab := atomic.AddUint64(&DefaultSnmp.CurrEstab, 1)
|
||||
maxconn := atomic.LoadUint64(&DefaultSnmp.MaxConn)
|
||||
if currestab > maxconn {
|
||||
@@ -186,11 +203,6 @@ func (s *UDPSession) Read(b []byte) (n int, err error) {
|
||||
return n, nil
|
||||
}
|
||||
|
||||
if s.isClosed {
|
||||
s.mu.Unlock()
|
||||
return 0, errors.New(errBrokenPipe)
|
||||
}
|
||||
|
||||
if size := s.kcp.PeekSize(); size > 0 { // peek data size from kcp
|
||||
if len(b) >= size { // receive data into 'b' directly
|
||||
s.kcp.Recv(b)
|
||||
@@ -220,7 +232,7 @@ func (s *UDPSession) Read(b []byte) (n int, err error) {
|
||||
if !s.rd.IsZero() {
|
||||
if time.Now().After(s.rd) {
|
||||
s.mu.Unlock()
|
||||
return 0, errTimeout{}
|
||||
return 0, errors.WithStack(errTimeout)
|
||||
}
|
||||
|
||||
delay := s.rd.Sub(time.Now())
|
||||
@@ -229,63 +241,66 @@ func (s *UDPSession) Read(b []byte) (n int, err error) {
|
||||
}
|
||||
s.mu.Unlock()
|
||||
|
||||
// wait for read event or timeout
|
||||
// wait for read event or timeout or error
|
||||
select {
|
||||
case <-s.chReadEvent:
|
||||
case <-c:
|
||||
case <-s.die:
|
||||
case err = <-s.chReadError:
|
||||
if timeout != nil {
|
||||
timeout.Stop()
|
||||
}
|
||||
return n, err
|
||||
}
|
||||
|
||||
if timeout != nil {
|
||||
timeout.Stop()
|
||||
case <-c:
|
||||
return 0, errors.WithStack(errTimeout)
|
||||
case <-s.chSocketReadError:
|
||||
return 0, s.socketReadError.Load().(error)
|
||||
case <-s.die:
|
||||
return 0, errors.WithStack(io.ErrClosedPipe)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Write implements net.Conn
|
||||
func (s *UDPSession) Write(b []byte) (n int, err error) {
|
||||
func (s *UDPSession) Write(b []byte) (n int, err error) { return s.WriteBuffers([][]byte{b}) }
|
||||
|
||||
// WriteBuffers write a vector of byte slices to the underlying connection
|
||||
func (s *UDPSession) WriteBuffers(v [][]byte) (n int, err error) {
|
||||
for {
|
||||
s.mu.Lock()
|
||||
if s.isClosed {
|
||||
s.mu.Unlock()
|
||||
return 0, errors.New(errBrokenPipe)
|
||||
select {
|
||||
case <-s.chSocketWriteError:
|
||||
return 0, s.socketWriteError.Load().(error)
|
||||
case <-s.die:
|
||||
return 0, errors.WithStack(io.ErrClosedPipe)
|
||||
default:
|
||||
}
|
||||
|
||||
// controls how much data will be sent to kcp core
|
||||
// to prevent the memory from exhuasting
|
||||
s.mu.Lock()
|
||||
if s.kcp.WaitSnd() < int(s.kcp.snd_wnd) {
|
||||
n = len(b)
|
||||
for {
|
||||
if len(b) <= int(s.kcp.mss) {
|
||||
s.kcp.Send(b)
|
||||
break
|
||||
} else {
|
||||
s.kcp.Send(b[:s.kcp.mss])
|
||||
b = b[s.kcp.mss:]
|
||||
for _, b := range v {
|
||||
n += len(b)
|
||||
for {
|
||||
if len(b) <= int(s.kcp.mss) {
|
||||
s.kcp.Send(b)
|
||||
break
|
||||
} else {
|
||||
s.kcp.Send(b[:s.kcp.mss])
|
||||
b = b[s.kcp.mss:]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// flush immediately if the queue is full
|
||||
if s.kcp.WaitSnd() >= int(s.kcp.snd_wnd) || !s.writeDelay {
|
||||
s.kcp.flush(false)
|
||||
s.uncork()
|
||||
}
|
||||
s.mu.Unlock()
|
||||
atomic.AddUint64(&DefaultSnmp.BytesSent, uint64(n))
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// deadline for current writing operation
|
||||
var timeout *time.Timer
|
||||
var c <-chan time.Time
|
||||
if !s.wd.IsZero() {
|
||||
if time.Now().After(s.wd) {
|
||||
s.mu.Unlock()
|
||||
return 0, errTimeout{}
|
||||
return 0, errors.WithStack(errTimeout)
|
||||
}
|
||||
delay := s.wd.Sub(time.Now())
|
||||
timeout = time.NewTimer(delay)
|
||||
@@ -293,44 +308,52 @@ func (s *UDPSession) Write(b []byte) (n int, err error) {
|
||||
}
|
||||
s.mu.Unlock()
|
||||
|
||||
// wait for write event or timeout
|
||||
select {
|
||||
case <-s.chWriteEvent:
|
||||
case <-c:
|
||||
case <-s.die:
|
||||
case err = <-s.chWriteError:
|
||||
if timeout != nil {
|
||||
timeout.Stop()
|
||||
}
|
||||
return n, err
|
||||
}
|
||||
|
||||
if timeout != nil {
|
||||
timeout.Stop()
|
||||
case <-c:
|
||||
return 0, errors.WithStack(errTimeout)
|
||||
case <-s.chSocketWriteError:
|
||||
return 0, s.socketWriteError.Load().(error)
|
||||
case <-s.die:
|
||||
return 0, errors.WithStack(io.ErrClosedPipe)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// uncork sends data in txqueue if there is any
|
||||
func (s *UDPSession) uncork() {
|
||||
if len(s.txqueue) > 0 {
|
||||
s.tx(s.txqueue)
|
||||
s.txqueue = s.txqueue[:0]
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Close closes the connection.
|
||||
func (s *UDPSession) Close() error {
|
||||
// remove current session from updater & listener(if necessary)
|
||||
updater.removeSession(s)
|
||||
if s.l != nil { // notify listener
|
||||
s.l.closeSession(s.remote)
|
||||
}
|
||||
var once bool
|
||||
s.dieOnce.Do(func() {
|
||||
close(s.die)
|
||||
once = true
|
||||
})
|
||||
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
if s.isClosed {
|
||||
return errors.New(errBrokenPipe)
|
||||
if once {
|
||||
// remove from updater
|
||||
updater.removeSession(s)
|
||||
atomic.AddUint64(&DefaultSnmp.CurrEstab, ^uint64(0))
|
||||
|
||||
if s.l != nil { // belongs to listener
|
||||
s.l.closeSession(s.remote)
|
||||
return nil
|
||||
} else { // client socket close
|
||||
return s.conn.Close()
|
||||
}
|
||||
} else {
|
||||
return errors.WithStack(io.ErrClosedPipe)
|
||||
}
|
||||
close(s.die)
|
||||
s.isClosed = true
|
||||
atomic.AddUint64(&DefaultSnmp.CurrEstab, ^uint64(0))
|
||||
if s.l == nil { // client socket close
|
||||
return s.conn.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// LocalAddr returns the local network address. The Addr returned is shared by all invocations of LocalAddr, so do not modify it.
|
||||
@@ -390,7 +413,7 @@ func (s *UDPSession) SetMtu(mtu int) bool {
|
||||
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
s.kcp.SetMtu(mtu - s.headerSize)
|
||||
s.kcp.SetMtu(mtu)
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -412,7 +435,9 @@ func (s *UDPSession) SetACKNoDelay(nodelay bool) {
|
||||
s.ackNoDelay = nodelay
|
||||
}
|
||||
|
||||
// SetDUP duplicates udp packets for kcp output, for testing purpose only
|
||||
// (deprecated)
|
||||
//
|
||||
// SetDUP duplicates udp packets for kcp output.
|
||||
func (s *UDPSession) SetDUP(dup int) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
@@ -427,19 +452,29 @@ func (s *UDPSession) SetNoDelay(nodelay, interval, resend, nc int) {
|
||||
s.kcp.NoDelay(nodelay, interval, resend, nc)
|
||||
}
|
||||
|
||||
// SetDSCP sets the 6bit DSCP field of IP header, no effect if it's accepted from Listener
|
||||
// SetDSCP sets the 6bit DSCP field in IPv4 header, or 8bit Traffic Class in IPv6 header.
|
||||
//
|
||||
// It has no effect if it's accepted from Listener.
|
||||
func (s *UDPSession) SetDSCP(dscp int) error {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
if s.l == nil {
|
||||
if nc, ok := s.conn.(net.Conn); ok {
|
||||
if err := ipv4.NewConn(nc).SetTOS(dscp << 2); err != nil {
|
||||
return ipv6.NewConn(nc).SetTrafficClass(dscp)
|
||||
}
|
||||
if s.l != nil {
|
||||
return errInvalidOperation
|
||||
}
|
||||
if nc, ok := s.conn.(net.Conn); ok {
|
||||
var succeed bool
|
||||
if err := ipv4.NewConn(nc).SetTOS(dscp << 2); err == nil {
|
||||
succeed = true
|
||||
}
|
||||
if err := ipv6.NewConn(nc).SetTrafficClass(dscp); err == nil {
|
||||
succeed = true
|
||||
}
|
||||
|
||||
if succeed {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return errors.New(errInvalidOperation)
|
||||
return errInvalidOperation
|
||||
}
|
||||
|
||||
// SetReadBuffer sets the socket read buffer, no effect if it's accepted from Listener
|
||||
@@ -451,7 +486,7 @@ func (s *UDPSession) SetReadBuffer(bytes int) error {
|
||||
return nc.SetReadBuffer(bytes)
|
||||
}
|
||||
}
|
||||
return errors.New(errInvalidOperation)
|
||||
return errInvalidOperation
|
||||
}
|
||||
|
||||
// SetWriteBuffer sets the socket write buffer, no effect if it's accepted from Listener
|
||||
@@ -463,37 +498,29 @@ func (s *UDPSession) SetWriteBuffer(bytes int) error {
|
||||
return nc.SetWriteBuffer(bytes)
|
||||
}
|
||||
}
|
||||
return errors.New(errInvalidOperation)
|
||||
return errInvalidOperation
|
||||
}
|
||||
|
||||
// post-processing for sending a packet from kcp core
|
||||
// steps:
|
||||
// 0. Header extending
|
||||
// 1. FEC packet generation
|
||||
// 2. CRC32 integrity
|
||||
// 3. Encryption
|
||||
// 4. WriteTo kernel
|
||||
// 4. TxQueue
|
||||
func (s *UDPSession) output(buf []byte) {
|
||||
var ecc [][]byte
|
||||
|
||||
// 0. extend buf's header space(if necessary)
|
||||
ext := buf
|
||||
if s.headerSize > 0 {
|
||||
ext = s.ext[:s.headerSize+len(buf)]
|
||||
copy(ext[s.headerSize:], buf)
|
||||
}
|
||||
|
||||
// 1. FEC encoding
|
||||
if s.fecEncoder != nil {
|
||||
ecc = s.fecEncoder.encode(ext)
|
||||
ecc = s.fecEncoder.encode(buf)
|
||||
}
|
||||
|
||||
// 2&3. crc32 & encryption
|
||||
if s.block != nil {
|
||||
s.nonce.Fill(ext[:nonceSize])
|
||||
checksum := crc32.ChecksumIEEE(ext[cryptHeaderSize:])
|
||||
binary.LittleEndian.PutUint32(ext[nonceSize:], checksum)
|
||||
s.block.Encrypt(ext, ext)
|
||||
s.nonce.Fill(buf[:nonceSize])
|
||||
checksum := crc32.ChecksumIEEE(buf[cryptHeaderSize:])
|
||||
binary.LittleEndian.PutUint32(buf[nonceSize:], checksum)
|
||||
s.block.Encrypt(buf, buf)
|
||||
|
||||
for k := range ecc {
|
||||
s.nonce.Fill(ecc[k][:nonceSize])
|
||||
@@ -503,28 +530,23 @@ func (s *UDPSession) output(buf []byte) {
|
||||
}
|
||||
}
|
||||
|
||||
// 4. WriteTo kernel
|
||||
nbytes := 0
|
||||
npkts := 0
|
||||
// 4. TxQueue
|
||||
var msg ipv4.Message
|
||||
for i := 0; i < s.dup+1; i++ {
|
||||
if n, err := s.conn.WriteTo(ext, s.remote); err == nil {
|
||||
nbytes += n
|
||||
npkts++
|
||||
} else {
|
||||
s.notifyWriteError(err)
|
||||
}
|
||||
bts := xmitBuf.Get().([]byte)[:len(buf)]
|
||||
copy(bts, buf)
|
||||
msg.Buffers = [][]byte{bts}
|
||||
msg.Addr = s.remote
|
||||
s.txqueue = append(s.txqueue, msg)
|
||||
}
|
||||
|
||||
for k := range ecc {
|
||||
if n, err := s.conn.WriteTo(ecc[k], s.remote); err == nil {
|
||||
nbytes += n
|
||||
npkts++
|
||||
} else {
|
||||
s.notifyWriteError(err)
|
||||
}
|
||||
bts := xmitBuf.Get().([]byte)[:len(ecc[k])]
|
||||
copy(bts, ecc[k])
|
||||
msg.Buffers = [][]byte{bts}
|
||||
msg.Addr = s.remote
|
||||
s.txqueue = append(s.txqueue, msg)
|
||||
}
|
||||
atomic.AddUint64(&DefaultSnmp.OutPkts, uint64(npkts))
|
||||
atomic.AddUint64(&DefaultSnmp.OutBytes, uint64(nbytes))
|
||||
}
|
||||
|
||||
// kcp update, returns interval for next calling
|
||||
@@ -535,6 +557,7 @@ func (s *UDPSession) update() (interval time.Duration) {
|
||||
if s.kcp.WaitSnd() < waitsnd {
|
||||
s.notifyWriteEvent()
|
||||
}
|
||||
s.uncork()
|
||||
s.mu.Unlock()
|
||||
return
|
||||
}
|
||||
@@ -556,10 +579,39 @@ func (s *UDPSession) notifyWriteEvent() {
|
||||
}
|
||||
}
|
||||
|
||||
func (s *UDPSession) notifyReadError(err error) {
|
||||
s.socketReadErrorOnce.Do(func() {
|
||||
s.socketReadError.Store(err)
|
||||
close(s.chSocketReadError)
|
||||
})
|
||||
}
|
||||
|
||||
func (s *UDPSession) notifyWriteError(err error) {
|
||||
select {
|
||||
case s.chWriteError <- err:
|
||||
default:
|
||||
s.socketWriteErrorOnce.Do(func() {
|
||||
s.socketWriteError.Store(err)
|
||||
close(s.chSocketWriteError)
|
||||
})
|
||||
}
|
||||
|
||||
// packet input stage
|
||||
func (s *UDPSession) packetInput(data []byte) {
|
||||
dataValid := false
|
||||
if s.block != nil {
|
||||
s.block.Decrypt(data, data)
|
||||
data = data[nonceSize:]
|
||||
checksum := crc32.ChecksumIEEE(data[crcSize:])
|
||||
if checksum == binary.LittleEndian.Uint32(data) {
|
||||
data = data[crcSize:]
|
||||
dataValid = true
|
||||
} else {
|
||||
atomic.AddUint64(&DefaultSnmp.InCsumErrors, 1)
|
||||
}
|
||||
} else if s.block == nil {
|
||||
dataValid = true
|
||||
}
|
||||
|
||||
if dataValid {
|
||||
s.kcpInput(data)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -568,16 +620,16 @@ func (s *UDPSession) kcpInput(data []byte) {
|
||||
|
||||
if s.fecDecoder != nil {
|
||||
if len(data) > fecHeaderSize { // must be larger than fec header size
|
||||
f := s.fecDecoder.decodeBytes(data)
|
||||
if f.flag == typeData || f.flag == typeFEC { // header check
|
||||
if f.flag == typeFEC {
|
||||
f := fecPacket(data)
|
||||
if f.flag() == typeData || f.flag() == typeParity { // header check
|
||||
if f.flag() == typeParity {
|
||||
fecParityShards++
|
||||
}
|
||||
recovers := s.fecDecoder.decode(f)
|
||||
|
||||
s.mu.Lock()
|
||||
waitsnd := s.kcp.WaitSnd()
|
||||
if f.flag == typeData {
|
||||
if f.flag() == typeData {
|
||||
if ret := s.kcp.Input(data[fecHeaderSizePlus2:], true, s.ackNoDelay); ret != 0 {
|
||||
kcpInErrors++
|
||||
}
|
||||
@@ -598,6 +650,8 @@ func (s *UDPSession) kcpInput(data []byte) {
|
||||
} else {
|
||||
fecErrs++
|
||||
}
|
||||
// recycle the recovers
|
||||
xmitBuf.Put(r)
|
||||
}
|
||||
|
||||
// to notify the readers to receive the data
|
||||
@@ -608,6 +662,7 @@ func (s *UDPSession) kcpInput(data []byte) {
|
||||
if s.kcp.WaitSnd() < waitsnd {
|
||||
s.notifyWriteEvent()
|
||||
}
|
||||
s.uncork()
|
||||
s.mu.Unlock()
|
||||
} else {
|
||||
atomic.AddUint64(&DefaultSnmp.InErrs, 1)
|
||||
@@ -627,6 +682,7 @@ func (s *UDPSession) kcpInput(data []byte) {
|
||||
if s.kcp.WaitSnd() < waitsnd {
|
||||
s.notifyWriteEvent()
|
||||
}
|
||||
s.uncork()
|
||||
s.mu.Unlock()
|
||||
}
|
||||
|
||||
@@ -644,50 +700,7 @@ func (s *UDPSession) kcpInput(data []byte) {
|
||||
if fecRecovered > 0 {
|
||||
atomic.AddUint64(&DefaultSnmp.FECRecovered, fecRecovered)
|
||||
}
|
||||
}
|
||||
|
||||
// the read loop for a client session
|
||||
func (s *UDPSession) readLoop() {
|
||||
buf := make([]byte, mtuLimit)
|
||||
var src string
|
||||
for {
|
||||
if n, addr, err := s.conn.ReadFrom(buf); err == nil {
|
||||
// make sure the packet is from the same source
|
||||
if src == "" { // set source address
|
||||
src = addr.String()
|
||||
} else if addr.String() != src {
|
||||
atomic.AddUint64(&DefaultSnmp.InErrs, 1)
|
||||
continue
|
||||
}
|
||||
|
||||
if n >= s.headerSize+IKCP_OVERHEAD {
|
||||
data := buf[:n]
|
||||
dataValid := false
|
||||
if s.block != nil {
|
||||
s.block.Decrypt(data, data)
|
||||
data = data[nonceSize:]
|
||||
checksum := crc32.ChecksumIEEE(data[crcSize:])
|
||||
if checksum == binary.LittleEndian.Uint32(data) {
|
||||
data = data[crcSize:]
|
||||
dataValid = true
|
||||
} else {
|
||||
atomic.AddUint64(&DefaultSnmp.InCsumErrors, 1)
|
||||
}
|
||||
} else if s.block == nil {
|
||||
dataValid = true
|
||||
}
|
||||
|
||||
if dataValid {
|
||||
s.kcpInput(data)
|
||||
}
|
||||
} else {
|
||||
atomic.AddUint64(&DefaultSnmp.InErrs, 1)
|
||||
}
|
||||
} else {
|
||||
s.chReadError <- err
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type (
|
||||
@@ -704,98 +717,91 @@ type (
|
||||
chAccepts chan *UDPSession // Listen() backlog
|
||||
chSessionClosed chan net.Addr // session close queue
|
||||
headerSize int // the additional header to a KCP frame
|
||||
die chan struct{} // notify the listener has closed
|
||||
rd atomic.Value // read deadline for Accept()
|
||||
wd atomic.Value
|
||||
|
||||
die chan struct{} // notify the listener has closed
|
||||
dieOnce sync.Once
|
||||
|
||||
// socket error handling
|
||||
socketReadError atomic.Value
|
||||
chSocketReadError chan struct{}
|
||||
socketReadErrorOnce sync.Once
|
||||
|
||||
rd atomic.Value // read deadline for Accept()
|
||||
}
|
||||
)
|
||||
|
||||
// monitor incoming data for all connections of server
|
||||
func (l *Listener) monitor() {
|
||||
// a cache for session object last used
|
||||
var lastAddr string
|
||||
var lastSession *UDPSession
|
||||
buf := make([]byte, mtuLimit)
|
||||
for {
|
||||
if n, from, err := l.conn.ReadFrom(buf); err == nil {
|
||||
if n >= l.headerSize+IKCP_OVERHEAD {
|
||||
data := buf[:n]
|
||||
dataValid := false
|
||||
if l.block != nil {
|
||||
l.block.Decrypt(data, data)
|
||||
data = data[nonceSize:]
|
||||
checksum := crc32.ChecksumIEEE(data[crcSize:])
|
||||
if checksum == binary.LittleEndian.Uint32(data) {
|
||||
data = data[crcSize:]
|
||||
dataValid = true
|
||||
} else {
|
||||
atomic.AddUint64(&DefaultSnmp.InCsumErrors, 1)
|
||||
// packet input stage
|
||||
func (l *Listener) packetInput(data []byte, addr net.Addr) {
|
||||
dataValid := false
|
||||
if l.block != nil {
|
||||
l.block.Decrypt(data, data)
|
||||
data = data[nonceSize:]
|
||||
checksum := crc32.ChecksumIEEE(data[crcSize:])
|
||||
if checksum == binary.LittleEndian.Uint32(data) {
|
||||
data = data[crcSize:]
|
||||
dataValid = true
|
||||
} else {
|
||||
atomic.AddUint64(&DefaultSnmp.InCsumErrors, 1)
|
||||
}
|
||||
} else if l.block == nil {
|
||||
dataValid = true
|
||||
}
|
||||
|
||||
if dataValid {
|
||||
l.sessionLock.Lock()
|
||||
s, ok := l.sessions[addr.String()]
|
||||
l.sessionLock.Unlock()
|
||||
|
||||
if !ok { // new address:port
|
||||
if len(l.chAccepts) < cap(l.chAccepts) { // do not let the new sessions overwhelm accept queue
|
||||
var conv uint32
|
||||
convValid := false
|
||||
if l.fecDecoder != nil {
|
||||
isfec := binary.LittleEndian.Uint16(data[4:])
|
||||
if isfec == typeData {
|
||||
conv = binary.LittleEndian.Uint32(data[fecHeaderSizePlus2:])
|
||||
convValid = true
|
||||
}
|
||||
} else if l.block == nil {
|
||||
dataValid = true
|
||||
} else {
|
||||
conv = binary.LittleEndian.Uint32(data)
|
||||
convValid = true
|
||||
}
|
||||
|
||||
if dataValid {
|
||||
addr := from.String()
|
||||
var s *UDPSession
|
||||
var ok bool
|
||||
|
||||
// the packets received from an address always come in batch,
|
||||
// cache the session for next packet, without querying map.
|
||||
if addr == lastAddr {
|
||||
s, ok = lastSession, true
|
||||
} else {
|
||||
l.sessionLock.Lock()
|
||||
if s, ok = l.sessions[addr]; ok {
|
||||
lastSession = s
|
||||
lastAddr = addr
|
||||
}
|
||||
l.sessionLock.Unlock()
|
||||
}
|
||||
|
||||
if !ok { // new session
|
||||
if len(l.chAccepts) < cap(l.chAccepts) { // do not let the new sessions overwhelm accept queue
|
||||
var conv uint32
|
||||
convValid := false
|
||||
if l.fecDecoder != nil {
|
||||
isfec := binary.LittleEndian.Uint16(data[4:])
|
||||
if isfec == typeData {
|
||||
conv = binary.LittleEndian.Uint32(data[fecHeaderSizePlus2:])
|
||||
convValid = true
|
||||
}
|
||||
} else {
|
||||
conv = binary.LittleEndian.Uint32(data)
|
||||
convValid = true
|
||||
}
|
||||
|
||||
if convValid { // creates a new session only if the 'conv' field in kcp is accessible
|
||||
s := newUDPSession(conv, l.dataShards, l.parityShards, l, l.conn, from, l.block)
|
||||
s.kcpInput(data)
|
||||
l.sessionLock.Lock()
|
||||
l.sessions[addr] = s
|
||||
l.sessionLock.Unlock()
|
||||
l.chAccepts <- s
|
||||
}
|
||||
}
|
||||
} else {
|
||||
s.kcpInput(data)
|
||||
}
|
||||
if convValid { // creates a new session only if the 'conv' field in kcp is accessible
|
||||
s := newUDPSession(conv, l.dataShards, l.parityShards, l, l.conn, addr, l.block)
|
||||
s.kcpInput(data)
|
||||
l.sessionLock.Lock()
|
||||
l.sessions[addr.String()] = s
|
||||
l.sessionLock.Unlock()
|
||||
l.chAccepts <- s
|
||||
}
|
||||
} else {
|
||||
atomic.AddUint64(&DefaultSnmp.InErrs, 1)
|
||||
}
|
||||
} else {
|
||||
return
|
||||
s.kcpInput(data)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (l *Listener) notifyReadError(err error) {
|
||||
l.socketReadErrorOnce.Do(func() {
|
||||
l.socketReadError.Store(err)
|
||||
close(l.chSocketReadError)
|
||||
|
||||
// propagate read error to all sessions
|
||||
l.sessionLock.Lock()
|
||||
for _, s := range l.sessions {
|
||||
s.notifyReadError(err)
|
||||
}
|
||||
l.sessionLock.Unlock()
|
||||
})
|
||||
}
|
||||
|
||||
// SetReadBuffer sets the socket read buffer for the Listener
|
||||
func (l *Listener) SetReadBuffer(bytes int) error {
|
||||
if nc, ok := l.conn.(setReadBuffer); ok {
|
||||
return nc.SetReadBuffer(bytes)
|
||||
}
|
||||
return errors.New(errInvalidOperation)
|
||||
return errInvalidOperation
|
||||
}
|
||||
|
||||
// SetWriteBuffer sets the socket write buffer for the Listener
|
||||
@@ -803,18 +809,25 @@ func (l *Listener) SetWriteBuffer(bytes int) error {
|
||||
if nc, ok := l.conn.(setWriteBuffer); ok {
|
||||
return nc.SetWriteBuffer(bytes)
|
||||
}
|
||||
return errors.New(errInvalidOperation)
|
||||
return errInvalidOperation
|
||||
}
|
||||
|
||||
// SetDSCP sets the 6bit DSCP field of IP header
|
||||
// SetDSCP sets the 6bit DSCP field in IPv4 header, or 8bit Traffic Class in IPv6 header.
|
||||
func (l *Listener) SetDSCP(dscp int) error {
|
||||
if nc, ok := l.conn.(net.Conn); ok {
|
||||
if err := ipv4.NewConn(nc).SetTOS(dscp << 2); err != nil {
|
||||
return ipv6.NewConn(nc).SetTrafficClass(dscp)
|
||||
var succeed bool
|
||||
if err := ipv4.NewConn(nc).SetTOS(dscp << 2); err == nil {
|
||||
succeed = true
|
||||
}
|
||||
if err := ipv6.NewConn(nc).SetTrafficClass(dscp); err == nil {
|
||||
succeed = true
|
||||
}
|
||||
|
||||
if succeed {
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return errors.New(errInvalidOperation)
|
||||
return errInvalidOperation
|
||||
}
|
||||
|
||||
// Accept implements the Accept method in the Listener interface; it waits for the next call and returns a generic Conn.
|
||||
@@ -831,11 +844,13 @@ func (l *Listener) AcceptKCP() (*UDPSession, error) {
|
||||
|
||||
select {
|
||||
case <-timeout:
|
||||
return nil, &errTimeout{}
|
||||
return nil, errors.WithStack(errTimeout)
|
||||
case c := <-l.chAccepts:
|
||||
return c, nil
|
||||
case <-l.chSocketReadError:
|
||||
return nil, l.socketReadError.Load().(error)
|
||||
case <-l.die:
|
||||
return nil, errors.New(errBrokenPipe)
|
||||
return nil, errors.WithStack(io.ErrClosedPipe)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -853,15 +868,21 @@ func (l *Listener) SetReadDeadline(t time.Time) error {
|
||||
}
|
||||
|
||||
// SetWriteDeadline implements the Conn SetWriteDeadline method.
|
||||
func (l *Listener) SetWriteDeadline(t time.Time) error {
|
||||
l.wd.Store(t)
|
||||
return nil
|
||||
}
|
||||
func (l *Listener) SetWriteDeadline(t time.Time) error { return errInvalidOperation }
|
||||
|
||||
// Close stops listening on the UDP address. Already Accepted connections are not closed.
|
||||
// Close stops listening on the UDP address, and closes the socket
|
||||
func (l *Listener) Close() error {
|
||||
close(l.die)
|
||||
return l.conn.Close()
|
||||
var once bool
|
||||
l.dieOnce.Do(func() {
|
||||
close(l.die)
|
||||
once = true
|
||||
})
|
||||
|
||||
if once {
|
||||
return l.conn.Close()
|
||||
} else {
|
||||
return errors.WithStack(io.ErrClosedPipe)
|
||||
}
|
||||
}
|
||||
|
||||
// closeSession notify the listener that a session has closed
|
||||
@@ -881,16 +902,21 @@ func (l *Listener) Addr() net.Addr { return l.conn.LocalAddr() }
|
||||
// Listen listens for incoming KCP packets addressed to the local address laddr on the network "udp",
|
||||
func Listen(laddr string) (net.Listener, error) { return ListenWithOptions(laddr, nil, 0, 0) }
|
||||
|
||||
// ListenWithOptions listens for incoming KCP packets addressed to the local address laddr on the network "udp" with packet encryption,
|
||||
// dataShards, parityShards defines Reed-Solomon Erasure Coding parameters
|
||||
// ListenWithOptions listens for incoming KCP packets addressed to the local address laddr on the network "udp" with packet encryption.
|
||||
//
|
||||
// 'block' is the block encryption algorithm to encrypt packets.
|
||||
//
|
||||
// 'dataShards', 'parityShards' specifiy how many parity packets will be generated following the data packets.
|
||||
//
|
||||
// Check https://github.com/klauspost/reedsolomon for details
|
||||
func ListenWithOptions(laddr string, block BlockCrypt, dataShards, parityShards int) (*Listener, error) {
|
||||
udpaddr, err := net.ResolveUDPAddr("udp", laddr)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "net.ResolveUDPAddr")
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
conn, err := net.ListenUDP("udp", udpaddr)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "net.ListenUDP")
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
return ServeConn(block, dataShards, parityShards, conn)
|
||||
@@ -908,6 +934,7 @@ func ServeConn(block BlockCrypt, dataShards, parityShards int, conn net.PacketCo
|
||||
l.parityShards = parityShards
|
||||
l.block = block
|
||||
l.fecDecoder = newFECDecoder(rxFECMulti*(dataShards+parityShards), dataShards, parityShards)
|
||||
l.chSocketReadError = make(chan struct{})
|
||||
|
||||
// calculate header size
|
||||
if l.block != nil {
|
||||
@@ -921,15 +948,21 @@ func ServeConn(block BlockCrypt, dataShards, parityShards int, conn net.PacketCo
|
||||
return l, nil
|
||||
}
|
||||
|
||||
// Dial connects to the remote address "raddr" on the network "udp"
|
||||
// Dial connects to the remote address "raddr" on the network "udp" without encryption and FEC
|
||||
func Dial(raddr string) (net.Conn, error) { return DialWithOptions(raddr, nil, 0, 0) }
|
||||
|
||||
// DialWithOptions connects to the remote address "raddr" on the network "udp" with packet encryption
|
||||
//
|
||||
// 'block' is the block encryption algorithm to encrypt packets.
|
||||
//
|
||||
// 'dataShards', 'parityShards' specifiy how many parity packets will be generated following the data packets.
|
||||
//
|
||||
// Check https://github.com/klauspost/reedsolomon for details
|
||||
func DialWithOptions(raddr string, block BlockCrypt, dataShards, parityShards int) (*UDPSession, error) {
|
||||
// network type detection
|
||||
udpaddr, err := net.ResolveUDPAddr("udp", raddr)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "net.ResolveUDPAddr")
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
network := "udp4"
|
||||
if udpaddr.IP.To4() == nil {
|
||||
@@ -938,30 +971,33 @@ func DialWithOptions(raddr string, block BlockCrypt, dataShards, parityShards in
|
||||
|
||||
conn, err := net.ListenUDP(network, nil)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "net.DialUDP")
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
return NewConn(raddr, block, dataShards, parityShards, conn)
|
||||
}
|
||||
|
||||
// NewConn3 establishes a session and talks KCP protocol over a packet connection.
|
||||
func NewConn3(convid uint32, raddr net.Addr, block BlockCrypt, dataShards, parityShards int, conn net.PacketConn) (*UDPSession, error) {
|
||||
return newUDPSession(convid, dataShards, parityShards, nil, conn, raddr, block), nil
|
||||
}
|
||||
|
||||
// NewConn2 establishes a session and talks KCP protocol over a packet connection.
|
||||
func NewConn2(raddr net.Addr, block BlockCrypt, dataShards, parityShards int, conn net.PacketConn) (*UDPSession, error) {
|
||||
var convid uint32
|
||||
binary.Read(rand.Reader, binary.LittleEndian, &convid)
|
||||
return NewConn3(convid, raddr, block, dataShards, parityShards, conn)
|
||||
}
|
||||
|
||||
// NewConn establishes a session and talks KCP protocol over a packet connection.
|
||||
func NewConn(raddr string, block BlockCrypt, dataShards, parityShards int, conn net.PacketConn) (*UDPSession, error) {
|
||||
udpaddr, err := net.ResolveUDPAddr("udp", raddr)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "net.ResolveUDPAddr")
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
var convid uint32
|
||||
binary.Read(rand.Reader, binary.LittleEndian, &convid)
|
||||
return newUDPSession(convid, dataShards, parityShards, nil, conn, udpaddr, block), nil
|
||||
return NewConn2(udpaddr, block, dataShards, parityShards, conn)
|
||||
}
|
||||
|
||||
// monotonic reference time point
|
||||
var refTime time.Time = time.Now()
|
||||
|
||||
// currentMs returns current elasped monotonic milliseconds since program startup
|
||||
func currentMs() uint32 { return uint32(time.Now().Sub(refTime) / time.Millisecond) }
|
||||
|
||||
func NewConnEx(convid uint32, connected bool, raddr string, block BlockCrypt, dataShards, parityShards int, conn *net.UDPConn) (*UDPSession, error) {
|
||||
udpaddr, err := net.ResolveUDPAddr("udp", raddr)
|
||||
if err != nil {
|
||||
|
||||
25
vendor/github.com/fatedier/kcp-go/tx.go
generated
vendored
Normal file
25
vendor/github.com/fatedier/kcp-go/tx.go
generated
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
package kcp
|
||||
|
||||
import (
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"golang.org/x/net/ipv4"
|
||||
)
|
||||
|
||||
func (s *UDPSession) defaultTx(txqueue []ipv4.Message) {
|
||||
nbytes := 0
|
||||
npkts := 0
|
||||
for k := range txqueue {
|
||||
if n, err := s.conn.WriteTo(txqueue[k].Buffers[0], txqueue[k].Addr); err == nil {
|
||||
nbytes += n
|
||||
npkts++
|
||||
xmitBuf.Put(txqueue[k].Buffers[0])
|
||||
} else {
|
||||
s.notifyWriteError(errors.WithStack(err))
|
||||
break
|
||||
}
|
||||
}
|
||||
atomic.AddUint64(&DefaultSnmp.OutPkts, uint64(npkts))
|
||||
atomic.AddUint64(&DefaultSnmp.OutBytes, uint64(nbytes))
|
||||
}
|
||||
11
vendor/github.com/fatedier/kcp-go/tx_generic.go
generated
vendored
Normal file
11
vendor/github.com/fatedier/kcp-go/tx_generic.go
generated
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
// +build !linux
|
||||
|
||||
package kcp
|
||||
|
||||
import (
|
||||
"golang.org/x/net/ipv4"
|
||||
)
|
||||
|
||||
func (s *UDPSession) tx(txqueue []ipv4.Message) {
|
||||
s.defaultTx(txqueue)
|
||||
}
|
||||
52
vendor/github.com/fatedier/kcp-go/tx_linux.go
generated
vendored
Normal file
52
vendor/github.com/fatedier/kcp-go/tx_linux.go
generated
vendored
Normal file
@@ -0,0 +1,52 @@
|
||||
// +build linux
|
||||
|
||||
package kcp
|
||||
|
||||
import (
|
||||
"net"
|
||||
"os"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"golang.org/x/net/ipv4"
|
||||
)
|
||||
|
||||
func (s *UDPSession) tx(txqueue []ipv4.Message) {
|
||||
// default version
|
||||
if s.xconn == nil || s.xconnWriteError != nil {
|
||||
s.defaultTx(txqueue)
|
||||
return
|
||||
}
|
||||
|
||||
// x/net version
|
||||
nbytes := 0
|
||||
npkts := 0
|
||||
for len(txqueue) > 0 {
|
||||
if n, err := s.xconn.WriteBatch(txqueue, 0); err == nil {
|
||||
for k := range txqueue[:n] {
|
||||
nbytes += len(txqueue[k].Buffers[0])
|
||||
xmitBuf.Put(txqueue[k].Buffers[0])
|
||||
}
|
||||
npkts += n
|
||||
txqueue = txqueue[n:]
|
||||
} else {
|
||||
// compatibility issue:
|
||||
// for linux kernel<=2.6.32, support for sendmmsg is not available
|
||||
// an error of type os.SyscallError will be returned
|
||||
if operr, ok := err.(*net.OpError); ok {
|
||||
if se, ok := operr.Err.(*os.SyscallError); ok {
|
||||
if se.Syscall == "sendmmsg" {
|
||||
s.xconnWriteError = se
|
||||
s.defaultTx(txqueue)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
s.notifyWriteError(errors.WithStack(err))
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
atomic.AddUint64(&DefaultSnmp.OutPkts, uint64(npkts))
|
||||
atomic.AddUint64(&DefaultSnmp.OutBytes, uint64(nbytes))
|
||||
}
|
||||
8
vendor/github.com/fatedier/kcp-go/updater.go
generated
vendored
8
vendor/github.com/fatedier/kcp-go/updater.go
generated
vendored
@@ -76,10 +76,10 @@ func (h *updateHeap) wakeup() {
|
||||
}
|
||||
|
||||
func (h *updateHeap) updateTask() {
|
||||
var timer <-chan time.Time
|
||||
timer := time.NewTimer(0)
|
||||
for {
|
||||
select {
|
||||
case <-timer:
|
||||
case <-timer.C:
|
||||
case <-h.chWakeUp:
|
||||
}
|
||||
|
||||
@@ -87,7 +87,7 @@ func (h *updateHeap) updateTask() {
|
||||
hlen := h.Len()
|
||||
for i := 0; i < hlen; i++ {
|
||||
entry := &h.entries[0]
|
||||
if time.Now().After(entry.ts) {
|
||||
if !time.Now().Before(entry.ts) {
|
||||
interval := entry.s.update()
|
||||
entry.ts = time.Now().Add(interval)
|
||||
heap.Fix(h, 0)
|
||||
@@ -97,7 +97,7 @@ func (h *updateHeap) updateTask() {
|
||||
}
|
||||
|
||||
if hlen > 0 {
|
||||
timer = time.After(h.entries[0].ts.Sub(time.Now()))
|
||||
timer.Reset(h.entries[0].ts.Sub(time.Now()))
|
||||
}
|
||||
h.mu.Unlock()
|
||||
}
|
||||
|
||||
19
vendor/github.com/gorilla/context/.travis.yml
generated
vendored
19
vendor/github.com/gorilla/context/.travis.yml
generated
vendored
@@ -1,19 +0,0 @@
|
||||
language: go
|
||||
sudo: false
|
||||
|
||||
matrix:
|
||||
include:
|
||||
- go: 1.3
|
||||
- go: 1.4
|
||||
- go: 1.5
|
||||
- go: 1.6
|
||||
- go: 1.7
|
||||
- go: tip
|
||||
allow_failures:
|
||||
- go: tip
|
||||
|
||||
script:
|
||||
- go get -t -v ./...
|
||||
- diff -u <(echo -n) <(gofmt -d .)
|
||||
- go vet $(go list ./... | grep -v /vendor/)
|
||||
- go test -v -race ./...
|
||||
10
vendor/github.com/gorilla/context/README.md
generated
vendored
10
vendor/github.com/gorilla/context/README.md
generated
vendored
@@ -1,10 +0,0 @@
|
||||
context
|
||||
=======
|
||||
[](https://travis-ci.org/gorilla/context)
|
||||
|
||||
gorilla/context is a general purpose registry for global request variables.
|
||||
|
||||
> Note: gorilla/context, having been born well before `context.Context` existed, does not play well
|
||||
> with the shallow copying of the request that [`http.Request.WithContext`](https://golang.org/pkg/net/http/#Request.WithContext) (added to net/http Go 1.7 onwards) performs. You should either use *just* gorilla/context, or moving forward, the new `http.Request.Context()`.
|
||||
|
||||
Read the full documentation here: http://www.gorillatoolkit.org/pkg/context
|
||||
143
vendor/github.com/gorilla/context/context.go
generated
vendored
143
vendor/github.com/gorilla/context/context.go
generated
vendored
@@ -1,143 +0,0 @@
|
||||
// Copyright 2012 The Gorilla Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package context
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
mutex sync.RWMutex
|
||||
data = make(map[*http.Request]map[interface{}]interface{})
|
||||
datat = make(map[*http.Request]int64)
|
||||
)
|
||||
|
||||
// Set stores a value for a given key in a given request.
|
||||
func Set(r *http.Request, key, val interface{}) {
|
||||
mutex.Lock()
|
||||
if data[r] == nil {
|
||||
data[r] = make(map[interface{}]interface{})
|
||||
datat[r] = time.Now().Unix()
|
||||
}
|
||||
data[r][key] = val
|
||||
mutex.Unlock()
|
||||
}
|
||||
|
||||
// Get returns a value stored for a given key in a given request.
|
||||
func Get(r *http.Request, key interface{}) interface{} {
|
||||
mutex.RLock()
|
||||
if ctx := data[r]; ctx != nil {
|
||||
value := ctx[key]
|
||||
mutex.RUnlock()
|
||||
return value
|
||||
}
|
||||
mutex.RUnlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetOk returns stored value and presence state like multi-value return of map access.
|
||||
func GetOk(r *http.Request, key interface{}) (interface{}, bool) {
|
||||
mutex.RLock()
|
||||
if _, ok := data[r]; ok {
|
||||
value, ok := data[r][key]
|
||||
mutex.RUnlock()
|
||||
return value, ok
|
||||
}
|
||||
mutex.RUnlock()
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// GetAll returns all stored values for the request as a map. Nil is returned for invalid requests.
|
||||
func GetAll(r *http.Request) map[interface{}]interface{} {
|
||||
mutex.RLock()
|
||||
if context, ok := data[r]; ok {
|
||||
result := make(map[interface{}]interface{}, len(context))
|
||||
for k, v := range context {
|
||||
result[k] = v
|
||||
}
|
||||
mutex.RUnlock()
|
||||
return result
|
||||
}
|
||||
mutex.RUnlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetAllOk returns all stored values for the request as a map and a boolean value that indicates if
|
||||
// the request was registered.
|
||||
func GetAllOk(r *http.Request) (map[interface{}]interface{}, bool) {
|
||||
mutex.RLock()
|
||||
context, ok := data[r]
|
||||
result := make(map[interface{}]interface{}, len(context))
|
||||
for k, v := range context {
|
||||
result[k] = v
|
||||
}
|
||||
mutex.RUnlock()
|
||||
return result, ok
|
||||
}
|
||||
|
||||
// Delete removes a value stored for a given key in a given request.
|
||||
func Delete(r *http.Request, key interface{}) {
|
||||
mutex.Lock()
|
||||
if data[r] != nil {
|
||||
delete(data[r], key)
|
||||
}
|
||||
mutex.Unlock()
|
||||
}
|
||||
|
||||
// Clear removes all values stored for a given request.
|
||||
//
|
||||
// This is usually called by a handler wrapper to clean up request
|
||||
// variables at the end of a request lifetime. See ClearHandler().
|
||||
func Clear(r *http.Request) {
|
||||
mutex.Lock()
|
||||
clear(r)
|
||||
mutex.Unlock()
|
||||
}
|
||||
|
||||
// clear is Clear without the lock.
|
||||
func clear(r *http.Request) {
|
||||
delete(data, r)
|
||||
delete(datat, r)
|
||||
}
|
||||
|
||||
// Purge removes request data stored for longer than maxAge, in seconds.
|
||||
// It returns the amount of requests removed.
|
||||
//
|
||||
// If maxAge <= 0, all request data is removed.
|
||||
//
|
||||
// This is only used for sanity check: in case context cleaning was not
|
||||
// properly set some request data can be kept forever, consuming an increasing
|
||||
// amount of memory. In case this is detected, Purge() must be called
|
||||
// periodically until the problem is fixed.
|
||||
func Purge(maxAge int) int {
|
||||
mutex.Lock()
|
||||
count := 0
|
||||
if maxAge <= 0 {
|
||||
count = len(data)
|
||||
data = make(map[*http.Request]map[interface{}]interface{})
|
||||
datat = make(map[*http.Request]int64)
|
||||
} else {
|
||||
min := time.Now().Unix() - int64(maxAge)
|
||||
for r := range data {
|
||||
if datat[r] < min {
|
||||
clear(r)
|
||||
count++
|
||||
}
|
||||
}
|
||||
}
|
||||
mutex.Unlock()
|
||||
return count
|
||||
}
|
||||
|
||||
// ClearHandler wraps an http.Handler and clears request values at the end
|
||||
// of a request lifetime.
|
||||
func ClearHandler(h http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
defer Clear(r)
|
||||
h.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
88
vendor/github.com/gorilla/context/doc.go
generated
vendored
88
vendor/github.com/gorilla/context/doc.go
generated
vendored
@@ -1,88 +0,0 @@
|
||||
// Copyright 2012 The Gorilla Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
/*
|
||||
Package context stores values shared during a request lifetime.
|
||||
|
||||
Note: gorilla/context, having been born well before `context.Context` existed,
|
||||
does not play well > with the shallow copying of the request that
|
||||
[`http.Request.WithContext`](https://golang.org/pkg/net/http/#Request.WithContext)
|
||||
(added to net/http Go 1.7 onwards) performs. You should either use *just*
|
||||
gorilla/context, or moving forward, the new `http.Request.Context()`.
|
||||
|
||||
For example, a router can set variables extracted from the URL and later
|
||||
application handlers can access those values, or it can be used to store
|
||||
sessions values to be saved at the end of a request. There are several
|
||||
others common uses.
|
||||
|
||||
The idea was posted by Brad Fitzpatrick to the go-nuts mailing list:
|
||||
|
||||
http://groups.google.com/group/golang-nuts/msg/e2d679d303aa5d53
|
||||
|
||||
Here's the basic usage: first define the keys that you will need. The key
|
||||
type is interface{} so a key can be of any type that supports equality.
|
||||
Here we define a key using a custom int type to avoid name collisions:
|
||||
|
||||
package foo
|
||||
|
||||
import (
|
||||
"github.com/gorilla/context"
|
||||
)
|
||||
|
||||
type key int
|
||||
|
||||
const MyKey key = 0
|
||||
|
||||
Then set a variable. Variables are bound to an http.Request object, so you
|
||||
need a request instance to set a value:
|
||||
|
||||
context.Set(r, MyKey, "bar")
|
||||
|
||||
The application can later access the variable using the same key you provided:
|
||||
|
||||
func MyHandler(w http.ResponseWriter, r *http.Request) {
|
||||
// val is "bar".
|
||||
val := context.Get(r, foo.MyKey)
|
||||
|
||||
// returns ("bar", true)
|
||||
val, ok := context.GetOk(r, foo.MyKey)
|
||||
// ...
|
||||
}
|
||||
|
||||
And that's all about the basic usage. We discuss some other ideas below.
|
||||
|
||||
Any type can be stored in the context. To enforce a given type, make the key
|
||||
private and wrap Get() and Set() to accept and return values of a specific
|
||||
type:
|
||||
|
||||
type key int
|
||||
|
||||
const mykey key = 0
|
||||
|
||||
// GetMyKey returns a value for this package from the request values.
|
||||
func GetMyKey(r *http.Request) SomeType {
|
||||
if rv := context.Get(r, mykey); rv != nil {
|
||||
return rv.(SomeType)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetMyKey sets a value for this package in the request values.
|
||||
func SetMyKey(r *http.Request, val SomeType) {
|
||||
context.Set(r, mykey, val)
|
||||
}
|
||||
|
||||
Variables must be cleared at the end of a request, to remove all values
|
||||
that were stored. This can be done in an http.Handler, after a request was
|
||||
served. Just call Clear() passing the request:
|
||||
|
||||
context.Clear(r)
|
||||
|
||||
...or use ClearHandler(), which conveniently wraps an http.Handler to clear
|
||||
variables at the end of a request lifetime.
|
||||
|
||||
The Routers from the packages gorilla/mux and gorilla/pat call Clear()
|
||||
so if you are using either of them you don't need to clear the context manually.
|
||||
*/
|
||||
package context
|
||||
23
vendor/github.com/gorilla/mux/.travis.yml
generated
vendored
23
vendor/github.com/gorilla/mux/.travis.yml
generated
vendored
@@ -1,23 +0,0 @@
|
||||
language: go
|
||||
sudo: false
|
||||
|
||||
matrix:
|
||||
include:
|
||||
- go: 1.5.x
|
||||
- go: 1.6.x
|
||||
- go: 1.7.x
|
||||
- go: 1.8.x
|
||||
- go: 1.9.x
|
||||
- go: 1.10.x
|
||||
- go: tip
|
||||
allow_failures:
|
||||
- go: tip
|
||||
|
||||
install:
|
||||
- # Skip
|
||||
|
||||
script:
|
||||
- go get -t -v ./...
|
||||
- diff -u <(echo -n) <(gofmt -d .)
|
||||
- go tool vet .
|
||||
- go test -v -race ./...
|
||||
8
vendor/github.com/gorilla/mux/AUTHORS
generated
vendored
Normal file
8
vendor/github.com/gorilla/mux/AUTHORS
generated
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
# This is the official list of gorilla/mux authors for copyright purposes.
|
||||
#
|
||||
# Please keep the list sorted.
|
||||
|
||||
Google LLC (https://opensource.google.com/)
|
||||
Kamil Kisielk <kamil@kamilkisiel.net>
|
||||
Matt Silverlock <matt@eatsleeprepeat.net>
|
||||
Rodrigo Moraes (https://github.com/moraes)
|
||||
11
vendor/github.com/gorilla/mux/ISSUE_TEMPLATE.md
generated
vendored
11
vendor/github.com/gorilla/mux/ISSUE_TEMPLATE.md
generated
vendored
@@ -1,11 +0,0 @@
|
||||
**What version of Go are you running?** (Paste the output of `go version`)
|
||||
|
||||
|
||||
**What version of gorilla/mux are you at?** (Paste the output of `git rev-parse HEAD` inside `$GOPATH/src/github.com/gorilla/mux`)
|
||||
|
||||
|
||||
**Describe your problem** (and what you have tried so far)
|
||||
|
||||
|
||||
**Paste a minimal, runnable, reproduction of your issue below** (use backticks to format it)
|
||||
|
||||
2
vendor/github.com/gorilla/mux/LICENSE
generated
vendored
2
vendor/github.com/gorilla/mux/LICENSE
generated
vendored
@@ -1,4 +1,4 @@
|
||||
Copyright (c) 2012 Rodrigo Moraes. All rights reserved.
|
||||
Copyright (c) 2012-2018 The Gorilla Authors. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
|
||||
85
vendor/github.com/gorilla/mux/README.md
generated
vendored
85
vendor/github.com/gorilla/mux/README.md
generated
vendored
@@ -2,11 +2,12 @@
|
||||
|
||||
[](https://godoc.org/github.com/gorilla/mux)
|
||||
[](https://travis-ci.org/gorilla/mux)
|
||||
[](https://circleci.com/gh/gorilla/mux)
|
||||
[](https://sourcegraph.com/github.com/gorilla/mux?badge)
|
||||
|
||||

|
||||
|
||||
http://www.gorillatoolkit.org/pkg/mux
|
||||
https://www.gorillatoolkit.org/pkg/mux
|
||||
|
||||
Package `gorilla/mux` implements a request router and dispatcher for matching incoming requests to
|
||||
their respective handler.
|
||||
@@ -29,6 +30,7 @@ The name mux stands for "HTTP request multiplexer". Like the standard `http.Serv
|
||||
* [Walking Routes](#walking-routes)
|
||||
* [Graceful Shutdown](#graceful-shutdown)
|
||||
* [Middleware](#middleware)
|
||||
* [Handling CORS Requests](#handling-cors-requests)
|
||||
* [Testing Handlers](#testing-handlers)
|
||||
* [Full Example](#full-example)
|
||||
|
||||
@@ -88,7 +90,7 @@ r := mux.NewRouter()
|
||||
// Only matches if domain is "www.example.com".
|
||||
r.Host("www.example.com")
|
||||
// Matches a dynamic subdomain.
|
||||
r.Host("{subdomain:[a-z]+}.domain.com")
|
||||
r.Host("{subdomain:[a-z]+}.example.com")
|
||||
```
|
||||
|
||||
There are several other matchers that can be added. To match path prefixes:
|
||||
@@ -238,13 +240,13 @@ This also works for host and query value variables:
|
||||
|
||||
```go
|
||||
r := mux.NewRouter()
|
||||
r.Host("{subdomain}.domain.com").
|
||||
r.Host("{subdomain}.example.com").
|
||||
Path("/articles/{category}/{id:[0-9]+}").
|
||||
Queries("filter", "{filter}").
|
||||
HandlerFunc(ArticleHandler).
|
||||
Name("article")
|
||||
|
||||
// url.String() will be "http://news.domain.com/articles/technology/42?filter=gorilla"
|
||||
// url.String() will be "http://news.example.com/articles/technology/42?filter=gorilla"
|
||||
url, err := r.Get("article").URL("subdomain", "news",
|
||||
"category", "technology",
|
||||
"id", "42",
|
||||
@@ -264,7 +266,7 @@ r.HeadersRegexp("Content-Type", "application/(text|json)")
|
||||
There's also a way to build only the URL host or path for a route: use the methods `URLHost()` or `URLPath()` instead. For the previous route, we would do:
|
||||
|
||||
```go
|
||||
// "http://news.domain.com/"
|
||||
// "http://news.example.com/"
|
||||
host, err := r.Get("article").URLHost("subdomain", "news")
|
||||
|
||||
// "/articles/technology/42"
|
||||
@@ -275,12 +277,12 @@ And if you use subrouters, host and path defined separately can be built as well
|
||||
|
||||
```go
|
||||
r := mux.NewRouter()
|
||||
s := r.Host("{subdomain}.domain.com").Subrouter()
|
||||
s := r.Host("{subdomain}.example.com").Subrouter()
|
||||
s.Path("/articles/{category}/{id:[0-9]+}").
|
||||
HandlerFunc(ArticleHandler).
|
||||
Name("article")
|
||||
|
||||
// "http://news.domain.com/articles/technology/42"
|
||||
// "http://news.example.com/articles/technology/42"
|
||||
url, err := r.Get("article").URL("subdomain", "news",
|
||||
"category", "technology",
|
||||
"id", "42")
|
||||
@@ -491,6 +493,73 @@ r.Use(amw.Middleware)
|
||||
|
||||
Note: The handler chain will be stopped if your middleware doesn't call `next.ServeHTTP()` with the corresponding parameters. This can be used to abort a request if the middleware writer wants to. Middlewares _should_ write to `ResponseWriter` if they _are_ going to terminate the request, and they _should not_ write to `ResponseWriter` if they _are not_ going to terminate it.
|
||||
|
||||
### Handling CORS Requests
|
||||
|
||||
[CORSMethodMiddleware](https://godoc.org/github.com/gorilla/mux#CORSMethodMiddleware) intends to make it easier to strictly set the `Access-Control-Allow-Methods` response header.
|
||||
|
||||
* You will still need to use your own CORS handler to set the other CORS headers such as `Access-Control-Allow-Origin`
|
||||
* The middleware will set the `Access-Control-Allow-Methods` header to all the method matchers (e.g. `r.Methods(http.MethodGet, http.MethodPut, http.MethodOptions)` -> `Access-Control-Allow-Methods: GET,PUT,OPTIONS`) on a route
|
||||
* If you do not specify any methods, then:
|
||||
> _Important_: there must be an `OPTIONS` method matcher for the middleware to set the headers.
|
||||
|
||||
Here is an example of using `CORSMethodMiddleware` along with a custom `OPTIONS` handler to set all the required CORS headers:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
func main() {
|
||||
r := mux.NewRouter()
|
||||
|
||||
// IMPORTANT: you must specify an OPTIONS method matcher for the middleware to set CORS headers
|
||||
r.HandleFunc("/foo", fooHandler).Methods(http.MethodGet, http.MethodPut, http.MethodPatch, http.MethodOptions)
|
||||
r.Use(mux.CORSMethodMiddleware(r))
|
||||
|
||||
http.ListenAndServe(":8080", r)
|
||||
}
|
||||
|
||||
func fooHandler(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Access-Control-Allow-Origin", "*")
|
||||
if r.Method == http.MethodOptions {
|
||||
return
|
||||
}
|
||||
|
||||
w.Write([]byte("foo"))
|
||||
}
|
||||
```
|
||||
|
||||
And an request to `/foo` using something like:
|
||||
|
||||
```bash
|
||||
curl localhost:8080/foo -v
|
||||
```
|
||||
|
||||
Would look like:
|
||||
|
||||
```bash
|
||||
* Trying ::1...
|
||||
* TCP_NODELAY set
|
||||
* Connected to localhost (::1) port 8080 (#0)
|
||||
> GET /foo HTTP/1.1
|
||||
> Host: localhost:8080
|
||||
> User-Agent: curl/7.59.0
|
||||
> Accept: */*
|
||||
>
|
||||
< HTTP/1.1 200 OK
|
||||
< Access-Control-Allow-Methods: GET,PUT,PATCH,OPTIONS
|
||||
< Access-Control-Allow-Origin: *
|
||||
< Date: Fri, 28 Jun 2019 20:13:30 GMT
|
||||
< Content-Length: 3
|
||||
< Content-Type: text/plain; charset=utf-8
|
||||
<
|
||||
* Connection #0 to host localhost left intact
|
||||
foo
|
||||
```
|
||||
|
||||
### Testing Handlers
|
||||
|
||||
Testing handlers in a Go web application is straightforward, and _mux_ doesn't complicate this any further. Given two files: `endpoints.go` and `endpoints_test.go`, here's how we'd test an application using _mux_.
|
||||
@@ -503,8 +572,8 @@ package main
|
||||
|
||||
func HealthCheckHandler(w http.ResponseWriter, r *http.Request) {
|
||||
// A very simple health check.
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
|
||||
// In the future we could report back on the status of our DB, or our cache
|
||||
// (e.g. Redis) by performing a simple PING, and include them in the response.
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
// +build go1.7
|
||||
|
||||
package mux
|
||||
|
||||
import (
|
||||
@@ -18,7 +16,3 @@ func contextSet(r *http.Request, key, val interface{}) *http.Request {
|
||||
|
||||
return r.WithContext(context.WithValue(r.Context(), key, val))
|
||||
}
|
||||
|
||||
func contextClear(r *http.Request) {
|
||||
return
|
||||
}
|
||||
26
vendor/github.com/gorilla/mux/context_gorilla.go
generated
vendored
26
vendor/github.com/gorilla/mux/context_gorilla.go
generated
vendored
@@ -1,26 +0,0 @@
|
||||
// +build !go1.7
|
||||
|
||||
package mux
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gorilla/context"
|
||||
)
|
||||
|
||||
func contextGet(r *http.Request, key interface{}) interface{} {
|
||||
return context.Get(r, key)
|
||||
}
|
||||
|
||||
func contextSet(r *http.Request, key, val interface{}) *http.Request {
|
||||
if val == nil {
|
||||
return r
|
||||
}
|
||||
|
||||
context.Set(r, key, val)
|
||||
return r
|
||||
}
|
||||
|
||||
func contextClear(r *http.Request) {
|
||||
context.Clear(r)
|
||||
}
|
||||
2
vendor/github.com/gorilla/mux/doc.go
generated
vendored
2
vendor/github.com/gorilla/mux/doc.go
generated
vendored
@@ -295,7 +295,7 @@ A more complex authentication middleware, which maps session token to users, cou
|
||||
r := mux.NewRouter()
|
||||
r.HandleFunc("/", handler)
|
||||
|
||||
amw := authenticationMiddleware{}
|
||||
amw := authenticationMiddleware{tokenUsers: make(map[string]string)}
|
||||
amw.Populate()
|
||||
|
||||
r.Use(amw.Middleware)
|
||||
|
||||
1
vendor/github.com/gorilla/mux/go.mod
generated
vendored
Normal file
1
vendor/github.com/gorilla/mux/go.mod
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
module github.com/gorilla/mux
|
||||
61
vendor/github.com/gorilla/mux/middleware.go
generated
vendored
61
vendor/github.com/gorilla/mux/middleware.go
generated
vendored
@@ -32,37 +32,19 @@ func (r *Router) useInterface(mw middleware) {
|
||||
r.middlewares = append(r.middlewares, mw)
|
||||
}
|
||||
|
||||
// CORSMethodMiddleware sets the Access-Control-Allow-Methods response header
|
||||
// on a request, by matching routes based only on paths. It also handles
|
||||
// OPTIONS requests, by settings Access-Control-Allow-Methods, and then
|
||||
// returning without calling the next http handler.
|
||||
// CORSMethodMiddleware automatically sets the Access-Control-Allow-Methods response header
|
||||
// on requests for routes that have an OPTIONS method matcher to all the method matchers on
|
||||
// the route. Routes that do not explicitly handle OPTIONS requests will not be processed
|
||||
// by the middleware. See examples for usage.
|
||||
func CORSMethodMiddleware(r *Router) MiddlewareFunc {
|
||||
return func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
var allMethods []string
|
||||
|
||||
err := r.Walk(func(route *Route, _ *Router, _ []*Route) error {
|
||||
for _, m := range route.matchers {
|
||||
if _, ok := m.(*routeRegexp); ok {
|
||||
if m.Match(req, &RouteMatch{}) {
|
||||
methods, err := route.GetMethods()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
allMethods = append(allMethods, methods...)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
allMethods, err := getAllMethodsForRoute(r, req)
|
||||
if err == nil {
|
||||
w.Header().Set("Access-Control-Allow-Methods", strings.Join(append(allMethods, "OPTIONS"), ","))
|
||||
|
||||
if req.Method == "OPTIONS" {
|
||||
return
|
||||
for _, v := range allMethods {
|
||||
if v == http.MethodOptions {
|
||||
w.Header().Set("Access-Control-Allow-Methods", strings.Join(allMethods, ","))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,3 +52,28 @@ func CORSMethodMiddleware(r *Router) MiddlewareFunc {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// getAllMethodsForRoute returns all the methods from method matchers matching a given
|
||||
// request.
|
||||
func getAllMethodsForRoute(r *Router, req *http.Request) ([]string, error) {
|
||||
var allMethods []string
|
||||
|
||||
err := r.Walk(func(route *Route, _ *Router, _ []*Route) error {
|
||||
for _, m := range route.matchers {
|
||||
if _, ok := m.(*routeRegexp); ok {
|
||||
if m.Match(req, &RouteMatch{}) {
|
||||
methods, err := route.GetMethods()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
allMethods = append(allMethods, methods...)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
return allMethods, err
|
||||
}
|
||||
|
||||
129
vendor/github.com/gorilla/mux/mux.go
generated
vendored
129
vendor/github.com/gorilla/mux/mux.go
generated
vendored
@@ -22,7 +22,7 @@ var (
|
||||
|
||||
// NewRouter returns a new router instance.
|
||||
func NewRouter() *Router {
|
||||
return &Router{namedRoutes: make(map[string]*Route), KeepContext: false}
|
||||
return &Router{namedRoutes: make(map[string]*Route)}
|
||||
}
|
||||
|
||||
// Router registers routes to be matched and dispatches a handler.
|
||||
@@ -50,24 +50,78 @@ type Router struct {
|
||||
// Configurable Handler to be used when the request method does not match the route.
|
||||
MethodNotAllowedHandler http.Handler
|
||||
|
||||
// Parent route, if this is a subrouter.
|
||||
parent parentRoute
|
||||
// Routes to be matched, in order.
|
||||
routes []*Route
|
||||
|
||||
// Routes by name for URL building.
|
||||
namedRoutes map[string]*Route
|
||||
// See Router.StrictSlash(). This defines the flag for new routes.
|
||||
strictSlash bool
|
||||
// See Router.SkipClean(). This defines the flag for new routes.
|
||||
skipClean bool
|
||||
|
||||
// If true, do not clear the request context after handling the request.
|
||||
// This has no effect when go1.7+ is used, since the context is stored
|
||||
//
|
||||
// Deprecated: No effect when go1.7+ is used, since the context is stored
|
||||
// on the request itself.
|
||||
KeepContext bool
|
||||
// see Router.UseEncodedPath(). This defines a flag for all routes.
|
||||
useEncodedPath bool
|
||||
|
||||
// Slice of middlewares to be called after a match is found
|
||||
middlewares []middleware
|
||||
|
||||
// configuration shared with `Route`
|
||||
routeConf
|
||||
}
|
||||
|
||||
// common route configuration shared between `Router` and `Route`
|
||||
type routeConf struct {
|
||||
// If true, "/path/foo%2Fbar/to" will match the path "/path/{var}/to"
|
||||
useEncodedPath bool
|
||||
|
||||
// If true, when the path pattern is "/path/", accessing "/path" will
|
||||
// redirect to the former and vice versa.
|
||||
strictSlash bool
|
||||
|
||||
// If true, when the path pattern is "/path//to", accessing "/path//to"
|
||||
// will not redirect
|
||||
skipClean bool
|
||||
|
||||
// Manager for the variables from host and path.
|
||||
regexp routeRegexpGroup
|
||||
|
||||
// List of matchers.
|
||||
matchers []matcher
|
||||
|
||||
// The scheme used when building URLs.
|
||||
buildScheme string
|
||||
|
||||
buildVarsFunc BuildVarsFunc
|
||||
}
|
||||
|
||||
// returns an effective deep copy of `routeConf`
|
||||
func copyRouteConf(r routeConf) routeConf {
|
||||
c := r
|
||||
|
||||
if r.regexp.path != nil {
|
||||
c.regexp.path = copyRouteRegexp(r.regexp.path)
|
||||
}
|
||||
|
||||
if r.regexp.host != nil {
|
||||
c.regexp.host = copyRouteRegexp(r.regexp.host)
|
||||
}
|
||||
|
||||
c.regexp.queries = make([]*routeRegexp, 0, len(r.regexp.queries))
|
||||
for _, q := range r.regexp.queries {
|
||||
c.regexp.queries = append(c.regexp.queries, copyRouteRegexp(q))
|
||||
}
|
||||
|
||||
c.matchers = make([]matcher, 0, len(r.matchers))
|
||||
for _, m := range r.matchers {
|
||||
c.matchers = append(c.matchers, m)
|
||||
}
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
func copyRouteRegexp(r *routeRegexp) *routeRegexp {
|
||||
c := *r
|
||||
return &c
|
||||
}
|
||||
|
||||
// Match attempts to match the given request against the router's registered routes.
|
||||
@@ -155,22 +209,18 @@ func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||
handler = http.NotFoundHandler()
|
||||
}
|
||||
|
||||
if !r.KeepContext {
|
||||
defer contextClear(req)
|
||||
}
|
||||
|
||||
handler.ServeHTTP(w, req)
|
||||
}
|
||||
|
||||
// Get returns a route registered with the given name.
|
||||
func (r *Router) Get(name string) *Route {
|
||||
return r.getNamedRoutes()[name]
|
||||
return r.namedRoutes[name]
|
||||
}
|
||||
|
||||
// GetRoute returns a route registered with the given name. This method
|
||||
// was renamed to Get() and remains here for backwards compatibility.
|
||||
func (r *Router) GetRoute(name string) *Route {
|
||||
return r.getNamedRoutes()[name]
|
||||
return r.namedRoutes[name]
|
||||
}
|
||||
|
||||
// StrictSlash defines the trailing slash behavior for new routes. The initial
|
||||
@@ -221,55 +271,24 @@ func (r *Router) UseEncodedPath() *Router {
|
||||
return r
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// parentRoute
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
func (r *Router) getBuildScheme() string {
|
||||
if r.parent != nil {
|
||||
return r.parent.getBuildScheme()
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// getNamedRoutes returns the map where named routes are registered.
|
||||
func (r *Router) getNamedRoutes() map[string]*Route {
|
||||
if r.namedRoutes == nil {
|
||||
if r.parent != nil {
|
||||
r.namedRoutes = r.parent.getNamedRoutes()
|
||||
} else {
|
||||
r.namedRoutes = make(map[string]*Route)
|
||||
}
|
||||
}
|
||||
return r.namedRoutes
|
||||
}
|
||||
|
||||
// getRegexpGroup returns regexp definitions from the parent route, if any.
|
||||
func (r *Router) getRegexpGroup() *routeRegexpGroup {
|
||||
if r.parent != nil {
|
||||
return r.parent.getRegexpGroup()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Router) buildVars(m map[string]string) map[string]string {
|
||||
if r.parent != nil {
|
||||
m = r.parent.buildVars(m)
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Route factories
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
// NewRoute registers an empty route.
|
||||
func (r *Router) NewRoute() *Route {
|
||||
route := &Route{parent: r, strictSlash: r.strictSlash, skipClean: r.skipClean, useEncodedPath: r.useEncodedPath}
|
||||
// initialize a route with a copy of the parent router's configuration
|
||||
route := &Route{routeConf: copyRouteConf(r.routeConf), namedRoutes: r.namedRoutes}
|
||||
r.routes = append(r.routes, route)
|
||||
return route
|
||||
}
|
||||
|
||||
// Name registers a new route with a name.
|
||||
// See Route.Name().
|
||||
func (r *Router) Name(name string) *Route {
|
||||
return r.NewRoute().Name(name)
|
||||
}
|
||||
|
||||
// Handle registers a new route with a matcher for the URL path.
|
||||
// See Route.Path() and Route.Handler().
|
||||
func (r *Router) Handle(path string, handler http.Handler) *Route {
|
||||
|
||||
51
vendor/github.com/gorilla/mux/regexp.go
generated
vendored
51
vendor/github.com/gorilla/mux/regexp.go
generated
vendored
@@ -113,6 +113,13 @@ func newRouteRegexp(tpl string, typ regexpType, options routeRegexpOptions) (*ro
|
||||
if typ != regexpTypePrefix {
|
||||
pattern.WriteByte('$')
|
||||
}
|
||||
|
||||
var wildcardHostPort bool
|
||||
if typ == regexpTypeHost {
|
||||
if !strings.Contains(pattern.String(), ":") {
|
||||
wildcardHostPort = true
|
||||
}
|
||||
}
|
||||
reverse.WriteString(raw)
|
||||
if endSlash {
|
||||
reverse.WriteByte('/')
|
||||
@@ -131,13 +138,14 @@ func newRouteRegexp(tpl string, typ regexpType, options routeRegexpOptions) (*ro
|
||||
|
||||
// Done!
|
||||
return &routeRegexp{
|
||||
template: template,
|
||||
regexpType: typ,
|
||||
options: options,
|
||||
regexp: reg,
|
||||
reverse: reverse.String(),
|
||||
varsN: varsN,
|
||||
varsR: varsR,
|
||||
template: template,
|
||||
regexpType: typ,
|
||||
options: options,
|
||||
regexp: reg,
|
||||
reverse: reverse.String(),
|
||||
varsN: varsN,
|
||||
varsR: varsR,
|
||||
wildcardHostPort: wildcardHostPort,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -158,11 +166,22 @@ type routeRegexp struct {
|
||||
varsN []string
|
||||
// Variable regexps (validators).
|
||||
varsR []*regexp.Regexp
|
||||
// Wildcard host-port (no strict port match in hostname)
|
||||
wildcardHostPort bool
|
||||
}
|
||||
|
||||
// Match matches the regexp against the URL host or path.
|
||||
func (r *routeRegexp) Match(req *http.Request, match *RouteMatch) bool {
|
||||
if r.regexpType != regexpTypeHost {
|
||||
if r.regexpType == regexpTypeHost {
|
||||
host := getHost(req)
|
||||
if r.wildcardHostPort {
|
||||
// Don't be strict on the port match
|
||||
if i := strings.Index(host, ":"); i != -1 {
|
||||
host = host[:i]
|
||||
}
|
||||
}
|
||||
return r.regexp.MatchString(host)
|
||||
} else {
|
||||
if r.regexpType == regexpTypeQuery {
|
||||
return r.matchQueryString(req)
|
||||
}
|
||||
@@ -172,8 +191,6 @@ func (r *routeRegexp) Match(req *http.Request, match *RouteMatch) bool {
|
||||
}
|
||||
return r.regexp.MatchString(path)
|
||||
}
|
||||
|
||||
return r.regexp.MatchString(getHost(req))
|
||||
}
|
||||
|
||||
// url builds a URL part using the given values.
|
||||
@@ -267,7 +284,7 @@ type routeRegexpGroup struct {
|
||||
}
|
||||
|
||||
// setMatch extracts the variables from the URL once a route matches.
|
||||
func (v *routeRegexpGroup) setMatch(req *http.Request, m *RouteMatch, r *Route) {
|
||||
func (v routeRegexpGroup) setMatch(req *http.Request, m *RouteMatch, r *Route) {
|
||||
// Store host variables.
|
||||
if v.host != nil {
|
||||
host := getHost(req)
|
||||
@@ -296,7 +313,7 @@ func (v *routeRegexpGroup) setMatch(req *http.Request, m *RouteMatch, r *Route)
|
||||
} else {
|
||||
u.Path += "/"
|
||||
}
|
||||
m.Handler = http.RedirectHandler(u.String(), 301)
|
||||
m.Handler = http.RedirectHandler(u.String(), http.StatusMovedPermanently)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -312,17 +329,13 @@ func (v *routeRegexpGroup) setMatch(req *http.Request, m *RouteMatch, r *Route)
|
||||
}
|
||||
|
||||
// getHost tries its best to return the request host.
|
||||
// According to section 14.23 of RFC 2616 the Host header
|
||||
// can include the port number if the default value of 80 is not used.
|
||||
func getHost(r *http.Request) string {
|
||||
if r.URL.IsAbs() {
|
||||
return r.URL.Host
|
||||
}
|
||||
host := r.Host
|
||||
// Slice off any port information.
|
||||
if i := strings.Index(host, ":"); i != -1 {
|
||||
host = host[:i]
|
||||
}
|
||||
return host
|
||||
|
||||
return r.Host
|
||||
}
|
||||
|
||||
func extractVars(input string, matches []int, names []string, output map[string]string) {
|
||||
|
||||
141
vendor/github.com/gorilla/mux/route.go
generated
vendored
141
vendor/github.com/gorilla/mux/route.go
generated
vendored
@@ -15,24 +15,8 @@ import (
|
||||
|
||||
// Route stores information to match a request and build URLs.
|
||||
type Route struct {
|
||||
// Parent where the route was registered (a Router).
|
||||
parent parentRoute
|
||||
// Request handler for the route.
|
||||
handler http.Handler
|
||||
// List of matchers.
|
||||
matchers []matcher
|
||||
// Manager for the variables from host and path.
|
||||
regexp *routeRegexpGroup
|
||||
// If true, when the path pattern is "/path/", accessing "/path" will
|
||||
// redirect to the former and vice versa.
|
||||
strictSlash bool
|
||||
// If true, when the path pattern is "/path//to", accessing "/path//to"
|
||||
// will not redirect
|
||||
skipClean bool
|
||||
// If true, "/path/foo%2Fbar/to" will match the path "/path/{var}/to"
|
||||
useEncodedPath bool
|
||||
// The scheme used when building URLs.
|
||||
buildScheme string
|
||||
// If true, this route never matches: it is only used to build URLs.
|
||||
buildOnly bool
|
||||
// The name used to build URLs.
|
||||
@@ -40,7 +24,11 @@ type Route struct {
|
||||
// Error resulted from building a route.
|
||||
err error
|
||||
|
||||
buildVarsFunc BuildVarsFunc
|
||||
// "global" reference to all named routes
|
||||
namedRoutes map[string]*Route
|
||||
|
||||
// config possibly passed in from `Router`
|
||||
routeConf
|
||||
}
|
||||
|
||||
// SkipClean reports whether path cleaning is enabled for this route via
|
||||
@@ -64,6 +52,18 @@ func (r *Route) Match(req *http.Request, match *RouteMatch) bool {
|
||||
matchErr = ErrMethodMismatch
|
||||
continue
|
||||
}
|
||||
|
||||
// Ignore ErrNotFound errors. These errors arise from match call
|
||||
// to Subrouters.
|
||||
//
|
||||
// This prevents subsequent matching subrouters from failing to
|
||||
// run middleware. If not ignored, the middleware would see a
|
||||
// non-nil MatchErr and be skipped, even when there was a
|
||||
// matching route.
|
||||
if match.MatchErr == ErrNotFound {
|
||||
match.MatchErr = nil
|
||||
}
|
||||
|
||||
matchErr = nil
|
||||
return false
|
||||
}
|
||||
@@ -93,9 +93,7 @@ func (r *Route) Match(req *http.Request, match *RouteMatch) bool {
|
||||
}
|
||||
|
||||
// Set variables.
|
||||
if r.regexp != nil {
|
||||
r.regexp.setMatch(req, match, r)
|
||||
}
|
||||
r.regexp.setMatch(req, match, r)
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -137,7 +135,7 @@ func (r *Route) GetHandler() http.Handler {
|
||||
// Name -----------------------------------------------------------------------
|
||||
|
||||
// Name sets the name for the route, used to build URLs.
|
||||
// If the name was registered already it will be overwritten.
|
||||
// It is an error to call Name more than once on a route.
|
||||
func (r *Route) Name(name string) *Route {
|
||||
if r.name != "" {
|
||||
r.err = fmt.Errorf("mux: route already has name %q, can't set %q",
|
||||
@@ -145,7 +143,7 @@ func (r *Route) Name(name string) *Route {
|
||||
}
|
||||
if r.err == nil {
|
||||
r.name = name
|
||||
r.getNamedRoutes()[name] = r
|
||||
r.namedRoutes[name] = r
|
||||
}
|
||||
return r
|
||||
}
|
||||
@@ -177,7 +175,6 @@ func (r *Route) addRegexpMatcher(tpl string, typ regexpType) error {
|
||||
if r.err != nil {
|
||||
return r.err
|
||||
}
|
||||
r.regexp = r.getRegexpGroup()
|
||||
if typ == regexpTypePath || typ == regexpTypePrefix {
|
||||
if len(tpl) > 0 && tpl[0] != '/' {
|
||||
return fmt.Errorf("mux: path must start with a slash, got %q", tpl)
|
||||
@@ -386,7 +383,7 @@ func (r *Route) PathPrefix(tpl string) *Route {
|
||||
// The above route will only match if the URL contains the defined queries
|
||||
// values, e.g.: ?foo=bar&id=42.
|
||||
//
|
||||
// It the value is an empty string, it will match any value if the key is set.
|
||||
// If the value is an empty string, it will match any value if the key is set.
|
||||
//
|
||||
// Variables can define an optional regexp pattern to be matched:
|
||||
//
|
||||
@@ -424,7 +421,7 @@ func (r *Route) Schemes(schemes ...string) *Route {
|
||||
for k, v := range schemes {
|
||||
schemes[k] = strings.ToLower(v)
|
||||
}
|
||||
if r.buildScheme == "" && len(schemes) > 0 {
|
||||
if len(schemes) > 0 {
|
||||
r.buildScheme = schemes[0]
|
||||
}
|
||||
return r.addMatcher(schemeMatcher(schemes))
|
||||
@@ -439,7 +436,15 @@ type BuildVarsFunc func(map[string]string) map[string]string
|
||||
// BuildVarsFunc adds a custom function to be used to modify build variables
|
||||
// before a route's URL is built.
|
||||
func (r *Route) BuildVarsFunc(f BuildVarsFunc) *Route {
|
||||
r.buildVarsFunc = f
|
||||
if r.buildVarsFunc != nil {
|
||||
// compose the old and new functions
|
||||
old := r.buildVarsFunc
|
||||
r.buildVarsFunc = func(m map[string]string) map[string]string {
|
||||
return f(old(m))
|
||||
}
|
||||
} else {
|
||||
r.buildVarsFunc = f
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
@@ -458,7 +463,8 @@ func (r *Route) BuildVarsFunc(f BuildVarsFunc) *Route {
|
||||
// Here, the routes registered in the subrouter won't be tested if the host
|
||||
// doesn't match.
|
||||
func (r *Route) Subrouter() *Router {
|
||||
router := &Router{parent: r, strictSlash: r.strictSlash}
|
||||
// initialize a subrouter with a copy of the parent route's configuration
|
||||
router := &Router{routeConf: copyRouteConf(r.routeConf), namedRoutes: r.namedRoutes}
|
||||
r.addMatcher(router)
|
||||
return router
|
||||
}
|
||||
@@ -502,9 +508,6 @@ func (r *Route) URL(pairs ...string) (*url.URL, error) {
|
||||
if r.err != nil {
|
||||
return nil, r.err
|
||||
}
|
||||
if r.regexp == nil {
|
||||
return nil, errors.New("mux: route doesn't have a host or path")
|
||||
}
|
||||
values, err := r.prepareVars(pairs...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -516,8 +519,8 @@ func (r *Route) URL(pairs ...string) (*url.URL, error) {
|
||||
return nil, err
|
||||
}
|
||||
scheme = "http"
|
||||
if s := r.getBuildScheme(); s != "" {
|
||||
scheme = s
|
||||
if r.buildScheme != "" {
|
||||
scheme = r.buildScheme
|
||||
}
|
||||
}
|
||||
if r.regexp.path != nil {
|
||||
@@ -547,7 +550,7 @@ func (r *Route) URLHost(pairs ...string) (*url.URL, error) {
|
||||
if r.err != nil {
|
||||
return nil, r.err
|
||||
}
|
||||
if r.regexp == nil || r.regexp.host == nil {
|
||||
if r.regexp.host == nil {
|
||||
return nil, errors.New("mux: route doesn't have a host")
|
||||
}
|
||||
values, err := r.prepareVars(pairs...)
|
||||
@@ -562,8 +565,8 @@ func (r *Route) URLHost(pairs ...string) (*url.URL, error) {
|
||||
Scheme: "http",
|
||||
Host: host,
|
||||
}
|
||||
if s := r.getBuildScheme(); s != "" {
|
||||
u.Scheme = s
|
||||
if r.buildScheme != "" {
|
||||
u.Scheme = r.buildScheme
|
||||
}
|
||||
return u, nil
|
||||
}
|
||||
@@ -575,7 +578,7 @@ func (r *Route) URLPath(pairs ...string) (*url.URL, error) {
|
||||
if r.err != nil {
|
||||
return nil, r.err
|
||||
}
|
||||
if r.regexp == nil || r.regexp.path == nil {
|
||||
if r.regexp.path == nil {
|
||||
return nil, errors.New("mux: route doesn't have a path")
|
||||
}
|
||||
values, err := r.prepareVars(pairs...)
|
||||
@@ -600,7 +603,7 @@ func (r *Route) GetPathTemplate() (string, error) {
|
||||
if r.err != nil {
|
||||
return "", r.err
|
||||
}
|
||||
if r.regexp == nil || r.regexp.path == nil {
|
||||
if r.regexp.path == nil {
|
||||
return "", errors.New("mux: route doesn't have a path")
|
||||
}
|
||||
return r.regexp.path.template, nil
|
||||
@@ -614,7 +617,7 @@ func (r *Route) GetPathRegexp() (string, error) {
|
||||
if r.err != nil {
|
||||
return "", r.err
|
||||
}
|
||||
if r.regexp == nil || r.regexp.path == nil {
|
||||
if r.regexp.path == nil {
|
||||
return "", errors.New("mux: route does not have a path")
|
||||
}
|
||||
return r.regexp.path.regexp.String(), nil
|
||||
@@ -629,7 +632,7 @@ func (r *Route) GetQueriesRegexp() ([]string, error) {
|
||||
if r.err != nil {
|
||||
return nil, r.err
|
||||
}
|
||||
if r.regexp == nil || r.regexp.queries == nil {
|
||||
if r.regexp.queries == nil {
|
||||
return nil, errors.New("mux: route doesn't have queries")
|
||||
}
|
||||
var queries []string
|
||||
@@ -648,7 +651,7 @@ func (r *Route) GetQueriesTemplates() ([]string, error) {
|
||||
if r.err != nil {
|
||||
return nil, r.err
|
||||
}
|
||||
if r.regexp == nil || r.regexp.queries == nil {
|
||||
if r.regexp.queries == nil {
|
||||
return nil, errors.New("mux: route doesn't have queries")
|
||||
}
|
||||
var queries []string
|
||||
@@ -683,7 +686,7 @@ func (r *Route) GetHostTemplate() (string, error) {
|
||||
if r.err != nil {
|
||||
return "", r.err
|
||||
}
|
||||
if r.regexp == nil || r.regexp.host == nil {
|
||||
if r.regexp.host == nil {
|
||||
return "", errors.New("mux: route doesn't have a host")
|
||||
}
|
||||
return r.regexp.host.template, nil
|
||||
@@ -700,64 +703,8 @@ func (r *Route) prepareVars(pairs ...string) (map[string]string, error) {
|
||||
}
|
||||
|
||||
func (r *Route) buildVars(m map[string]string) map[string]string {
|
||||
if r.parent != nil {
|
||||
m = r.parent.buildVars(m)
|
||||
}
|
||||
if r.buildVarsFunc != nil {
|
||||
m = r.buildVarsFunc(m)
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// parentRoute
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
// parentRoute allows routes to know about parent host and path definitions.
|
||||
type parentRoute interface {
|
||||
getBuildScheme() string
|
||||
getNamedRoutes() map[string]*Route
|
||||
getRegexpGroup() *routeRegexpGroup
|
||||
buildVars(map[string]string) map[string]string
|
||||
}
|
||||
|
||||
func (r *Route) getBuildScheme() string {
|
||||
if r.buildScheme != "" {
|
||||
return r.buildScheme
|
||||
}
|
||||
if r.parent != nil {
|
||||
return r.parent.getBuildScheme()
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// getNamedRoutes returns the map where named routes are registered.
|
||||
func (r *Route) getNamedRoutes() map[string]*Route {
|
||||
if r.parent == nil {
|
||||
// During tests router is not always set.
|
||||
r.parent = NewRouter()
|
||||
}
|
||||
return r.parent.getNamedRoutes()
|
||||
}
|
||||
|
||||
// getRegexpGroup returns regexp definitions from this route.
|
||||
func (r *Route) getRegexpGroup() *routeRegexpGroup {
|
||||
if r.regexp == nil {
|
||||
if r.parent == nil {
|
||||
// During tests router is not always set.
|
||||
r.parent = NewRouter()
|
||||
}
|
||||
regexp := r.parent.getRegexpGroup()
|
||||
if regexp == nil {
|
||||
r.regexp = new(routeRegexpGroup)
|
||||
} else {
|
||||
// Copy.
|
||||
r.regexp = &routeRegexpGroup{
|
||||
host: regexp.host,
|
||||
path: regexp.path,
|
||||
queries: regexp.queries,
|
||||
}
|
||||
}
|
||||
}
|
||||
return r.regexp
|
||||
}
|
||||
|
||||
2
vendor/github.com/gorilla/websocket/.gitignore
generated
vendored
2
vendor/github.com/gorilla/websocket/.gitignore
generated
vendored
@@ -22,4 +22,4 @@ _testmain.go
|
||||
*.exe
|
||||
|
||||
.idea/
|
||||
*.iml
|
||||
*.iml
|
||||
|
||||
10
vendor/github.com/gorilla/websocket/.travis.yml
generated
vendored
10
vendor/github.com/gorilla/websocket/.travis.yml
generated
vendored
@@ -3,11 +3,11 @@ sudo: false
|
||||
|
||||
matrix:
|
||||
include:
|
||||
- go: 1.4
|
||||
- go: 1.5
|
||||
- go: 1.6
|
||||
- go: 1.7
|
||||
- go: 1.8
|
||||
- go: 1.7.x
|
||||
- go: 1.8.x
|
||||
- go: 1.9.x
|
||||
- go: 1.10.x
|
||||
- go: 1.11.x
|
||||
- go: tip
|
||||
allow_failures:
|
||||
- go: tip
|
||||
|
||||
1
vendor/github.com/gorilla/websocket/AUTHORS
generated
vendored
1
vendor/github.com/gorilla/websocket/AUTHORS
generated
vendored
@@ -4,5 +4,6 @@
|
||||
# Please keep the list sorted.
|
||||
|
||||
Gary Burd <gary@beagledreams.com>
|
||||
Google LLC (https://opensource.google.com/)
|
||||
Joachim Bauch <mail@joachim-bauch.de>
|
||||
|
||||
|
||||
2
vendor/github.com/gorilla/websocket/README.md
generated
vendored
2
vendor/github.com/gorilla/websocket/README.md
generated
vendored
@@ -51,7 +51,7 @@ subdirectory](https://github.com/gorilla/websocket/tree/master/examples/autobahn
|
||||
<tr><td>Write message using io.WriteCloser</td><td><a href="http://godoc.org/github.com/gorilla/websocket#Conn.NextWriter">Yes</a></td><td>No, see note 3</td></tr>
|
||||
</table>
|
||||
|
||||
Notes:
|
||||
Notes:
|
||||
|
||||
1. Large messages are fragmented in [Chrome's new WebSocket implementation](http://www.ietf.org/mail-archive/web/hybi/current/msg10503.html).
|
||||
2. The application can get the type of a received data message by implementing
|
||||
|
||||
253
vendor/github.com/gorilla/websocket/client.go
generated
vendored
253
vendor/github.com/gorilla/websocket/client.go
generated
vendored
@@ -5,15 +5,15 @@
|
||||
package websocket
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httptrace"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -53,6 +53,10 @@ type Dialer struct {
|
||||
// NetDial is nil, net.Dial is used.
|
||||
NetDial func(network, addr string) (net.Conn, error)
|
||||
|
||||
// NetDialContext specifies the dial function for creating TCP connections. If
|
||||
// NetDialContext is nil, net.DialContext is used.
|
||||
NetDialContext func(ctx context.Context, network, addr string) (net.Conn, error)
|
||||
|
||||
// Proxy specifies a function to return a proxy for a given
|
||||
// Request. If the function returns a non-nil error, the
|
||||
// request is aborted with the provided error.
|
||||
@@ -71,6 +75,17 @@ type Dialer struct {
|
||||
// do not limit the size of the messages that can be sent or received.
|
||||
ReadBufferSize, WriteBufferSize int
|
||||
|
||||
// WriteBufferPool is a pool of buffers for write operations. If the value
|
||||
// is not set, then write buffers are allocated to the connection for the
|
||||
// lifetime of the connection.
|
||||
//
|
||||
// A pool is most useful when the application has a modest volume of writes
|
||||
// across a large number of connections.
|
||||
//
|
||||
// Applications should use a single pool for each unique value of
|
||||
// WriteBufferSize.
|
||||
WriteBufferPool BufferPool
|
||||
|
||||
// Subprotocols specifies the client's requested subprotocols.
|
||||
Subprotocols []string
|
||||
|
||||
@@ -86,52 +101,13 @@ type Dialer struct {
|
||||
Jar http.CookieJar
|
||||
}
|
||||
|
||||
var errMalformedURL = errors.New("malformed ws or wss URL")
|
||||
|
||||
// parseURL parses the URL.
|
||||
//
|
||||
// This function is a replacement for the standard library url.Parse function.
|
||||
// In Go 1.4 and earlier, url.Parse loses information from the path.
|
||||
func parseURL(s string) (*url.URL, error) {
|
||||
// From the RFC:
|
||||
//
|
||||
// ws-URI = "ws:" "//" host [ ":" port ] path [ "?" query ]
|
||||
// wss-URI = "wss:" "//" host [ ":" port ] path [ "?" query ]
|
||||
var u url.URL
|
||||
switch {
|
||||
case strings.HasPrefix(s, "ws://"):
|
||||
u.Scheme = "ws"
|
||||
s = s[len("ws://"):]
|
||||
case strings.HasPrefix(s, "wss://"):
|
||||
u.Scheme = "wss"
|
||||
s = s[len("wss://"):]
|
||||
default:
|
||||
return nil, errMalformedURL
|
||||
}
|
||||
|
||||
if i := strings.Index(s, "?"); i >= 0 {
|
||||
u.RawQuery = s[i+1:]
|
||||
s = s[:i]
|
||||
}
|
||||
|
||||
if i := strings.Index(s, "/"); i >= 0 {
|
||||
u.Opaque = s[i:]
|
||||
s = s[:i]
|
||||
} else {
|
||||
u.Opaque = "/"
|
||||
}
|
||||
|
||||
u.Host = s
|
||||
|
||||
if strings.Contains(u.Host, "@") {
|
||||
// Don't bother parsing user information because user information is
|
||||
// not allowed in websocket URIs.
|
||||
return nil, errMalformedURL
|
||||
}
|
||||
|
||||
return &u, nil
|
||||
// Dial creates a new client connection by calling DialContext with a background context.
|
||||
func (d *Dialer) Dial(urlStr string, requestHeader http.Header) (*Conn, *http.Response, error) {
|
||||
return d.DialContext(context.Background(), urlStr, requestHeader)
|
||||
}
|
||||
|
||||
var errMalformedURL = errors.New("malformed ws or wss URL")
|
||||
|
||||
func hostPortNoPort(u *url.URL) (hostPort, hostNoPort string) {
|
||||
hostPort = u.Host
|
||||
hostNoPort = u.Host
|
||||
@@ -150,26 +126,29 @@ func hostPortNoPort(u *url.URL) (hostPort, hostNoPort string) {
|
||||
return hostPort, hostNoPort
|
||||
}
|
||||
|
||||
// DefaultDialer is a dialer with all fields set to the default zero values.
|
||||
// DefaultDialer is a dialer with all fields set to the default values.
|
||||
var DefaultDialer = &Dialer{
|
||||
Proxy: http.ProxyFromEnvironment,
|
||||
Proxy: http.ProxyFromEnvironment,
|
||||
HandshakeTimeout: 45 * time.Second,
|
||||
}
|
||||
|
||||
// Dial creates a new client connection. Use requestHeader to specify the
|
||||
// nilDialer is dialer to use when receiver is nil.
|
||||
var nilDialer = *DefaultDialer
|
||||
|
||||
// DialContext creates a new client connection. Use requestHeader to specify the
|
||||
// origin (Origin), subprotocols (Sec-WebSocket-Protocol) and cookies (Cookie).
|
||||
// Use the response.Header to get the selected subprotocol
|
||||
// (Sec-WebSocket-Protocol) and cookies (Set-Cookie).
|
||||
//
|
||||
// The context will be used in the request and in the Dialer
|
||||
//
|
||||
// If the WebSocket handshake fails, ErrBadHandshake is returned along with a
|
||||
// non-nil *http.Response so that callers can handle redirects, authentication,
|
||||
// etcetera. The response body may not contain the entire response and does not
|
||||
// need to be closed by the application.
|
||||
func (d *Dialer) Dial(urlStr string, requestHeader http.Header) (*Conn, *http.Response, error) {
|
||||
|
||||
func (d *Dialer) DialContext(ctx context.Context, urlStr string, requestHeader http.Header) (*Conn, *http.Response, error) {
|
||||
if d == nil {
|
||||
d = &Dialer{
|
||||
Proxy: http.ProxyFromEnvironment,
|
||||
}
|
||||
d = &nilDialer
|
||||
}
|
||||
|
||||
challengeKey, err := generateChallengeKey()
|
||||
@@ -177,7 +156,7 @@ func (d *Dialer) Dial(urlStr string, requestHeader http.Header) (*Conn, *http.Re
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
u, err := parseURL(urlStr)
|
||||
u, err := url.Parse(urlStr)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
@@ -205,6 +184,7 @@ func (d *Dialer) Dial(urlStr string, requestHeader http.Header) (*Conn, *http.Re
|
||||
Header: make(http.Header),
|
||||
Host: u.Host,
|
||||
}
|
||||
req = req.WithContext(ctx)
|
||||
|
||||
// Set the cookies present in the cookie jar of the dialer
|
||||
if d.Jar != nil {
|
||||
@@ -237,45 +217,83 @@ func (d *Dialer) Dial(urlStr string, requestHeader http.Header) (*Conn, *http.Re
|
||||
k == "Sec-Websocket-Extensions" ||
|
||||
(k == "Sec-Websocket-Protocol" && len(d.Subprotocols) > 0):
|
||||
return nil, nil, errors.New("websocket: duplicate header not allowed: " + k)
|
||||
case k == "Sec-Websocket-Protocol":
|
||||
req.Header["Sec-WebSocket-Protocol"] = vs
|
||||
default:
|
||||
req.Header[k] = vs
|
||||
}
|
||||
}
|
||||
|
||||
if d.EnableCompression {
|
||||
req.Header.Set("Sec-Websocket-Extensions", "permessage-deflate; server_no_context_takeover; client_no_context_takeover")
|
||||
req.Header["Sec-WebSocket-Extensions"] = []string{"permessage-deflate; server_no_context_takeover; client_no_context_takeover"}
|
||||
}
|
||||
|
||||
if d.HandshakeTimeout != 0 {
|
||||
var cancel func()
|
||||
ctx, cancel = context.WithTimeout(ctx, d.HandshakeTimeout)
|
||||
defer cancel()
|
||||
}
|
||||
|
||||
// Get network dial function.
|
||||
var netDial func(network, add string) (net.Conn, error)
|
||||
|
||||
if d.NetDialContext != nil {
|
||||
netDial = func(network, addr string) (net.Conn, error) {
|
||||
return d.NetDialContext(ctx, network, addr)
|
||||
}
|
||||
} else if d.NetDial != nil {
|
||||
netDial = d.NetDial
|
||||
} else {
|
||||
netDialer := &net.Dialer{}
|
||||
netDial = func(network, addr string) (net.Conn, error) {
|
||||
return netDialer.DialContext(ctx, network, addr)
|
||||
}
|
||||
}
|
||||
|
||||
// If needed, wrap the dial function to set the connection deadline.
|
||||
if deadline, ok := ctx.Deadline(); ok {
|
||||
forwardDial := netDial
|
||||
netDial = func(network, addr string) (net.Conn, error) {
|
||||
c, err := forwardDial(network, addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = c.SetDeadline(deadline)
|
||||
if err != nil {
|
||||
c.Close()
|
||||
return nil, err
|
||||
}
|
||||
return c, nil
|
||||
}
|
||||
}
|
||||
|
||||
// If needed, wrap the dial function to connect through a proxy.
|
||||
if d.Proxy != nil {
|
||||
proxyURL, err := d.Proxy(req)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if proxyURL != nil {
|
||||
dialer, err := proxy_FromURL(proxyURL, netDialerFunc(netDial))
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
netDial = dialer.Dial
|
||||
}
|
||||
}
|
||||
|
||||
hostPort, hostNoPort := hostPortNoPort(u)
|
||||
|
||||
var proxyURL *url.URL
|
||||
// Check wether the proxy method has been configured
|
||||
if d.Proxy != nil {
|
||||
proxyURL, err = d.Proxy(req)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
trace := httptrace.ContextClientTrace(ctx)
|
||||
if trace != nil && trace.GetConn != nil {
|
||||
trace.GetConn(hostPort)
|
||||
}
|
||||
|
||||
var targetHostPort string
|
||||
if proxyURL != nil {
|
||||
targetHostPort, _ = hostPortNoPort(proxyURL)
|
||||
} else {
|
||||
targetHostPort = hostPort
|
||||
netConn, err := netDial("tcp", hostPort)
|
||||
if trace != nil && trace.GotConn != nil {
|
||||
trace.GotConn(httptrace.GotConnInfo{
|
||||
Conn: netConn,
|
||||
})
|
||||
}
|
||||
|
||||
var deadline time.Time
|
||||
if d.HandshakeTimeout != 0 {
|
||||
deadline = time.Now().Add(d.HandshakeTimeout)
|
||||
}
|
||||
|
||||
netDial := d.NetDial
|
||||
if netDial == nil {
|
||||
netDialer := &net.Dialer{Deadline: deadline}
|
||||
netDial = netDialer.Dial
|
||||
}
|
||||
|
||||
netConn, err := netDial("tcp", targetHostPort)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
@@ -286,42 +304,6 @@ func (d *Dialer) Dial(urlStr string, requestHeader http.Header) (*Conn, *http.Re
|
||||
}
|
||||
}()
|
||||
|
||||
if err := netConn.SetDeadline(deadline); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
if proxyURL != nil {
|
||||
connectHeader := make(http.Header)
|
||||
if user := proxyURL.User; user != nil {
|
||||
proxyUser := user.Username()
|
||||
if proxyPassword, passwordSet := user.Password(); passwordSet {
|
||||
credential := base64.StdEncoding.EncodeToString([]byte(proxyUser + ":" + proxyPassword))
|
||||
connectHeader.Set("Proxy-Authorization", "Basic "+credential)
|
||||
}
|
||||
}
|
||||
connectReq := &http.Request{
|
||||
Method: "CONNECT",
|
||||
URL: &url.URL{Opaque: hostPort},
|
||||
Host: hostPort,
|
||||
Header: connectHeader,
|
||||
}
|
||||
|
||||
connectReq.Write(netConn)
|
||||
|
||||
// Read response.
|
||||
// Okay to use and discard buffered reader here, because
|
||||
// TLS server will not speak until spoken to.
|
||||
br := bufio.NewReader(netConn)
|
||||
resp, err := http.ReadResponse(br, connectReq)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if resp.StatusCode != 200 {
|
||||
f := strings.SplitN(resp.Status, " ", 2)
|
||||
return nil, nil, errors.New(f[1])
|
||||
}
|
||||
}
|
||||
|
||||
if u.Scheme == "https" {
|
||||
cfg := cloneTLSConfig(d.TLSClientConfig)
|
||||
if cfg.ServerName == "" {
|
||||
@@ -329,22 +311,31 @@ func (d *Dialer) Dial(urlStr string, requestHeader http.Header) (*Conn, *http.Re
|
||||
}
|
||||
tlsConn := tls.Client(netConn, cfg)
|
||||
netConn = tlsConn
|
||||
if err := tlsConn.Handshake(); err != nil {
|
||||
return nil, nil, err
|
||||
|
||||
var err error
|
||||
if trace != nil {
|
||||
err = doHandshakeWithTrace(trace, tlsConn, cfg)
|
||||
} else {
|
||||
err = doHandshake(tlsConn, cfg)
|
||||
}
|
||||
if !cfg.InsecureSkipVerify {
|
||||
if err := tlsConn.VerifyHostname(cfg.ServerName); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
|
||||
conn := newConn(netConn, false, d.ReadBufferSize, d.WriteBufferSize)
|
||||
conn := newConn(netConn, false, d.ReadBufferSize, d.WriteBufferSize, d.WriteBufferPool, nil, nil)
|
||||
|
||||
if err := req.Write(netConn); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
if trace != nil && trace.GotFirstResponseByte != nil {
|
||||
if peek, err := conn.br.Peek(1); err == nil && len(peek) == 1 {
|
||||
trace.GotFirstResponseByte()
|
||||
}
|
||||
}
|
||||
|
||||
resp, err := http.ReadResponse(conn.br, req)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
@@ -390,3 +381,15 @@ func (d *Dialer) Dial(urlStr string, requestHeader http.Header) (*Conn, *http.Re
|
||||
netConn = nil // to avoid close in defer.
|
||||
return conn, resp, nil
|
||||
}
|
||||
|
||||
func doHandshake(tlsConn *tls.Conn, cfg *tls.Config) error {
|
||||
if err := tlsConn.Handshake(); err != nil {
|
||||
return err
|
||||
}
|
||||
if !cfg.InsecureSkipVerify {
|
||||
if err := tlsConn.VerifyHostname(cfg.ServerName); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
174
vendor/github.com/gorilla/websocket/conn.go
generated
vendored
174
vendor/github.com/gorilla/websocket/conn.go
generated
vendored
@@ -76,7 +76,7 @@ const (
|
||||
// is UTF-8 encoded text.
|
||||
PingMessage = 9
|
||||
|
||||
// PongMessage denotes a ping control message. The optional message payload
|
||||
// PongMessage denotes a pong control message. The optional message payload
|
||||
// is UTF-8 encoded text.
|
||||
PongMessage = 10
|
||||
)
|
||||
@@ -100,9 +100,8 @@ func (e *netError) Error() string { return e.msg }
|
||||
func (e *netError) Temporary() bool { return e.temporary }
|
||||
func (e *netError) Timeout() bool { return e.timeout }
|
||||
|
||||
// CloseError represents close frame.
|
||||
// CloseError represents a close message.
|
||||
type CloseError struct {
|
||||
|
||||
// Code is defined in RFC 6455, section 11.7.
|
||||
Code int
|
||||
|
||||
@@ -224,6 +223,20 @@ func isValidReceivedCloseCode(code int) bool {
|
||||
return validReceivedCloseCodes[code] || (code >= 3000 && code <= 4999)
|
||||
}
|
||||
|
||||
// BufferPool represents a pool of buffers. The *sync.Pool type satisfies this
|
||||
// interface. The type of the value stored in a pool is not specified.
|
||||
type BufferPool interface {
|
||||
// Get gets a value from the pool or returns nil if the pool is empty.
|
||||
Get() interface{}
|
||||
// Put adds a value to the pool.
|
||||
Put(interface{})
|
||||
}
|
||||
|
||||
// writePoolData is the type added to the write buffer pool. This wrapper is
|
||||
// used to prevent applications from peeking at and depending on the values
|
||||
// added to the pool.
|
||||
type writePoolData struct{ buf []byte }
|
||||
|
||||
// The Conn type represents a WebSocket connection.
|
||||
type Conn struct {
|
||||
conn net.Conn
|
||||
@@ -233,6 +246,8 @@ type Conn struct {
|
||||
// Write fields
|
||||
mu chan bool // used as mutex to protect write to conn
|
||||
writeBuf []byte // frame is constructed in this buffer.
|
||||
writePool BufferPool
|
||||
writeBufSize int
|
||||
writeDeadline time.Time
|
||||
writer io.WriteCloser // the current writer returned to the application
|
||||
isWriting bool // for best-effort concurrent write detection
|
||||
@@ -264,64 +279,29 @@ type Conn struct {
|
||||
newDecompressionReader func(io.Reader) io.ReadCloser
|
||||
}
|
||||
|
||||
func newConn(conn net.Conn, isServer bool, readBufferSize, writeBufferSize int) *Conn {
|
||||
return newConnBRW(conn, isServer, readBufferSize, writeBufferSize, nil)
|
||||
}
|
||||
func newConn(conn net.Conn, isServer bool, readBufferSize, writeBufferSize int, writeBufferPool BufferPool, br *bufio.Reader, writeBuf []byte) *Conn {
|
||||
|
||||
type writeHook struct {
|
||||
p []byte
|
||||
}
|
||||
|
||||
func (wh *writeHook) Write(p []byte) (int, error) {
|
||||
wh.p = p
|
||||
return len(p), nil
|
||||
}
|
||||
|
||||
func newConnBRW(conn net.Conn, isServer bool, readBufferSize, writeBufferSize int, brw *bufio.ReadWriter) *Conn {
|
||||
mu := make(chan bool, 1)
|
||||
mu <- true
|
||||
|
||||
var br *bufio.Reader
|
||||
if readBufferSize == 0 && brw != nil && brw.Reader != nil {
|
||||
// Reuse the supplied bufio.Reader if the buffer has a useful size.
|
||||
// This code assumes that peek on a reader returns
|
||||
// bufio.Reader.buf[:0].
|
||||
brw.Reader.Reset(conn)
|
||||
if p, err := brw.Reader.Peek(0); err == nil && cap(p) >= 256 {
|
||||
br = brw.Reader
|
||||
}
|
||||
}
|
||||
if br == nil {
|
||||
if readBufferSize == 0 {
|
||||
readBufferSize = defaultReadBufferSize
|
||||
}
|
||||
if readBufferSize < maxControlFramePayloadSize {
|
||||
} else if readBufferSize < maxControlFramePayloadSize {
|
||||
// must be large enough for control frame
|
||||
readBufferSize = maxControlFramePayloadSize
|
||||
}
|
||||
br = bufio.NewReaderSize(conn, readBufferSize)
|
||||
}
|
||||
|
||||
var writeBuf []byte
|
||||
if writeBufferSize == 0 && brw != nil && brw.Writer != nil {
|
||||
// Use the bufio.Writer's buffer if the buffer has a useful size. This
|
||||
// code assumes that bufio.Writer.buf[:1] is passed to the
|
||||
// bufio.Writer's underlying writer.
|
||||
var wh writeHook
|
||||
brw.Writer.Reset(&wh)
|
||||
brw.Writer.WriteByte(0)
|
||||
brw.Flush()
|
||||
if cap(wh.p) >= maxFrameHeaderSize+256 {
|
||||
writeBuf = wh.p[:cap(wh.p)]
|
||||
}
|
||||
}
|
||||
|
||||
if writeBuf == nil {
|
||||
if writeBufferSize == 0 {
|
||||
writeBufferSize = defaultWriteBufferSize
|
||||
}
|
||||
writeBuf = make([]byte, writeBufferSize+maxFrameHeaderSize)
|
||||
if writeBufferSize <= 0 {
|
||||
writeBufferSize = defaultWriteBufferSize
|
||||
}
|
||||
writeBufferSize += maxFrameHeaderSize
|
||||
|
||||
if writeBuf == nil && writeBufferPool == nil {
|
||||
writeBuf = make([]byte, writeBufferSize)
|
||||
}
|
||||
|
||||
mu := make(chan bool, 1)
|
||||
mu <- true
|
||||
c := &Conn{
|
||||
isServer: isServer,
|
||||
br: br,
|
||||
@@ -329,6 +309,8 @@ func newConnBRW(conn net.Conn, isServer bool, readBufferSize, writeBufferSize in
|
||||
mu: mu,
|
||||
readFinal: true,
|
||||
writeBuf: writeBuf,
|
||||
writePool: writeBufferPool,
|
||||
writeBufSize: writeBufferSize,
|
||||
enableWriteCompression: true,
|
||||
compressionLevel: defaultCompressionLevel,
|
||||
}
|
||||
@@ -343,7 +325,8 @@ func (c *Conn) Subprotocol() string {
|
||||
return c.subprotocol
|
||||
}
|
||||
|
||||
// Close closes the underlying network connection without sending or waiting for a close frame.
|
||||
// Close closes the underlying network connection without sending or waiting
|
||||
// for a close message.
|
||||
func (c *Conn) Close() error {
|
||||
return c.conn.Close()
|
||||
}
|
||||
@@ -370,7 +353,16 @@ func (c *Conn) writeFatal(err error) error {
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *Conn) write(frameType int, deadline time.Time, bufs ...[]byte) error {
|
||||
func (c *Conn) read(n int) ([]byte, error) {
|
||||
p, err := c.br.Peek(n)
|
||||
if err == io.EOF {
|
||||
err = errUnexpectedEOF
|
||||
}
|
||||
c.br.Discard(len(p))
|
||||
return p, err
|
||||
}
|
||||
|
||||
func (c *Conn) write(frameType int, deadline time.Time, buf0, buf1 []byte) error {
|
||||
<-c.mu
|
||||
defer func() { c.mu <- true }()
|
||||
|
||||
@@ -382,15 +374,14 @@ func (c *Conn) write(frameType int, deadline time.Time, bufs ...[]byte) error {
|
||||
}
|
||||
|
||||
c.conn.SetWriteDeadline(deadline)
|
||||
for _, buf := range bufs {
|
||||
if len(buf) > 0 {
|
||||
_, err := c.conn.Write(buf)
|
||||
if err != nil {
|
||||
return c.writeFatal(err)
|
||||
}
|
||||
}
|
||||
if len(buf1) == 0 {
|
||||
_, err = c.conn.Write(buf0)
|
||||
} else {
|
||||
err = c.writeBufs(buf0, buf1)
|
||||
}
|
||||
if err != nil {
|
||||
return c.writeFatal(err)
|
||||
}
|
||||
|
||||
if frameType == CloseMessage {
|
||||
c.writeFatal(ErrCloseSent)
|
||||
}
|
||||
@@ -476,7 +467,19 @@ func (c *Conn) prepWrite(messageType int) error {
|
||||
c.writeErrMu.Lock()
|
||||
err := c.writeErr
|
||||
c.writeErrMu.Unlock()
|
||||
return err
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if c.writeBuf == nil {
|
||||
wpd, ok := c.writePool.Get().(writePoolData)
|
||||
if ok {
|
||||
c.writeBuf = wpd.buf
|
||||
} else {
|
||||
c.writeBuf = make([]byte, c.writeBufSize)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// NextWriter returns a writer for the next message to send. The writer's Close
|
||||
@@ -484,6 +487,9 @@ func (c *Conn) prepWrite(messageType int) error {
|
||||
//
|
||||
// There can be at most one open writer on a connection. NextWriter closes the
|
||||
// previous writer if the application has not already done so.
|
||||
//
|
||||
// All message types (TextMessage, BinaryMessage, CloseMessage, PingMessage and
|
||||
// PongMessage) are supported.
|
||||
func (c *Conn) NextWriter(messageType int) (io.WriteCloser, error) {
|
||||
if err := c.prepWrite(messageType); err != nil {
|
||||
return nil, err
|
||||
@@ -599,6 +605,10 @@ func (w *messageWriter) flushFrame(final bool, extra []byte) error {
|
||||
|
||||
if final {
|
||||
c.writer = nil
|
||||
if c.writePool != nil {
|
||||
c.writePool.Put(writePoolData{buf: c.writeBuf})
|
||||
c.writeBuf = nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -764,7 +774,6 @@ func (c *Conn) SetWriteDeadline(t time.Time) error {
|
||||
// Read methods
|
||||
|
||||
func (c *Conn) advanceFrame() (int, error) {
|
||||
|
||||
// 1. Skip remainder of previous frame.
|
||||
|
||||
if c.readRemaining > 0 {
|
||||
@@ -1033,7 +1042,7 @@ func (c *Conn) SetReadDeadline(t time.Time) error {
|
||||
}
|
||||
|
||||
// SetReadLimit sets the maximum size for a message read from the peer. If a
|
||||
// message exceeds the limit, the connection sends a close frame to the peer
|
||||
// message exceeds the limit, the connection sends a close message to the peer
|
||||
// and returns ErrReadLimit to the application.
|
||||
func (c *Conn) SetReadLimit(limit int64) {
|
||||
c.readLimit = limit
|
||||
@@ -1046,24 +1055,22 @@ func (c *Conn) CloseHandler() func(code int, text string) error {
|
||||
|
||||
// SetCloseHandler sets the handler for close messages received from the peer.
|
||||
// The code argument to h is the received close code or CloseNoStatusReceived
|
||||
// if the close message is empty. The default close handler sends a close frame
|
||||
// back to the peer.
|
||||
// if the close message is empty. The default close handler sends a close
|
||||
// message back to the peer.
|
||||
//
|
||||
// The application must read the connection to process close messages as
|
||||
// described in the section on Control Frames above.
|
||||
// The handler function is called from the NextReader, ReadMessage and message
|
||||
// reader Read methods. The application must read the connection to process
|
||||
// close messages as described in the section on Control Messages above.
|
||||
//
|
||||
// The connection read methods return a CloseError when a close frame is
|
||||
// The connection read methods return a CloseError when a close message is
|
||||
// received. Most applications should handle close messages as part of their
|
||||
// normal error handling. Applications should only set a close handler when the
|
||||
// application must perform some action before sending a close frame back to
|
||||
// application must perform some action before sending a close message back to
|
||||
// the peer.
|
||||
func (c *Conn) SetCloseHandler(h func(code int, text string) error) {
|
||||
if h == nil {
|
||||
h = func(code int, text string) error {
|
||||
message := []byte{}
|
||||
if code != CloseNoStatusReceived {
|
||||
message = FormatCloseMessage(code, "")
|
||||
}
|
||||
message := FormatCloseMessage(code, "")
|
||||
c.WriteControl(CloseMessage, message, time.Now().Add(writeWait))
|
||||
return nil
|
||||
}
|
||||
@@ -1077,11 +1084,12 @@ func (c *Conn) PingHandler() func(appData string) error {
|
||||
}
|
||||
|
||||
// SetPingHandler sets the handler for ping messages received from the peer.
|
||||
// The appData argument to h is the PING frame application data. The default
|
||||
// The appData argument to h is the PING message application data. The default
|
||||
// ping handler sends a pong to the peer.
|
||||
//
|
||||
// The application must read the connection to process ping messages as
|
||||
// described in the section on Control Frames above.
|
||||
// The handler function is called from the NextReader, ReadMessage and message
|
||||
// reader Read methods. The application must read the connection to process
|
||||
// ping messages as described in the section on Control Messages above.
|
||||
func (c *Conn) SetPingHandler(h func(appData string) error) {
|
||||
if h == nil {
|
||||
h = func(message string) error {
|
||||
@@ -1103,11 +1111,12 @@ func (c *Conn) PongHandler() func(appData string) error {
|
||||
}
|
||||
|
||||
// SetPongHandler sets the handler for pong messages received from the peer.
|
||||
// The appData argument to h is the PONG frame application data. The default
|
||||
// The appData argument to h is the PONG message application data. The default
|
||||
// pong handler does nothing.
|
||||
//
|
||||
// The application must read the connection to process ping messages as
|
||||
// described in the section on Control Frames above.
|
||||
// The handler function is called from the NextReader, ReadMessage and message
|
||||
// reader Read methods. The application must read the connection to process
|
||||
// pong messages as described in the section on Control Messages above.
|
||||
func (c *Conn) SetPongHandler(h func(appData string) error) {
|
||||
if h == nil {
|
||||
h = func(string) error { return nil }
|
||||
@@ -1141,7 +1150,14 @@ func (c *Conn) SetCompressionLevel(level int) error {
|
||||
}
|
||||
|
||||
// FormatCloseMessage formats closeCode and text as a WebSocket close message.
|
||||
// An empty message is returned for code CloseNoStatusReceived.
|
||||
func FormatCloseMessage(closeCode int, text string) []byte {
|
||||
if closeCode == CloseNoStatusReceived {
|
||||
// Return empty message because it's illegal to send
|
||||
// CloseNoStatusReceived. Return non-nil value in case application
|
||||
// checks for nil.
|
||||
return []byte{}
|
||||
}
|
||||
buf := make([]byte, 2+len(text))
|
||||
binary.BigEndian.PutUint16(buf, uint16(closeCode))
|
||||
copy(buf[2:], text)
|
||||
|
||||
21
vendor/github.com/gorilla/websocket/conn_read_legacy.go
generated
vendored
21
vendor/github.com/gorilla/websocket/conn_read_legacy.go
generated
vendored
@@ -1,21 +0,0 @@
|
||||
// Copyright 2016 The Gorilla WebSocket Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build !go1.5
|
||||
|
||||
package websocket
|
||||
|
||||
import "io"
|
||||
|
||||
func (c *Conn) read(n int) ([]byte, error) {
|
||||
p, err := c.br.Peek(n)
|
||||
if err == io.EOF {
|
||||
err = errUnexpectedEOF
|
||||
}
|
||||
if len(p) > 0 {
|
||||
// advance over the bytes just read
|
||||
io.ReadFull(c.br, p)
|
||||
}
|
||||
return p, err
|
||||
}
|
||||
@@ -2,17 +2,14 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build go1.5
|
||||
// +build go1.8
|
||||
|
||||
package websocket
|
||||
|
||||
import "io"
|
||||
import "net"
|
||||
|
||||
func (c *Conn) read(n int) ([]byte, error) {
|
||||
p, err := c.br.Peek(n)
|
||||
if err == io.EOF {
|
||||
err = errUnexpectedEOF
|
||||
}
|
||||
c.br.Discard(len(p))
|
||||
return p, err
|
||||
func (c *Conn) writeBufs(bufs ...[]byte) error {
|
||||
b := net.Buffers(bufs)
|
||||
_, err := b.WriteTo(c.conn)
|
||||
return err
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user