Pārlūkot izejas kodu

Merge pull request #41 from fatedier/dev

release v0.7.0
fatedier 8 gadi atpakaļ
vecāks
revīzija
28251a8104

+ 1 - 0
README.md

@@ -46,3 +46,4 @@ Interested in getting involved? We would like to help you!
 * [fatedier](https://github.com/fatedier)
 * [Hurricanezwf](https://github.com/Hurricanezwf)
 * [vashstorm](https://github.com/vashstorm)
+* [maodanp](https://github.com/maodanp)

+ 1 - 0
README_zh.md

@@ -45,3 +45,4 @@ frp 目前正在前期开发阶段,master 分支用于发布稳定版本,dev
 * [fatedier](https://github.com/fatedier)
 * [Hurricanezwf](https://github.com/Hurricanezwf)
 * [vashstorm](https://github.com/vashstorm)
+* [maodanp](https://github.com/maodanp)

+ 23 - 1
conf/frpc.ini

@@ -9,6 +9,8 @@ log_level = info
 log_max_days = 3
 # for authentication
 auth_token = 123
+# for privilege mode
+privilege_token = 12345678
 
 # ssh is the proxy name same as server's configuration
 [ssh]
@@ -18,15 +20,35 @@ local_ip = 127.0.0.1
 local_port = 22
 # true or false, if true, messages between frps and frpc will be encrypted, default is false
 use_encryption = true
+# default is false
+use_gzip = false
 
 # Resolve your domain names to [server_addr] so you can use http://web01.yourdomain.com to browse web01 and http://web02.yourdomain.com to browse web02, the domains are set in frps.ini
 [web01]
 type = http
 local_ip = 127.0.0.1
 local_port = 80
-use_encryption = true
+use_gzip = true
 
 [web02]
 type = http
 local_ip = 127.0.0.1
 local_port = 8000
+
+[privilege_ssh]
+# if privilege_mode is enabled, this proxy will be created automatically
+privilege_mode = true
+type = tcp
+local_ip = 127.0.0.1
+local_port = 22
+use_encryption = true
+use_gzip = false
+remote_port = 6001
+
+[privilege_web]
+privilege_mode = true
+type = http
+local_ip = 127.0.0.1
+local_port = 80
+use_gzip = true
+custom_domains = web03.yourdomain.com

+ 7 - 1
conf/frps.ini

@@ -4,6 +4,7 @@ bind_addr = 0.0.0.0
 bind_port = 7000
 # if you want to support virtual host, you must set the http port for listening (optional)
 vhost_http_port = 80
+vhost_https_port = 443
 # if you want to configure or reload frps by dashboard, dashboard_port must be set
 dashboard_port = 7500
 # console or real logFile path like ./frps.log
@@ -11,6 +12,9 @@ log_file = ./frps.log
 # debug, info, warn, error
 log_level = info
 log_max_days = 3
+# if you enable privilege mode, frpc can create a proxy without pre-configure in frps when privilege_token is correct
+privilege_mode = true
+privilege_token = 12345678
 
 # ssh is the proxy name, client will use this name and auth_token to connect to server
 [ssh]
@@ -20,12 +24,14 @@ bind_addr = 0.0.0.0
 listen_port = 6000
 
 [web01]
+# if type equals http, vhost_http_port must be set
 type = http
 auth_token = 123
 # if proxy type equals http, custom_domains must be set separated by commas
 custom_domains = web01.yourdomain.com,web01.yourdomain2.com
 
 [web02]
-type = http
+# if type equals https, vhost_https_port must be set
+type = https
 auth_token = 123
 custom_domains = web02.yourdomain.com

+ 13 - 2
src/frp/cmd/frpc/control.go

@@ -137,14 +137,25 @@ func loginToServer(cli *client.ProxyClient) (c *conn.Conn, err error) {
 	}
 
 	nowTime := time.Now().Unix()
-	authKey := pcrypto.GetAuthKey(cli.Name + cli.AuthToken + fmt.Sprintf("%d", nowTime))
 	req := &msg.ControlReq{
 		Type:          consts.NewCtlConn,
 		ProxyName:     cli.Name,
-		AuthKey:       authKey,
 		UseEncryption: cli.UseEncryption,
+		UseGzip:       cli.UseGzip,
+		PrivilegeMode: cli.PrivilegeMode,
+		ProxyType:     cli.Type,
 		Timestamp:     nowTime,
 	}
+	if cli.PrivilegeMode {
+		privilegeKey := pcrypto.GetAuthKey(cli.Name + client.PrivilegeToken + fmt.Sprintf("%d", nowTime))
+		req.RemotePort = cli.RemotePort
+		req.CustomDomains = cli.CustomDomains
+		req.PrivilegeKey = privilegeKey
+	} else {
+		authKey := pcrypto.GetAuthKey(cli.Name + cli.AuthToken + fmt.Sprintf("%d", nowTime))
+		req.AuthKey = authKey
+	}
+
 	buf, _ := json.Marshal(req)
 	err = c.Write(string(buf) + "\n")
 	if err != nil {

+ 69 - 16
src/frp/cmd/frps/control.go

@@ -194,38 +194,88 @@ func msgSender(s *server.ProxyServer, c *conn.Conn, msgSendChan chan interface{}
 // if success, ret equals 0, otherwise greater than 0
 func doLogin(req *msg.ControlReq, c *conn.Conn) (ret int64, info string) {
 	ret = 1
-	// check if proxy name exist
-	s, ok := server.ProxyServers[req.ProxyName]
-	if !ok {
-		info = fmt.Sprintf("ProxyName [%s] is not exist", req.ProxyName)
-		log.Warn(info)
+	if req.PrivilegeMode && !server.PrivilegeMode {
+		info = fmt.Sprintf("ProxyName [%s], PrivilegeMode is disabled in frps", req.ProxyName)
+		log.Warn("info")
 		return
 	}
 
-	// check authKey
+	var (
+		s  *server.ProxyServer
+		ok bool
+	)
+	s, ok = server.ProxyServers[req.ProxyName]
+	if req.PrivilegeMode && req.Type == consts.NewCtlConn {
+		log.Debug("ProxyName [%s], doLogin and privilege mode is enabled", req.ProxyName)
+	} else {
+		if !ok {
+			info = fmt.Sprintf("ProxyName [%s] is not exist", req.ProxyName)
+			log.Warn(info)
+			return
+		}
+	}
+
+	// check authKey or privilegeKey
 	nowTime := time.Now().Unix()
-	authKey := pcrypto.GetAuthKey(req.ProxyName + s.AuthToken + fmt.Sprintf("%d", req.Timestamp))
-	// authKey avaiable in 15 minutes
-	if nowTime-req.Timestamp > 15*60 {
-		info = fmt.Sprintf("ProxyName [%s], authorization timeout", req.ProxyName)
-		log.Warn(info)
-		return
-	} else if req.AuthKey != authKey {
-		info = fmt.Sprintf("ProxyName [%s], authorization failed", req.ProxyName)
-		log.Warn(info)
-		return
+	if req.PrivilegeMode {
+		privilegeKey := pcrypto.GetAuthKey(req.ProxyName + server.PrivilegeToken + fmt.Sprintf("%d", req.Timestamp))
+		// privilegeKey avaiable in 15 minutes
+		if nowTime-req.Timestamp > 15*60 {
+			info = fmt.Sprintf("ProxyName [%s], privilege mode authorization timeout", req.ProxyName)
+			log.Warn(info)
+			return
+		} else if req.PrivilegeKey != privilegeKey {
+			info = fmt.Sprintf("ProxyName [%s], privilege mode authorization failed", req.ProxyName)
+			log.Warn(info)
+			return
+		}
+	} else {
+		authKey := pcrypto.GetAuthKey(req.ProxyName + s.AuthToken + fmt.Sprintf("%d", req.Timestamp))
+		// authKey avaiable in 15 minutes
+		if nowTime-req.Timestamp > 15*60 {
+			info = fmt.Sprintf("ProxyName [%s], authorization timeout", req.ProxyName)
+			log.Warn(info)
+			return
+		} else if req.AuthKey != authKey {
+			info = fmt.Sprintf("ProxyName [%s], authorization failed", req.ProxyName)
+			log.Warn(info)
+			return
+		}
 	}
 
 	// control conn
 	if req.Type == consts.NewCtlConn {
+		if req.PrivilegeMode {
+			s = server.NewProxyServerFromCtlMsg(req)
+			err := server.CreateProxy(s)
+			if err != nil {
+				info = fmt.Sprintf("ProxyName [%s], %v", req.ProxyName, err)
+				log.Warn(info)
+				return
+			}
+		}
+
 		if s.Status == consts.Working {
 			info = fmt.Sprintf("ProxyName [%s], already in use", req.ProxyName)
 			log.Warn(info)
 			return
 		}
 
+		// check if vhost_port is set
+		if s.Type == "http" && server.VhostHttpMuxer == nil {
+			info = fmt.Sprintf("ProxyName [%s], type [http] not support when vhost_http_port is not set", req.ProxyName)
+			log.Warn(info)
+			return
+		}
+		if s.Type == "https" && server.VhostHttpsMuxer == nil {
+			info = fmt.Sprintf("ProxyName [%s], type [https] not support when vhost_https_port is not set", req.ProxyName)
+			log.Warn(info)
+			return
+		}
+
 		// set infomations from frpc
 		s.UseEncryption = req.UseEncryption
+		s.UseGzip = req.UseGzip
 
 		// start proxy and listen for user connections, no block
 		err := s.Start(c)
@@ -235,6 +285,9 @@ func doLogin(req *msg.ControlReq, c *conn.Conn) (ret int64, info string) {
 			return
 		}
 		log.Info("ProxyName [%s], start proxy success", req.ProxyName)
+		if req.PrivilegeMode {
+			log.Info("ProxyName [%s], created by PrivilegeMode", req.ProxyName)
+		}
 	} else if req.Type == consts.NewWorkConn {
 		// work conn
 		if s.Status != consts.Working {

+ 17 - 1
src/frp/cmd/frps/main.go

@@ -143,12 +143,25 @@ func main() {
 			log.Error("Create vhost http listener error, %v", err)
 			os.Exit(1)
 		}
-		server.VhostMuxer, err = vhost.NewHttpMuxer(vhostListener, 30*time.Second)
+		server.VhostHttpMuxer, err = vhost.NewHttpMuxer(vhostListener, 30*time.Second)
 		if err != nil {
 			log.Error("Create vhost httpMuxer error, %v", err)
 		}
 	}
 
+	// create vhost if VhostHttpPort != 0
+	if server.VhostHttpsPort != 0 {
+		vhostListener, err := conn.Listen(server.BindAddr, server.VhostHttpsPort)
+		if err != nil {
+			log.Error("Create vhost https listener error, %v", err)
+			os.Exit(1)
+		}
+		server.VhostHttpsMuxer, err = vhost.NewHttpsMuxer(vhostListener, 30*time.Second)
+		if err != nil {
+			log.Error("Create vhost httpsMuxer error, %v", err)
+		}
+	}
+
 	// create dashboard web server if DashboardPort is set, so it won't be 0
 	if server.DashboardPort != 0 {
 		err := server.RunDashboardServer(server.BindAddr, server.DashboardPort)
@@ -159,5 +172,8 @@ func main() {
 	}
 
 	log.Info("Start frps success")
+	if server.PrivilegeMode == true {
+		log.Info("PrivilegeMode is enabled, you should pay more attention to security issues")
+	}
 	ProcessControlConn(l)
 }

+ 19 - 16
src/frp/models/client/client.go

@@ -19,6 +19,7 @@ import (
 	"fmt"
 	"time"
 
+	"frp/models/config"
 	"frp/models/consts"
 	"frp/models/msg"
 	"frp/utils/conn"
@@ -27,12 +28,12 @@ import (
 )
 
 type ProxyClient struct {
-	Name          string
-	AuthToken     string
-	LocalIp       string
-	LocalPort     int64
-	Type          string
-	UseEncryption bool
+	config.BaseConf
+	LocalIp   string
+	LocalPort int64
+
+	RemotePort    int64
+	CustomDomains []string
 }
 
 func (p *ProxyClient) GetLocalConn() (c *conn.Conn, err error) {
@@ -57,12 +58,18 @@ func (p *ProxyClient) GetRemoteConn(addr string, port int64) (c *conn.Conn, err
 	}
 
 	nowTime := time.Now().Unix()
-	authKey := pcrypto.GetAuthKey(p.Name + p.AuthToken + fmt.Sprintf("%d", nowTime))
 	req := &msg.ControlReq{
-		Type:      consts.NewWorkConn,
-		ProxyName: p.Name,
-		AuthKey:   authKey,
-		Timestamp: nowTime,
+		Type:          consts.NewWorkConn,
+		ProxyName:     p.Name,
+		PrivilegeMode: p.PrivilegeMode,
+		Timestamp:     nowTime,
+	}
+	if p.PrivilegeMode == true {
+		privilegeKey := pcrypto.GetAuthKey(p.Name + PrivilegeToken + fmt.Sprintf("%d", nowTime))
+		req.PrivilegeKey = privilegeKey
+	} else {
+		authKey := pcrypto.GetAuthKey(p.Name + p.AuthToken + fmt.Sprintf("%d", nowTime))
+		req.AuthKey = authKey
 	}
 
 	buf, _ := json.Marshal(req)
@@ -89,11 +96,7 @@ func (p *ProxyClient) StartTunnel(serverAddr string, serverPort int64) (err erro
 	// l means local, r means remote
 	log.Debug("Join two connections, (l[%s] r[%s]) (l[%s] r[%s])", localConn.GetLocalAddr(), localConn.GetRemoteAddr(),
 		remoteConn.GetLocalAddr(), remoteConn.GetRemoteAddr())
-	if p.UseEncryption {
-		go conn.JoinMore(localConn, remoteConn, p.AuthToken)
-	} else {
-		go conn.Join(localConn, remoteConn)
-	}
+	go msg.JoinMore(localConn, remoteConn, p.BaseConf)
 
 	return nil
 }

+ 80 - 13
src/frp/models/client/config.go

@@ -17,6 +17,7 @@ package client
 import (
 	"fmt"
 	"strconv"
+	"strings"
 
 	ini "github.com/vaughan0/go-ini"
 )
@@ -29,6 +30,7 @@ var (
 	LogWay            string = "console"
 	LogLevel          string = "info"
 	LogMaxDays        int64  = 3
+	PrivilegeToken    string = ""
 	HeartBeatInterval int64  = 20
 	HeartBeatTimeout  int64  = 90
 )
@@ -75,12 +77,15 @@ func LoadConf(confFile string) (err error) {
 		LogMaxDays, _ = strconv.ParseInt(tmpStr, 10, 64)
 	}
 
+	tmpStr, ok = conf.Get("common", "privilege_token")
+	if ok {
+		PrivilegeToken = tmpStr
+	}
+
 	var authToken string
 	tmpStr, ok = conf.Get("common", "auth_token")
 	if ok {
 		authToken = tmpStr
-	} else {
-		return fmt.Errorf("auth_token not found")
 	}
 
 	// proxies
@@ -101,39 +106,101 @@ func LoadConf(confFile string) (err error) {
 			}
 
 			// local_port
-			portStr, ok := section["local_port"]
+			tmpStr, ok = section["local_port"]
 			if ok {
-				proxyClient.LocalPort, err = strconv.ParseInt(portStr, 10, 64)
+				proxyClient.LocalPort, err = strconv.ParseInt(tmpStr, 10, 64)
 				if err != nil {
-					return fmt.Errorf("Parse ini file error: proxy [%s] local_port error", proxyClient.Name)
+					return fmt.Errorf("Parse conf error: proxy [%s] local_port error", proxyClient.Name)
 				}
 			} else {
-				return fmt.Errorf("Parse ini file error: proxy [%s] local_port not found", proxyClient.Name)
+				return fmt.Errorf("Parse conf error: proxy [%s] local_port not found", proxyClient.Name)
 			}
 
 			// type
 			proxyClient.Type = "tcp"
-			typeStr, ok := section["type"]
+			tmpStr, ok = section["type"]
 			if ok {
-				if typeStr != "tcp" && typeStr != "http" {
-					return fmt.Errorf("Parse ini file error: proxy [%s] type error", proxyClient.Name)
+				if tmpStr != "tcp" && tmpStr != "http" && tmpStr != "https" {
+					return fmt.Errorf("Parse conf error: proxy [%s] type error", proxyClient.Name)
 				}
-				proxyClient.Type = typeStr
+				proxyClient.Type = tmpStr
 			}
 
 			// use_encryption
 			proxyClient.UseEncryption = false
-			useEncryptionStr, ok := section["use_encryption"]
-			if ok && useEncryptionStr == "true" {
+			tmpStr, ok = section["use_encryption"]
+			if ok && tmpStr == "true" {
 				proxyClient.UseEncryption = true
 			}
 
+			// use_gzip
+			proxyClient.UseGzip = false
+			tmpStr, ok = section["use_gzip"]
+			if ok && tmpStr == "true" {
+				proxyClient.UseGzip = true
+			}
+
+			// privilege_mode
+			proxyClient.PrivilegeMode = false
+			tmpStr, ok = section["privilege_mode"]
+			if ok && tmpStr == "true" {
+				proxyClient.PrivilegeMode = true
+			}
+
+			// configures used in privilege mode
+			if proxyClient.PrivilegeMode == true {
+				if PrivilegeToken == "" {
+					return fmt.Errorf("Parse conf error: proxy [%s] privilege_key must be set when privilege_mode = true", proxyClient.Name)
+				} else {
+					proxyClient.PrivilegeToken = PrivilegeToken
+				}
+
+				if proxyClient.Type == "tcp" {
+					// remote_port
+					tmpStr, ok = section["remote_port"]
+					if ok {
+						proxyClient.RemotePort, err = strconv.ParseInt(tmpStr, 10, 64)
+						if err != nil {
+							return fmt.Errorf("Parse conf error: proxy [%s] remote_port error", proxyClient.Name)
+						}
+					} else {
+						return fmt.Errorf("Parse conf error: proxy [%s] remote_port not found", proxyClient.Name)
+					}
+				} else if proxyClient.Type == "http" {
+					domainStr, ok := section["custom_domains"]
+					if ok {
+						proxyClient.CustomDomains = strings.Split(domainStr, ",")
+						if len(proxyClient.CustomDomains) == 0 {
+							return fmt.Errorf("Parse conf error: proxy [%s] custom_domains must be set when type equals http", proxyClient.Name)
+						}
+						for i, domain := range proxyClient.CustomDomains {
+							proxyClient.CustomDomains[i] = strings.ToLower(strings.TrimSpace(domain))
+						}
+					} else {
+						return fmt.Errorf("Parse conf error: proxy [%s] custom_domains must be set when type equals http", proxyClient.Name)
+					}
+				} else if proxyClient.Type == "https" {
+					domainStr, ok := section["custom_domains"]
+					if ok {
+						proxyClient.CustomDomains = strings.Split(domainStr, ",")
+						if len(proxyClient.CustomDomains) == 0 {
+							return fmt.Errorf("Parse conf error: proxy [%s] custom_domains must be set when type equals https", proxyClient.Name)
+						}
+						for i, domain := range proxyClient.CustomDomains {
+							proxyClient.CustomDomains[i] = strings.ToLower(strings.TrimSpace(domain))
+						}
+					} else {
+						return fmt.Errorf("Parse conf error: proxy [%s] custom_domains must be set when type equals http", proxyClient.Name)
+					}
+				}
+			}
+
 			ProxyClients[proxyClient.Name] = proxyClient
 		}
 	}
 
 	if len(ProxyClients) == 0 {
-		return fmt.Errorf("Parse ini file error: no proxy config found")
+		return fmt.Errorf("Parse conf error: no proxy config found")
 	}
 
 	return nil

+ 25 - 0
src/frp/models/config/config.go

@@ -0,0 +1,25 @@
+// Copyright 2016 fatedier, fatedier@gmail.com
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package config
+
+type BaseConf struct {
+	Name           string
+	AuthToken      string
+	Type           string
+	UseEncryption  bool
+	UseGzip        bool
+	PrivilegeMode  bool
+	PrivilegeToken string
+}

+ 13 - 5
src/frp/models/msg/msg.go

@@ -19,13 +19,21 @@ type GeneralRes struct {
 	Msg  string `json:"msg"`
 }
 
-// messages between control connection of frpc and frps
+// messages between control connections of frpc and frps
 type ControlReq struct {
 	Type          int64  `json:"type"`
-	ProxyName     string `json:"proxy_name,omitempty"`
-	AuthKey       string `json:"auth_key, omitempty"`
-	UseEncryption bool   `json:"use_encryption, omitempty"`
-	Timestamp     int64  `json:"timestamp, omitempty"`
+	ProxyName     string `json:"proxy_name"`
+	AuthKey       string `json:"auth_key"`
+	UseEncryption bool   `json:"use_encryption"`
+	UseGzip       bool   `json:"use_gzip"`
+
+	// configures used if privilege_mode is enabled
+	PrivilegeMode bool     `json:"privilege_mode"`
+	PrivilegeKey  string   `json:"privilege_key"`
+	ProxyType     string   `json:"proxy_type"`
+	RemotePort    int64    `json:"remote_port"`
+	CustomDomains []string `json:"custom_domains, omitempty"`
+	Timestamp     int64    `json:"timestamp"`
 }
 
 type ControlRes struct {

+ 209 - 0
src/frp/models/msg/process.go

@@ -0,0 +1,209 @@
+// Copyright 2016 fatedier, fatedier@gmail.com
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package msg
+
+import (
+	"bufio"
+	"bytes"
+	"encoding/binary"
+	"fmt"
+	"io"
+	"net"
+	"sync"
+
+	"frp/models/config"
+	"frp/utils/conn"
+	"frp/utils/log"
+	"frp/utils/pcrypto"
+)
+
+// will block until connection close
+func Join(c1 *conn.Conn, c2 *conn.Conn) {
+	var wait sync.WaitGroup
+	pipe := func(to *conn.Conn, from *conn.Conn) {
+		defer to.Close()
+		defer from.Close()
+		defer wait.Done()
+
+		var err error
+		_, err = io.Copy(to.TcpConn, from.TcpConn)
+		if err != nil {
+			log.Warn("join connections error, %v", err)
+		}
+	}
+
+	wait.Add(2)
+	go pipe(c1, c2)
+	go pipe(c2, c1)
+	wait.Wait()
+	return
+}
+
+// join two connections and do some operations
+func JoinMore(c1 *conn.Conn, c2 *conn.Conn, conf config.BaseConf) {
+	var wait sync.WaitGroup
+	encryptPipe := func(from *conn.Conn, to *conn.Conn) {
+		defer from.Close()
+		defer to.Close()
+		defer wait.Done()
+
+		// we don't care about errors here
+		pipeEncrypt(from.TcpConn, to.TcpConn, conf)
+	}
+
+	decryptPipe := func(to *conn.Conn, from *conn.Conn) {
+		defer from.Close()
+		defer to.Close()
+		defer wait.Done()
+
+		// we don't care about errors here
+		pipeDecrypt(to.TcpConn, from.TcpConn, conf)
+	}
+
+	wait.Add(2)
+	go encryptPipe(c1, c2)
+	go decryptPipe(c2, c1)
+	wait.Wait()
+	log.Debug("ProxyName [%s], One tunnel stopped", conf.Name)
+	return
+}
+
+func pkgMsg(data []byte) []byte {
+	llen := uint32(len(data))
+	buf := new(bytes.Buffer)
+	binary.Write(buf, binary.BigEndian, llen)
+	buf.Write(data)
+	return buf.Bytes()
+}
+
+func unpkgMsg(data []byte) (int, []byte, []byte) {
+	if len(data) < 4 {
+		return -1, nil, data
+	}
+	llen := int(binary.BigEndian.Uint32(data[0:4]))
+	// no complete
+	if len(data) < llen+4 {
+		return -1, nil, data
+	}
+
+	return 0, data[4 : llen+4], data[llen+4:]
+}
+
+// decrypt msg from reader, then write into writer
+func pipeDecrypt(r net.Conn, w net.Conn, conf config.BaseConf) (err error) {
+	laes := new(pcrypto.Pcrypto)
+	key := conf.AuthToken
+	if conf.PrivilegeMode {
+		key = conf.PrivilegeToken
+	}
+	if err := laes.Init([]byte(key)); err != nil {
+		log.Warn("ProxyName [%s], Pcrypto Init error: %v", conf.Name, err)
+		return fmt.Errorf("Pcrypto Init error: %v", err)
+	}
+
+	buf := make([]byte, 5*1024+4)
+	var left, res []byte
+	var cnt int
+	nreader := bufio.NewReader(r)
+	for {
+		// there may be more than 1 package in variable
+		// and we read more bytes if unpkgMsg returns an error
+		var newBuf []byte
+		if cnt < 0 {
+			n, err := nreader.Read(buf)
+			if err != nil {
+				return err
+			}
+			newBuf = append(left, buf[0:n]...)
+		} else {
+			newBuf = left
+		}
+		cnt, res, left = unpkgMsg(newBuf)
+		if cnt < 0 {
+			continue
+		}
+
+		// aes
+		if conf.UseEncryption {
+			res, err = laes.Decrypt(res)
+			if err != nil {
+				log.Warn("ProxyName [%s], decrypt error, %v", conf.Name, err)
+				return fmt.Errorf("Decrypt error: %v", err)
+			}
+		}
+		// gzip
+		if conf.UseGzip {
+			res, err = laes.Decompression(res)
+			if err != nil {
+				log.Warn("ProxyName [%s], decompression error, %v", conf.Name, err)
+				return fmt.Errorf("Decompression error: %v", err)
+			}
+		}
+
+		_, err = w.Write(res)
+		if err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+// recvive msg from reader, then encrypt msg into writer
+func pipeEncrypt(r net.Conn, w net.Conn, conf config.BaseConf) (err error) {
+	laes := new(pcrypto.Pcrypto)
+	key := conf.AuthToken
+	if conf.PrivilegeMode {
+		key = conf.PrivilegeToken
+	}
+	if err := laes.Init([]byte(key)); err != nil {
+		log.Warn("ProxyName [%s], Pcrypto Init error: %v", conf.Name, err)
+		return fmt.Errorf("Pcrypto Init error: %v", err)
+	}
+
+	nreader := bufio.NewReader(r)
+	buf := make([]byte, 5*1024)
+	for {
+		n, err := nreader.Read(buf)
+		if err != nil {
+			return err
+		}
+
+		res := buf[0:n]
+		// gzip
+		if conf.UseGzip {
+			res, err = laes.Compression(res)
+			if err != nil {
+				log.Warn("ProxyName [%s], compression error: %v", conf.Name, err)
+				return fmt.Errorf("Compression error: %v", err)
+			}
+		}
+		// aes
+		if conf.UseEncryption {
+			res, err = laes.Encrypt(res)
+			if err != nil {
+				log.Warn("ProxyName [%s], encrypt error: %v", conf.Name, err)
+				return fmt.Errorf("Encrypt error: %v", err)
+			}
+		}
+
+		res = pkgMsg(res)
+		_, err = w.Write(res)
+		if err != nil {
+			return err
+		}
+	}
+
+	return nil
+}

+ 91 - 13
src/frp/models/server/config.go

@@ -22,6 +22,7 @@ import (
 
 	ini "github.com/vaughan0/go-ini"
 
+	"frp/models/consts"
 	"frp/utils/log"
 	"frp/utils/vhost"
 )
@@ -31,16 +32,20 @@ var (
 	ConfigFile       string = "./frps.ini"
 	BindAddr         string = "0.0.0.0"
 	BindPort         int64  = 7000
-	VhostHttpPort    int64  = 0 // if VhostHttpPort equals 0, don't listen a public port for http
+	VhostHttpPort    int64  = 0 // if VhostHttpPort equals 0, don't listen a public port for http protocol
+	VhostHttpsPort   int64  = 0 // if VhostHttpsPort equals 0, don't listen a public port for https protocol
 	DashboardPort    int64  = 0 // if DashboardPort equals 0, dashboard is not available
 	LogFile          string = "console"
 	LogWay           string = "console" // console or file
 	LogLevel         string = "info"
 	LogMaxDays       int64  = 3
+	PrivilegeMode    bool   = false
+	PrivilegeToken   string = ""
 	HeartBeatTimeout int64  = 90
 	UserConnTimeout  int64  = 10
 
-	VhostMuxer        *vhost.HttpMuxer
+	VhostHttpMuxer    *vhost.HttpMuxer
+	VhostHttpsMuxer   *vhost.HttpsMuxer
 	ProxyServers      map[string]*ProxyServer = make(map[string]*ProxyServer) // all proxy servers info and resources
 	ProxyServersMutex sync.RWMutex
 )
@@ -81,7 +86,10 @@ func loadCommonConf(confFile string) error {
 
 	tmpStr, ok = conf.Get("common", "bind_port")
 	if ok {
-		BindPort, _ = strconv.ParseInt(tmpStr, 10, 64)
+		v, err := strconv.ParseInt(tmpStr, 10, 64)
+		if err == nil {
+			BindPort = v
+		}
 	}
 
 	tmpStr, ok = conf.Get("common", "vhost_http_port")
@@ -91,6 +99,13 @@ func loadCommonConf(confFile string) error {
 		VhostHttpPort = 0
 	}
 
+	tmpStr, ok = conf.Get("common", "vhost_https_port")
+	if ok {
+		VhostHttpsPort, _ = strconv.ParseInt(tmpStr, 10, 64)
+	} else {
+		VhostHttpsPort = 0
+	}
+
 	tmpStr, ok = conf.Get("common", "dashboard_port")
 	if ok {
 		DashboardPort, _ = strconv.ParseInt(tmpStr, 10, 64)
@@ -115,7 +130,29 @@ func loadCommonConf(confFile string) error {
 
 	tmpStr, ok = conf.Get("common", "log_max_days")
 	if ok {
-		LogMaxDays, _ = strconv.ParseInt(tmpStr, 10, 64)
+		v, err := strconv.ParseInt(tmpStr, 10, 64)
+		if err == nil {
+			LogMaxDays = v
+		}
+	}
+
+	tmpStr, ok = conf.Get("common", "privilege_mode")
+	if ok {
+		if tmpStr == "true" {
+			PrivilegeMode = true
+		}
+	}
+
+	if PrivilegeMode == true {
+		tmpStr, ok = conf.Get("common", "privilege_token")
+		if ok {
+			if tmpStr == "" {
+				return fmt.Errorf("Parse conf error: privilege_token can not be null")
+			}
+			PrivilegeToken = tmpStr
+		} else {
+			return fmt.Errorf("Parse conf error: privilege_token must be set if privilege_mode is enabled")
+		}
 	}
 	return nil
 }
@@ -135,7 +172,7 @@ func loadProxyConf(confFile string) (proxyServers map[string]*ProxyServer, err e
 
 			proxyServer.Type, ok = section["type"]
 			if ok {
-				if proxyServer.Type != "tcp" && proxyServer.Type != "http" {
+				if proxyServer.Type != "tcp" && proxyServer.Type != "http" && proxyServer.Type != "https" {
 					return proxyServers, fmt.Errorf("Parse conf error: proxy [%s] type error", proxyServer.Name)
 				}
 			} else {
@@ -167,17 +204,29 @@ func loadProxyConf(confFile string) (proxyServers map[string]*ProxyServer, err e
 				// for http
 				domainStr, ok := section["custom_domains"]
 				if ok {
-					var suffix string
-					if VhostHttpPort != 80 {
-						suffix = fmt.Sprintf(":%d", VhostHttpPort)
-					}
 					proxyServer.CustomDomains = strings.Split(domainStr, ",")
 					if len(proxyServer.CustomDomains) == 0 {
 						return proxyServers, fmt.Errorf("Parse conf error: proxy [%s] custom_domains must be set when type equals http", proxyServer.Name)
 					}
 					for i, domain := range proxyServer.CustomDomains {
-						proxyServer.CustomDomains[i] = strings.ToLower(strings.TrimSpace(domain)) + suffix
+						proxyServer.CustomDomains[i] = strings.ToLower(strings.TrimSpace(domain))
 					}
+				} else {
+					return proxyServers, fmt.Errorf("Parse conf error: proxy [%s] custom_domains must be set when type equals http", proxyServer.Name)
+				}
+			} else if proxyServer.Type == "https" {
+				// for https
+				domainStr, ok := section["custom_domains"]
+				if ok {
+					proxyServer.CustomDomains = strings.Split(domainStr, ",")
+					if len(proxyServer.CustomDomains) == 0 {
+						return proxyServers, fmt.Errorf("Parse conf error: proxy [%s] custom_domains must be set when type equals https", proxyServer.Name)
+					}
+					for i, domain := range proxyServer.CustomDomains {
+						proxyServer.CustomDomains[i] = strings.ToLower(strings.TrimSpace(domain))
+					}
+				} else {
+					return proxyServers, fmt.Errorf("Parse conf error: proxy [%s] custom_domains must be set when type equals https", proxyServer.Name)
 				}
 			}
 			proxyServers[proxyServer.Name] = proxyServer
@@ -211,14 +260,43 @@ func ReloadConf(confFile string) (err error) {
 		}
 	}
 
+	// proxies created by PrivilegeMode won't be deleted
 	for name, oldProxyServer := range ProxyServers {
 		_, ok := loadProxyServers[name]
 		if !ok {
-			oldProxyServer.Close()
-			delete(ProxyServers, name)
-			log.Info("ProxyName [%s] deleted, close it", name)
+			if !oldProxyServer.PrivilegeMode {
+				oldProxyServer.Close()
+				delete(ProxyServers, name)
+				log.Info("ProxyName [%s] deleted, close it", name)
+			} else {
+				log.Info("ProxyName [%s] created by PrivilegeMode, won't be closed", name)
+			}
 		}
 	}
 	ProxyServersMutex.Unlock()
 	return nil
 }
+
+func CreateProxy(s *ProxyServer) error {
+	ProxyServersMutex.Lock()
+	defer ProxyServersMutex.Unlock()
+	oldServer, ok := ProxyServers[s.Name]
+	if ok {
+		if oldServer.Status == consts.Working {
+			return fmt.Errorf("this proxy is already working now")
+		}
+		oldServer.Close()
+		if oldServer.PrivilegeMode {
+			delete(ProxyServers, s.Name)
+		}
+	}
+	s.Init()
+	ProxyServers[s.Name] = s
+	return nil
+}
+
+func DeleteProxy(proxyName string) {
+	ProxyServersMutex.Lock()
+	defer ProxyServersMutex.Unlock()
+	delete(ProxyServers, proxyName)
+}

+ 37 - 20
src/frp/models/server/server.go

@@ -19,7 +19,9 @@ import (
 	"sync"
 	"time"
 
+	"frp/models/config"
 	"frp/models/consts"
+	"frp/models/msg"
 	"frp/utils/conn"
 	"frp/utils/log"
 )
@@ -30,16 +32,11 @@ type Listener interface {
 }
 
 type ProxyServer struct {
-	Name          string
-	AuthToken     string
-	Type          string
+	config.BaseConf
 	BindAddr      string
 	ListenPort    int64
 	CustomDomains []string
 
-	// configure in frpc.ini
-	UseEncryption bool
-
 	Status       int64
 	CtlConn      *conn.Conn      // control connection with frpc
 	listeners    []Listener      // accept new connection from remote users
@@ -55,6 +52,20 @@ func NewProxyServer() (p *ProxyServer) {
 	return p
 }
 
+func NewProxyServerFromCtlMsg(req *msg.ControlReq) (p *ProxyServer) {
+	p = &ProxyServer{}
+	p.Name = req.ProxyName
+	p.Type = req.ProxyType
+	p.UseEncryption = req.UseEncryption
+	p.UseGzip = req.UseGzip
+	p.PrivilegeMode = req.PrivilegeMode
+	p.PrivilegeToken = PrivilegeToken
+	p.BindAddr = BindAddr
+	p.ListenPort = req.RemotePort
+	p.CustomDomains = req.CustomDomains
+	return
+}
+
 func (p *ProxyServer) Init() {
 	p.Lock()
 	p.Status = consts.Idle
@@ -100,7 +111,15 @@ func (p *ProxyServer) Start(c *conn.Conn) (err error) {
 		p.listeners = append(p.listeners, l)
 	} else if p.Type == "http" {
 		for _, domain := range p.CustomDomains {
-			l, err := VhostMuxer.Listen(domain)
+			l, err := VhostHttpMuxer.Listen(domain)
+			if err != nil {
+				return err
+			}
+			p.listeners = append(p.listeners, l)
+		}
+	} else if p.Type == "https" {
+		for _, domain := range p.CustomDomains {
+			l, err := VhostHttpsMuxer.Listen(domain)
 			if err != nil {
 				return err
 			}
@@ -144,11 +163,7 @@ func (p *ProxyServer) Start(c *conn.Conn) (err error) {
 					log.Debug("Join two connections, (l[%s] r[%s]) (l[%s] r[%s])", workConn.GetLocalAddr(), workConn.GetRemoteAddr(),
 						userConn.GetLocalAddr(), userConn.GetRemoteAddr())
 
-					if p.UseEncryption {
-						go conn.JoinMore(userConn, workConn, p.AuthToken)
-					} else {
-						go conn.Join(userConn, workConn)
-					}
+					go msg.JoinMore(userConn, workConn, p.BaseConf)
 				}()
 			}
 		}(listener)
@@ -160,11 +175,9 @@ func (p *ProxyServer) Close() {
 	p.Lock()
 	if p.Status != consts.Closed {
 		p.Status = consts.Closed
-		if len(p.listeners) != 0 {
-			for _, l := range p.listeners {
-				if l != nil {
-					l.Close()
-				}
+		for _, l := range p.listeners {
+			if l != nil {
+				l.Close()
 			}
 		}
 		close(p.ctlMsgChan)
@@ -173,6 +186,10 @@ func (p *ProxyServer) Close() {
 			p.CtlConn.Close()
 		}
 	}
+	// if the proxy created by PrivilegeMode, delete it when closed
+	if p.PrivilegeMode {
+		DeleteProxy(p.Name)
+	}
 	p.Unlock()
 }
 
@@ -190,9 +207,9 @@ func (p *ProxyServer) RegisterNewWorkConn(c *conn.Conn) {
 	p.workConnChan <- c
 }
 
-// when frps get one user connection, we get one work connection from the pool and return it
-// if no workConn available in the pool, send message to frpc to get one or more
-// and wait until it is available
+// When frps get one user connection, we get one work connection from the pool and return it.
+// If no workConn available in the pool, send message to frpc to get one or more
+// and wait until it is available.
 // return an error if wait timeout
 func (p *ProxyServer) getWorkConn() (workConn *conn.Conn, err error) {
 	var ok bool

+ 0 - 115
src/frp/utils/conn/conn.go

@@ -22,9 +22,6 @@ import (
 	"strings"
 	"sync"
 	"time"
-
-	"frp/utils/log"
-	"frp/utils/pcrypto"
 )
 
 type Listener struct {
@@ -167,115 +164,3 @@ func (c *Conn) IsClosed() (closeFlag bool) {
 	c.mutex.RUnlock()
 	return
 }
-
-// will block until connection close
-func Join(c1 *Conn, c2 *Conn) {
-	var wait sync.WaitGroup
-	pipe := func(to *Conn, from *Conn) {
-		defer to.Close()
-		defer from.Close()
-		defer wait.Done()
-
-		var err error
-		_, err = io.Copy(to.TcpConn, from.TcpConn)
-		if err != nil {
-			log.Warn("join connections error, %v", err)
-		}
-	}
-
-	wait.Add(2)
-	go pipe(c1, c2)
-	go pipe(c2, c1)
-	wait.Wait()
-	return
-}
-
-// messages from c1 to c2 will be encrypted
-// and from c2 to c1 will be decrypted
-func JoinMore(c1 *Conn, c2 *Conn, cryptKey string) {
-	var wait sync.WaitGroup
-	encryptPipe := func(from *Conn, to *Conn, key string) {
-		defer from.Close()
-		defer to.Close()
-		defer wait.Done()
-
-		// we don't care about errors here
-		PipeEncrypt(from.TcpConn, to.TcpConn, key)
-	}
-
-	decryptPipe := func(to *Conn, from *Conn, key string) {
-		defer from.Close()
-		defer to.Close()
-		defer wait.Done()
-
-		// we don't care about errors here
-		PipeDecrypt(to.TcpConn, from.TcpConn, key)
-	}
-
-	wait.Add(2)
-	go encryptPipe(c1, c2, cryptKey)
-	go decryptPipe(c2, c1, cryptKey)
-	wait.Wait()
-	log.Debug("One tunnel stopped")
-	return
-}
-
-// decrypt msg from reader, then write into writer
-func PipeDecrypt(r net.Conn, w net.Conn, key string) error {
-	laes := new(pcrypto.Pcrypto)
-	if err := laes.Init([]byte(key)); err != nil {
-		log.Error("Pcrypto Init error: %v", err)
-		return fmt.Errorf("Pcrypto Init error: %v", err)
-	}
-
-	nreader := bufio.NewReader(r)
-	for {
-		buf, err := nreader.ReadBytes('\n')
-		if err != nil {
-			return err
-		}
-
-		res, err := laes.Decrypt(buf)
-		if err != nil {
-			log.Error("Decrypt [%s] error, %v", string(buf), err)
-			return fmt.Errorf("Decrypt [%s] error: %v", string(buf), err)
-		}
-
-		_, err = w.Write(res)
-		if err != nil {
-			return err
-		}
-	}
-	return nil
-}
-
-// recvive msg from reader, then encrypt msg into write
-func PipeEncrypt(r net.Conn, w net.Conn, key string) error {
-	laes := new(pcrypto.Pcrypto)
-	if err := laes.Init([]byte(key)); err != nil {
-		log.Error("Pcrypto Init error: %v", err)
-		return fmt.Errorf("Pcrypto Init error: %v", err)
-	}
-
-	nreader := bufio.NewReader(r)
-	buf := make([]byte, 10*1024)
-
-	for {
-		n, err := nreader.Read(buf)
-		if err != nil {
-			return err
-		}
-		res, err := laes.Encrypt(buf[:n])
-		if err != nil {
-			log.Error("Encrypt error: %v", err)
-			return fmt.Errorf("Encrypt error: %v", err)
-		}
-
-		res = append(res, '\n')
-		_, err = w.Write(res)
-		if err != nil {
-			return err
-		}
-	}
-	return nil
-}

+ 21 - 29
src/frp/utils/pcrypto/pcrypto.go

@@ -20,7 +20,6 @@ import (
 	"crypto/aes"
 	"crypto/cipher"
 	"crypto/md5"
-	"encoding/base64"
 	"encoding/hex"
 	"errors"
 	"fmt"
@@ -36,40 +35,21 @@ func (pc *Pcrypto) Init(key []byte) error {
 	var err error
 	pc.pkey = pKCS7Padding(key, aes.BlockSize)
 	pc.paes, err = aes.NewCipher(pc.pkey)
-
 	return err
 }
 
 func (pc *Pcrypto) Encrypt(src []byte) ([]byte, error) {
-	// gzip
-	var zbuf bytes.Buffer
-	zwr, err := gzip.NewWriterLevel(&zbuf, -1)
-	if err != nil {
-		return nil, err
-	}
-	defer zwr.Close()
-	zwr.Write(src)
-	zwr.Flush()
-
 	// aes
-	src = pKCS7Padding(zbuf.Bytes(), aes.BlockSize)
+	src = pKCS7Padding(src, aes.BlockSize)
 	blockMode := cipher.NewCBCEncrypter(pc.paes, pc.pkey)
 	crypted := make([]byte, len(src))
 	blockMode.CryptBlocks(crypted, src)
-
-	// base64
-	return []byte(base64.StdEncoding.EncodeToString(crypted)), nil
+	return crypted, nil
 }
 
 func (pc *Pcrypto) Decrypt(str []byte) ([]byte, error) {
-	// base64
-	data, err := base64.StdEncoding.DecodeString(string(str))
-	if err != nil {
-		return nil, err
-	}
-
 	// aes
-	decryptText, err := hex.DecodeString(fmt.Sprintf("%x", data))
+	decryptText, err := hex.DecodeString(fmt.Sprintf("%x", str))
 	if err != nil {
 		return nil, err
 	}
@@ -81,18 +61,30 @@ func (pc *Pcrypto) Decrypt(str []byte) ([]byte, error) {
 	blockMode := cipher.NewCBCDecrypter(pc.paes, pc.pkey)
 
 	blockMode.CryptBlocks(decryptText, decryptText)
-	decryptText = pKCS7UnPadding(decryptText)
+	return pKCS7UnPadding(decryptText), nil
+}
+
+func (pc *Pcrypto) Compression(src []byte) ([]byte, error) {
+	var zbuf bytes.Buffer
+	zwr, err := gzip.NewWriterLevel(&zbuf, gzip.DefaultCompression)
+	if err != nil {
+		return nil, err
+	}
+	defer zwr.Close()
+	zwr.Write(src)
+	zwr.Flush()
+	return zbuf.Bytes(), nil
+}
 
-	// gunzip
-	zbuf := bytes.NewBuffer(decryptText)
+func (pc *Pcrypto) Decompression(src []byte) ([]byte, error) {
+	zbuf := bytes.NewBuffer(src)
 	zrd, err := gzip.NewReader(zbuf)
 	if err != nil {
 		return nil, err
 	}
 	defer zrd.Close()
-	data, _ = ioutil.ReadAll(zrd)
-
-	return data, nil
+	str, _ := ioutil.ReadAll(zrd)
+	return str, nil
 }
 
 func pKCS7Padding(ciphertext []byte, blockSize int) []byte {

+ 29 - 14
src/frp/utils/pcrypto/pcrypto_test.go

@@ -15,33 +15,48 @@
 package pcrypto
 
 import (
-	"fmt"
 	"testing"
 )
 
-func TestEncrypt(t *testing.T) {
-	pp := new(Pcrypto)
+var (
+	pp *Pcrypto
+)
+
+func init() {
+	pp = &Pcrypto{}
 	pp.Init([]byte("Hana"))
-	res, err := pp.Encrypt([]byte("Just One Test!"))
+}
+
+func TestEncrypt(t *testing.T) {
+	testStr := "Test Encrypt!"
+	res, err := pp.Encrypt([]byte(testStr))
 	if err != nil {
-		t.Fatal(err)
+		t.Fatalf("encrypt error: %v", err)
 	}
 
-	fmt.Printf("[%x]\n", res)
+	res, err = pp.Decrypt([]byte(res))
+	if err != nil {
+		t.Fatalf("decrypt error: %v", err)
+	}
+
+	if string(res) != testStr {
+		t.Fatalf("test encrypt error, from [%s] to [%s]", testStr, string(res))
+	}
 }
 
-func TestDecrypt(t *testing.T) {
-	pp := new(Pcrypto)
-	pp.Init([]byte("Hana"))
-	res, err := pp.Encrypt([]byte("Just One Test!"))
+func TestCompression(t *testing.T) {
+	testStr := "Test Compression!"
+	res, err := pp.Compression([]byte(testStr))
 	if err != nil {
-		t.Fatal(err)
+		t.Fatalf("compression error: %v", err)
 	}
 
-	res, err = pp.Decrypt(res)
+	res, err = pp.Decompression(res)
 	if err != nil {
-		t.Fatal(err)
+		t.Fatalf("decompression error: %v", err)
 	}
 
-	fmt.Printf("[%s]\n", string(res))
+	if string(res) != testStr {
+		t.Fatalf("test compression error, from [%s] to [%s]", testStr, string(res))
+	}
 }

+ 1 - 1
src/frp/utils/version/version.go

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

+ 47 - 0
src/frp/utils/vhost/http.go

@@ -0,0 +1,47 @@
+// Copyright 2016 fatedier, fatedier@gmail.com
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package vhost
+
+import (
+	"bufio"
+	"net"
+	"net/http"
+	"strings"
+	"time"
+
+	"frp/utils/conn"
+)
+
+type HttpMuxer struct {
+	*VhostMuxer
+}
+
+func GetHttpHostname(c *conn.Conn) (_ net.Conn, routerName string, err error) {
+	sc, rd := newShareConn(c.TcpConn)
+
+	request, err := http.ReadRequest(bufio.NewReader(rd))
+	if err != nil {
+		return sc, "", err
+	}
+	tmpArr := strings.Split(request.Host, ":")
+	routerName = tmpArr[0]
+	request.Body.Close()
+	return sc, routerName, nil
+}
+
+func NewHttpMuxer(listener *conn.Listener, timeout time.Duration) (*HttpMuxer, error) {
+	mux, err := NewVhostMuxer(listener, GetHttpHostname, timeout)
+	return &HttpMuxer{mux}, err
+}

+ 185 - 0
src/frp/utils/vhost/https.go

@@ -0,0 +1,185 @@
+// Copyright 2016 fatedier, fatedier@gmail.com
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package vhost
+
+import (
+	"fmt"
+	"io"
+	"net"
+	"strings"
+	"time"
+
+	"frp/utils/conn"
+)
+
+const (
+	typeClientHello uint8 = 1 // Type client hello
+)
+
+// TLS extension numbers
+const (
+	extensionServerName          uint16 = 0
+	extensionStatusRequest       uint16 = 5
+	extensionSupportedCurves     uint16 = 10
+	extensionSupportedPoints     uint16 = 11
+	extensionSignatureAlgorithms uint16 = 13
+	extensionALPN                uint16 = 16
+	extensionSCT                 uint16 = 18
+	extensionSessionTicket       uint16 = 35
+	extensionNextProtoNeg        uint16 = 13172 // not IANA assigned
+	extensionRenegotiationInfo   uint16 = 0xff01
+)
+
+type HttpsMuxer struct {
+	*VhostMuxer
+}
+
+func NewHttpsMuxer(listener *conn.Listener, timeout time.Duration) (*HttpsMuxer, error) {
+	mux, err := NewVhostMuxer(listener, GetHttpsHostname, timeout)
+	return &HttpsMuxer{mux}, err
+}
+
+func readHandshake(rd io.Reader) (host string, err error) {
+	data := make([]byte, 1024)
+	length, err := rd.Read(data)
+	if err != nil {
+		return
+	} else {
+		if length < 47 {
+			err = fmt.Errorf("readHandshake: proto length[%d] is too short", length)
+			return
+		}
+	}
+	data = data[:length]
+	if uint8(data[5]) != typeClientHello {
+		err = fmt.Errorf("readHandshake: type[%d] is not clientHello", uint16(data[5]))
+		return
+	}
+
+	// session
+	sessionIdLen := int(data[43])
+	if sessionIdLen > 32 || len(data) < 44+sessionIdLen {
+		err = fmt.Errorf("readHandshake: sessionIdLen[%d] is long", sessionIdLen)
+		return
+	}
+	data = data[44+sessionIdLen:]
+	if len(data) < 2 {
+		err = fmt.Errorf("readHandshake: dataLen[%d] after session is short", len(data))
+		return
+	}
+
+	// cipher suite numbers
+	cipherSuiteLen := int(data[0])<<8 | int(data[1])
+	if cipherSuiteLen%2 == 1 || len(data) < 2+cipherSuiteLen {
+		err = fmt.Errorf("readHandshake: dataLen[%d] after cipher suite is short", len(data))
+		return
+	}
+	data = data[2+cipherSuiteLen:]
+	if len(data) < 1 {
+		err = fmt.Errorf("readHandshake: cipherSuiteLen[%d] is long", cipherSuiteLen)
+		return
+	}
+
+	// compression method
+	compressionMethodsLen := int(data[0])
+	if len(data) < 1+compressionMethodsLen {
+		err = fmt.Errorf("readHandshake: compressionMethodsLen[%d] is long", compressionMethodsLen)
+		return
+	}
+
+	data = data[1+compressionMethodsLen:]
+	if len(data) == 0 {
+		// ClientHello is optionally followed by extension data
+		err = fmt.Errorf("readHandshake: there is no extension data to get servername")
+		return
+	}
+	if len(data) < 2 {
+		err = fmt.Errorf("readHandshake: extension dataLen[%d] is too short")
+		return
+	}
+
+	extensionsLength := int(data[0])<<8 | int(data[1])
+	data = data[2:]
+	if extensionsLength != len(data) {
+		err = fmt.Errorf("readHandshake: extensionsLen[%d] is not equal to dataLen[%d]", extensionsLength, len(data))
+		return
+	}
+	for len(data) != 0 {
+		if len(data) < 4 {
+			err = fmt.Errorf("readHandshake: extensionsDataLen[%d] is too short", len(data))
+			return
+		}
+		extension := uint16(data[0])<<8 | uint16(data[1])
+		length := int(data[2])<<8 | int(data[3])
+		data = data[4:]
+		if len(data) < length {
+			err = fmt.Errorf("readHandshake: extensionLen[%d] is long", length)
+			return
+		}
+
+		switch extension {
+		case extensionRenegotiationInfo:
+			if length != 1 || data[0] != 0 {
+				err = fmt.Errorf("readHandshake: extension reNegotiationInfoLen[%d] is short", length)
+				return
+			}
+		case extensionNextProtoNeg:
+		case extensionStatusRequest:
+		case extensionServerName:
+			d := data[:length]
+			if len(d) < 2 {
+				err = fmt.Errorf("readHandshake: remiaining dataLen[%d] is short", len(d))
+				return
+			}
+			namesLen := int(d[0])<<8 | int(d[1])
+			d = d[2:]
+			if len(d) != namesLen {
+				err = fmt.Errorf("readHandshake: nameListLen[%d] is not equal to dataLen[%d]", namesLen, len(d))
+				return
+			}
+			for len(d) > 0 {
+				if len(d) < 3 {
+					err = fmt.Errorf("readHandshake: extension serverNameLen[%d] is short", len(d))
+					return
+				}
+				nameType := d[0]
+				nameLen := int(d[1])<<8 | int(d[2])
+				d = d[3:]
+				if len(d) < nameLen {
+					err = fmt.Errorf("readHandshake: nameLen[%d] is not equal to dataLen[%d]", nameLen, len(d))
+					return
+				}
+				if nameType == 0 {
+					serverName := string(d[:nameLen])
+					host = strings.TrimSpace(serverName)
+					return host, nil
+				}
+				d = d[nameLen:]
+			}
+		}
+		data = data[length:]
+	}
+	err = fmt.Errorf("Unknow error")
+	return
+}
+
+func GetHttpsHostname(c *conn.Conn) (sc net.Conn, routerName string, err error) {
+	sc, rd := newShareConn(c.TcpConn)
+	host, err := readHandshake(rd)
+	if err != nil {
+		return sc, "", err
+	}
+	return sc, host, nil
+}

+ 0 - 25
src/frp/utils/vhost/vhost.go

@@ -15,12 +15,10 @@
 package vhost
 
 import (
-	"bufio"
 	"bytes"
 	"fmt"
 	"io"
 	"net"
-	"net/http"
 	"strings"
 	"sync"
 	"time"
@@ -99,7 +97,6 @@ func (v *VhostMuxer) handle(c *conn.Conn) {
 	}
 
 	name = strings.ToLower(name)
-
 	l, ok := v.getListener(name)
 	if !ok {
 		return
@@ -113,28 +110,6 @@ func (v *VhostMuxer) handle(c *conn.Conn) {
 	l.accept <- c
 }
 
-type HttpMuxer struct {
-	*VhostMuxer
-}
-
-func GetHttpHostname(c *conn.Conn) (_ net.Conn, routerName string, err error) {
-	sc, rd := newShareConn(c.TcpConn)
-
-	request, err := http.ReadRequest(bufio.NewReader(rd))
-	if err != nil {
-		return sc, "", err
-	}
-	routerName = request.Host
-	request.Body.Close()
-
-	return sc, routerName, nil
-}
-
-func NewHttpMuxer(listener *conn.Listener, timeout time.Duration) (*HttpMuxer, error) {
-	mux, err := NewVhostMuxer(listener, GetHttpHostname, timeout)
-	return &HttpMuxer{mux}, err
-}
-
 type Listener struct {
 	name   string
 	mux    *VhostMuxer // for closing VhostMuxer

+ 2 - 0
test/conf/auto_test_frpc.ini

@@ -11,8 +11,10 @@ type = tcp
 local_ip = 127.0.0.1
 local_port = 10701
 use_encryption = true
+use_gzip = true
 
 [web]
 type = http
 local_ip = 127.0.0.1
 local_port = 10702
+use_gzip = true

+ 3 - 3
test/run_test.sh

@@ -12,19 +12,19 @@ do
     sleep 1
     str=`ss -ant|grep 10700|grep LISTEN`
     if [ -z "${str}" ]; then
-        echo "kong"
+        echo "wait"
         continue
     fi
 
     str=`ss -ant|grep 10710|grep LISTEN`
     if [ -z "${str}" ]; then
-        echo "kong"
+        echo "wait"
         continue
     fi
 
     str=`ss -ant|grep 10711|grep LISTEN`
     if [ -z "${str}" ]; then
-        echo "kong"
+        echo "wait"
         continue
     fi