diff --git a/README.md b/README.md index d0900e6..57ae6cc 100644 --- a/README.md +++ b/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) diff --git a/README_zh.md b/README_zh.md index 7138556..91c14b8 100644 --- a/README_zh.md +++ b/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) diff --git a/conf/frpc.ini b/conf/frpc.ini index 9a1d8bc..bbab6d0 100644 --- a/conf/frpc.ini +++ b/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 diff --git a/conf/frps.ini b/conf/frps.ini index e3087d8..aada47b 100644 --- a/conf/frps.ini +++ b/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 diff --git a/src/frp/cmd/frpc/control.go b/src/frp/cmd/frpc/control.go index 53a936a..9e8c3e6 100644 --- a/src/frp/cmd/frpc/control.go +++ b/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 { diff --git a/src/frp/cmd/frps/control.go b/src/frp/cmd/frps/control.go index 0784955..0e46088 100644 --- a/src/frp/cmd/frps/control.go +++ b/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 { diff --git a/src/frp/cmd/frps/main.go b/src/frp/cmd/frps/main.go index 910827f..a9d3f7e 100644 --- a/src/frp/cmd/frps/main.go +++ b/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) } diff --git a/src/frp/models/client/client.go b/src/frp/models/client/client.go index 6165eee..bdd4279 100644 --- a/src/frp/models/client/client.go +++ b/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 } diff --git a/src/frp/models/client/config.go b/src/frp/models/client/config.go index 1d19c33..0d942e9 100644 --- a/src/frp/models/client/config.go +++ b/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 diff --git a/src/frp/models/config/config.go b/src/frp/models/config/config.go new file mode 100644 index 0000000..14200eb --- /dev/null +++ b/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 +} diff --git a/src/frp/models/msg/msg.go b/src/frp/models/msg/msg.go index d1b57ad..e89bce1 100644 --- a/src/frp/models/msg/msg.go +++ b/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 { diff --git a/src/frp/models/msg/process.go b/src/frp/models/msg/process.go new file mode 100644 index 0000000..cfc782b --- /dev/null +++ b/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 +} diff --git a/src/frp/models/server/config.go b/src/frp/models/server/config.go index 14b9753..1d49c66 100644 --- a/src/frp/models/server/config.go +++ b/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) +} diff --git a/src/frp/models/server/server.go b/src/frp/models/server/server.go index b9affdf..b15f5cf 100644 --- a/src/frp/models/server/server.go +++ b/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 diff --git a/src/frp/utils/conn/conn.go b/src/frp/utils/conn/conn.go index 1c6eeb1..ed330f6 100644 --- a/src/frp/utils/conn/conn.go +++ b/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 -} diff --git a/src/frp/utils/pcrypto/pcrypto.go b/src/frp/utils/pcrypto/pcrypto.go index a4772a8..8f5e873 100644 --- a/src/frp/utils/pcrypto/pcrypto.go +++ b/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 +} - // gunzip - zbuf := bytes.NewBuffer(decryptText) +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 +} + +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 { diff --git a/src/frp/utils/pcrypto/pcrypto_test.go b/src/frp/utils/pcrypto/pcrypto_test.go index e86762f..41a1755 100644 --- a/src/frp/utils/pcrypto/pcrypto_test.go +++ b/src/frp/utils/pcrypto/pcrypto_test.go @@ -15,33 +15,48 @@ package pcrypto import ( - "fmt" "testing" ) +var ( + pp *Pcrypto +) + +func init() { + pp = &Pcrypto{} + pp.Init([]byte("Hana")) +} + func TestEncrypt(t *testing.T) { - pp := new(Pcrypto) - pp.Init([]byte("Hana")) - res, err := pp.Encrypt([]byte("Just One Test!")) + 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)) + } } diff --git a/src/frp/utils/version/version.go b/src/frp/utils/version/version.go index 842028d..9559803 100644 --- a/src/frp/utils/version/version.go +++ b/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 diff --git a/src/frp/utils/vhost/http.go b/src/frp/utils/vhost/http.go new file mode 100644 index 0000000..0f6aab5 --- /dev/null +++ b/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 +} diff --git a/src/frp/utils/vhost/https.go b/src/frp/utils/vhost/https.go new file mode 100644 index 0000000..2fd61c6 --- /dev/null +++ b/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 +} diff --git a/src/frp/utils/vhost/vhost.go b/src/frp/utils/vhost/vhost.go index d832f53..ecf080d 100644 --- a/src/frp/utils/vhost/vhost.go +++ b/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 diff --git a/test/conf/auto_test_frpc.ini b/test/conf/auto_test_frpc.ini index eb3fdc6..e46d85b 100644 --- a/test/conf/auto_test_frpc.ini +++ b/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 diff --git a/test/run_test.sh b/test/run_test.sh index 82289a2..cc171f7 100755 --- a/test/run_test.sh +++ b/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