all: support for virtual host

This commit is contained in:
fatedier 2016-04-18 15:16:40 +08:00
parent 6874688e07
commit f650d3f330
11 changed files with 400 additions and 75 deletions

View File

@ -9,9 +9,23 @@ log_level = info
# for authentication # for authentication
auth_token = 123 auth_token = 123
# test1 is the proxy name same as server's configuration # ssh is the proxy name same as server's configuration
[test1] [ssh]
# tcp | http, default is tcp
type = tcp
local_ip = 127.0.0.1 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
# 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
[web02]
type = http
local_ip = 127.0.0.1
local_port = 8000

View File

@ -2,13 +2,27 @@
[common] [common]
bind_addr = 0.0.0.0 bind_addr = 0.0.0.0
bind_port = 7000 bind_port = 7000
# optional
vhost_http_port = 80
# console or real logFile path like ./frps.log # console or real logFile path like ./frps.log
log_file = ./frps.log log_file = ./frps.log
# debug, info, warn, error # debug, info, warn, error
log_level = info log_level = info
# test1 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
[test1] [ssh]
type = tcp
auth_token = 123 auth_token = 123
bind_addr = 0.0.0.0 bind_addr = 0.0.0.0
listen_port = 6000 listen_port = 6000
[web01]
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
auth_token = 123
custom_domains = web02.yourdomain.com

View File

@ -30,7 +30,7 @@ import (
func ProcessControlConn(l *conn.Listener) { func ProcessControlConn(l *conn.Listener) {
for { for {
c, err := l.GetConn() c, err := l.Accept()
if err != nil { if err != nil {
return return
} }

View File

@ -19,6 +19,7 @@ import (
"os" "os"
"strconv" "strconv"
"strings" "strings"
"time"
docopt "github.com/docopt/docopt-go" docopt "github.com/docopt/docopt-go"
@ -26,6 +27,7 @@ import (
"frp/utils/conn" "frp/utils/conn"
"frp/utils/log" "frp/utils/log"
"frp/utils/version" "frp/utils/version"
"frp/utils/vhost"
) )
var ( var (
@ -92,8 +94,20 @@ func main() {
l, err := conn.Listen(server.BindAddr, server.BindPort) l, err := conn.Listen(server.BindAddr, server.BindPort)
if err != nil { if err != nil {
log.Error("Create listener error, %v", err) log.Error("Create server listener error, %v", err)
os.Exit(-1) os.Exit(1)
}
if server.VhostHttpPort != 0 {
vhostListener, err := conn.Listen(server.BindAddr, server.VhostHttpPort)
if err != nil {
log.Error("Create vhost http listener error, %v", err)
os.Exit(1)
}
server.VhostMuxer, err = vhost.NewHttpMuxer(vhostListener, 30*time.Second)
if err != nil {
log.Error("Create vhost httpMuxer error, %v", err)
}
} }
log.Info("Start frps success") log.Info("Start frps success")

View File

@ -31,6 +31,7 @@ type ProxyClient struct {
AuthToken string AuthToken string
LocalIp string LocalIp string
LocalPort int64 LocalPort int64
Type string
UseEncryption bool UseEncryption bool
} }

View File

@ -105,6 +105,16 @@ func LoadConf(confFile string) (err error) {
return fmt.Errorf("Parse ini file error: proxy [%s] local_port not found", proxyClient.Name) return fmt.Errorf("Parse ini file error: proxy [%s] local_port not found", proxyClient.Name)
} }
// type
proxyClient.Type = "tcp"
typeStr, ok := section["type"]
if ok {
if typeStr != "tcp" && typeStr != "http" {
return fmt.Errorf("Parse ini file error: proxy [%s] type error", proxyClient.Name)
}
proxyClient.Type = typeStr
}
// use_encryption // use_encryption
proxyClient.UseEncryption = false proxyClient.UseEncryption = false
useEncryptionStr, ok := section["use_encryption"] useEncryptionStr, ok := section["use_encryption"]

View File

@ -17,19 +17,25 @@ package server
import ( import (
"fmt" "fmt"
"strconv" "strconv"
"strings"
ini "github.com/vaughan0/go-ini" ini "github.com/vaughan0/go-ini"
"frp/utils/vhost"
) )
// common config // common config
var ( var (
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, do not listen a public port for http
LogFile string = "console" LogFile string = "console"
LogWay string = "console" // console or file LogWay string = "console" // console or file
LogLevel string = "info" LogLevel string = "info"
HeartBeatTimeout int64 = 90 HeartBeatTimeout int64 = 90
UserConnTimeout int64 = 10 UserConnTimeout int64 = 10
VhostMuxer *vhost.HttpMuxer
) )
var ProxyServers map[string]*ProxyServer = make(map[string]*ProxyServer) var ProxyServers map[string]*ProxyServer = make(map[string]*ProxyServer)
@ -54,6 +60,13 @@ func LoadConf(confFile string) (err error) {
BindPort, _ = strconv.ParseInt(tmpStr, 10, 64) BindPort, _ = strconv.ParseInt(tmpStr, 10, 64)
} }
tmpStr, ok = conf.Get("common", "vhost_http_port")
if ok {
VhostHttpPort, _ = strconv.ParseInt(tmpStr, 10, 64)
} else {
VhostHttpPort = 0
}
tmpStr, ok = conf.Get("common", "log_file") tmpStr, ok = conf.Get("common", "log_file")
if ok { if ok {
LogFile = tmpStr LogFile = tmpStr
@ -73,26 +86,52 @@ func LoadConf(confFile string) (err error) {
for name, section := range conf { for name, section := range conf {
if name != "common" { if name != "common" {
proxyServer := &ProxyServer{} proxyServer := &ProxyServer{}
proxyServer.CustomDomains = make([]string, 0)
proxyServer.Name = name proxyServer.Name = name
proxyServer.Type, ok = section["type"]
if ok {
if proxyServer.Type != "tcp" && proxyServer.Type != "http" {
return fmt.Errorf("Parse ini file error: proxy [%s] type error", proxyServer.Name)
}
} else {
proxyServer.Type = "tcp"
}
proxyServer.AuthToken, ok = section["auth_token"] proxyServer.AuthToken, ok = section["auth_token"]
if !ok { if !ok {
return fmt.Errorf("Parse ini file error: proxy [%s] no auth_token found", proxyServer.Name) return fmt.Errorf("Parse ini file error: proxy [%s] no auth_token found", proxyServer.Name)
} }
proxyServer.BindAddr, ok = section["bind_addr"] // for tcp
if !ok { if proxyServer.Type == "tcp" {
proxyServer.BindAddr = "0.0.0.0" proxyServer.BindAddr, ok = section["bind_addr"]
} if !ok {
proxyServer.BindAddr = "0.0.0.0"
portStr, ok := section["listen_port"] }
if ok {
proxyServer.ListenPort, err = strconv.ParseInt(portStr, 10, 64) portStr, ok := section["listen_port"]
if err != nil { if ok {
return fmt.Errorf("Parse ini file error: proxy [%s] listen_port error", proxyServer.Name) proxyServer.ListenPort, err = strconv.ParseInt(portStr, 10, 64)
if err != nil {
return fmt.Errorf("Parse ini file error: proxy [%s] listen_port error", proxyServer.Name)
}
} else {
return fmt.Errorf("Parse ini file error: proxy [%s] listen_port not found", proxyServer.Name)
}
} else if proxyServer.Type == "http" {
// 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, ",")
for i, domain := range proxyServer.CustomDomains {
proxyServer.CustomDomains[i] = strings.ToLower(strings.TrimSpace(domain)) + suffix
}
} }
} else {
return fmt.Errorf("Parse ini file error: proxy [%s] listen_port not found", proxyServer.Name)
} }
proxyServer.Init() proxyServer.Init()

View File

@ -24,15 +24,22 @@ import (
"frp/utils/log" "frp/utils/log"
) )
type Listener interface {
Accept() (*conn.Conn, error)
Close() error
}
type ProxyServer struct { type ProxyServer struct {
Name string Name string
AuthToken string AuthToken string
UseEncryption bool Type string
BindAddr string BindAddr string
ListenPort int64 ListenPort int64
Status int64 UseEncryption bool
CustomDomains []string
listener *conn.Listener // accept new connection from remote users Status int64
listeners []Listener // accept new connection from remote users
ctlMsgChan chan int64 // every time accept a new user conn, put "1" to the channel ctlMsgChan chan int64 // every time accept a new user conn, put "1" to the channel
workConnChan chan *conn.Conn // get new work conns from control goroutine workConnChan chan *conn.Conn // get new work conns from control goroutine
userConnList *list.List // store user conns userConnList *list.List // store user conns
@ -44,6 +51,7 @@ func (p *ProxyServer) Init() {
p.workConnChan = make(chan *conn.Conn) p.workConnChan = make(chan *conn.Conn)
p.ctlMsgChan = make(chan int64) p.ctlMsgChan = make(chan int64)
p.userConnList = list.New() p.userConnList = list.New()
p.listeners = make([]Listener, 0)
} }
func (p *ProxyServer) Lock() { func (p *ProxyServer) Lock() {
@ -57,57 +65,71 @@ func (p *ProxyServer) Unlock() {
// start listening for user conns // start listening for user conns
func (p *ProxyServer) Start() (err error) { func (p *ProxyServer) Start() (err error) {
p.Init() p.Init()
p.listener, err = conn.Listen(p.BindAddr, p.ListenPort) if p.Type == "tcp" {
if err != nil { l, err := conn.Listen(p.BindAddr, p.ListenPort)
return err if err != nil {
return err
}
p.listeners = append(p.listeners, l)
} else if p.Type == "http" {
for _, domain := range p.CustomDomains {
l, err := VhostMuxer.Listen(domain)
if err != nil {
return err
}
p.listeners = append(p.listeners, l)
}
} }
p.Status = consts.Working p.Status = consts.Working
// start a goroutine for listener to accept user connection // start a goroutine for listener to accept user connection
go func() { for _, listener := range p.listeners {
for { go func(l Listener) {
// block for {
// if listener is closed, err returned // block
c, err := p.listener.GetConn() // if listener is closed, err returned
if err != nil { c, err := l.Accept()
log.Info("ProxyName [%s], listener is closed", p.Name) if err != nil {
return log.Info("ProxyName [%s], listener is closed", p.Name)
}
log.Debug("ProxyName [%s], get one new user conn [%s]", p.Name, c.GetRemoteAddr())
// insert into list
p.Lock()
if p.Status != consts.Working {
log.Debug("ProxyName [%s] is not working, new user conn close", p.Name)
c.Close()
p.Unlock()
return
}
p.userConnList.PushBack(c)
p.Unlock()
// put msg to control conn
p.ctlMsgChan <- 1
// set timeout
time.AfterFunc(time.Duration(UserConnTimeout)*time.Second, func() {
p.Lock()
defer p.Unlock()
element := p.userConnList.Front()
if element == nil {
return return
} }
log.Debug("ProxyName [%s], get one new user conn [%s]", p.Name, c.GetRemoteAddr())
userConn := element.Value.(*conn.Conn) // insert into list
if userConn == c { p.Lock()
log.Warn("ProxyName [%s], user conn [%s] timeout", p.Name, c.GetRemoteAddr()) if p.Status != consts.Working {
log.Debug("ProxyName [%s] is not working, new user conn close", p.Name)
c.Close()
p.Unlock()
return
} }
}) p.userConnList.PushBack(c)
} p.Unlock()
}()
// start another goroutine for join two conns from client and user // put msg to control conn
p.ctlMsgChan <- 1
// set timeout
time.AfterFunc(time.Duration(UserConnTimeout)*time.Second, func() {
p.Lock()
defer p.Unlock()
element := p.userConnList.Front()
if element == nil {
return
}
userConn := element.Value.(*conn.Conn)
if userConn == c {
log.Warn("ProxyName [%s], user conn [%s] timeout", p.Name, c.GetRemoteAddr())
userConn.Close()
}
})
}
}(listener)
}
// start another goroutine for join two conns from frpc and user
go func() { go func() {
for { for {
workConn, ok := <-p.workConnChan workConn, ok := <-p.workConnChan
@ -149,8 +171,12 @@ 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 p.listener != nil { if len(p.listeners) != 0 {
p.listener.Close() for _, l := range p.listeners {
if l != nil {
l.Close()
}
}
} }
close(p.ctlMsgChan) close(p.ctlMsgChan)
close(p.workConnChan) close(p.workConnChan)

View File

@ -20,6 +20,7 @@ import (
"io" "io"
"net" "net"
"sync" "sync"
"time"
"frp/utils/log" "frp/utils/log"
"frp/utils/pcrypto" "frp/utils/pcrypto"
@ -28,7 +29,7 @@ import (
type Listener struct { type Listener struct {
addr net.Addr addr net.Addr
l *net.TCPListener l *net.TCPListener
conns chan *Conn accept chan *Conn
closeFlag bool closeFlag bool
} }
@ -42,7 +43,7 @@ func Listen(bindAddr string, bindPort int64) (l *Listener, err error) {
l = &Listener{ l = &Listener{
addr: listener.Addr(), addr: listener.Addr(),
l: listener, l: listener,
conns: make(chan *Conn), accept: make(chan *Conn),
closeFlag: false, closeFlag: false,
} }
@ -61,7 +62,7 @@ func Listen(bindAddr string, bindPort int64) (l *Listener, err error) {
closeFlag: false, closeFlag: false,
} }
c.Reader = bufio.NewReader(c.TcpConn) c.Reader = bufio.NewReader(c.TcpConn)
l.conns <- c l.accept <- c
} }
}() }()
return l, err return l, err
@ -69,30 +70,38 @@ func Listen(bindAddr string, bindPort int64) (l *Listener, err error) {
// wait util get one new connection or listener is closed // wait util get one new connection or listener is closed
// if listener is closed, err returned // if listener is closed, err returned
func (l *Listener) GetConn() (conn *Conn, err error) { func (l *Listener) Accept() (*Conn, error) {
var ok bool conn, ok := <-l.accept
conn, ok = <-l.conns
if !ok { if !ok {
return conn, fmt.Errorf("channel close") return conn, fmt.Errorf("channel close")
} }
return conn, nil return conn, nil
} }
func (l *Listener) Close() { func (l *Listener) Close() error {
if l.l != nil && l.closeFlag == false { if l.l != nil && l.closeFlag == false {
l.closeFlag = true l.closeFlag = true
l.l.Close() l.l.Close()
close(l.conns) close(l.accept)
} }
return nil
} }
// wrap for TCPConn // wrap for TCPConn
type Conn struct { type Conn struct {
TcpConn *net.TCPConn TcpConn net.Conn
Reader *bufio.Reader Reader *bufio.Reader
closeFlag bool closeFlag bool
} }
func NewConn(conn net.Conn) (c *Conn) {
c = &Conn{}
c.TcpConn = conn
c.Reader = bufio.NewReader(c.TcpConn)
c.closeFlag = false
return c
}
func ConnectServer(host string, port int64) (c *Conn, err error) { func ConnectServer(host string, port int64) (c *Conn, err error) {
c = &Conn{} c = &Conn{}
servertAddr, err := net.ResolveTCPAddr("tcp4", fmt.Sprintf("%s:%d", host, port)) servertAddr, err := net.ResolveTCPAddr("tcp4", fmt.Sprintf("%s:%d", host, port))
@ -131,6 +140,11 @@ func (c *Conn) Write(content string) (err error) {
} }
func (c *Conn) SetDeadline(t time.Time) error {
err := c.TcpConn.SetDeadline(t)
return err
}
func (c *Conn) Close() { func (c *Conn) Close() {
if c.TcpConn != nil && c.closeFlag == false { if c.TcpConn != nil && c.closeFlag == false {
c.closeFlag = true c.closeFlag = true

View File

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

View File

@ -0,0 +1,193 @@
// 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"
"bytes"
"fmt"
"io"
"net"
"net/http"
"strings"
"sync"
"time"
"frp/utils/conn"
)
type muxFunc func(*conn.Conn) (net.Conn, string, error)
type VhostMuxer struct {
listener *conn.Listener
timeout time.Duration
vhostFunc muxFunc
registryMap map[string]*Listener
mutex sync.RWMutex
}
func NewVhostMuxer(listener *conn.Listener, vhostFunc muxFunc, timeout time.Duration) (mux *VhostMuxer, err error) {
mux = &VhostMuxer{
listener: listener,
timeout: timeout,
vhostFunc: vhostFunc,
registryMap: make(map[string]*Listener),
}
go mux.run()
return mux, nil
}
func (v *VhostMuxer) Listen(name string) (l *Listener, err error) {
v.mutex.Lock()
defer v.mutex.Unlock()
if _, exist := v.registryMap[name]; exist {
return nil, fmt.Errorf("name %s is already bound", name)
}
l = &Listener{
name: name,
mux: v,
accept: make(chan *conn.Conn),
}
v.registryMap[name] = l
return l, nil
}
func (v *VhostMuxer) getListener(name string) (l *Listener, exist bool) {
v.mutex.RLock()
defer v.mutex.RUnlock()
l, exist = v.registryMap[name]
return l, exist
}
func (v *VhostMuxer) unRegister(name string) {
v.mutex.Lock()
defer v.mutex.Unlock()
delete(v.registryMap, name)
}
func (v *VhostMuxer) run() {
for {
conn, err := v.listener.Accept()
if err != nil {
return
}
go v.handle(conn)
}
}
func (v *VhostMuxer) handle(c *conn.Conn) {
if err := c.SetDeadline(time.Now().Add(v.timeout)); err != nil {
return
}
sConn, name, err := v.vhostFunc(c)
if err != nil {
return
}
name = strings.ToLower(name)
l, ok := v.getListener(name)
if !ok {
return
}
if err = sConn.SetDeadline(time.Time{}); err != nil {
return
}
c.TcpConn = sConn
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
accept chan *conn.Conn
}
func (l *Listener) Accept() (*conn.Conn, error) {
conn, ok := <-l.accept
if !ok {
return nil, fmt.Errorf("Listener closed")
}
return conn, nil
}
func (l *Listener) Close() error {
l.mux.unRegister(l.name)
close(l.accept)
return nil
}
func (l *Listener) Name() string {
return l.name
}
type sharedConn struct {
net.Conn
sync.Mutex
buff *bytes.Buffer
}
func newShareConn(conn net.Conn) (*sharedConn, io.Reader) {
sc := &sharedConn{
Conn: conn,
buff: bytes.NewBuffer(make([]byte, 0, 1024)),
}
return sc, io.TeeReader(conn, sc.buff)
}
func (sc *sharedConn) Read(p []byte) (n int, err error) {
sc.Lock()
if sc.buff == nil {
sc.Unlock()
return sc.Conn.Read(p)
}
n, err = sc.buff.Read(p)
if err == io.EOF {
sc.buff = nil
var n2 int
n2, err = sc.Conn.Read(p[n:])
n += n2
}
sc.Unlock()
return
}