Merge pull request #41 from fatedier/dev

release v0.7.0
This commit is contained in:
fatedier 2016-07-04 15:11:10 +08:00 committed by GitHub
commit 28251a8104
23 changed files with 893 additions and 275 deletions

View File

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

View File

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

View File

@ -9,6 +9,8 @@ log_level = info
log_max_days = 3 log_max_days = 3
# for authentication # for authentication
auth_token = 123 auth_token = 123
# for privilege mode
privilege_token = 12345678
# ssh is the proxy name same as server's configuration # ssh is the proxy name same as server's configuration
[ssh] [ssh]
@ -18,15 +20,35 @@ local_ip = 127.0.0.1
local_port = 22 local_port = 22
# true or false, if true, messages between frps and frpc will be encrypted, default is false # true or false, if true, messages between frps and frpc will be encrypted, default is false
use_encryption = true 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 # 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] [web01]
type = http type = http
local_ip = 127.0.0.1 local_ip = 127.0.0.1
local_port = 80 local_port = 80
use_encryption = true use_gzip = true
[web02] [web02]
type = http type = http
local_ip = 127.0.0.1 local_ip = 127.0.0.1
local_port = 8000 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

View File

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

View File

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

View File

@ -194,16 +194,42 @@ func msgSender(s *server.ProxyServer, c *conn.Conn, msgSendChan chan interface{}
// if success, ret equals 0, otherwise greater than 0 // if success, ret equals 0, otherwise greater than 0
func doLogin(req *msg.ControlReq, c *conn.Conn) (ret int64, info string) { func doLogin(req *msg.ControlReq, c *conn.Conn) (ret int64, info string) {
ret = 1 ret = 1
// check if proxy name exist if req.PrivilegeMode && !server.PrivilegeMode {
s, ok := server.ProxyServers[req.ProxyName] info = fmt.Sprintf("ProxyName [%s], PrivilegeMode is disabled in frps", req.ProxyName)
log.Warn("info")
return
}
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 { if !ok {
info = fmt.Sprintf("ProxyName [%s] is not exist", req.ProxyName) info = fmt.Sprintf("ProxyName [%s] is not exist", req.ProxyName)
log.Warn(info) log.Warn(info)
return return
} }
}
// check authKey // check authKey or privilegeKey
nowTime := time.Now().Unix() nowTime := time.Now().Unix()
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 := pcrypto.GetAuthKey(req.ProxyName + s.AuthToken + fmt.Sprintf("%d", req.Timestamp))
// authKey avaiable in 15 minutes // authKey avaiable in 15 minutes
if nowTime-req.Timestamp > 15*60 { if nowTime-req.Timestamp > 15*60 {
@ -215,17 +241,41 @@ func doLogin(req *msg.ControlReq, c *conn.Conn) (ret int64, info string) {
log.Warn(info) log.Warn(info)
return return
} }
}
// control conn // control conn
if req.Type == consts.NewCtlConn { 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 { if s.Status == consts.Working {
info = fmt.Sprintf("ProxyName [%s], already in use", req.ProxyName) info = fmt.Sprintf("ProxyName [%s], already in use", req.ProxyName)
log.Warn(info) log.Warn(info)
return 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 // set infomations from frpc
s.UseEncryption = req.UseEncryption s.UseEncryption = req.UseEncryption
s.UseGzip = req.UseGzip
// start proxy and listen for user connections, no block // start proxy and listen for user connections, no block
err := s.Start(c) err := s.Start(c)
@ -235,6 +285,9 @@ func doLogin(req *msg.ControlReq, c *conn.Conn) (ret int64, info string) {
return return
} }
log.Info("ProxyName [%s], start proxy success", req.ProxyName) 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 { } else if req.Type == consts.NewWorkConn {
// work conn // work conn
if s.Status != consts.Working { if s.Status != consts.Working {

View File

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

View File

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

View File

@ -17,6 +17,7 @@ package client
import ( import (
"fmt" "fmt"
"strconv" "strconv"
"strings"
ini "github.com/vaughan0/go-ini" ini "github.com/vaughan0/go-ini"
) )
@ -29,6 +30,7 @@ var (
LogWay string = "console" LogWay string = "console"
LogLevel string = "info" LogLevel string = "info"
LogMaxDays int64 = 3 LogMaxDays int64 = 3
PrivilegeToken string = ""
HeartBeatInterval int64 = 20 HeartBeatInterval int64 = 20
HeartBeatTimeout int64 = 90 HeartBeatTimeout int64 = 90
) )
@ -75,12 +77,15 @@ func LoadConf(confFile string) (err error) {
LogMaxDays, _ = strconv.ParseInt(tmpStr, 10, 64) LogMaxDays, _ = strconv.ParseInt(tmpStr, 10, 64)
} }
tmpStr, ok = conf.Get("common", "privilege_token")
if ok {
PrivilegeToken = tmpStr
}
var authToken string var authToken string
tmpStr, ok = conf.Get("common", "auth_token") tmpStr, ok = conf.Get("common", "auth_token")
if ok { if ok {
authToken = tmpStr authToken = tmpStr
} else {
return fmt.Errorf("auth_token not found")
} }
// proxies // proxies
@ -101,39 +106,101 @@ func LoadConf(confFile string) (err error) {
} }
// local_port // local_port
portStr, ok := section["local_port"] tmpStr, ok = section["local_port"]
if ok { if ok {
proxyClient.LocalPort, err = strconv.ParseInt(portStr, 10, 64) proxyClient.LocalPort, err = strconv.ParseInt(tmpStr, 10, 64)
if err != nil { 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 { } 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 // type
proxyClient.Type = "tcp" proxyClient.Type = "tcp"
typeStr, ok := section["type"] tmpStr, ok = section["type"]
if ok { if ok {
if typeStr != "tcp" && typeStr != "http" { if tmpStr != "tcp" && tmpStr != "http" && tmpStr != "https" {
return fmt.Errorf("Parse ini file error: proxy [%s] type error", proxyClient.Name) return fmt.Errorf("Parse conf error: proxy [%s] type error", proxyClient.Name)
} }
proxyClient.Type = typeStr proxyClient.Type = tmpStr
} }
// use_encryption // use_encryption
proxyClient.UseEncryption = false proxyClient.UseEncryption = false
useEncryptionStr, ok := section["use_encryption"] tmpStr, ok = section["use_encryption"]
if ok && useEncryptionStr == "true" { if ok && tmpStr == "true" {
proxyClient.UseEncryption = 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 ProxyClients[proxyClient.Name] = proxyClient
} }
} }
if len(ProxyClients) == 0 { 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 return nil

View File

@ -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
}

View File

@ -19,13 +19,21 @@ type GeneralRes struct {
Msg string `json:"msg"` Msg string `json:"msg"`
} }
// messages between control connection of frpc and frps // messages between control connections of frpc and frps
type ControlReq struct { type ControlReq struct {
Type int64 `json:"type"` Type int64 `json:"type"`
ProxyName string `json:"proxy_name,omitempty"` ProxyName string `json:"proxy_name"`
AuthKey string `json:"auth_key, omitempty"` AuthKey string `json:"auth_key"`
UseEncryption bool `json:"use_encryption, omitempty"` UseEncryption bool `json:"use_encryption"`
Timestamp int64 `json:"timestamp, omitempty"` 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 { type ControlRes struct {

View File

@ -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
}

View File

@ -22,6 +22,7 @@ import (
ini "github.com/vaughan0/go-ini" ini "github.com/vaughan0/go-ini"
"frp/models/consts"
"frp/utils/log" "frp/utils/log"
"frp/utils/vhost" "frp/utils/vhost"
) )
@ -31,16 +32,20 @@ var (
ConfigFile string = "./frps.ini" ConfigFile string = "./frps.ini"
BindAddr string = "0.0.0.0" BindAddr string = "0.0.0.0"
BindPort int64 = 7000 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 DashboardPort int64 = 0 // if DashboardPort equals 0, dashboard is not available
LogFile string = "console" LogFile string = "console"
LogWay string = "console" // console or file LogWay string = "console" // console or file
LogLevel string = "info" LogLevel string = "info"
LogMaxDays int64 = 3 LogMaxDays int64 = 3
PrivilegeMode bool = false
PrivilegeToken string = ""
HeartBeatTimeout int64 = 90 HeartBeatTimeout int64 = 90
UserConnTimeout int64 = 10 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 ProxyServers map[string]*ProxyServer = make(map[string]*ProxyServer) // all proxy servers info and resources
ProxyServersMutex sync.RWMutex ProxyServersMutex sync.RWMutex
) )
@ -81,7 +86,10 @@ func loadCommonConf(confFile string) error {
tmpStr, ok = conf.Get("common", "bind_port") tmpStr, ok = conf.Get("common", "bind_port")
if ok { 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") tmpStr, ok = conf.Get("common", "vhost_http_port")
@ -91,6 +99,13 @@ func loadCommonConf(confFile string) error {
VhostHttpPort = 0 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") tmpStr, ok = conf.Get("common", "dashboard_port")
if ok { if ok {
DashboardPort, _ = strconv.ParseInt(tmpStr, 10, 64) DashboardPort, _ = strconv.ParseInt(tmpStr, 10, 64)
@ -115,7 +130,29 @@ func loadCommonConf(confFile string) error {
tmpStr, ok = conf.Get("common", "log_max_days") tmpStr, ok = conf.Get("common", "log_max_days")
if ok { 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 return nil
} }
@ -135,7 +172,7 @@ func loadProxyConf(confFile string) (proxyServers map[string]*ProxyServer, err e
proxyServer.Type, ok = section["type"] proxyServer.Type, ok = section["type"]
if ok { 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) return proxyServers, fmt.Errorf("Parse conf error: proxy [%s] type error", proxyServer.Name)
} }
} else { } else {
@ -167,17 +204,29 @@ func loadProxyConf(confFile string) (proxyServers map[string]*ProxyServer, err e
// for http // for http
domainStr, ok := section["custom_domains"] domainStr, ok := section["custom_domains"]
if ok { if ok {
var suffix string
if VhostHttpPort != 80 {
suffix = fmt.Sprintf(":%d", VhostHttpPort)
}
proxyServer.CustomDomains = strings.Split(domainStr, ",") proxyServer.CustomDomains = strings.Split(domainStr, ",")
if len(proxyServer.CustomDomains) == 0 { 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) 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 { 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 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 { for name, oldProxyServer := range ProxyServers {
_, ok := loadProxyServers[name] _, ok := loadProxyServers[name]
if !ok { if !ok {
if !oldProxyServer.PrivilegeMode {
oldProxyServer.Close() oldProxyServer.Close()
delete(ProxyServers, name) delete(ProxyServers, name)
log.Info("ProxyName [%s] deleted, close it", name) log.Info("ProxyName [%s] deleted, close it", name)
} else {
log.Info("ProxyName [%s] created by PrivilegeMode, won't be closed", name)
}
} }
} }
ProxyServersMutex.Unlock() ProxyServersMutex.Unlock()
return nil 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)
}

View File

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

View File

@ -22,9 +22,6 @@ import (
"strings" "strings"
"sync" "sync"
"time" "time"
"frp/utils/log"
"frp/utils/pcrypto"
) )
type Listener struct { type Listener struct {
@ -167,115 +164,3 @@ func (c *Conn) IsClosed() (closeFlag bool) {
c.mutex.RUnlock() c.mutex.RUnlock()
return 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
}

View File

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

View File

@ -15,33 +15,48 @@
package pcrypto package pcrypto
import ( import (
"fmt"
"testing" "testing"
) )
var (
pp *Pcrypto
)
func init() {
pp = &Pcrypto{}
pp.Init([]byte("Hana"))
}
func TestEncrypt(t *testing.T) { func TestEncrypt(t *testing.T) {
pp := new(Pcrypto) testStr := "Test Encrypt!"
pp.Init([]byte("Hana")) res, err := pp.Encrypt([]byte(testStr))
res, err := pp.Encrypt([]byte("Just One Test!"))
if err != nil { 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) { func TestCompression(t *testing.T) {
pp := new(Pcrypto) testStr := "Test Compression!"
pp.Init([]byte("Hana")) res, err := pp.Compression([]byte(testStr))
res, err := pp.Encrypt([]byte("Just One Test!"))
if err != nil { 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 { 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))
}
} }

View File

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

View File

@ -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
}

View File

@ -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
}

View File

@ -15,12 +15,10 @@
package vhost package vhost
import ( import (
"bufio"
"bytes" "bytes"
"fmt" "fmt"
"io" "io"
"net" "net"
"net/http"
"strings" "strings"
"sync" "sync"
"time" "time"
@ -99,7 +97,6 @@ func (v *VhostMuxer) handle(c *conn.Conn) {
} }
name = strings.ToLower(name) name = strings.ToLower(name)
l, ok := v.getListener(name) l, ok := v.getListener(name)
if !ok { if !ok {
return return
@ -113,28 +110,6 @@ func (v *VhostMuxer) handle(c *conn.Conn) {
l.accept <- c 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 { type Listener struct {
name string name string
mux *VhostMuxer // for closing VhostMuxer mux *VhostMuxer // for closing VhostMuxer

View File

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

View File

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