diff --git a/src/assets/static/index.html b/src/assets/static/index.html index 6ac0650d..dc7f3626 100644 --- a/src/assets/static/index.html +++ b/src/assets/static/index.html @@ -63,7 +63,8 @@ listen_port: "<<< .ListenPort >>>", current_conns: <<< .CurrentConns >>> , domains: [ <<< range.CustomDomains >>> "<<< . >>>", <<< end >>> ], - stat: "<<< .Status >>>", + locations: [ <<< range.Locations >>> "<<< . >>>", <<< end >>> ], + stat: "<<< .StatusDesc >>>", use_encryption: "<<< .UseEncryption >>>", use_gzip: "<<< .UseGzip >>>", privilege_mode: "<<< .PrivilegeMode >>>", @@ -222,6 +223,10 @@ newrow += "Domains" + alldata[index].domains[domainindex] + ""; } + for (var locindex in alldata[index].locations) { + newrow += "Locations" + + alldata[index].locations[locindex] + ""; + } newrow += "Ip" + alldata[index].bind_addr + ""; newrow += "Status" + alldata[index].stat + ""; newrow += "Encryption" + alldata[index].use_encryption + ""; diff --git a/src/cmd/frps/control.go b/src/cmd/frps/control.go index 15620ff6..f120d77b 100644 --- a/src/cmd/frps/control.go +++ b/src/cmd/frps/control.go @@ -302,7 +302,7 @@ func doLogin(req *msg.ControlReq, c *conn.Conn) (ret int64, info string) { } // update metric's proxy status - metric.SetProxyInfo(s.Name, s.Type, s.BindAddr, s.UseEncryption, s.UseGzip, s.PrivilegeMode, s.CustomDomains, s.ListenPort) + metric.SetProxyInfo(*s.ProxyServerConf) // start proxy and listen for user connections, no block err := s.Start(c) diff --git a/src/cmd/frps/main.go b/src/cmd/frps/main.go index 8e94d8d5..a68947a3 100644 --- a/src/cmd/frps/main.go +++ b/src/cmd/frps/main.go @@ -151,6 +151,7 @@ func main() { log.Error("Create vhost http listener error, %v", err) os.Exit(1) } + server.VhostHttpMuxer, err = vhost.NewHttpMuxer(vhostListener, 30*time.Second) if err != nil { log.Error("Create vhost httpMuxer error, %v", err) diff --git a/src/models/config/config.go b/src/models/config/config.go index 325dcb9b..b86491a3 100644 --- a/src/models/config/config.go +++ b/src/models/config/config.go @@ -15,13 +15,23 @@ package config type BaseConf struct { - Name string - AuthToken string - Type string - UseEncryption bool - UseGzip bool - PrivilegeMode bool - PrivilegeToken string - PoolCount int64 - HostHeaderRewrite string + Name string `json:"name"` + AuthToken string `json:"-"` + Type string `json:"type"` + UseEncryption bool `json:"use_encryption"` + UseGzip bool `json:"use_gzip"` + PrivilegeMode bool `json:"privilege_mode"` + PrivilegeToken string `json:"-"` + PoolCount int64 `json:"pool_count"` + HostHeaderRewrite string `json:"host_header_rewrite"` +} + +type ProxyServerConf struct { + BaseConf + BindAddr string `json:"bind_addr"` + ListenPort int64 `json:"bind_port"` + CustomDomains []string `json:"custom_domains"` + Locations []string `json:"custom_locations"` + + Status int64 `json:"status"` } diff --git a/src/models/metric/server.go b/src/models/metric/server.go index fce7811a..d7775df9 100644 --- a/src/models/metric/server.go +++ b/src/models/metric/server.go @@ -19,6 +19,7 @@ import ( "sync" "time" + "github.com/fatedier/frp/src/models/config" "github.com/fatedier/frp/src/models/consts" ) @@ -29,15 +30,8 @@ var ( ) type ServerMetric struct { - Name string `json:"name"` - Type string `json:"type"` - BindAddr string `json:"bind_addr"` - ListenPort int64 `json:"listen_port"` - CustomDomains []string `json:"custom_domains"` - Status string `json:"status"` - UseEncryption bool `json:"use_encryption"` - UseGzip bool `json:"use_gzip"` - PrivilegeMode bool `json:"privilege_mode"` + config.ProxyServerConf + StatusDesc string `json:"status_desc"` // statistics CurrentConns int64 `json:"current_conns"` @@ -110,24 +104,16 @@ func GetProxyMetrics(proxyName string) *ServerMetric { } } -func SetProxyInfo(proxyName string, proxyType, bindAddr string, - useEncryption, useGzip, privilegeMode bool, customDomains []string, - listenPort int64) { +func SetProxyInfo(p config.ProxyServerConf) { smMutex.Lock() - info, ok := ServerMetricInfoMap[proxyName] + info, ok := ServerMetricInfoMap[p.Name] if !ok { info = &ServerMetric{} info.Daily = make([]*DailyServerStats, 0) } - info.Name = proxyName - info.Type = proxyType - info.UseEncryption = useEncryption - info.UseGzip = useGzip - info.PrivilegeMode = privilegeMode - info.BindAddr = bindAddr - info.ListenPort = listenPort - info.CustomDomains = customDomains - ServerMetricInfoMap[proxyName] = info + + info.ProxyServerConf = p + ServerMetricInfoMap[p.Name] = info smMutex.Unlock() } @@ -137,7 +123,7 @@ func SetStatus(proxyName string, status int64) { smMutex.RUnlock() if ok { metric.mutex.Lock() - metric.Status = consts.StatusStr[status] + metric.StatusDesc = consts.StatusStr[status] metric.mutex.Unlock() } } diff --git a/src/models/server/config.go b/src/models/server/config.go index e3296d05..66698be5 100644 --- a/src/models/server/config.go +++ b/src/models/server/config.go @@ -272,6 +272,12 @@ func loadProxyConf(confFile string) (proxyServers map[string]*ProxyServer, err e } else { return proxyServers, fmt.Errorf("Parse conf error: proxy [%s] custom_domains must be set when type equals http", proxyServer.Name) } + + //location + locStr, loc_ok := section["custom_location"] + if loc_ok { + proxyServer.Locations = strings.Split(locStr, ",") + } } else if proxyServer.Type == "https" { // for https proxyServer.ListenPort = VhostHttpsPort @@ -294,9 +300,8 @@ func loadProxyConf(confFile string) (proxyServers map[string]*ProxyServer, err e } // set metric statistics of all proxies - for name, p := range proxyServers { - metric.SetProxyInfo(name, p.Type, p.BindAddr, p.UseEncryption, p.UseGzip, - p.PrivilegeMode, p.CustomDomains, p.ListenPort) + for _, p := range proxyServers { + metric.SetProxyInfo(*p.ProxyServerConf) } return proxyServers, nil } @@ -357,8 +362,7 @@ func CreateProxy(s *ProxyServer) error { } } ProxyServers[s.Name] = s - metric.SetProxyInfo(s.Name, s.Type, s.BindAddr, s.UseEncryption, s.UseGzip, - s.PrivilegeMode, s.CustomDomains, s.ListenPort) + metric.SetProxyInfo(*s.ProxyServerConf) s.Init() return nil } diff --git a/src/models/server/server.go b/src/models/server/server.go index d1502116..5a1c0902 100644 --- a/src/models/server/server.go +++ b/src/models/server/server.go @@ -33,13 +33,9 @@ type Listener interface { } type ProxyServer struct { - config.BaseConf - BindAddr string - ListenPort int64 - CustomDomains []string + *config.ProxyServerConf - Status int64 - CtlConn *conn.Conn // control connection with frpc + CtlConn *conn.Conn `json:"-"` // control connection with frpc 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 @@ -48,8 +44,9 @@ type ProxyServer struct { } func NewProxyServer() (p *ProxyServer) { + psc := &config.ProxyServerConf{CustomDomains: make([]string, 0)} p = &ProxyServer{ - CustomDomains: make([]string, 0), + ProxyServerConf: psc, } return p } @@ -99,6 +96,14 @@ func (p *ProxyServer) Compare(p2 *ProxyServer) bool { return false } } + if len(p.Locations) != len(p2.Locations) { + return false + } + for i, _ := range p.Locations { + if p.Locations[i] != p2.Locations[i] { + return false + } + } return true } @@ -121,19 +126,13 @@ 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 := VhostHttpMuxer.Listen(domain, p.HostHeaderRewrite) - if err != nil { - return err - } + ls := VhostHttpMuxer.ListenByRouter(p.Name, p.CustomDomains, p.Locations, p.HostHeaderRewrite) + for _, l := range ls { p.listeners = append(p.listeners, l) } } else if p.Type == "https" { for _, domain := range p.CustomDomains { - l, err := VhostHttpsMuxer.Listen(domain, p.HostHeaderRewrite) - if err != nil { - return err - } + l := VhostHttpsMuxer.Listen(p.Name, domain, p.HostHeaderRewrite) p.listeners = append(p.listeners, l) } } diff --git a/src/utils/vhost/http.go b/src/utils/vhost/http.go index 0d1d8e07..2a282f06 100644 --- a/src/utils/vhost/http.go +++ b/src/utils/vhost/http.go @@ -41,7 +41,8 @@ func GetHttpHostname(c *conn.Conn) (_ net.Conn, routerName string, err error) { return sc, "", err } tmpArr := strings.Split(request.Host, ":") - routerName = tmpArr[0] + //routerName = tmpArr[0] + routerName = tmpArr[0] + ":" + request.URL.Path request.Body.Close() return sc, routerName, nil } diff --git a/src/utils/vhost/router.go b/src/utils/vhost/router.go new file mode 100644 index 00000000..d4735df1 --- /dev/null +++ b/src/utils/vhost/router.go @@ -0,0 +1,107 @@ +package vhost + +import ( + "sort" + "strings" + "sync" +) + +type VhostRouters struct { + RouterByDomain map[string][]*VhostRouter + mutex sync.RWMutex +} + +type VhostRouter struct { + name string + domain string + location string + listener *Listener +} + +func NewVhostRouters() *VhostRouters { + return &VhostRouters{ + RouterByDomain: make(map[string][]*VhostRouter), + } +} + +//sort by location +type ByLocation []*VhostRouter + +func (a ByLocation) Len() int { + return len(a) +} +func (a ByLocation) Swap(i, j int) { + a[i], a[j] = a[j], a[i] +} +func (a ByLocation) Less(i, j int) bool { + return strings.Compare(a[i].location, a[j].location) < 0 +} + +func (r *VhostRouters) add(name, domain string, locations []string, l *Listener) { + r.mutex.Lock() + defer r.mutex.Unlock() + + vrs, found := r.RouterByDomain[domain] + if !found { + vrs = make([]*VhostRouter, 0) + } + + for _, loc := range locations { + vr := &VhostRouter{ + name: name, + domain: domain, + location: loc, + listener: l, + } + vrs = append(vrs, vr) + } + + sort.Reverse(ByLocation(vrs)) + r.RouterByDomain[domain] = vrs +} + +func (r *VhostRouters) del(l *Listener) { + r.mutex.Lock() + defer r.mutex.Unlock() + + vrs, found := r.RouterByDomain[l.domain] + if !found { + return + } + + for i, vr := range vrs { + if vr.listener == l { + if len(vrs) > i+1 { + r.RouterByDomain[l.domain] = append(vrs[:i], vrs[i+1:]...) + } else { + r.RouterByDomain[l.domain] = vrs[:i] + } + } + } +} + +func (r *VhostRouters) get(rname string) (vr *VhostRouter, exist bool) { + r.mutex.RLock() + defer r.mutex.RUnlock() + + var domain, url string + tmparray := strings.SplitN(rname, ":", 2) + if len(tmparray) == 2 { + domain = tmparray[0] + url = tmparray[1] + } + + vrs, exist := r.RouterByDomain[domain] + if !exist { + return + } + + //can't support load balance,will to do + for _, vr = range vrs { + if strings.HasPrefix(url, vr.location) { + return vr, true + } + } + + return +} diff --git a/src/utils/vhost/vhost.go b/src/utils/vhost/vhost.go index 93279b3b..73b49cde 100644 --- a/src/utils/vhost/vhost.go +++ b/src/utils/vhost/vhost.go @@ -19,66 +19,94 @@ import ( "fmt" "io" "net" - "strings" + //"strings" "sync" "time" "github.com/fatedier/frp/src/utils/conn" + "github.com/fatedier/frp/src/utils/log" ) type muxFunc func(*conn.Conn) (net.Conn, string, error) type hostRewriteFunc func(*conn.Conn, string) (net.Conn, error) type VhostMuxer struct { - listener *conn.Listener - timeout time.Duration - vhostFunc muxFunc - rewriteFunc hostRewriteFunc - registryMap map[string]*Listener - mutex sync.RWMutex + listener *conn.Listener + timeout time.Duration + vhostFunc muxFunc + rewriteFunc hostRewriteFunc + registryRouter *VhostRouters + mutex sync.RWMutex } func NewVhostMuxer(listener *conn.Listener, vhostFunc muxFunc, rewriteFunc hostRewriteFunc, timeout time.Duration) (mux *VhostMuxer, err error) { mux = &VhostMuxer{ - listener: listener, - timeout: timeout, - vhostFunc: vhostFunc, - rewriteFunc: rewriteFunc, - registryMap: make(map[string]*Listener), + listener: listener, + timeout: timeout, + vhostFunc: vhostFunc, + rewriteFunc: rewriteFunc, + registryRouter: NewVhostRouters(), } go mux.run() return mux, nil } // listen for a new domain name, if rewriteHost is not empty and rewriteFunc is not nil, then rewrite the host header to rewriteHost -func (v *VhostMuxer) Listen(name string, rewriteHost string) (l *Listener, err error) { +func (v *VhostMuxer) Listen(name, domain string, rewriteHost string) (l *Listener) { v.mutex.Lock() defer v.mutex.Unlock() - if _, exist := v.registryMap[name]; exist { - return nil, fmt.Errorf("domain name %s is already bound", name) - } + + locations := []string{""} l = &Listener{ name: name, + domain: domain, + locations: locations, rewriteHost: rewriteHost, mux: v, accept: make(chan *conn.Conn), } - v.registryMap[name] = l - return l, nil + + v.registryRouter.add(name, domain, locations, l) + return l } -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) { +func (v *VhostMuxer) ListenByRouter(name string, domains []string, locations []string, rewriteHost string) (ls []*Listener) { v.mutex.Lock() defer v.mutex.Unlock() - delete(v.registryMap, name) + + ls = make([]*Listener, 0) + for _, domain := range domains { + l := &Listener{ + name: name, + domain: domain, + locations: locations, + rewriteHost: rewriteHost, + mux: v, + accept: make(chan *conn.Conn), + } + v.registryRouter.add(name, domain, locations, l) + ls = append(ls, l) + } + + return ls +} + +func (v *VhostMuxer) getListener(rname string) (l *Listener, exist bool) { + v.mutex.RLock() + defer v.mutex.RUnlock() + + var frcname string + vr, found := v.registryRouter.get(rname) + if found { + frcname = vr.name + } else { + log.Warn("can't found the router for %s", rname) + return + } + + log.Debug("get frcname %s for %s", frcname, rname) + return vr.listener, true } func (v *VhostMuxer) run() { @@ -101,32 +129,38 @@ func (v *VhostMuxer) handle(c *conn.Conn) { return } - name = strings.ToLower(name) + //name = strings.ToLower(name) l, ok := v.getListener(name) if !ok { return } if err = sConn.SetDeadline(time.Time{}); err != nil { + log.Error("set dead line err: %v", err) return } c.SetTcpConn(sConn) + log.Debug("handle request: %s", c.GetRemoteAddr()) l.accept <- c } type Listener struct { name string + domain string + locations []string rewriteHost string mux *VhostMuxer // for closing VhostMuxer accept chan *conn.Conn } func (l *Listener) Accept() (*conn.Conn, error) { + log.Debug("[%s][%s] now to accept ...", l.name, l.domain) conn, ok := <-l.accept if !ok { return nil, fmt.Errorf("Listener closed") } + log.Debug("[%s][%s] accept something ...", l.name, l.domain) // if rewriteFunc is exist and rewriteHost is set // rewrite http requests with a modified host header @@ -141,7 +175,7 @@ func (l *Listener) Accept() (*conn.Conn, error) { } func (l *Listener) Close() error { - l.mux.unRegister(l.name) + l.mux.registryRouter.del(l) close(l.accept) return nil }