mirror of https://github.com/fatedier/frp.git

17 changed files with 1458 additions and 1141 deletions
-
2models/nathole/nathole.go
-
90server/control.go
-
46server/controller/resource.go
-
79server/controller/visitor.go
-
12server/dashboard_api.go
-
316server/metric.go
-
687server/proxy.go
-
138server/proxy/http.go
-
72server/proxy/https.go
-
250server/proxy/proxy.go
-
47server/proxy/stcp.go
-
84server/proxy/tcp.go
-
225server/proxy/udp.go
-
73server/proxy/xtcp.go
-
76server/service.go
-
273server/stats/internal.go
-
129server/stats/stats.go
@ -1,4 +1,4 @@ |
|||
package server |
|||
package nathole |
|||
|
|||
import ( |
|||
"bytes" |
@ -0,0 +1,46 @@ |
|||
// Copyright 2019 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 controller |
|||
|
|||
import ( |
|||
"github.com/fatedier/frp/models/nathole" |
|||
"github.com/fatedier/frp/server/group" |
|||
"github.com/fatedier/frp/server/ports" |
|||
"github.com/fatedier/frp/utils/vhost" |
|||
) |
|||
|
|||
// All resource managers and controllers
|
|||
type ResourceController struct { |
|||
// Manage all visitor listeners
|
|||
VisitorManager *VisitorManager |
|||
|
|||
// Tcp Group Controller
|
|||
TcpGroupCtl *group.TcpGroupCtl |
|||
|
|||
// Manage all tcp ports
|
|||
TcpPortManager *ports.PortManager |
|||
|
|||
// Manage all udp ports
|
|||
UdpPortManager *ports.PortManager |
|||
|
|||
// For http proxies, forwarding http requests
|
|||
HttpReverseProxy *vhost.HttpReverseProxy |
|||
|
|||
// For https proxies, route requests to different clients by hostname and other infomation
|
|||
VhostHttpsMuxer *vhost.HttpsMuxer |
|||
|
|||
// Controller for nat hole connections
|
|||
NatHoleController *nathole.NatHoleController |
|||
} |
@ -1,316 +0,0 @@ |
|||
// Copyright 2017 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 server |
|||
|
|||
import ( |
|||
"sync" |
|||
"time" |
|||
|
|||
"github.com/fatedier/frp/g" |
|||
"github.com/fatedier/frp/utils/log" |
|||
"github.com/fatedier/frp/utils/metric" |
|||
) |
|||
|
|||
const ( |
|||
ReserveDays = 7 |
|||
) |
|||
|
|||
var globalStats *ServerStatistics |
|||
|
|||
type ServerStatistics struct { |
|||
TotalTrafficIn metric.DateCounter |
|||
TotalTrafficOut metric.DateCounter |
|||
CurConns metric.Counter |
|||
|
|||
// counter for clients
|
|||
ClientCounts metric.Counter |
|||
|
|||
// counter for proxy types
|
|||
ProxyTypeCounts map[string]metric.Counter |
|||
|
|||
// statistics for different proxies
|
|||
// key is proxy name
|
|||
ProxyStatistics map[string]*ProxyStatistics |
|||
|
|||
mu sync.Mutex |
|||
} |
|||
|
|||
type ProxyStatistics struct { |
|||
Name string |
|||
ProxyType string |
|||
TrafficIn metric.DateCounter |
|||
TrafficOut metric.DateCounter |
|||
CurConns metric.Counter |
|||
LastStartTime time.Time |
|||
LastCloseTime time.Time |
|||
} |
|||
|
|||
func init() { |
|||
globalStats = &ServerStatistics{ |
|||
TotalTrafficIn: metric.NewDateCounter(ReserveDays), |
|||
TotalTrafficOut: metric.NewDateCounter(ReserveDays), |
|||
CurConns: metric.NewCounter(), |
|||
|
|||
ClientCounts: metric.NewCounter(), |
|||
ProxyTypeCounts: make(map[string]metric.Counter), |
|||
|
|||
ProxyStatistics: make(map[string]*ProxyStatistics), |
|||
} |
|||
|
|||
go func() { |
|||
for { |
|||
time.Sleep(12 * time.Hour) |
|||
log.Debug("start to clear useless proxy statistics data...") |
|||
StatsClearUselessInfo() |
|||
log.Debug("finish to clear useless proxy statistics data") |
|||
} |
|||
}() |
|||
} |
|||
|
|||
func StatsClearUselessInfo() { |
|||
// To check if there are proxies that closed than 7 days and drop them.
|
|||
globalStats.mu.Lock() |
|||
defer globalStats.mu.Unlock() |
|||
for name, data := range globalStats.ProxyStatistics { |
|||
if !data.LastCloseTime.IsZero() && time.Since(data.LastCloseTime) > time.Duration(7*24)*time.Hour { |
|||
delete(globalStats.ProxyStatistics, name) |
|||
log.Trace("clear proxy [%s]'s statistics data, lastCloseTime: [%s]", name, data.LastCloseTime.String()) |
|||
} |
|||
} |
|||
} |
|||
|
|||
func StatsNewClient() { |
|||
if g.GlbServerCfg.DashboardPort != 0 { |
|||
globalStats.ClientCounts.Inc(1) |
|||
} |
|||
} |
|||
|
|||
func StatsCloseClient() { |
|||
if g.GlbServerCfg.DashboardPort != 0 { |
|||
globalStats.ClientCounts.Dec(1) |
|||
} |
|||
} |
|||
|
|||
func StatsNewProxy(name string, proxyType string) { |
|||
if g.GlbServerCfg.DashboardPort != 0 { |
|||
globalStats.mu.Lock() |
|||
defer globalStats.mu.Unlock() |
|||
counter, ok := globalStats.ProxyTypeCounts[proxyType] |
|||
if !ok { |
|||
counter = metric.NewCounter() |
|||
} |
|||
counter.Inc(1) |
|||
globalStats.ProxyTypeCounts[proxyType] = counter |
|||
|
|||
proxyStats, ok := globalStats.ProxyStatistics[name] |
|||
if !(ok && proxyStats.ProxyType == proxyType) { |
|||
proxyStats = &ProxyStatistics{ |
|||
Name: name, |
|||
ProxyType: proxyType, |
|||
CurConns: metric.NewCounter(), |
|||
TrafficIn: metric.NewDateCounter(ReserveDays), |
|||
TrafficOut: metric.NewDateCounter(ReserveDays), |
|||
} |
|||
globalStats.ProxyStatistics[name] = proxyStats |
|||
} |
|||
proxyStats.LastStartTime = time.Now() |
|||
} |
|||
} |
|||
|
|||
func StatsCloseProxy(proxyName string, proxyType string) { |
|||
if g.GlbServerCfg.DashboardPort != 0 { |
|||
globalStats.mu.Lock() |
|||
defer globalStats.mu.Unlock() |
|||
if counter, ok := globalStats.ProxyTypeCounts[proxyType]; ok { |
|||
counter.Dec(1) |
|||
} |
|||
if proxyStats, ok := globalStats.ProxyStatistics[proxyName]; ok { |
|||
proxyStats.LastCloseTime = time.Now() |
|||
} |
|||
} |
|||
} |
|||
|
|||
func StatsOpenConnection(name string) { |
|||
if g.GlbServerCfg.DashboardPort != 0 { |
|||
globalStats.CurConns.Inc(1) |
|||
|
|||
globalStats.mu.Lock() |
|||
defer globalStats.mu.Unlock() |
|||
proxyStats, ok := globalStats.ProxyStatistics[name] |
|||
if ok { |
|||
proxyStats.CurConns.Inc(1) |
|||
globalStats.ProxyStatistics[name] = proxyStats |
|||
} |
|||
} |
|||
} |
|||
|
|||
func StatsCloseConnection(name string) { |
|||
if g.GlbServerCfg.DashboardPort != 0 { |
|||
globalStats.CurConns.Dec(1) |
|||
|
|||
globalStats.mu.Lock() |
|||
defer globalStats.mu.Unlock() |
|||
proxyStats, ok := globalStats.ProxyStatistics[name] |
|||
if ok { |
|||
proxyStats.CurConns.Dec(1) |
|||
globalStats.ProxyStatistics[name] = proxyStats |
|||
} |
|||
} |
|||
} |
|||
|
|||
func StatsAddTrafficIn(name string, trafficIn int64) { |
|||
if g.GlbServerCfg.DashboardPort != 0 { |
|||
globalStats.TotalTrafficIn.Inc(trafficIn) |
|||
|
|||
globalStats.mu.Lock() |
|||
defer globalStats.mu.Unlock() |
|||
|
|||
proxyStats, ok := globalStats.ProxyStatistics[name] |
|||
if ok { |
|||
proxyStats.TrafficIn.Inc(trafficIn) |
|||
globalStats.ProxyStatistics[name] = proxyStats |
|||
} |
|||
} |
|||
} |
|||
|
|||
func StatsAddTrafficOut(name string, trafficOut int64) { |
|||
if g.GlbServerCfg.DashboardPort != 0 { |
|||
globalStats.TotalTrafficOut.Inc(trafficOut) |
|||
|
|||
globalStats.mu.Lock() |
|||
defer globalStats.mu.Unlock() |
|||
|
|||
proxyStats, ok := globalStats.ProxyStatistics[name] |
|||
if ok { |
|||
proxyStats.TrafficOut.Inc(trafficOut) |
|||
globalStats.ProxyStatistics[name] = proxyStats |
|||
} |
|||
} |
|||
} |
|||
|
|||
// Functions for getting server stats.
|
|||
type ServerStats struct { |
|||
TotalTrafficIn int64 |
|||
TotalTrafficOut int64 |
|||
CurConns int64 |
|||
ClientCounts int64 |
|||
ProxyTypeCounts map[string]int64 |
|||
} |
|||
|
|||
func StatsGetServer() *ServerStats { |
|||
globalStats.mu.Lock() |
|||
defer globalStats.mu.Unlock() |
|||
s := &ServerStats{ |
|||
TotalTrafficIn: globalStats.TotalTrafficIn.TodayCount(), |
|||
TotalTrafficOut: globalStats.TotalTrafficOut.TodayCount(), |
|||
CurConns: globalStats.CurConns.Count(), |
|||
ClientCounts: globalStats.ClientCounts.Count(), |
|||
ProxyTypeCounts: make(map[string]int64), |
|||
} |
|||
for k, v := range globalStats.ProxyTypeCounts { |
|||
s.ProxyTypeCounts[k] = v.Count() |
|||
} |
|||
return s |
|||
} |
|||
|
|||
type ProxyStats struct { |
|||
Name string |
|||
Type string |
|||
TodayTrafficIn int64 |
|||
TodayTrafficOut int64 |
|||
LastStartTime string |
|||
LastCloseTime string |
|||
CurConns int64 |
|||
} |
|||
|
|||
func StatsGetProxiesByType(proxyType string) []*ProxyStats { |
|||
res := make([]*ProxyStats, 0) |
|||
globalStats.mu.Lock() |
|||
defer globalStats.mu.Unlock() |
|||
|
|||
for name, proxyStats := range globalStats.ProxyStatistics { |
|||
if proxyStats.ProxyType != proxyType { |
|||
continue |
|||
} |
|||
|
|||
ps := &ProxyStats{ |
|||
Name: name, |
|||
Type: proxyStats.ProxyType, |
|||
TodayTrafficIn: proxyStats.TrafficIn.TodayCount(), |
|||
TodayTrafficOut: proxyStats.TrafficOut.TodayCount(), |
|||
CurConns: proxyStats.CurConns.Count(), |
|||
} |
|||
if !proxyStats.LastStartTime.IsZero() { |
|||
ps.LastStartTime = proxyStats.LastStartTime.Format("01-02 15:04:05") |
|||
} |
|||
if !proxyStats.LastCloseTime.IsZero() { |
|||
ps.LastCloseTime = proxyStats.LastCloseTime.Format("01-02 15:04:05") |
|||
} |
|||
res = append(res, ps) |
|||
} |
|||
return res |
|||
} |
|||
|
|||
func StatsGetProxiesByTypeAndName(proxyType string, proxyName string) (res *ProxyStats) { |
|||
globalStats.mu.Lock() |
|||
defer globalStats.mu.Unlock() |
|||
|
|||
for name, proxyStats := range globalStats.ProxyStatistics { |
|||
if proxyStats.ProxyType != proxyType { |
|||
continue |
|||
} |
|||
|
|||
if name != proxyName { |
|||
continue |
|||
} |
|||
|
|||
res = &ProxyStats{ |
|||
Name: name, |
|||
Type: proxyStats.ProxyType, |
|||
TodayTrafficIn: proxyStats.TrafficIn.TodayCount(), |
|||
TodayTrafficOut: proxyStats.TrafficOut.TodayCount(), |
|||
CurConns: proxyStats.CurConns.Count(), |
|||
} |
|||
if !proxyStats.LastStartTime.IsZero() { |
|||
res.LastStartTime = proxyStats.LastStartTime.Format("01-02 15:04:05") |
|||
} |
|||
if !proxyStats.LastCloseTime.IsZero() { |
|||
res.LastCloseTime = proxyStats.LastCloseTime.Format("01-02 15:04:05") |
|||
} |
|||
break |
|||
} |
|||
return |
|||
} |
|||
|
|||
type ProxyTrafficInfo struct { |
|||
Name string |
|||
TrafficIn []int64 |
|||
TrafficOut []int64 |
|||
} |
|||
|
|||
func StatsGetProxyTraffic(name string) (res *ProxyTrafficInfo) { |
|||
globalStats.mu.Lock() |
|||
defer globalStats.mu.Unlock() |
|||
|
|||
proxyStats, ok := globalStats.ProxyStatistics[name] |
|||
if ok { |
|||
res = &ProxyTrafficInfo{ |
|||
Name: name, |
|||
} |
|||
res.TrafficIn = proxyStats.TrafficIn.GetLastDaysCount(ReserveDays) |
|||
res.TrafficOut = proxyStats.TrafficOut.GetLastDaysCount(ReserveDays) |
|||
} |
|||
return |
|||
} |
@ -1,687 +0,0 @@ |
|||
// Copyright 2017 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 server |
|||
|
|||
import ( |
|||
"context" |
|||
"fmt" |
|||
"io" |
|||
"net" |
|||
"strings" |
|||
"sync" |
|||
"time" |
|||
|
|||
"github.com/fatedier/frp/g" |
|||
"github.com/fatedier/frp/models/config" |
|||
"github.com/fatedier/frp/models/msg" |
|||
"github.com/fatedier/frp/models/proto/udp" |
|||
"github.com/fatedier/frp/utils/log" |
|||
frpNet "github.com/fatedier/frp/utils/net" |
|||
"github.com/fatedier/frp/utils/util" |
|||
"github.com/fatedier/frp/utils/vhost" |
|||
|
|||
"github.com/fatedier/golib/errors" |
|||
frpIo "github.com/fatedier/golib/io" |
|||
) |
|||
|
|||
type GetWorkConnFn func() (frpNet.Conn, error) |
|||
|
|||
type Proxy interface { |
|||
Run() (remoteAddr string, err error) |
|||
GetName() string |
|||
GetConf() config.ProxyConf |
|||
GetWorkConnFromPool() (workConn frpNet.Conn, err error) |
|||
GetUsedPortsNum() int |
|||
Close() |
|||
log.Logger |
|||
} |
|||
|
|||
type BaseProxy struct { |
|||
name string |
|||
rc *ResourceController |
|||
listeners []frpNet.Listener |
|||
usedPortsNum int |
|||
poolCount int |
|||
getWorkConnFn GetWorkConnFn |
|||
|
|||
mu sync.RWMutex |
|||
log.Logger |
|||
} |
|||
|
|||
func (pxy *BaseProxy) GetName() string { |
|||
return pxy.name |
|||
} |
|||
|
|||
func (pxy *BaseProxy) GetUsedPortsNum() int { |
|||
return pxy.usedPortsNum |
|||
} |
|||
|
|||
func (pxy *BaseProxy) Close() { |
|||
pxy.Info("proxy closing") |
|||
for _, l := range pxy.listeners { |
|||
l.Close() |
|||
} |
|||
} |
|||
|
|||
func (pxy *BaseProxy) GetWorkConnFromPool() (workConn frpNet.Conn, err error) { |
|||
// try all connections from the pool
|
|||
for i := 0; i < pxy.poolCount+1; i++ { |
|||
if workConn, err = pxy.getWorkConnFn(); err != nil { |
|||
pxy.Warn("failed to get work connection: %v", err) |
|||
return |
|||
} |
|||
pxy.Info("get a new work connection: [%s]", workConn.RemoteAddr().String()) |
|||
workConn.AddLogPrefix(pxy.GetName()) |
|||
|
|||
err := msg.WriteMsg(workConn, &msg.StartWorkConn{ |
|||
ProxyName: pxy.GetName(), |
|||
}) |
|||
if err != nil { |
|||
workConn.Warn("failed to send message to work connection from pool: %v, times: %d", err, i) |
|||
workConn.Close() |
|||
} else { |
|||
break |
|||
} |
|||
} |
|||
|
|||
if err != nil { |
|||
pxy.Error("try to get work connection failed in the end") |
|||
return |
|||
} |
|||
return |
|||
} |
|||
|
|||
// startListenHandler start a goroutine handler for each listener.
|
|||
// p: p will just be passed to handler(Proxy, frpNet.Conn).
|
|||
// handler: each proxy type can set different handler function to deal with connections accepted from listeners.
|
|||
func (pxy *BaseProxy) startListenHandler(p Proxy, handler func(Proxy, frpNet.Conn)) { |
|||
for _, listener := range pxy.listeners { |
|||
go func(l frpNet.Listener) { |
|||
for { |
|||
// block
|
|||
// if listener is closed, err returned
|
|||
c, err := l.Accept() |
|||
if err != nil { |
|||
pxy.Info("listener is closed") |
|||
return |
|||
} |
|||
pxy.Debug("get a user connection [%s]", c.RemoteAddr().String()) |
|||
go handler(p, c) |
|||
} |
|||
}(listener) |
|||
} |
|||
} |
|||
|
|||
func NewProxy(runId string, rc *ResourceController, poolCount int, getWorkConnFn GetWorkConnFn, pxyConf config.ProxyConf) (pxy Proxy, err error) { |
|||
basePxy := BaseProxy{ |
|||
name: pxyConf.GetBaseInfo().ProxyName, |
|||
rc: rc, |
|||
listeners: make([]frpNet.Listener, 0), |
|||
poolCount: poolCount, |
|||
getWorkConnFn: getWorkConnFn, |
|||
Logger: log.NewPrefixLogger(runId), |
|||
} |
|||
switch cfg := pxyConf.(type) { |
|||
case *config.TcpProxyConf: |
|||
basePxy.usedPortsNum = 1 |
|||
pxy = &TcpProxy{ |
|||
BaseProxy: basePxy, |
|||
cfg: cfg, |
|||
} |
|||
case *config.HttpProxyConf: |
|||
pxy = &HttpProxy{ |
|||
BaseProxy: basePxy, |
|||
cfg: cfg, |
|||
} |
|||
case *config.HttpsProxyConf: |
|||
pxy = &HttpsProxy{ |
|||
BaseProxy: basePxy, |
|||
cfg: cfg, |
|||
} |
|||
case *config.UdpProxyConf: |
|||
basePxy.usedPortsNum = 1 |
|||
pxy = &UdpProxy{ |
|||
BaseProxy: basePxy, |
|||
cfg: cfg, |
|||
} |
|||
case *config.StcpProxyConf: |
|||
pxy = &StcpProxy{ |
|||
BaseProxy: basePxy, |
|||
cfg: cfg, |
|||
} |
|||
case *config.XtcpProxyConf: |
|||
pxy = &XtcpProxy{ |
|||
BaseProxy: basePxy, |
|||
cfg: cfg, |
|||
} |
|||
default: |
|||
return pxy, fmt.Errorf("proxy type not support") |
|||
} |
|||
pxy.AddLogPrefix(pxy.GetName()) |
|||
return |
|||
} |
|||
|
|||
type TcpProxy struct { |
|||
BaseProxy |
|||
cfg *config.TcpProxyConf |
|||
|
|||
realPort int |
|||
} |
|||
|
|||
func (pxy *TcpProxy) Run() (remoteAddr string, err error) { |
|||
if pxy.cfg.Group != "" { |
|||
l, realPort, errRet := pxy.rc.TcpGroupCtl.Listen(pxy.name, pxy.cfg.Group, pxy.cfg.GroupKey, g.GlbServerCfg.ProxyBindAddr, pxy.cfg.RemotePort) |
|||
if errRet != nil { |
|||
err = errRet |
|||
return |
|||
} |
|||
defer func() { |
|||
if err != nil { |
|||
l.Close() |
|||
} |
|||
}() |
|||
pxy.realPort = realPort |
|||
listener := frpNet.WrapLogListener(l) |
|||
listener.AddLogPrefix(pxy.name) |
|||
pxy.listeners = append(pxy.listeners, listener) |
|||
pxy.Info("tcp proxy listen port [%d] in group [%s]", pxy.cfg.RemotePort, pxy.cfg.Group) |
|||
} else { |
|||
pxy.realPort, err = pxy.rc.TcpPortManager.Acquire(pxy.name, pxy.cfg.RemotePort) |
|||
if err != nil { |
|||
return |
|||
} |
|||
defer func() { |
|||
if err != nil { |
|||
pxy.rc.TcpPortManager.Release(pxy.realPort) |
|||
} |
|||
}() |
|||
listener, errRet := frpNet.ListenTcp(g.GlbServerCfg.ProxyBindAddr, pxy.realPort) |
|||
if errRet != nil { |
|||
err = errRet |
|||
return |
|||
} |
|||
listener.AddLogPrefix(pxy.name) |
|||
pxy.listeners = append(pxy.listeners, listener) |
|||
pxy.Info("tcp proxy listen port [%d]", pxy.cfg.RemotePort) |
|||
} |
|||
|
|||
pxy.cfg.RemotePort = pxy.realPort |
|||
remoteAddr = fmt.Sprintf(":%d", pxy.realPort) |
|||
pxy.startListenHandler(pxy, HandleUserTcpConnection) |
|||
return |
|||
} |
|||
|
|||
func (pxy *TcpProxy) GetConf() config.ProxyConf { |
|||
return pxy.cfg |
|||
} |
|||
|
|||
func (pxy *TcpProxy) Close() { |
|||
pxy.BaseProxy.Close() |
|||
if pxy.cfg.Group == "" { |
|||
pxy.rc.TcpPortManager.Release(pxy.realPort) |
|||
} |
|||
} |
|||
|
|||
type HttpProxy struct { |
|||
BaseProxy |
|||
cfg *config.HttpProxyConf |
|||
|
|||
closeFuncs []func() |
|||
} |
|||
|
|||
func (pxy *HttpProxy) Run() (remoteAddr string, err error) { |
|||
routeConfig := vhost.VhostRouteConfig{ |
|||
RewriteHost: pxy.cfg.HostHeaderRewrite, |
|||
Headers: pxy.cfg.Headers, |
|||
Username: pxy.cfg.HttpUser, |
|||
Password: pxy.cfg.HttpPwd, |
|||
CreateConnFn: pxy.GetRealConn, |
|||
} |
|||
|
|||
locations := pxy.cfg.Locations |
|||
if len(locations) == 0 { |
|||
locations = []string{""} |
|||
} |
|||
|
|||
addrs := make([]string, 0) |
|||
for _, domain := range pxy.cfg.CustomDomains { |
|||
routeConfig.Domain = domain |
|||
for _, location := range locations { |
|||
routeConfig.Location = location |
|||
err = pxy.rc.HttpReverseProxy.Register(routeConfig) |
|||
if err != nil { |
|||
return |
|||
} |
|||
tmpDomain := routeConfig.Domain |
|||
tmpLocation := routeConfig.Location |
|||
addrs = append(addrs, util.CanonicalAddr(tmpDomain, int(g.GlbServerCfg.VhostHttpPort))) |
|||
pxy.closeFuncs = append(pxy.closeFuncs, func() { |
|||
pxy.rc.HttpReverseProxy.UnRegister(tmpDomain, tmpLocation) |
|||
}) |
|||
pxy.Info("http proxy listen for host [%s] location [%s]", routeConfig.Domain, routeConfig.Location) |
|||
} |
|||
} |
|||
|
|||
if pxy.cfg.SubDomain != "" { |
|||
routeConfig.Domain = pxy.cfg.SubDomain + "." + g.GlbServerCfg.SubDomainHost |
|||
for _, location := range locations { |
|||
routeConfig.Location = location |
|||
err = pxy.rc.HttpReverseProxy.Register(routeConfig) |
|||
if err != nil { |
|||
return |
|||
} |
|||
tmpDomain := routeConfig.Domain |
|||
tmpLocation := routeConfig.Location |
|||
addrs = append(addrs, util.CanonicalAddr(tmpDomain, g.GlbServerCfg.VhostHttpPort)) |
|||
pxy.closeFuncs = append(pxy.closeFuncs, func() { |
|||
pxy.rc.HttpReverseProxy.UnRegister(tmpDomain, tmpLocation) |
|||
}) |
|||
pxy.Info("http proxy listen for host [%s] location [%s]", routeConfig.Domain, routeConfig.Location) |
|||
} |
|||
} |
|||
remoteAddr = strings.Join(addrs, ",") |
|||
return |
|||
} |
|||
|
|||
func (pxy *HttpProxy) GetConf() config.ProxyConf { |
|||
return pxy.cfg |
|||
} |
|||
|
|||
func (pxy *HttpProxy) GetRealConn() (workConn frpNet.Conn, err error) { |
|||
tmpConn, errRet := pxy.GetWorkConnFromPool() |
|||
if errRet != nil { |
|||
err = errRet |
|||
return |
|||
} |
|||
|
|||
var rwc io.ReadWriteCloser = tmpConn |
|||
if pxy.cfg.UseEncryption { |
|||
rwc, err = frpIo.WithEncryption(rwc, []byte(g.GlbServerCfg.Token)) |
|||
if err != nil { |
|||
pxy.Error("create encryption stream error: %v", err) |
|||
return |
|||
} |
|||
} |
|||
if pxy.cfg.UseCompression { |
|||
rwc = frpIo.WithCompression(rwc) |
|||
} |
|||
workConn = frpNet.WrapReadWriteCloserToConn(rwc, tmpConn) |
|||
workConn = frpNet.WrapStatsConn(workConn, pxy.updateStatsAfterClosedConn) |
|||
StatsOpenConnection(pxy.GetName()) |
|||
return |
|||
} |
|||
|
|||
func (pxy *HttpProxy) updateStatsAfterClosedConn(totalRead, totalWrite int64) { |
|||
name := pxy.GetName() |
|||
StatsCloseConnection(name) |
|||
StatsAddTrafficIn(name, totalWrite) |
|||
StatsAddTrafficOut(name, totalRead) |
|||
} |
|||
|
|||
func (pxy *HttpProxy) Close() { |
|||
pxy.BaseProxy.Close() |
|||
for _, closeFn := range pxy.closeFuncs { |
|||
closeFn() |
|||
} |
|||
} |
|||
|
|||
type HttpsProxy struct { |
|||
BaseProxy |
|||
cfg *config.HttpsProxyConf |
|||
} |
|||
|
|||
func (pxy *HttpsProxy) Run() (remoteAddr string, err error) { |
|||
routeConfig := &vhost.VhostRouteConfig{} |
|||
|
|||
addrs := make([]string, 0) |
|||
for _, domain := range pxy.cfg.CustomDomains { |
|||
routeConfig.Domain = domain |
|||
l, errRet := pxy.rc.VhostHttpsMuxer.Listen(routeConfig) |
|||
if errRet != nil { |
|||
err = errRet |
|||
return |
|||
} |
|||
l.AddLogPrefix(pxy.name) |
|||
pxy.Info("https proxy listen for host [%s]", routeConfig.Domain) |
|||
pxy.listeners = append(pxy.listeners, l) |
|||
addrs = append(addrs, util.CanonicalAddr(routeConfig.Domain, g.GlbServerCfg.VhostHttpsPort)) |
|||
} |
|||
|
|||
if pxy.cfg.SubDomain != "" { |
|||
routeConfig.Domain = pxy.cfg.SubDomain + "." + g.GlbServerCfg.SubDomainHost |
|||
l, errRet := pxy.rc.VhostHttpsMuxer.Listen(routeConfig) |
|||
if errRet != nil { |
|||
err = errRet |
|||
return |
|||
} |
|||
l.AddLogPrefix(pxy.name) |
|||
pxy.Info("https proxy listen for host [%s]", routeConfig.Domain) |
|||
pxy.listeners = append(pxy.listeners, l) |
|||
addrs = append(addrs, util.CanonicalAddr(routeConfig.Domain, int(g.GlbServerCfg.VhostHttpsPort))) |
|||
} |
|||
|
|||
pxy.startListenHandler(pxy, HandleUserTcpConnection) |
|||
remoteAddr = strings.Join(addrs, ",") |
|||
return |
|||
} |
|||
|
|||
func (pxy *HttpsProxy) GetConf() config.ProxyConf { |
|||
return pxy.cfg |
|||
} |
|||
|
|||
func (pxy *HttpsProxy) Close() { |
|||
pxy.BaseProxy.Close() |
|||
} |
|||
|
|||
type StcpProxy struct { |
|||
BaseProxy |
|||
cfg *config.StcpProxyConf |
|||
} |
|||
|
|||
func (pxy *StcpProxy) Run() (remoteAddr string, err error) { |
|||
listener, errRet := pxy.rc.VisitorManager.Listen(pxy.GetName(), pxy.cfg.Sk) |
|||
if errRet != nil { |
|||
err = errRet |
|||
return |
|||
} |
|||
listener.AddLogPrefix(pxy.name) |
|||
pxy.listeners = append(pxy.listeners, listener) |
|||
pxy.Info("stcp proxy custom listen success") |
|||
|
|||
pxy.startListenHandler(pxy, HandleUserTcpConnection) |
|||
return |
|||
} |
|||
|
|||
func (pxy *StcpProxy) GetConf() config.ProxyConf { |
|||
return pxy.cfg |
|||
} |
|||
|
|||
func (pxy *StcpProxy) Close() { |
|||
pxy.BaseProxy.Close() |
|||
pxy.rc.VisitorManager.CloseListener(pxy.GetName()) |
|||
} |
|||
|
|||
type XtcpProxy struct { |
|||
BaseProxy |
|||
cfg *config.XtcpProxyConf |
|||
|
|||
closeCh chan struct{} |
|||
} |
|||
|
|||
func (pxy *XtcpProxy) Run() (remoteAddr string, err error) { |
|||
if pxy.rc.NatHoleController == nil { |
|||
pxy.Error("udp port for xtcp is not specified.") |
|||
err = fmt.Errorf("xtcp is not supported in frps") |
|||
return |
|||
} |
|||
sidCh := pxy.rc.NatHoleController.ListenClient(pxy.GetName(), pxy.cfg.Sk) |
|||
go func() { |
|||
for { |
|||
select { |
|||
case <-pxy.closeCh: |
|||
break |
|||
case sid := <-sidCh: |
|||
workConn, errRet := pxy.GetWorkConnFromPool() |
|||
if errRet != nil { |
|||
continue |
|||
} |
|||
m := &msg.NatHoleSid{ |
|||
Sid: sid, |
|||
} |
|||
errRet = msg.WriteMsg(workConn, m) |
|||
if errRet != nil { |
|||
pxy.Warn("write nat hole sid package error, %v", errRet) |
|||
} |
|||
} |
|||
} |
|||
}() |
|||
return |
|||
} |
|||
|
|||
func (pxy *XtcpProxy) GetConf() config.ProxyConf { |
|||
return pxy.cfg |
|||
} |
|||
|
|||
func (pxy *XtcpProxy) Close() { |
|||
pxy.BaseProxy.Close() |
|||
pxy.rc.NatHoleController.CloseClient(pxy.GetName()) |
|||
errors.PanicToError(func() { |
|||
close(pxy.closeCh) |
|||
}) |
|||
} |
|||
|
|||
type UdpProxy struct { |
|||
BaseProxy |
|||
cfg *config.UdpProxyConf |
|||
|
|||
realPort int |
|||
|
|||
// udpConn is the listener of udp packages
|
|||
udpConn *net.UDPConn |
|||
|
|||
// there are always only one workConn at the same time
|
|||
// get another one if it closed
|
|||
workConn net.Conn |
|||
|
|||
// sendCh is used for sending packages to workConn
|
|||
sendCh chan *msg.UdpPacket |
|||
|
|||
// readCh is used for reading packages from workConn
|
|||
readCh chan *msg.UdpPacket |
|||
|
|||
// checkCloseCh is used for watching if workConn is closed
|
|||
checkCloseCh chan int |
|||
|
|||
isClosed bool |
|||
} |
|||
|
|||
func (pxy *UdpProxy) Run() (remoteAddr string, err error) { |
|||
pxy.realPort, err = pxy.rc.UdpPortManager.Acquire(pxy.name, pxy.cfg.RemotePort) |
|||
if err != nil { |
|||
return |
|||
} |
|||
defer func() { |
|||
if err != nil { |
|||
pxy.rc.UdpPortManager.Release(pxy.realPort) |
|||
} |
|||
}() |
|||
|
|||
remoteAddr = fmt.Sprintf(":%d", pxy.realPort) |
|||
pxy.cfg.RemotePort = pxy.realPort |
|||
addr, errRet := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", g.GlbServerCfg.ProxyBindAddr, pxy.realPort)) |
|||
if errRet != nil { |
|||
err = errRet |
|||
return |
|||
} |
|||
udpConn, errRet := net.ListenUDP("udp", addr) |
|||
if errRet != nil { |
|||
err = errRet |
|||
pxy.Warn("listen udp port error: %v", err) |
|||
return |
|||
} |
|||
pxy.Info("udp proxy listen port [%d]", pxy.cfg.RemotePort) |
|||
|
|||
pxy.udpConn = udpConn |
|||
pxy.sendCh = make(chan *msg.UdpPacket, 1024) |
|||
pxy.readCh = make(chan *msg.UdpPacket, 1024) |
|||
pxy.checkCloseCh = make(chan int) |
|||
|
|||
// read message from workConn, if it returns any error, notify proxy to start a new workConn
|
|||
workConnReaderFn := func(conn net.Conn) { |
|||
for { |
|||
var ( |
|||
rawMsg msg.Message |
|||
errRet error |
|||
) |
|||
pxy.Trace("loop waiting message from udp workConn") |
|||
// client will send heartbeat in workConn for keeping alive
|
|||
conn.SetReadDeadline(time.Now().Add(time.Duration(60) * time.Second)) |
|||
if rawMsg, errRet = msg.ReadMsg(conn); errRet != nil { |
|||
pxy.Warn("read from workConn for udp error: %v", errRet) |
|||
conn.Close() |
|||
// notify proxy to start a new work connection
|
|||
// ignore error here, it means the proxy is closed
|
|||
errors.PanicToError(func() { |
|||
pxy.checkCloseCh <- 1 |
|||
}) |
|||
return |
|||
} |
|||
conn.SetReadDeadline(time.Time{}) |
|||
switch m := rawMsg.(type) { |
|||
case *msg.Ping: |
|||
pxy.Trace("udp work conn get ping message") |
|||
continue |
|||
case *msg.UdpPacket: |
|||
if errRet := errors.PanicToError(func() { |
|||
pxy.Trace("get udp message from workConn: %s", m.Content) |
|||
pxy.readCh <- m |
|||
StatsAddTrafficOut(pxy.GetName(), int64(len(m.Content))) |
|||
}); errRet != nil { |
|||
conn.Close() |
|||
pxy.Info("reader goroutine for udp work connection closed") |
|||
return |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
// send message to workConn
|
|||
workConnSenderFn := func(conn net.Conn, ctx context.Context) { |
|||
var errRet error |
|||
for { |
|||
select { |
|||
case udpMsg, ok := <-pxy.sendCh: |
|||
if !ok { |
|||
pxy.Info("sender goroutine for udp work connection closed") |
|||
return |
|||
} |
|||
if errRet = msg.WriteMsg(conn, udpMsg); errRet != nil { |
|||
pxy.Info("sender goroutine for udp work connection closed: %v", errRet) |
|||
conn.Close() |
|||
return |
|||
} else { |
|||
pxy.Trace("send message to udp workConn: %s", udpMsg.Content) |
|||
StatsAddTrafficIn(pxy.GetName(), int64(len(udpMsg.Content))) |
|||
continue |
|||
} |
|||
case <-ctx.Done(): |
|||
pxy.Info("sender goroutine for udp work connection closed") |
|||
return |
|||
} |
|||
} |
|||
} |
|||
|
|||
go func() { |
|||
// Sleep a while for waiting control send the NewProxyResp to client.
|
|||
time.Sleep(500 * time.Millisecond) |
|||
for { |
|||
workConn, err := pxy.GetWorkConnFromPool() |
|||
if err != nil { |
|||
time.Sleep(1 * time.Second) |
|||
// check if proxy is closed
|
|||
select { |
|||
case _, ok := <-pxy.checkCloseCh: |
|||
if !ok { |
|||
return |
|||
} |
|||
default: |
|||
} |
|||
continue |
|||
} |
|||
// close the old workConn and replac it with a new one
|
|||
if pxy.workConn != nil { |
|||
pxy.workConn.Close() |
|||
} |
|||
pxy.workConn = workConn |
|||
ctx, cancel := context.WithCancel(context.Background()) |
|||
go workConnReaderFn(workConn) |
|||
go workConnSenderFn(workConn, ctx) |
|||
_, ok := <-pxy.checkCloseCh |
|||
cancel() |
|||
if !ok { |
|||
return |
|||
} |
|||
} |
|||
}() |
|||
|
|||
// Read from user connections and send wrapped udp message to sendCh (forwarded by workConn).
|
|||
// Client will transfor udp message to local udp service and waiting for response for a while.
|
|||
// Response will be wrapped to be forwarded by work connection to server.
|
|||
// Close readCh and sendCh at the end.
|
|||
go func() { |
|||
udp.ForwardUserConn(udpConn, pxy.readCh, pxy.sendCh) |
|||
pxy.Close() |
|||
}() |
|||
return remoteAddr, nil |
|||
} |
|||
|
|||
func (pxy *UdpProxy) GetConf() config.ProxyConf { |
|||
return pxy.cfg |
|||
} |
|||
|
|||
func (pxy *UdpProxy) Close() { |
|||
pxy.mu.Lock() |
|||
defer pxy.mu.Unlock() |
|||
if !pxy.isClosed { |
|||
pxy.isClosed = true |
|||
|
|||
pxy.BaseProxy.Close() |
|||
if pxy.workConn != nil { |
|||
pxy.workConn.Close() |
|||
} |
|||
pxy.udpConn.Close() |
|||
|
|||
// all channels only closed here
|
|||
close(pxy.checkCloseCh) |
|||
close(pxy.readCh) |
|||
close(pxy.sendCh) |
|||
} |
|||
pxy.rc.UdpPortManager.Release(pxy.realPort) |
|||
} |
|||
|
|||
// HandleUserTcpConnection is used for incoming tcp user connections.
|
|||
// It can be used for tcp, http, https type.
|
|||
func HandleUserTcpConnection(pxy Proxy, userConn frpNet.Conn) { |
|||
defer userConn.Close() |
|||
|
|||
// try all connections from the pool
|
|||
workConn, err := pxy.GetWorkConnFromPool() |
|||
if err != nil { |
|||
return |
|||
} |
|||
defer workConn.Close() |
|||
|
|||
var local io.ReadWriteCloser = workConn |
|||
cfg := pxy.GetConf().GetBaseInfo() |
|||
if cfg.UseEncryption { |
|||
local, err = frpIo.WithEncryption(local, []byte(g.GlbServerCfg.Token)) |
|||
if err != nil { |
|||
pxy.Error("create encryption stream error: %v", err) |
|||
return |
|||
} |
|||
} |
|||
if cfg.UseCompression { |
|||
local = frpIo.WithCompression(local) |
|||
} |
|||
pxy.Debug("join connections, workConn(l[%s] r[%s]) userConn(l[%s] r[%s])", workConn.LocalAddr().String(), |
|||
workConn.RemoteAddr().String(), userConn.LocalAddr().String(), userConn.RemoteAddr().String()) |
|||
|
|||
StatsOpenConnection(pxy.GetName()) |
|||
inCount, outCount := frpIo.Join(local, userConn) |
|||
StatsCloseConnection(pxy.GetName()) |
|||
StatsAddTrafficIn(pxy.GetName(), inCount) |
|||
StatsAddTrafficOut(pxy.GetName(), outCount) |
|||
pxy.Debug("join connections closed") |
|||
} |
@ -0,0 +1,138 @@ |
|||
// Copyright 2019 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 proxy |
|||
|
|||
import ( |
|||
"io" |
|||
"strings" |
|||
|
|||
"github.com/fatedier/frp/g" |
|||
"github.com/fatedier/frp/models/config" |
|||
"github.com/fatedier/frp/server/stats" |
|||
frpNet "github.com/fatedier/frp/utils/net" |
|||
"github.com/fatedier/frp/utils/util" |
|||
"github.com/fatedier/frp/utils/vhost" |
|||
|
|||
frpIo "github.com/fatedier/golib/io" |
|||
) |
|||
|
|||
type HttpProxy struct { |
|||
BaseProxy |
|||
cfg *config.HttpProxyConf |
|||
|
|||
closeFuncs []func() |
|||
} |
|||
|
|||
func (pxy *HttpProxy) Run() (remoteAddr string, err error) { |
|||
routeConfig := vhost.VhostRouteConfig{ |
|||
RewriteHost: pxy.cfg.HostHeaderRewrite, |
|||
Headers: pxy.cfg.Headers, |
|||
Username: pxy.cfg.HttpUser, |
|||
Password: pxy.cfg.HttpPwd, |
|||
CreateConnFn: pxy.GetRealConn, |
|||
} |
|||
|
|||
locations := pxy.cfg.Locations |
|||
if len(locations) == 0 { |
|||
locations = []string{""} |
|||
} |
|||
|
|||
addrs := make([]string, 0) |
|||
for _, domain := range pxy.cfg.CustomDomains { |
|||
routeConfig.Domain = domain |
|||
for _, location := range locations { |
|||
routeConfig.Location = location |
|||
err = pxy.rc.HttpReverseProxy.Register(routeConfig) |
|||
if err != nil { |
|||
return |
|||
} |
|||
tmpDomain := routeConfig.Domain |
|||
tmpLocation := routeConfig.Location |
|||
addrs = append(addrs, util.CanonicalAddr(tmpDomain, int(g.GlbServerCfg.VhostHttpPort))) |
|||
pxy.closeFuncs = append(pxy.closeFuncs, func() { |
|||
pxy.rc.HttpReverseProxy.UnRegister(tmpDomain, tmpLocation) |
|||
}) |
|||
pxy.Info("http proxy listen for host [%s] location [%s]", routeConfig.Domain, routeConfig.Location) |
|||
} |
|||
} |
|||
|
|||
if pxy.cfg.SubDomain != "" { |
|||
routeConfig.Domain = pxy.cfg.SubDomain + "." + g.GlbServerCfg.SubDomainHost |
|||
for _, location := range locations { |
|||
routeConfig.Location = location |
|||
err = pxy.rc.HttpReverseProxy.Register(routeConfig) |
|||
if err != nil { |
|||
return |
|||
} |
|||
tmpDomain := routeConfig.Domain |
|||
tmpLocation := routeConfig.Location |
|||
addrs = append(addrs, util.CanonicalAddr(tmpDomain, g.GlbServerCfg.VhostHttpPort)) |
|||
pxy.closeFuncs = append(pxy.closeFuncs, func() { |
|||
pxy.rc.HttpReverseProxy.UnRegister(tmpDomain, tmpLocation) |
|||
}) |
|||
pxy.Info("http proxy listen for host [%s] location [%s]", routeConfig.Domain, routeConfig.Location) |
|||
} |
|||
} |
|||
remoteAddr = strings.Join(addrs, ",") |
|||
return |
|||
} |
|||
|
|||
func (pxy *HttpProxy) GetConf() config.ProxyConf { |
|||
return pxy.cfg |
|||
} |
|||
|
|||
func (pxy *HttpProxy) GetRealConn() (workConn frpNet.Conn, err error) { |
|||
tmpConn, errRet := pxy.GetWorkConnFromPool() |
|||
if errRet != nil { |
|||
err = errRet |
|||
return |
|||
} |
|||
|
|||
var rwc io.ReadWriteCloser = tmpConn |
|||
if pxy.cfg.UseEncryption { |
|||
rwc, err = frpIo.WithEncryption(rwc, []byte(g.GlbServerCfg.Token)) |
|||
if err != nil { |
|||
pxy.Error("create encryption stream error: %v", err) |
|||
return |
|||
} |
|||
} |
|||
if pxy.cfg.UseCompression { |
|||
rwc = frpIo.WithCompression(rwc) |
|||
} |
|||
workConn = frpNet.WrapReadWriteCloserToConn(rwc, tmpConn) |
|||
workConn = frpNet.WrapStatsConn(workConn, pxy.updateStatsAfterClosedConn) |
|||
pxy.statsCollector.Mark(stats.TypeOpenConnection, &stats.OpenConnectionPayload{ProxyName: pxy.GetName()}) |
|||
return |
|||
} |
|||
|
|||
func (pxy *HttpProxy) updateStatsAfterClosedConn(totalRead, totalWrite int64) { |
|||
name := pxy.GetName() |
|||
pxy.statsCollector.Mark(stats.TypeCloseProxy, &stats.CloseConnectionPayload{ProxyName: name}) |
|||
pxy.statsCollector.Mark(stats.TypeAddTrafficIn, &stats.AddTrafficInPayload{ |
|||
ProxyName: name, |
|||
TrafficBytes: totalWrite, |
|||
}) |
|||
pxy.statsCollector.Mark(stats.TypeAddTrafficOut, &stats.AddTrafficOutPayload{ |
|||
ProxyName: name, |
|||
TrafficBytes: totalRead, |
|||
}) |
|||
} |
|||
|
|||
func (pxy *HttpProxy) Close() { |
|||
pxy.BaseProxy.Close() |
|||
for _, closeFn := range pxy.closeFuncs { |
|||
closeFn() |
|||
} |
|||
} |
@ -0,0 +1,72 @@ |
|||
// Copyright 2019 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 proxy |
|||
|
|||
import ( |
|||
"strings" |
|||
|
|||
"github.com/fatedier/frp/g" |
|||
"github.com/fatedier/frp/models/config" |
|||
"github.com/fatedier/frp/utils/util" |
|||
"github.com/fatedier/frp/utils/vhost" |
|||
) |
|||
|
|||
type HttpsProxy struct { |
|||
BaseProxy |
|||
cfg *config.HttpsProxyConf |
|||
} |
|||
|
|||
func (pxy *HttpsProxy) Run() (remoteAddr string, err error) { |
|||
routeConfig := &vhost.VhostRouteConfig{} |
|||
|
|||
addrs := make([]string, 0) |
|||
for _, domain := range pxy.cfg.CustomDomains { |
|||
routeConfig.Domain = domain |
|||
l, errRet := pxy.rc.VhostHttpsMuxer.Listen(routeConfig) |
|||
if errRet != nil { |
|||
err = errRet |
|||
return |
|||
} |
|||
l.AddLogPrefix(pxy.name) |
|||
pxy.Info("https proxy listen for host [%s]", routeConfig.Domain) |
|||
pxy.listeners = append(pxy.listeners, l) |
|||
addrs = append(addrs, util.CanonicalAddr(routeConfig.Domain, g.GlbServerCfg.VhostHttpsPort)) |
|||
} |
|||
|
|||
if pxy.cfg.SubDomain != "" { |
|||
routeConfig.Domain = pxy.cfg.SubDomain + "." + g.GlbServerCfg.SubDomainHost |
|||
l, errRet := pxy.rc.VhostHttpsMuxer.Listen(routeConfig) |
|||
if errRet != nil { |
|||
err = errRet |
|||
return |
|||
} |
|||
l.AddLogPrefix(pxy.name) |
|||
pxy.Info("https proxy listen for host [%s]", routeConfig.Domain) |
|||
pxy.listeners = append(pxy.listeners, l) |
|||
addrs = append(addrs, util.CanonicalAddr(routeConfig.Domain, int(g.GlbServerCfg.VhostHttpsPort))) |
|||
} |
|||
|
|||
pxy.startListenHandler(pxy, HandleUserTcpConnection) |
|||
remoteAddr = strings.Join(addrs, ",") |
|||
return |
|||
} |
|||
|
|||
func (pxy *HttpsProxy) GetConf() config.ProxyConf { |
|||
return pxy.cfg |
|||
} |
|||
|
|||
func (pxy *HttpsProxy) Close() { |
|||
pxy.BaseProxy.Close() |
|||
} |
@ -0,0 +1,250 @@ |
|||
// Copyright 2017 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 proxy |
|||
|
|||
import ( |
|||
"fmt" |
|||
"io" |
|||
"sync" |
|||
|
|||
"github.com/fatedier/frp/g" |
|||
"github.com/fatedier/frp/models/config" |
|||
"github.com/fatedier/frp/models/msg" |
|||
"github.com/fatedier/frp/server/controller" |
|||
"github.com/fatedier/frp/server/stats" |
|||
"github.com/fatedier/frp/utils/log" |
|||
frpNet "github.com/fatedier/frp/utils/net" |
|||
|
|||
frpIo "github.com/fatedier/golib/io" |
|||
) |
|||
|
|||
type GetWorkConnFn func() (frpNet.Conn, error) |
|||
|
|||
type Proxy interface { |
|||
Run() (remoteAddr string, err error) |
|||
GetName() string |
|||
GetConf() config.ProxyConf |
|||
GetWorkConnFromPool() (workConn frpNet.Conn, err error) |
|||
GetUsedPortsNum() int |
|||
Close() |
|||
log.Logger |
|||
} |
|||
|
|||
type BaseProxy struct { |
|||
name string |
|||
rc *controller.ResourceController |
|||
statsCollector stats.Collector |
|||
listeners []frpNet.Listener |
|||
usedPortsNum int |
|||
poolCount int |
|||
getWorkConnFn GetWorkConnFn |
|||
|
|||
mu sync.RWMutex |
|||
log.Logger |
|||
} |
|||
|
|||
func (pxy *BaseProxy) GetName() string { |
|||
return pxy.name |
|||
} |
|||
|
|||
func (pxy *BaseProxy) GetUsedPortsNum() int { |
|||
return pxy.usedPortsNum |
|||
} |
|||
|
|||
func (pxy *BaseProxy) Close() { |
|||
pxy.Info("proxy closing") |
|||
for _, l := range pxy.listeners { |
|||
l.Close() |
|||
} |
|||
} |
|||
|
|||
func (pxy *BaseProxy) GetWorkConnFromPool() (workConn frpNet.Conn, err error) { |
|||
// try all connections from the pool
|
|||
for i := 0; i < pxy.poolCount+1; i++ { |
|||
if workConn, err = pxy.getWorkConnFn(); err != nil { |
|||
pxy.Warn("failed to get work connection: %v", err) |
|||
return |
|||
} |
|||
pxy.Info("get a new work connection: [%s]", workConn.RemoteAddr().String()) |
|||
workConn.AddLogPrefix(pxy.GetName()) |
|||
|
|||
err := msg.WriteMsg(workConn, &msg.StartWorkConn{ |
|||
ProxyName: pxy.GetName(), |
|||
}) |
|||
if err != nil { |
|||
workConn.Warn("failed to send message to work connection from pool: %v, times: %d", err, i) |
|||
workConn.Close() |
|||
} else { |
|||
break |
|||
} |
|||
} |
|||
|
|||
if err != nil { |
|||
pxy.Error("try to get work connection failed in the end") |
|||
return |
|||
} |
|||
return |
|||
} |
|||
|
|||
// startListenHandler start a goroutine handler for each listener.
|
|||
// p: p will just be passed to handler(Proxy, frpNet.Conn).
|
|||
// handler: each proxy type can set different handler function to deal with connections accepted from listeners.
|
|||
func (pxy *BaseProxy) startListenHandler(p Proxy, handler func(Proxy, frpNet.Conn, stats.Collector)) { |
|||
for _, listener := range pxy.listeners { |
|||
go func(l frpNet.Listener) { |
|||
for { |
|||
// block
|
|||
// if listener is closed, err returned
|
|||
c, err := l.Accept() |
|||
if err != nil { |
|||
pxy.Info("listener is closed") |
|||
return |
|||
} |
|||
pxy.Debug("get a user connection [%s]", c.RemoteAddr().String()) |
|||
go handler(p, c, pxy.statsCollector) |
|||
} |
|||
}(listener) |
|||
} |
|||
} |
|||
|
|||
func NewProxy(runId string, rc *controller.ResourceController, statsCollector stats.Collector, poolCount int, |
|||
getWorkConnFn GetWorkConnFn, pxyConf config.ProxyConf) (pxy Proxy, err error) { |
|||
|
|||
basePxy := BaseProxy{ |
|||
name: pxyConf.GetBaseInfo().ProxyName, |
|||
rc: rc, |
|||
statsCollector: statsCollector, |
|||
listeners: make([]frpNet.Listener, 0), |
|||
poolCount: poolCount, |
|||
getWorkConnFn: getWorkConnFn, |
|||
Logger: log.NewPrefixLogger(runId), |
|||
} |
|||
switch cfg := pxyConf.(type) { |
|||
case *config.TcpProxyConf: |
|||
basePxy.usedPortsNum = 1 |
|||
pxy = &TcpProxy{ |
|||
BaseProxy: basePxy, |
|||
cfg: cfg, |
|||
} |
|||
case *config.HttpProxyConf: |
|||
pxy = &HttpProxy{ |
|||
BaseProxy: basePxy, |
|||
cfg: cfg, |
|||
} |
|||
case *config.HttpsProxyConf: |
|||
pxy = &HttpsProxy{ |
|||
BaseProxy: basePxy, |
|||
cfg: cfg, |
|||
} |
|||
case *config.UdpProxyConf: |
|||
basePxy.usedPortsNum = 1 |
|||
pxy = &UdpProxy{ |
|||
BaseProxy: basePxy, |
|||
cfg: cfg, |
|||
} |
|||
case *config.StcpProxyConf: |
|||
pxy = &StcpProxy{ |
|||
BaseProxy: basePxy, |
|||
cfg: cfg, |
|||
} |
|||
case *config.XtcpProxyConf: |
|||
pxy = &XtcpProxy{ |
|||
BaseProxy: basePxy, |
|||
cfg: cfg, |
|||
} |
|||
default: |
|||
return pxy, fmt.Errorf("proxy type not support") |
|||
} |
|||
pxy.AddLogPrefix(pxy.GetName()) |
|||
return |
|||
} |
|||
|
|||
// HandleUserTcpConnection is used for incoming tcp user connections.
|
|||
// It can be used for tcp, http, https type.
|
|||
func HandleUserTcpConnection(pxy Proxy, userConn frpNet.Conn, statsCollector stats.Collector) { |
|||
defer userConn.Close() |
|||
|
|||
// try all connections from the pool
|
|||
workConn, err := pxy.GetWorkConnFromPool() |
|||
if err != nil { |
|||
return |
|||
} |
|||
defer workConn.Close() |
|||
|
|||
var local io.ReadWriteCloser = workConn |
|||
cfg := pxy.GetConf().GetBaseInfo() |
|||
if cfg.UseEncryption { |
|||
local, err = frpIo.WithEncryption(local, []byte(g.GlbServerCfg.Token)) |
|||
if err != nil { |
|||
pxy.Error("create encryption stream error: %v", err) |
|||
return |
|||
} |
|||
} |
|||
if cfg.UseCompression { |
|||
local = frpIo.WithCompression(local) |
|||
} |
|||
pxy.Debug("join connections, workConn(l[%s] r[%s]) userConn(l[%s] r[%s])", workConn.LocalAddr().String(), |
|||
workConn.RemoteAddr().String(), userConn.LocalAddr().String(), userConn.RemoteAddr().String()) |
|||
|
|||
statsCollector.Mark(stats.TypeOpenConnection, &stats.OpenConnectionPayload{ProxyName: pxy.GetName()}) |
|||
inCount, outCount := frpIo.Join(local, userConn) |
|||
statsCollector.Mark(stats.TypeCloseConnection, &stats.CloseConnectionPayload{ProxyName: pxy.GetName()}) |
|||
statsCollector.Mark(stats.TypeAddTrafficIn, &stats.AddTrafficInPayload{ |
|||
ProxyName: pxy.GetName(), |
|||
TrafficBytes: inCount, |
|||
}) |
|||
statsCollector.Mark(stats.TypeAddTrafficOut, &stats.AddTrafficOutPayload{ |
|||
ProxyName: pxy.GetName(), |
|||
TrafficBytes: outCount, |
|||
}) |
|||
pxy.Debug("join connections closed") |
|||
} |
|||
|
|||
type ProxyManager struct { |
|||
// proxies indexed by proxy name
|
|||
pxys map[string]Proxy |
|||
|
|||
mu sync.RWMutex |
|||
} |
|||
|
|||
func NewProxyManager() *ProxyManager { |
|||
return &ProxyManager{ |
|||
pxys: make(map[string]Proxy), |
|||
} |
|||
} |
|||
|
|||
func (pm *ProxyManager) Add(name string, pxy Proxy) error { |
|||
pm.mu.Lock() |
|||
defer pm.mu.Unlock() |
|||
if _, ok := pm.pxys[name]; ok { |
|||
return fmt.Errorf("proxy name [%s] is already in use", name) |
|||
} |
|||
|
|||
pm.pxys[name] = pxy |
|||
return nil |
|||
} |
|||
|
|||
func (pm *ProxyManager) Del(name string) { |
|||
pm.mu.Lock() |
|||
defer pm.mu.Unlock() |
|||
delete(pm.pxys, name) |
|||
} |
|||
|
|||
func (pm *ProxyManager) GetByName(name string) (pxy Proxy, ok bool) { |
|||
pm.mu.RLock() |
|||
defer pm.mu.RUnlock() |
|||
pxy, ok = pm.pxys[name] |
|||
return |
|||
} |
@ -0,0 +1,47 @@ |
|||
// Copyright 2019 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 proxy |
|||
|
|||
import ( |
|||
"github.com/fatedier/frp/models/config" |
|||
) |
|||
|
|||
type StcpProxy struct { |
|||
BaseProxy |
|||
cfg *config.StcpProxyConf |
|||
} |
|||
|
|||
func (pxy *StcpProxy) Run() (remoteAddr string, err error) { |
|||
listener, errRet := pxy.rc.VisitorManager.Listen(pxy.GetName(), pxy.cfg.Sk) |
|||
if errRet != nil { |
|||
err = errRet |
|||
return |
|||
} |
|||
listener.AddLogPrefix(pxy.name) |
|||
pxy.listeners = append(pxy.listeners, listener) |
|||
pxy.Info("stcp proxy custom listen success") |
|||
|
|||
pxy.startListenHandler(pxy, HandleUserTcpConnection) |
|||
return |
|||
} |
|||
|
|||
func (pxy *StcpProxy) GetConf() config.ProxyConf { |
|||
return pxy.cfg |
|||
} |
|||
|
|||
func (pxy *StcpProxy) Close() { |
|||
pxy.BaseProxy.Close() |
|||
pxy.rc.VisitorManager.CloseListener(pxy.GetName()) |
|||
} |
@ -0,0 +1,84 @@ |
|||
// Copyright 2019 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 proxy |
|||
|
|||
import ( |
|||
"fmt" |
|||
|
|||
"github.com/fatedier/frp/g" |
|||
"github.com/fatedier/frp/models/config" |
|||
frpNet "github.com/fatedier/frp/utils/net" |
|||
) |
|||
|
|||
type TcpProxy struct { |
|||
BaseProxy |
|||
cfg *config.TcpProxyConf |
|||
|
|||
realPort int |
|||
} |
|||
|
|||
func (pxy *TcpProxy) Run() (remoteAddr string, err error) { |
|||
if pxy.cfg.Group != "" { |
|||
l, realPort, errRet := pxy.rc.TcpGroupCtl.Listen(pxy.name, pxy.cfg.Group, pxy.cfg.GroupKey, g.GlbServerCfg.ProxyBindAddr, pxy.cfg.RemotePort) |
|||
if errRet != nil { |
|||
err = errRet |
|||
return |
|||
} |
|||
defer func() { |
|||
if err != nil { |
|||
l.Close() |
|||
} |
|||
}() |
|||
pxy.realPort = realPort |
|||
listener := frpNet.WrapLogListener(l) |
|||
listener.AddLogPrefix(pxy.name) |
|||
pxy.listeners = append(pxy.listeners, listener) |
|||
pxy.Info("tcp proxy listen port [%d] in group [%s]", pxy.cfg.RemotePort, pxy.cfg.Group) |
|||
} else { |
|||
pxy.realPort, err = pxy.rc.TcpPortManager.Acquire(pxy.name, pxy.cfg.RemotePort) |
|||
if err != nil { |
|||
return |
|||
} |
|||
defer func() { |
|||
if err != nil { |
|||
pxy.rc.TcpPortManager.Release(pxy.realPort) |
|||
} |
|||
}() |
|||
listener, errRet := frpNet.ListenTcp(g.GlbServerCfg.ProxyBindAddr, pxy.realPort) |
|||
if errRet != nil { |
|||
err = errRet |
|||
return |
|||
} |
|||
listener.AddLogPrefix(pxy.name) |
|||
pxy.listeners = append(pxy.listeners, listener) |
|||
pxy.Info("tcp proxy listen port [%d]", pxy.cfg.RemotePort) |
|||
} |
|||
|
|||
pxy.cfg.RemotePort = pxy.realPort |
|||
remoteAddr = fmt.Sprintf(":%d", pxy.realPort) |
|||
pxy.startListenHandler(pxy, HandleUserTcpConnection) |
|||
return |
|||
} |
|||
|
|||
func (pxy *TcpProxy) GetConf() config.ProxyConf { |
|||
return pxy.cfg |
|||
} |
|||
|
|||
func (pxy *TcpProxy) Close() { |
|||
pxy.BaseProxy.Close() |
|||
if pxy.cfg.Group == "" { |
|||
pxy.rc.TcpPortManager.Release(pxy.realPort) |
|||
} |
|||
} |
@ -0,0 +1,225 @@ |
|||
// Copyright 2019 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 proxy |
|||
|
|||
import ( |
|||
"context" |
|||
"fmt" |
|||
"net" |
|||
"time" |
|||
|
|||
"github.com/fatedier/frp/g" |
|||
"github.com/fatedier/frp/models/config" |
|||
"github.com/fatedier/frp/models/msg" |
|||
"github.com/fatedier/frp/models/proto/udp" |
|||
"github.com/fatedier/frp/server/stats" |
|||
|
|||
"github.com/fatedier/golib/errors" |
|||
) |
|||
|
|||
type UdpProxy struct { |
|||
BaseProxy |
|||
cfg *config.UdpProxyConf |
|||
|
|||
realPort int |
|||
|
|||
// udpConn is the listener of udp packages
|
|||
udpConn *net.UDPConn |
|||
|
|||
// there are always only one workConn at the same time
|
|||
// get another one if it closed
|
|||
workConn net.Conn |
|||
|
|||
// sendCh is used for sending packages to workConn
|
|||
sendCh chan *msg.UdpPacket |
|||
|
|||
// readCh is used for reading packages from workConn
|
|||
readCh chan *msg.UdpPacket |
|||
|
|||
// checkCloseCh is used for watching if workConn is closed
|
|||
checkCloseCh chan int |
|||
|
|||
isClosed bool |
|||
} |
|||
|
|||
func (pxy *UdpProxy) Run() (remoteAddr string, err error) { |
|||
pxy.realPort, err = pxy.rc.UdpPortManager.Acquire(pxy.name, pxy.cfg.RemotePort) |
|||
if err != nil { |
|||
return |
|||
} |
|||
defer func() { |
|||
if err != nil { |
|||
pxy.rc.UdpPortManager.Release(pxy.realPort) |
|||
} |
|||
}() |
|||
|
|||
remoteAddr = fmt.Sprintf(":%d", pxy.realPort) |
|||
pxy.cfg.RemotePort = pxy.realPort |
|||
addr, errRet := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", g.GlbServerCfg.ProxyBindAddr, pxy.realPort)) |
|||
if errRet != nil { |
|||
err = errRet |
|||
return |
|||
} |
|||
udpConn, errRet := net.ListenUDP("udp", addr) |
|||
if errRet != nil { |
|||
err = errRet |
|||
pxy.Warn("listen udp port error: %v", err) |
|||
return |
|||
} |
|||
pxy.Info("udp proxy listen port [%d]", pxy.cfg.RemotePort) |
|||
|
|||
pxy.udpConn = udpConn |
|||
pxy.sendCh = make(chan *msg.UdpPacket, 1024) |
|||
pxy.readCh = make(chan *msg.UdpPacket, 1024) |
|||
pxy.checkCloseCh = make(chan int) |
|||
|
|||
// read message from workConn, if it returns any error, notify proxy to start a new workConn
|
|||
workConnReaderFn := func(conn net.Conn) { |
|||
for { |
|||
var ( |
|||
rawMsg msg.Message |
|||
errRet error |
|||
) |
|||
pxy.Trace("loop waiting message from udp workConn") |
|||
// client will send heartbeat in workConn for keeping alive
|
|||
conn.SetReadDeadline(time.Now().Add(time.Duration(60) * time.Second)) |
|||
if rawMsg, errRet = msg.ReadMsg(conn); errRet != nil { |
|||
pxy.Warn("read from workConn for udp error: %v", errRet) |
|||
conn.Close() |
|||
// notify proxy to start a new work connection
|
|||
// ignore error here, it means the proxy is closed
|
|||
errors.PanicToError(func() { |
|||
pxy.checkCloseCh <- 1 |
|||
}) |
|||
return |
|||
} |
|||
conn.SetReadDeadline(time.Time{}) |
|||
switch m := rawMsg.(type) { |
|||
case *msg.Ping: |
|||
pxy.Trace("udp work conn get ping message") |
|||
continue |
|||
case *msg.UdpPacket: |
|||
if errRet := errors.PanicToError(func() { |
|||
pxy.Trace("get udp message from workConn: %s", m.Content) |
|||
pxy.readCh <- m |
|||
pxy.statsCollector.Mark(stats.TypeAddTrafficOut, &stats.AddTrafficOutPayload{ |
|||
ProxyName: pxy.GetName(), |
|||
TrafficBytes: int64(len(m.Content)), |
|||
}) |
|||
}); errRet != nil { |
|||
conn.Close() |
|||
pxy.Info("reader goroutine for udp work connection closed") |
|||
return |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
// send message to workConn
|
|||
workConnSenderFn := func(conn net.Conn, ctx context.Context) { |
|||
var errRet error |
|||
for { |
|||
select { |
|||
case udpMsg, ok := <-pxy.sendCh: |
|||
if !ok { |
|||
pxy.Info("sender goroutine for udp work connection closed") |
|||
return |
|||
} |
|||
if errRet = msg.WriteMsg(conn, udpMsg); errRet != nil { |
|||
pxy.Info("sender goroutine for udp work connection closed: %v", errRet) |
|||
conn.Close() |
|||
return |
|||
} else { |
|||
pxy.Trace("send message to udp workConn: %s", udpMsg.Content) |
|||
pxy.statsCollector.Mark(stats.TypeAddTrafficIn, &stats.AddTrafficInPayload{ |
|||
ProxyName: pxy.GetName(), |
|||
TrafficBytes: int64(len(udpMsg.Content)), |
|||
}) |
|||
continue |
|||
} |
|||
case <-ctx.Done(): |
|||
pxy.Info("sender goroutine for udp work connection closed") |
|||
return |
|||
} |
|||
} |
|||
} |
|||
|
|||
go func() { |
|||
// Sleep a while for waiting control send the NewProxyResp to client.
|
|||
time.Sleep(500 * time.Millisecond) |
|||
for { |
|||
workConn, err := pxy.GetWorkConnFromPool() |
|||
if err != nil { |
|||
time.Sleep(1 * time.Second) |
|||
// check if proxy is closed
|
|||
select { |
|||
case _, ok := <-pxy.checkCloseCh: |
|||
if !ok { |
|||
return |
|||
} |
|||
default: |
|||
} |
|||
continue |
|||
} |
|||
// close the old workConn and replac it with a new one
|
|||
if pxy.workConn != nil { |
|||
pxy.workConn.Close() |
|||
} |
|||
pxy.workConn = workConn |
|||
ctx, cancel := context.WithCancel(context.Background()) |
|||
go workConnReaderFn(workConn) |
|||
go workConnSenderFn(workConn, ctx) |
|||
_, ok := <-pxy.checkCloseCh |
|||
cancel() |
|||
if !ok { |
|||
return |
|||
} |
|||
} |
|||
}() |
|||
|
|||
// Read from user connections and send wrapped udp message to sendCh (forwarded by workConn).
|
|||
// Client will transfor udp message to local udp service and waiting for response for a while.
|
|||
// Response will be wrapped to be forwarded by work connection to server.
|
|||
// Close readCh and sendCh at the end.
|
|||
go func() { |
|||
udp.ForwardUserConn(udpConn, pxy.readCh, pxy.sendCh) |
|||
pxy.Close() |
|||
}() |
|||
return remoteAddr, nil |
|||
} |
|||
|
|||
func (pxy *UdpProxy) GetConf() config.ProxyConf { |
|||
return pxy.cfg |
|||
} |
|||
|
|||
func (pxy *UdpProxy) Close() { |
|||
pxy.mu.Lock() |
|||
defer pxy.mu.Unlock() |
|||
if !pxy.isClosed { |
|||
pxy.isClosed = true |
|||
|
|||
pxy.BaseProxy.Close() |
|||
if pxy.workConn != nil { |
|||
pxy.workConn.Close() |
|||
} |
|||
pxy.udpConn.Close() |
|||
|
|||
// all channels only closed here
|
|||
close(pxy.checkCloseCh) |
|||
close(pxy.readCh) |
|||
close(pxy.sendCh) |
|||
} |
|||
pxy.rc.UdpPortManager.Release(pxy.realPort) |
|||
} |
@ -0,0 +1,73 @@ |
|||
// Copyright 2019 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 proxy |
|||
|
|||
import ( |
|||
"fmt" |
|||
|
|||
"github.com/fatedier/frp/models/config" |
|||
"github.com/fatedier/frp/models/msg" |
|||
|
|||
"github.com/fatedier/golib/errors" |
|||
) |
|||
|
|||
type XtcpProxy struct { |
|||
BaseProxy |
|||
cfg *config.XtcpProxyConf |
|||
|
|||
closeCh chan struct{} |
|||
} |
|||
|
|||
func (pxy *XtcpProxy) Run() (remoteAddr string, err error) { |
|||
if pxy.rc.NatHoleController == nil { |
|||
pxy.Error("udp port for xtcp is not specified.") |
|||
err = fmt.Errorf("xtcp is not supported in frps") |
|||
return |
|||
} |
|||
sidCh := pxy.rc.NatHoleController.ListenClient(pxy.GetName(), pxy.cfg.Sk) |
|||
go func() { |
|||
for { |
|||
select { |
|||
case <-pxy.closeCh: |
|||
break |
|||
case sid := <-sidCh: |
|||
workConn, errRet := pxy.GetWorkConnFromPool() |
|||
if errRet != nil { |
|||
continue |
|||
} |
|||
m := &msg.NatHoleSid{ |
|||
Sid: sid, |
|||
} |
|||
errRet = msg.WriteMsg(workConn, m) |
|||
if errRet != nil { |
|||
pxy.Warn("write nat hole sid package error, %v", errRet) |
|||
} |
|||
} |
|||
} |
|||
}() |
|||
return |
|||
} |
|||
|
|||
func (pxy *XtcpProxy) GetConf() config.ProxyConf { |
|||
return pxy.cfg |
|||
} |
|||
|
|||
func (pxy *XtcpProxy) Close() { |
|||
pxy.BaseProxy.Close() |
|||
pxy.rc.NatHoleController.CloseClient(pxy.GetName()) |
|||
errors.PanicToError(func() { |
|||
close(pxy.closeCh) |
|||
}) |
|||
} |
@ -0,0 +1,273 @@ |
|||
// Copyright 2019 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 stats |
|||
|
|||
import ( |
|||
"sync" |
|||
"time" |
|||
|
|||
"github.com/fatedier/frp/utils/log" |
|||
"github.com/fatedier/frp/utils/metric" |
|||
) |
|||
|
|||
type internalCollector struct { |
|||
enable bool |
|||
info *ServerStatistics |
|||
mu sync.Mutex |
|||
} |
|||
|
|||
func NewInternalCollector(enable bool) Collector { |
|||
return &internalCollector{ |
|||
enable: enable, |
|||
info: &ServerStatistics{ |
|||
TotalTrafficIn: metric.NewDateCounter(ReserveDays), |
|||
TotalTrafficOut: metric.NewDateCounter(ReserveDays), |
|||
CurConns: metric.NewCounter(), |
|||
|
|||
ClientCounts: metric.NewCounter(), |
|||
ProxyTypeCounts: make(map[string]metric.Counter), |
|||
|
|||
ProxyStatistics: make(map[string]*ProxyStatistics), |
|||
}, |
|||
} |
|||
} |
|||
|
|||
func (collector *internalCollector) Run() error { |
|||
go func() { |
|||
for { |
|||
time.Sleep(12 * time.Hour) |
|||
log.Debug("start to clear useless proxy statistics data...") |
|||
collector.ClearUselessInfo() |
|||
log.Debug("finish to clear useless proxy statistics data") |
|||
} |
|||
}() |
|||
return nil |
|||
} |
|||
|
|||
func (collector *internalCollector) ClearUselessInfo() { |
|||
// To check if there are proxies that closed than 7 days and drop them.
|
|||
collector.mu.Lock() |
|||
defer collector.mu.Unlock() |
|||
for name, data := range collector.info.ProxyStatistics { |
|||
if !data.LastCloseTime.IsZero() && time.Since(data.LastCloseTime) > time.Duration(7*24)*time.Hour { |
|||
delete(collector.info.ProxyStatistics, name) |
|||
log.Trace("clear proxy [%s]'s statistics data, lastCloseTime: [%s]", name, data.LastCloseTime.String()) |
|||
} |
|||
} |
|||
} |
|||
|
|||
func (collector *internalCollector) Mark(statsType StatsType, payload interface{}) { |
|||
if !collector.enable { |
|||
return |
|||
} |
|||
|
|||
switch v := payload.(type) { |
|||
case *NewClientPayload: |
|||
collector.newClient(v) |
|||
case *CloseClientPayload: |
|||
collector.closeClient(v) |
|||
case *OpenConnectionPayload: |
|||
collector.openConnection(v) |
|||
case *CloseConnectionPayload: |
|||
collector.closeConnection(v) |
|||
case *AddTrafficInPayload: |
|||
collector.addTrafficIn(v) |
|||
case *AddTrafficOutPayload: |
|||
collector.addTrafficOut(v) |
|||
} |
|||
} |
|||
|
|||
func (collector *internalCollector) newClient(payload *NewClientPayload) { |
|||
collector.info.ClientCounts.Inc(1) |
|||
} |
|||
|
|||
func (collector *internalCollector) closeClient(payload *CloseClientPayload) { |
|||
collector.info.ClientCounts.Dec(1) |
|||
} |
|||
|
|||
func (collector *internalCollector) newProxy(payload *NewProxyPayload) { |
|||
collector.mu.Lock() |
|||
defer collector.mu.Unlock() |
|||
counter, ok := collector.info.ProxyTypeCounts[payload.ProxyType] |
|||
if !ok { |
|||
counter = metric.NewCounter() |
|||
} |
|||
counter.Inc(1) |
|||
collector.info.ProxyTypeCounts[payload.ProxyType] = counter |
|||
|
|||
proxyStats, ok := collector.info.ProxyStatistics[payload.Name] |
|||
if !(ok && proxyStats.ProxyType == payload.ProxyType) { |
|||
proxyStats = &ProxyStatistics{ |
|||
Name: payload.Name, |
|||
ProxyType: payload.ProxyType, |
|||
CurConns: metric.NewCounter(), |
|||
TrafficIn: metric.NewDateCounter(ReserveDays), |
|||
TrafficOut: metric.NewDateCounter(ReserveDays), |
|||
} |
|||
collector.info.ProxyStatistics[payload.Name] = proxyStats |
|||
} |
|||
proxyStats.LastStartTime = time.Now() |
|||
} |
|||
|
|||
func (collector *internalCollector) closeProxy(payload *CloseProxyPayload) { |
|||
collector.mu.Lock() |
|||
defer collector.mu.Unlock() |
|||
if counter, ok := collector.info.ProxyTypeCounts[payload.ProxyType]; ok { |
|||
counter.Dec(1) |
|||
} |
|||
if proxyStats, ok := collector.info.ProxyStatistics[payload.Name]; ok { |
|||
proxyStats.LastCloseTime = time.Now() |
|||
} |
|||
} |
|||
|
|||
func (collector *internalCollector) openConnection(payload *OpenConnectionPayload) { |
|||
collector.info.CurConns.Inc(1) |
|||
|
|||
collector.mu.Lock() |
|||
defer collector.mu.Unlock() |
|||
proxyStats, ok := collector.info.ProxyStatistics[payload.ProxyName] |
|||
if ok { |
|||
proxyStats.CurConns.Inc(1) |
|||
collector.info.ProxyStatistics[payload.ProxyName] = proxyStats |
|||
} |
|||
} |
|||
|
|||
func (collector *internalCollector) closeConnection(payload *CloseConnectionPayload) { |
|||
collector.info.CurConns.Dec(1) |
|||
|
|||
collector.mu.Lock() |
|||
defer collector.mu.Unlock() |
|||
proxyStats, ok := collector.info.ProxyStatistics[payload.ProxyName] |
|||
if ok { |
|||
proxyStats.CurConns.Dec(1) |
|||
collector.info.ProxyStatistics[payload.ProxyName] = proxyStats |
|||
} |
|||
} |
|||
|
|||
func (collector *internalCollector) addTrafficIn(payload *AddTrafficInPayload) { |
|||
collector.info.TotalTrafficIn.Inc(payload.TrafficBytes) |
|||
|
|||
collector.mu.Lock() |
|||
defer collector.mu.Unlock() |
|||
|
|||
proxyStats, ok := collector.info.ProxyStatistics[payload.ProxyName] |
|||
if ok { |
|||
proxyStats.TrafficIn.Inc(payload.TrafficBytes) |
|||
collector.info.ProxyStatistics[payload.ProxyName] = proxyStats |
|||
} |
|||
} |
|||
|
|||
func (collector *internalCollector) addTrafficOut(payload *AddTrafficOutPayload) { |
|||
collector.info.TotalTrafficOut.Inc(payload.TrafficBytes) |
|||
|
|||
collector.mu.Lock() |
|||
defer collector.mu.Unlock() |
|||
|
|||
proxyStats, ok := collector.info.ProxyStatistics[payload.ProxyName] |
|||
if ok { |
|||
proxyStats.TrafficOut.Inc(payload.TrafficBytes) |
|||
collector.info.ProxyStatistics[payload.ProxyName] = proxyStats |
|||
} |
|||
} |
|||
|
|||
func (collector *internalCollector) GetServer() *ServerStats { |
|||
collector.mu.Lock() |
|||
defer collector.mu.Unlock() |
|||
s := &ServerStats{ |
|||
TotalTrafficIn: collector.info.TotalTrafficIn.TodayCount(), |
|||
TotalTrafficOut: collector.info.TotalTrafficOut.TodayCount(), |
|||
CurConns: collector.info.CurConns.Count(), |
|||
ClientCounts: collector.info.ClientCounts.Count(), |
|||
ProxyTypeCounts: make(map[string]int64), |
|||
} |
|||
for k, v := range collector.info.ProxyTypeCounts { |
|||
s.ProxyTypeCounts[k] = v.Count() |
|||
} |
|||
return s |
|||
} |
|||
|
|||
func (collector *internalCollector) GetProxiesByType(proxyType string) []*ProxyStats { |
|||
res := make([]*ProxyStats, 0) |
|||
collector.mu.Lock() |
|||
defer collector.mu.Unlock() |
|||
|
|||
for name, proxyStats := range collector.info.ProxyStatistics { |
|||
if proxyStats.ProxyType != proxyType { |
|||
continue |
|||
} |
|||
|
|||
ps := &ProxyStats{ |
|||
Name: name, |
|||
Type: proxyStats.ProxyType, |
|||
TodayTrafficIn: proxyStats.TrafficIn.TodayCount(), |
|||
TodayTrafficOut: proxyStats.TrafficOut.TodayCount(), |
|||
CurConns: proxyStats.CurConns.Count(), |
|||
} |
|||
if !proxyStats.LastStartTime.IsZero() { |
|||
ps.LastStartTime = proxyStats.LastStartTime.Format("01-02 15:04:05") |
|||
} |
|||
if !proxyStats.LastCloseTime.IsZero() { |
|||
ps.LastCloseTime = proxyStats.LastCloseTime.Format("01-02 15:04:05") |
|||
} |
|||
res = append(res, ps) |
|||
} |
|||
return res |
|||
} |
|||
|
|||
func (collector *internalCollector) GetProxiesByTypeAndName(proxyType string, proxyName string) (res *ProxyStats) { |
|||
collector.mu.Lock() |
|||
defer collector.mu.Unlock() |
|||
|
|||
for name, proxyStats := range collector.info.ProxyStatistics { |
|||
if proxyStats.ProxyType != proxyType { |
|||
continue |
|||
} |
|||
|
|||
if name != proxyName { |
|||
continue |
|||
} |
|||
|
|||
res = &ProxyStats{ |
|||
Name: name, |
|||
Type: proxyStats.ProxyType, |
|||
TodayTrafficIn: proxyStats.TrafficIn.TodayCount(), |
|||
TodayTrafficOut: proxyStats.TrafficOut.TodayCount(), |
|||
CurConns: proxyStats.CurConns.Count(), |
|||
} |
|||
if !proxyStats.LastStartTime.IsZero() { |
|||
res.LastStartTime = proxyStats.LastStartTime.Format("01-02 15:04:05") |
|||
} |
|||
if !proxyStats.LastCloseTime.IsZero() { |
|||
res.LastCloseTime = proxyStats.LastCloseTime.Format("01-02 15:04:05") |
|||
} |
|||
break |
|||
} |
|||
return |
|||
} |
|||
|
|||
func (collector *internalCollector) GetProxyTraffic(name string) (res *ProxyTrafficInfo) { |
|||
collector.mu.Lock() |
|||
defer collector.mu.Unlock() |
|||
|
|||
proxyStats, ok := collector.info.ProxyStatistics[name] |
|||
if ok { |
|||
res = &ProxyTrafficInfo{ |
|||
Name: name, |
|||
} |
|||
res.TrafficIn = proxyStats.TrafficIn.GetLastDaysCount(ReserveDays) |
|||
res.TrafficOut = proxyStats.TrafficOut.GetLastDaysCount(ReserveDays) |
|||
} |
|||
return |
|||
} |
@ -0,0 +1,129 @@ |
|||
// Copyright 2017 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 stats |
|||
|
|||
import ( |
|||
"time" |
|||
|
|||
"github.com/fatedier/frp/utils/metric" |
|||
) |
|||
|
|||
const ( |
|||
ReserveDays = 7 |
|||
) |
|||
|
|||
type StatsType int |
|||
|
|||
const ( |
|||
TypeNewClient StatsType = iota |
|||
TypeCloseClient |
|||
TypeNewProxy |
|||
TypeCloseProxy |
|||
TypeOpenConnection |
|||
TypeCloseConnection |
|||
TypeAddTrafficIn |
|||
TypeAddTrafficOut |
|||
) |
|||
|
|||
type ServerStats struct { |
|||
TotalTrafficIn int64 |
|||
TotalTrafficOut int64 |
|||
CurConns int64 |
|||
ClientCounts int64 |
|||
ProxyTypeCounts map[string]int64 |
|||
} |
|||
|
|||
type ProxyStats struct { |
|||
Name string |
|||
Type string |
|||
TodayTrafficIn int64 |
|||
TodayTrafficOut int64 |
|||
LastStartTime string |
|||
LastCloseTime string |
|||
CurConns int64 |
|||
} |
|||
|
|||
type ProxyTrafficInfo struct { |
|||
Name string |
|||
TrafficIn []int64 |
|||
TrafficOut []int64 |
|||
} |
|||
|
|||
type ProxyStatistics struct { |
|||
Name string |
|||
ProxyType string |
|||
TrafficIn metric.DateCounter |
|||
TrafficOut metric.DateCounter |
|||
CurConns metric.Counter |
|||
LastStartTime time.Time |
|||
LastCloseTime time.Time |
|||
} |
|||
|
|||
type ServerStatistics struct { |
|||
TotalTrafficIn metric.DateCounter |
|||
TotalTrafficOut metric.DateCounter |
|||
CurConns metric.Counter |
|||
|
|||
// counter for clients
|
|||
ClientCounts metric.Counter |
|||
|
|||
// counter for proxy types
|
|||
ProxyTypeCounts map[string]metric.Counter |
|||
|
|||
// statistics for different proxies
|
|||
// key is proxy name
|
|||
ProxyStatistics map[string]*ProxyStatistics |
|||
} |
|||
|
|||
type Collector interface { |
|||
Mark(statsType StatsType, payload interface{}) |
|||
Run() error |
|||
GetServer() *ServerStats |
|||
GetProxiesByType(proxyType string) []*ProxyStats |
|||
GetProxiesByTypeAndName(proxyType string, proxyName string) *ProxyStats |
|||
GetProxyTraffic(name string) *ProxyTrafficInfo |
|||
} |
|||
|
|||
type NewClientPayload struct{} |
|||
|
|||
type CloseClientPayload struct{} |
|||
|
|||
type NewProxyPayload struct { |
|||
Name string |
|||
ProxyType string |
|||
} |
|||
|
|||
type CloseProxyPayload struct { |
|||
Name string |
|||
ProxyType string |
|||
} |
|||
|
|||
type OpenConnectionPayload struct { |
|||
ProxyName string |
|||
} |
|||
|
|||
type CloseConnectionPayload struct { |
|||
ProxyName string |
|||
} |
|||
|
|||
type AddTrafficInPayload struct { |
|||
ProxyName string |
|||
TrafficBytes int64 |
|||
} |
|||
|
|||
type AddTrafficOutPayload struct { |
|||
ProxyName string |
|||
TrafficBytes int64 |
|||
} |
Write
Preview
Loading…
Cancel
Save
Reference in new issue