diff --git a/src/cmd/frpc/control.go b/src/cmd/frpc/control.go index ffd2779..55d8798 100644 --- a/src/cmd/frpc/control.go +++ b/src/cmd/frpc/control.go @@ -159,6 +159,7 @@ func loginToServer(cli *client.ProxyClient) (c *conn.Conn, err error) { privilegeKey := pcrypto.GetAuthKey(cli.Name + client.PrivilegeToken + fmt.Sprintf("%d", nowTime)) req.RemotePort = cli.RemotePort req.CustomDomains = cli.CustomDomains + req.Locations = cli.Locations req.PrivilegeKey = privilegeKey } else { authKey := pcrypto.GetAuthKey(cli.Name + cli.AuthToken + fmt.Sprintf("%d", nowTime)) diff --git a/src/cmd/frps/control.go b/src/cmd/frps/control.go index aba3cae..d0987ca 100644 --- a/src/cmd/frps/control.go +++ b/src/cmd/frps/control.go @@ -332,8 +332,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) + metric.SetProxyInfo(s.Name, s.Type, s.BindAddr, s.UseEncryption, s.UseGzip, s.PrivilegeMode, s.CustomDomains, s.ListenPort) // start proxy and listen for user connections, no block err := s.Start(c) diff --git a/src/models/client/client.go b/src/models/client/client.go index 6d73377..f5fe7d7 100644 --- a/src/models/client/client.go +++ b/src/models/client/client.go @@ -35,6 +35,7 @@ type ProxyClient struct { RemotePort int64 CustomDomains []string + Locations []string udpTunnel *conn.Conn once sync.Once diff --git a/src/models/client/config.go b/src/models/client/config.go index 7c00a10..ff933fa 100644 --- a/src/models/client/config.go +++ b/src/models/client/config.go @@ -227,6 +227,14 @@ func LoadConf(confFile string) (err error) { if !ok && proxyClient.SubDomain == "" { return fmt.Errorf("Parse conf error: proxy [%s] custom_domains and subdomain should set at least one of them when type is http", proxyClient.Name) } + + // locations + locations, ok := section["locations"] + if ok { + proxyClient.Locations = strings.Split(locations, ",") + } else { + proxyClient.Locations = []string{""} + } } else if proxyClient.Type == "https" { // custom_domains domainStr, ok := section["custom_domains"] diff --git a/src/models/config/config.go b/src/models/config/config.go index c772d0c..f71d24a 100644 --- a/src/models/config/config.go +++ b/src/models/config/config.go @@ -15,26 +15,16 @@ package config type BaseConf struct { - 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"` - HttpUserName string `json:"http_username"` - HttpPassWord string `json:"-"` - SubDomain string `json:"subdomain"` -} - -type ProxyServerConf struct { - BaseConf - BindAddr string `json:"bind_addr"` - ListenPort int64 `json:"listen_port"` - CustomDomains []string `json:"custom_domains"` - Locations []string `json:"custom_locations"` - - Status int64 `json:"status"` + Name string + AuthToken string + Type string + UseEncryption bool + UseGzip bool + PrivilegeMode bool + PrivilegeToken string + PoolCount int64 + HostHeaderRewrite string + HttpUserName string + HttpPassWord string + SubDomain string } diff --git a/src/models/metric/server.go b/src/models/metric/server.go index d7775df..fce7811 100644 --- a/src/models/metric/server.go +++ b/src/models/metric/server.go @@ -19,7 +19,6 @@ import ( "sync" "time" - "github.com/fatedier/frp/src/models/config" "github.com/fatedier/frp/src/models/consts" ) @@ -30,8 +29,15 @@ var ( ) type ServerMetric struct { - config.ProxyServerConf - StatusDesc string `json:"status_desc"` + 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"` // statistics CurrentConns int64 `json:"current_conns"` @@ -104,16 +110,24 @@ func GetProxyMetrics(proxyName string) *ServerMetric { } } -func SetProxyInfo(p config.ProxyServerConf) { +func SetProxyInfo(proxyName string, proxyType, bindAddr string, + useEncryption, useGzip, privilegeMode bool, customDomains []string, + listenPort int64) { smMutex.Lock() - info, ok := ServerMetricInfoMap[p.Name] + info, ok := ServerMetricInfoMap[proxyName] if !ok { info = &ServerMetric{} info.Daily = make([]*DailyServerStats, 0) } - - info.ProxyServerConf = p - ServerMetricInfoMap[p.Name] = info + 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 smMutex.Unlock() } @@ -123,7 +137,7 @@ func SetStatus(proxyName string, status int64) { smMutex.RUnlock() if ok { metric.mutex.Lock() - metric.StatusDesc = consts.StatusStr[status] + metric.Status = consts.StatusStr[status] metric.mutex.Unlock() } } diff --git a/src/models/msg/msg.go b/src/models/msg/msg.go index 64119ec..b5d61e3 100644 --- a/src/models/msg/msg.go +++ b/src/models/msg/msg.go @@ -34,6 +34,7 @@ type ControlReq struct { ProxyType string `json:"proxy_type"` RemotePort int64 `json:"remote_port"` CustomDomains []string `json:"custom_domains, omitempty"` + Locations []string `json:"locations"` HostHeaderRewrite string `json:"host_header_rewrite"` HttpUserName string `json:"http_username"` HttpPassWord string `json:"http_password"` diff --git a/src/models/server/config.go b/src/models/server/config.go index 61b7b77..ac7d779 100644 --- a/src/models/server/config.go +++ b/src/models/server/config.go @@ -305,10 +305,10 @@ func loadProxyConf(confFile string) (proxyServers map[string]*ProxyServer, err e return proxyServers, fmt.Errorf("Parse conf error: proxy [%s] custom_domains must be set when type is http", proxyServer.Name) } - //location - locStr, loc_ok := section["custom_location"] - if loc_ok { - proxyServer.Locations = strings.Split(locStr, ",") + // locations + locations, ok := section["locations"] + if ok { + proxyServer.Locations = strings.Split(locations, ",") } else { proxyServer.Locations = []string{""} } @@ -338,8 +338,9 @@ func loadProxyConf(confFile string) (proxyServers map[string]*ProxyServer, err e } // set metric statistics of all proxies - for _, p := range proxyServers { - metric.SetProxyInfo(*p.ProxyServerConf) + for name, p := range proxyServers { + metric.SetProxyInfo(name, p.Type, p.BindAddr, p.UseEncryption, p.UseGzip, + p.PrivilegeMode, p.CustomDomains, p.ListenPort) } return proxyServers, nil } @@ -400,7 +401,8 @@ func CreateProxy(s *ProxyServer) error { } } ProxyServers[s.Name] = s - metric.SetProxyInfo(*s.ProxyServerConf) + metric.SetProxyInfo(s.Name, s.Type, s.BindAddr, s.UseEncryption, s.UseGzip, + s.PrivilegeMode, s.CustomDomains, s.ListenPort) s.Init() return nil } diff --git a/src/models/server/server.go b/src/models/server/server.go index f6c52a9..37eacc7 100644 --- a/src/models/server/server.go +++ b/src/models/server/server.go @@ -35,7 +35,11 @@ type Listener interface { } type ProxyServer struct { - *config.ProxyServerConf + config.BaseConf + BindAddr string + ListenPort int64 + CustomDomains []string + Locations []string Status int64 CtlConn *conn.Conn // control connection with frpc @@ -51,9 +55,9 @@ type ProxyServer struct { } func NewProxyServer() (p *ProxyServer) { - psc := &config.ProxyServerConf{CustomDomains: make([]string, 0)} p = &ProxyServer{ - ProxyServerConf: psc, + CustomDomains: make([]string, 0), + Locations: make([]string, 0), } return p } @@ -75,6 +79,7 @@ func NewProxyServerFromCtlMsg(req *msg.ControlReq) (p *ProxyServer) { p.ListenPort = VhostHttpsPort } p.CustomDomains = req.CustomDomains + p.Locations = req.Locations p.HostHeaderRewrite = req.HostHeaderRewrite p.HttpUserName = req.HttpUserName p.HttpPassWord = req.HttpPassWord @@ -137,13 +142,46 @@ func (p *ProxyServer) Start(c *conn.Conn) (err error) { } p.listeners = append(p.listeners, l) } else if p.Type == "http" { - ls := VhostHttpMuxer.Listen(p.ProxyServerConf) - for _, l := range ls { - p.listeners = append(p.listeners, l) + for _, domain := range p.CustomDomains { + if len(p.Locations) == 0 { + l, err := VhostHttpMuxer.Listen(domain, "", p.HostHeaderRewrite, p.HttpUserName, p.HttpPassWord) + if err != nil { + return err + } + p.listeners = append(p.listeners, l) + } else { + for _, location := range p.Locations { + l, err := VhostHttpMuxer.Listen(domain, location, p.HostHeaderRewrite, p.HttpUserName, p.HttpPassWord) + if err != nil { + return err + } + p.listeners = append(p.listeners, l) + } + } + } + if p.SubDomain != "" { + if len(p.Locations) == 0 { + l, err := VhostHttpMuxer.Listen(p.SubDomain, "", p.HostHeaderRewrite, p.HttpUserName, p.HttpPassWord) + if err != nil { + return err + } + p.listeners = append(p.listeners, l) + } else { + for _, location := range p.Locations { + l, err := VhostHttpMuxer.Listen(p.SubDomain, location, p.HostHeaderRewrite, p.HttpUserName, p.HttpPassWord) + if err != nil { + return err + } + p.listeners = append(p.listeners, l) + } + } } } else if p.Type == "https" { - ls := VhostHttpsMuxer.Listen(p.ProxyServerConf) - for _, l := range ls { + for _, domain := range p.CustomDomains { + l, err := VhostHttpsMuxer.Listen(domain, "", p.HostHeaderRewrite, p.HttpUserName, p.HttpPassWord) + if err != nil { + return err + } p.listeners = append(p.listeners, l) } } diff --git a/src/utils/vhost/router.go b/src/utils/vhost/router.go index b245874..803d0f8 100644 --- a/src/utils/vhost/router.go +++ b/src/utils/vhost/router.go @@ -12,7 +12,6 @@ type VhostRouters struct { } type VhostRouter struct { - name string domain string location string listener *Listener @@ -24,6 +23,65 @@ func NewVhostRouters() *VhostRouters { } } +func (r *VhostRouters) Add(domain, location string, l *Listener) { + r.mutex.Lock() + defer r.mutex.Unlock() + + vrs, found := r.RouterByDomain[domain] + if !found { + vrs = make([]*VhostRouter, 0) + } + + vr := &VhostRouter{ + domain: domain, + location: location, + 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.name] + if !found { + return + } + + for i, vr := range vrs { + if vr.listener == l { + if len(vrs) > i+1 { + r.RouterByDomain[l.name] = append(vrs[:i], vrs[i+1:]...) + } else { + r.RouterByDomain[l.name] = vrs[:i] + } + } + } +} + +func (r *VhostRouters) Get(host, path string) (vr *VhostRouter, exist bool) { + r.mutex.RLock() + defer r.mutex.RUnlock() + + vrs, found := r.RouterByDomain[host] + if !found { + return + } + + //can't support load balance,will to do + for _, vr = range vrs { + if strings.HasPrefix(path, vr.location) { + return vr, true + } + } + + return +} + //sort by location type ByLocation []*VhostRouter @@ -36,72 +94,3 @@ func (a ByLocation) Swap(i, j int) { 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, found := r.RouterByDomain[domain] - if !found { - 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 a7e953f..4a8dc52 100644 --- a/src/utils/vhost/vhost.go +++ b/src/utils/vhost/vhost.go @@ -21,9 +21,7 @@ import ( "sync" "time" - "github.com/fatedier/frp/src/models/config" "github.com/fatedier/frp/src/utils/conn" - "github.com/fatedier/frp/src/utils/log" ) type muxFunc func(*conn.Conn) (net.Conn, map[string]string, error) @@ -54,59 +52,42 @@ func NewVhostMuxer(listener *conn.Listener, vhostFunc muxFunc, authFunc httpAuth } // 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(p *config.ProxyServerConf) (ls []*Listener) { +func (v *VhostMuxer) Listen(name, location, rewriteHost, userName, passWord string) (l *Listener, err error) { v.mutex.Lock() defer v.mutex.Unlock() - ls = make([]*Listener, 0) - for _, domain := range p.CustomDomains { - l := &Listener{ - name: p.Name, - domain: domain, - locations: p.Locations, - rewriteHost: p.HostHeaderRewrite, - userName: p.HttpUserName, - passWord: p.HttpPassWord, - mux: v, - accept: make(chan *conn.Conn), - } - v.registryRouter.add(p.Name, domain, p.Locations, l) - ls = append(ls, l) + l = &Listener{ + name: name, + rewriteHost: rewriteHost, + userName: userName, + passWord: passWord, + mux: v, + accept: make(chan *conn.Conn), } - return ls + v.registryRouter.Add(name, location, l) + return l, nil } -func (v *VhostMuxer) getListener(reqInfoMap map[string]string) (l *Listener, exist bool) { +func (v *VhostMuxer) getListener(name, path string) (l *Listener, exist bool) { v.mutex.RLock() defer v.mutex.RUnlock() - //host - name := strings.ToLower(reqInfoMap["Host"]) - - // http - scheme := strings.ToLower(reqInfoMap["Scheme"]) - if scheme == "http" || scheme == "" { - name = name + ":" + reqInfoMap["Path"] - } - - // // first we check the full hostname - vr, found := v.registryRouter.get(name) + // first we check the full hostname + // if not exist, then check the wildcard_domain such as *.example.com + vr, found := v.registryRouter.Get(name, path) if found { return vr.listener, true } - //if not exist, then check the wildcard_domain such as *.example.com domainSplit := strings.Split(name, ".") if len(domainSplit) < 3 { - log.Warn("can't found the router for %s", name) return l, false } domainSplit[0] = "*" name = strings.Join(domainSplit, ".") - vr, found = v.registryRouter.get(name) + vr, found = v.registryRouter.Get(name, path) if !found { - log.Warn("can't found the router for %s", name) return } @@ -135,7 +116,9 @@ func (v *VhostMuxer) handle(c *conn.Conn) { return } - l, ok := v.getListener(reqInfoMap) + name := strings.ToLower(reqInfoMap["Host"]) + path := strings.ToLower(reqInfoMap["Path"]) + l, ok := v.getListener(name, path) if !ok { c.Close() return @@ -165,8 +148,6 @@ func (v *VhostMuxer) handle(c *conn.Conn) { type Listener struct { name string - domain string - locations []string rewriteHost string userName string passWord string @@ -194,7 +175,7 @@ func (l *Listener) Accept() (*conn.Conn, error) { } func (l *Listener) Close() error { - l.mux.registryRouter.del(l) + l.mux.registryRouter.Del(l) close(l.accept) return nil }