Browse Source

Merge pull request #1122 from fatedier/dev

bump version to v0.25.0
fatedier 6 years ago
parent
commit
90b7f2080f

+ 1 - 1
.travis.yml

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

+ 27 - 3
README.md

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

+ 29 - 0
README_zh.md

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

+ 9 - 2
client/control.go

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

+ 79 - 10
client/proxy/proxy.go

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

+ 9 - 2
client/service.go

@@ -15,6 +15,7 @@
 package client
 package client
 
 
 import (
 import (
+	"crypto/tls"
 	"fmt"
 	"fmt"
 	"io/ioutil"
 	"io/ioutil"
 	"runtime"
 	"runtime"
@@ -151,8 +152,14 @@ func (svr *Service) keepControllerWorking() {
 // conn: control connection
 // conn: control connection
 // session: if it's not nil, using tcp mux
 // session: if it's not nil, using tcp mux
 func (svr *Service) login() (conn frpNet.Conn, session *fmux.Session, err error) {
 func (svr *Service) login() (conn frpNet.Conn, session *fmux.Session, err error) {
-	conn, err = frpNet.ConnectServerByProxy(g.GlbClientCfg.HttpProxy, g.GlbClientCfg.Protocol,
-		fmt.Sprintf("%s:%d", g.GlbClientCfg.ServerAddr, g.GlbClientCfg.ServerPort))
+	var tlsConfig *tls.Config
+	if g.GlbClientCfg.TLSEnable {
+		tlsConfig = &tls.Config{
+			InsecureSkipVerify: true,
+		}
+	}
+	conn, err = frpNet.ConnectServerByProxyWithTLS(g.GlbClientCfg.HttpProxy, g.GlbClientCfg.Protocol,
+		fmt.Sprintf("%s:%d", g.GlbClientCfg.ServerAddr, g.GlbClientCfg.ServerPort), tlsConfig)
 	if err != nil {
 	if err != nil {
 		return
 		return
 	}
 	}

+ 31 - 44
client/visitor.go

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

+ 2 - 2
cmd/frpc/sub/xtcp.go

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

+ 3 - 0
conf/frpc_full.ini

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

+ 1 - 1
go.mod

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

+ 2 - 1
go.sum

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

+ 8 - 0
models/config/client_common.go

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

+ 39 - 34
models/msg/msg.go

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

+ 12 - 5
models/nathole/nathole.go

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

+ 24 - 2
server/proxy/xtcp.go

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

+ 41 - 0
server/service.go

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

+ 6 - 6
tests/ci/cmd_test.go

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

+ 5 - 5
tests/ci/reconnect_test.go

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

+ 2 - 2
tests/ci/reload_test.go

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

+ 2 - 2
tests/ci/template_test.go

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

+ 188 - 0
tests/ci/tls_test.go

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

+ 11 - 0
utils/net/conn.go

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

+ 44 - 0
utils/net/tls.go

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

+ 1 - 1
utils/version/version.go

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

+ 1 - 0
vendor/github.com/hashicorp/yamux/go.mod

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

+ 12 - 1
vendor/github.com/hashicorp/yamux/mux.go

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

+ 10 - 3
vendor/github.com/hashicorp/yamux/session.go

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

+ 5 - 5
vendor/modules.txt

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