Explorar o código

all: support for virtual host

fatedier %!s(int64=9) %!d(string=hai) anos
pai
achega
f650d3f330

+ 16 - 2
conf/frpc.ini

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

+ 16 - 2
conf/frps.ini

@@ -2,13 +2,27 @@
 [common]
 bind_addr = 0.0.0.0
 bind_port = 7000
+# optional
+vhost_http_port = 80
 # console or real logFile path like ./frps.log
 log_file = ./frps.log
 # debug, info, warn, error
 log_level = info
 
-# test1 is the proxy name, client will use this name and auth_token to connect to server
-[test1]
+# ssh is the proxy name, client will use this name and auth_token to connect to server
+[ssh]
+type = tcp
 auth_token = 123
 bind_addr = 0.0.0.0
 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

+ 1 - 1
src/frp/cmd/frps/control.go

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

+ 16 - 2
src/frp/cmd/frps/main.go

@@ -19,6 +19,7 @@ import (
 	"os"
 	"strconv"
 	"strings"
+	"time"
 
 	docopt "github.com/docopt/docopt-go"
 
@@ -26,6 +27,7 @@ import (
 	"frp/utils/conn"
 	"frp/utils/log"
 	"frp/utils/version"
+	"frp/utils/vhost"
 )
 
 var (
@@ -92,8 +94,20 @@ func main() {
 
 	l, err := conn.Listen(server.BindAddr, server.BindPort)
 	if err != nil {
-		log.Error("Create listener error, %v", err)
-		os.Exit(-1)
+		log.Error("Create server listener error, %v", err)
+		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")

+ 1 - 0
src/frp/models/client/client.go

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

+ 10 - 0
src/frp/models/client/config.go

@@ -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)
 			}
 
+			// 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
 			proxyClient.UseEncryption = false
 			useEncryptionStr, ok := section["use_encryption"]

+ 50 - 11
src/frp/models/server/config.go

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

+ 71 - 45
src/frp/models/server/server.go

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

+ 23 - 9
src/frp/utils/conn/conn.go

@@ -20,6 +20,7 @@ import (
 	"io"
 	"net"
 	"sync"
+	"time"
 
 	"frp/utils/log"
 	"frp/utils/pcrypto"
@@ -28,7 +29,7 @@ import (
 type Listener struct {
 	addr      net.Addr
 	l         *net.TCPListener
-	conns     chan *Conn
+	accept    chan *Conn
 	closeFlag bool
 }
 
@@ -42,7 +43,7 @@ func Listen(bindAddr string, bindPort int64) (l *Listener, err error) {
 	l = &Listener{
 		addr:      listener.Addr(),
 		l:         listener,
-		conns:     make(chan *Conn),
+		accept:    make(chan *Conn),
 		closeFlag: false,
 	}
 
@@ -61,7 +62,7 @@ func Listen(bindAddr string, bindPort int64) (l *Listener, err error) {
 				closeFlag: false,
 			}
 			c.Reader = bufio.NewReader(c.TcpConn)
-			l.conns <- c
+			l.accept <- c
 		}
 	}()
 	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
 // if listener is closed, err returned
-func (l *Listener) GetConn() (conn *Conn, err error) {
-	var ok bool
-	conn, ok = <-l.conns
+func (l *Listener) Accept() (*Conn, error) {
+	conn, ok := <-l.accept
 	if !ok {
 		return conn, fmt.Errorf("channel close")
 	}
 	return conn, nil
 }
 
-func (l *Listener) Close() {
+func (l *Listener) Close() error {
 	if l.l != nil && l.closeFlag == false {
 		l.closeFlag = true
 		l.l.Close()
-		close(l.conns)
+		close(l.accept)
 	}
+	return nil
 }
 
 // wrap for TCPConn
 type Conn struct {
-	TcpConn   *net.TCPConn
+	TcpConn   net.Conn
 	Reader    *bufio.Reader
 	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) {
 	c = &Conn{}
 	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() {
 	if c.TcpConn != nil && c.closeFlag == false {
 		c.closeFlag = true

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

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

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

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