add observer

This commit is contained in:
ginuerzh 2024-01-03 20:55:06 +08:00
parent e1ae379048
commit c959fc2f73
95 changed files with 2371 additions and 890 deletions

67
admission/plugin/grpc.go Normal file
View File

@ -0,0 +1,67 @@
package admission
import (
"context"
"io"
"github.com/go-gost/core/admission"
"github.com/go-gost/core/logger"
"github.com/go-gost/plugin/admission/proto"
"github.com/go-gost/x/internal/plugin"
"google.golang.org/grpc"
)
type grpcPlugin struct {
conn grpc.ClientConnInterface
client proto.AdmissionClient
log logger.Logger
}
// NewGRPCPlugin creates an Admission plugin based on gRPC.
func NewGRPCPlugin(name string, addr string, opts ...plugin.Option) admission.Admission {
var options plugin.Options
for _, opt := range opts {
opt(&options)
}
log := logger.Default().WithFields(map[string]any{
"kind": "admission",
"admission": name,
})
conn, err := plugin.NewGRPCConn(addr, &options)
if err != nil {
log.Error(err)
}
p := &grpcPlugin{
conn: conn,
log: log,
}
if conn != nil {
p.client = proto.NewAdmissionClient(conn)
}
return p
}
func (p *grpcPlugin) Admit(ctx context.Context, addr string, opts ...admission.Option) bool {
if p.client == nil {
return false
}
r, err := p.client.Admit(ctx,
&proto.AdmissionRequest{
Addr: addr,
})
if err != nil {
p.log.Error(err)
return false
}
return r.Ok
}
func (p *grpcPlugin) Close() error {
if closer, ok := p.conn.(io.Closer); ok {
return closer.Close()
}
return nil
}

View File

@ -4,71 +4,13 @@ import (
"bytes"
"context"
"encoding/json"
"io"
"net/http"
"github.com/go-gost/core/admission"
"github.com/go-gost/core/logger"
"github.com/go-gost/plugin/admission/proto"
"github.com/go-gost/x/internal/plugin"
"google.golang.org/grpc"
)
type grpcPlugin struct {
conn grpc.ClientConnInterface
client proto.AdmissionClient
log logger.Logger
}
// NewGRPCPlugin creates an Admission plugin based on gRPC.
func NewGRPCPlugin(name string, addr string, opts ...plugin.Option) admission.Admission {
var options plugin.Options
for _, opt := range opts {
opt(&options)
}
log := logger.Default().WithFields(map[string]any{
"kind": "admission",
"admission": name,
})
conn, err := plugin.NewGRPCConn(addr, &options)
if err != nil {
log.Error(err)
}
p := &grpcPlugin{
conn: conn,
log: log,
}
if conn != nil {
p.client = proto.NewAdmissionClient(conn)
}
return p
}
func (p *grpcPlugin) Admit(ctx context.Context, addr string, opts ...admission.Option) bool {
if p.client == nil {
return false
}
r, err := p.client.Admit(ctx,
&proto.AdmissionRequest{
Addr: addr,
})
if err != nil {
p.log.Error(err)
return false
}
return r.Ok
}
func (p *grpcPlugin) Close() error {
if closer, ok := p.conn.(io.Closer); ok {
return closer.Close()
}
return nil
}
type httpPluginRequest struct {
Addr string `json:"addr"`
}

View File

@ -8,8 +8,15 @@ import (
"github.com/gin-gonic/gin"
"github.com/go-gost/x/config"
"github.com/go-gost/x/registry"
"github.com/go-gost/x/service"
"github.com/go-gost/x/stats"
)
type serviceStatus interface {
Status() *service.Status
}
// swagger:parameters getConfigRequest
type getConfigRequest struct {
// output format, one of yaml|json, default is json.
@ -37,6 +44,40 @@ func getConfig(ctx *gin.Context) {
var req getConfigRequest
ctx.ShouldBindQuery(&req)
config.OnUpdate(func(c *config.Config) error {
for _, svc := range c.Services {
if svc == nil {
continue
}
s := registry.ServiceRegistry().Get(svc.Name)
ss, ok := s.(serviceStatus)
if ok && ss != nil {
status := ss.Status()
svc.Status = &config.ServiceStatus{
CreateTime: status.CreateTime().Unix(),
State: string(status.State()),
}
if st := status.Stats(); st != nil {
svc.Status.Stats = &config.ServiceStats{
TotalConns: st.Get(stats.KindTotalConns),
CurrentConns: st.Get(stats.KindCurrentConns),
TotalErrs: st.Get(stats.KindTotalErrs),
InputBytes: st.Get(stats.KindInputBytes),
OutputBytes: st.Get(stats.KindOutputBytes),
}
}
for _, ev := range status.Events() {
if !ev.Time.IsZero() {
svc.Status.Events = append(svc.Status.Events, config.ServiceEvent{
Time: ev.Time.Unix(),
Msg: ev.Message,
})
}
}
}
}
return nil
})
var resp getConfigResponse
resp.Config = config.Global()

View File

@ -110,7 +110,7 @@ func (p *authenticator) Authenticate(ctx context.Context, user, password string,
}
v, ok := p.kvs[user]
return "", ok && (v == "" || password == v)
return user, ok && (v == "" || password == v)
}
func (p *authenticator) periodReload(ctx context.Context) error {

72
auth/plugin/grpc.go Normal file
View File

@ -0,0 +1,72 @@
package auth
import (
"context"
"io"
"github.com/go-gost/core/auth"
"github.com/go-gost/core/logger"
"github.com/go-gost/plugin/auth/proto"
ctxvalue "github.com/go-gost/x/internal/ctx"
"github.com/go-gost/x/internal/plugin"
"google.golang.org/grpc"
)
type grpcPlugin struct {
conn grpc.ClientConnInterface
client proto.AuthenticatorClient
log logger.Logger
}
// NewGRPCPlugin creates an Authenticator plugin based on gRPC.
func NewGRPCPlugin(name string, addr string, opts ...plugin.Option) auth.Authenticator {
var options plugin.Options
for _, opt := range opts {
opt(&options)
}
log := logger.Default().WithFields(map[string]any{
"kind": "auther",
"auther": name,
})
conn, err := plugin.NewGRPCConn(addr, &options)
if err != nil {
log.Error(err)
}
p := &grpcPlugin{
conn: conn,
log: log,
}
if conn != nil {
p.client = proto.NewAuthenticatorClient(conn)
}
return p
}
// Authenticate checks the validity of the provided user-password pair.
func (p *grpcPlugin) Authenticate(ctx context.Context, user, password string, opts ...auth.Option) (string, bool) {
if p.client == nil {
return "", false
}
r, err := p.client.Authenticate(ctx,
&proto.AuthenticateRequest{
Username: user,
Password: password,
Client: string(ctxvalue.ClientAddrFromContext(ctx)),
})
if err != nil {
p.log.Error(err)
return "", false
}
return r.Id, r.Ok
}
func (p *grpcPlugin) Close() error {
if closer, ok := p.conn.(io.Closer); ok {
return closer.Close()
}
return nil
}

View File

@ -4,76 +4,14 @@ import (
"bytes"
"context"
"encoding/json"
"io"
"net/http"
"github.com/go-gost/core/auth"
"github.com/go-gost/core/logger"
"github.com/go-gost/plugin/auth/proto"
ctxvalue "github.com/go-gost/x/internal/ctx"
"github.com/go-gost/x/internal/plugin"
"google.golang.org/grpc"
)
type grpcPlugin struct {
conn grpc.ClientConnInterface
client proto.AuthenticatorClient
log logger.Logger
}
// NewGRPCPlugin creates an Authenticator plugin based on gRPC.
func NewGRPCPlugin(name string, addr string, opts ...plugin.Option) auth.Authenticator {
var options plugin.Options
for _, opt := range opts {
opt(&options)
}
log := logger.Default().WithFields(map[string]any{
"kind": "auther",
"auther": name,
})
conn, err := plugin.NewGRPCConn(addr, &options)
if err != nil {
log.Error(err)
}
p := &grpcPlugin{
conn: conn,
log: log,
}
if conn != nil {
p.client = proto.NewAuthenticatorClient(conn)
}
return p
}
// Authenticate checks the validity of the provided user-password pair.
func (p *grpcPlugin) Authenticate(ctx context.Context, user, password string, opts ...auth.Option) (string, bool) {
if p.client == nil {
return "", false
}
r, err := p.client.Authenticate(ctx,
&proto.AuthenticateRequest{
Username: user,
Password: password,
Client: string(ctxvalue.ClientAddrFromContext(ctx)),
})
if err != nil {
p.log.Error(err)
return "", false
}
return r.Id, r.Ok
}
func (p *grpcPlugin) Close() error {
if closer, ok := p.conn.(io.Closer); ok {
return closer.Close()
}
return nil
}
type httpPluginRequest struct {
Username string `json:"username"`
Password string `json:"password"`

77
bypass/plugin/grpc.go Normal file
View File

@ -0,0 +1,77 @@
package bypass
import (
"context"
"io"
"github.com/go-gost/core/bypass"
"github.com/go-gost/core/logger"
"github.com/go-gost/plugin/bypass/proto"
ctxvalue "github.com/go-gost/x/internal/ctx"
"github.com/go-gost/x/internal/plugin"
"google.golang.org/grpc"
)
type grpcPlugin struct {
conn grpc.ClientConnInterface
client proto.BypassClient
log logger.Logger
}
// NewGRPCPlugin creates a Bypass plugin based on gRPC.
func NewGRPCPlugin(name string, addr string, opts ...plugin.Option) bypass.Bypass {
var options plugin.Options
for _, opt := range opts {
opt(&options)
}
log := logger.Default().WithFields(map[string]any{
"kind": "bypass",
"bypass": name,
})
conn, err := plugin.NewGRPCConn(addr, &options)
if err != nil {
log.Error(err)
}
p := &grpcPlugin{
conn: conn,
log: log,
}
if conn != nil {
p.client = proto.NewBypassClient(conn)
}
return p
}
func (p *grpcPlugin) Contains(ctx context.Context, network, addr string, opts ...bypass.Option) bool {
if p.client == nil {
return true
}
var options bypass.Options
for _, opt := range opts {
opt(&options)
}
r, err := p.client.Bypass(ctx,
&proto.BypassRequest{
Network: network,
Addr: addr,
Client: string(ctxvalue.ClientIDFromContext(ctx)),
Host: options.Host,
Path: options.Path,
})
if err != nil {
p.log.Error(err)
return true
}
return r.Ok
}
func (p *grpcPlugin) Close() error {
if closer, ok := p.conn.(io.Closer); ok {
return closer.Close()
}
return nil
}

View File

@ -4,81 +4,14 @@ import (
"bytes"
"context"
"encoding/json"
"io"
"net/http"
"github.com/go-gost/core/bypass"
"github.com/go-gost/core/logger"
"github.com/go-gost/plugin/bypass/proto"
ctxvalue "github.com/go-gost/x/internal/ctx"
"github.com/go-gost/x/internal/plugin"
"google.golang.org/grpc"
)
type grpcPlugin struct {
conn grpc.ClientConnInterface
client proto.BypassClient
log logger.Logger
}
// NewGRPCPlugin creates a Bypass plugin based on gRPC.
func NewGRPCPlugin(name string, addr string, opts ...plugin.Option) bypass.Bypass {
var options plugin.Options
for _, opt := range opts {
opt(&options)
}
log := logger.Default().WithFields(map[string]any{
"kind": "bypass",
"bypass": name,
})
conn, err := plugin.NewGRPCConn(addr, &options)
if err != nil {
log.Error(err)
}
p := &grpcPlugin{
conn: conn,
log: log,
}
if conn != nil {
p.client = proto.NewBypassClient(conn)
}
return p
}
func (p *grpcPlugin) Contains(ctx context.Context, network, addr string, opts ...bypass.Option) bool {
if p.client == nil {
return true
}
var options bypass.Options
for _, opt := range opts {
opt(&options)
}
r, err := p.client.Bypass(ctx,
&proto.BypassRequest{
Network: network,
Addr: addr,
Client: string(ctxvalue.ClientIDFromContext(ctx)),
Host: options.Host,
Path: options.Path,
})
if err != nil {
p.log.Error(err)
return true
}
return r.Ok
}
func (p *grpcPlugin) Close() error {
if closer, ok := p.conn.(io.Closer); ok {
return closer.Close()
}
return nil
}
type httpPluginRequest struct {
Network string `json:"network"`
Addr string `json:"addr"`

View File

@ -312,6 +312,11 @@ type LimiterConfig struct {
Plugin *PluginConfig `yaml:",omitempty" json:"plugin,omitempty"`
}
type ObserverConfig struct {
Name string `json:"name"`
Plugin *PluginConfig `yaml:",omitempty" json:"plugin,omitempty"`
}
type ListenerConfig struct {
Type string `json:"type"`
Chain string `yaml:",omitempty" json:"chain,omitempty"`
@ -333,6 +338,7 @@ type HandlerConfig struct {
Auth *AuthConfig `yaml:",omitempty" json:"auth,omitempty"`
TLS *TLSConfig `yaml:",omitempty" json:"tls,omitempty"`
Limiter string `yaml:",omitempty" json:"limiter,omitempty"`
Observer string `yaml:",omitempty" json:"observer,omitempty"`
Metadata map[string]any `yaml:",omitempty" json:"metadata,omitempty"`
}
@ -403,11 +409,34 @@ type ServiceConfig struct {
RLimiter string `yaml:"rlimiter,omitempty" json:"rlimiter,omitempty"`
Logger string `yaml:",omitempty" json:"logger,omitempty"`
Loggers []string `yaml:",omitempty" json:"loggers,omitempty"`
Observer string `yaml:",omitempty" json:"observer,omitempty"`
Recorders []*RecorderObject `yaml:",omitempty" json:"recorders,omitempty"`
Handler *HandlerConfig `yaml:",omitempty" json:"handler,omitempty"`
Listener *ListenerConfig `yaml:",omitempty" json:"listener,omitempty"`
Forwarder *ForwarderConfig `yaml:",omitempty" json:"forwarder,omitempty"`
Metadata map[string]any `yaml:",omitempty" json:"metadata,omitempty"`
// service status, read-only
Status *ServiceStatus `yaml:",omitempty" json:"status,omitempty"`
}
type ServiceStatus struct {
CreateTime int64 `yaml:"createTime" json:"createTime"`
State string `yaml:"state" json:"state"`
Events []ServiceEvent `yaml:",omitempty" json:"events,omitempty"`
Stats *ServiceStats `yaml:",omitempty" json:"stats,omitempty"`
}
type ServiceEvent struct {
Time int64 `yaml:"time" json:"time"`
Msg string `yaml:"msg" json:"msg"`
}
type ServiceStats struct {
TotalConns uint64 `yaml:"totalConns" json:"totalConns"`
CurrentConns uint64 `yaml:"currentConns" json:"currentConns"`
TotalErrs uint64 `yaml:"totalErrs" json:"totalErrs"`
InputBytes uint64 `yaml:"inputBytes" json:"inputBytes"`
OutputBytes uint64 `yaml:"outputBytes" json:"outputBytes"`
}
type ChainConfig struct {
@ -475,6 +504,7 @@ type Config struct {
Limiters []*LimiterConfig `yaml:",omitempty" json:"limiters,omitempty"`
CLimiters []*LimiterConfig `yaml:"climiters,omitempty" json:"climiters,omitempty"`
RLimiters []*LimiterConfig `yaml:"rlimiters,omitempty" json:"rlimiters,omitempty"`
Observers []*ObserverConfig `yaml:",omitempty" json:"observers,omitempty"`
Loggers []*LoggerConfig `yaml:",omitempty" json:"loggers,omitempty"`
TLS *TLSConfig `yaml:",omitempty" json:"tls,omitempty"`
Log *LogConfig `yaml:",omitempty" json:"log,omitempty"`

View File

@ -7,6 +7,7 @@ import (
"github.com/go-gost/core/admission"
"github.com/go-gost/core/logger"
xadmission "github.com/go-gost/x/admission"
admission_plugin "github.com/go-gost/x/admission/plugin"
"github.com/go-gost/x/config"
"github.com/go-gost/x/internal/loader"
"github.com/go-gost/x/internal/plugin"
@ -28,13 +29,13 @@ func ParseAdmission(cfg *config.AdmissionConfig) admission.Admission {
}
switch strings.ToLower(cfg.Plugin.Type) {
case "http":
return xadmission.NewHTTPPlugin(
return admission_plugin.NewHTTPPlugin(
cfg.Name, cfg.Plugin.Addr,
plugin.TLSConfigOption(tlsCfg),
plugin.TimeoutOption(cfg.Plugin.Timeout),
)
default:
return xadmission.NewGRPCPlugin(
return admission_plugin.NewGRPCPlugin(
cfg.Name, cfg.Plugin.Addr,
plugin.TokenOption(cfg.Plugin.Token),
plugin.TLSConfigOption(tlsCfg),

View File

@ -7,6 +7,7 @@ import (
"github.com/go-gost/core/auth"
"github.com/go-gost/core/logger"
xauth "github.com/go-gost/x/auth"
auth_plugin "github.com/go-gost/x/auth/plugin"
"github.com/go-gost/x/config"
"github.com/go-gost/x/internal/loader"
"github.com/go-gost/x/internal/plugin"
@ -28,13 +29,13 @@ func ParseAuther(cfg *config.AutherConfig) auth.Authenticator {
}
switch cfg.Plugin.Type {
case "http":
return xauth.NewHTTPPlugin(
return auth_plugin.NewHTTPPlugin(
cfg.Name, cfg.Plugin.Addr,
plugin.TLSConfigOption(tlsCfg),
plugin.TimeoutOption(cfg.Plugin.Timeout),
)
default:
return xauth.NewGRPCPlugin(
return auth_plugin.NewGRPCPlugin(
cfg.Name, cfg.Plugin.Addr,
plugin.TokenOption(cfg.Plugin.Token),
plugin.TLSConfigOption(tlsCfg),

View File

@ -7,6 +7,7 @@ import (
"github.com/go-gost/core/bypass"
"github.com/go-gost/core/logger"
xbypass "github.com/go-gost/x/bypass"
bypass_plugin "github.com/go-gost/x/bypass/plugin"
"github.com/go-gost/x/config"
"github.com/go-gost/x/internal/loader"
"github.com/go-gost/x/internal/plugin"
@ -28,13 +29,13 @@ func ParseBypass(cfg *config.BypassConfig) bypass.Bypass {
}
switch strings.ToLower(cfg.Plugin.Type) {
case "http":
return xbypass.NewHTTPPlugin(
return bypass_plugin.NewHTTPPlugin(
cfg.Name, cfg.Plugin.Addr,
plugin.TLSConfigOption(tlsCfg),
plugin.TimeoutOption(cfg.Plugin.Timeout),
)
default:
return xbypass.NewGRPCPlugin(
return bypass_plugin.NewGRPCPlugin(
cfg.Name, cfg.Plugin.Addr,
plugin.TokenOption(cfg.Plugin.Token),
plugin.TLSConfigOption(tlsCfg),

View File

@ -13,6 +13,7 @@ import (
node_parser "github.com/go-gost/x/config/parsing/node"
selector_parser "github.com/go-gost/x/config/parsing/selector"
xhop "github.com/go-gost/x/hop"
hop_plugin "github.com/go-gost/x/hop/plugin"
"github.com/go-gost/x/internal/loader"
"github.com/go-gost/x/internal/plugin"
)
@ -32,13 +33,13 @@ func ParseHop(cfg *config.HopConfig, log logger.Logger) (hop.Hop, error) {
}
switch strings.ToLower(cfg.Plugin.Type) {
case "http":
return xhop.NewHTTPPlugin(
return hop_plugin.NewHTTPPlugin(
cfg.Name, cfg.Plugin.Addr,
plugin.TLSConfigOption(tlsCfg),
plugin.TimeoutOption(cfg.Plugin.Timeout),
), nil
default:
return xhop.NewGRPCPlugin(
return hop_plugin.NewGRPCPlugin(
cfg.Name, cfg.Plugin.Addr,
plugin.TokenOption(cfg.Plugin.Token),
plugin.TLSConfigOption(tlsCfg),

View File

@ -9,6 +9,7 @@ import (
"github.com/go-gost/core/logger"
"github.com/go-gost/x/config"
xhosts "github.com/go-gost/x/hosts"
hosts_plugin "github.com/go-gost/x/hosts/plugin"
"github.com/go-gost/x/internal/loader"
"github.com/go-gost/x/internal/plugin"
)
@ -28,13 +29,13 @@ func ParseHostMapper(cfg *config.HostsConfig) hosts.HostMapper {
}
switch strings.ToLower(cfg.Plugin.Type) {
case "http":
return xhosts.NewHTTPPlugin(
return hosts_plugin.NewHTTPPlugin(
cfg.Name, cfg.Plugin.Addr,
plugin.TLSConfigOption(tlsCfg),
plugin.TimeoutOption(cfg.Plugin.Timeout),
)
default:
return xhosts.NewGRPCPlugin(
return hosts_plugin.NewGRPCPlugin(
cfg.Name, cfg.Plugin.Addr,
plugin.TokenOption(cfg.Plugin.Token),
plugin.TLSConfigOption(tlsCfg),

View File

@ -8,6 +8,7 @@ import (
"github.com/go-gost/core/logger"
"github.com/go-gost/x/config"
xingress "github.com/go-gost/x/ingress"
ingress_plugin "github.com/go-gost/x/ingress/plugin"
"github.com/go-gost/x/internal/loader"
"github.com/go-gost/x/internal/plugin"
)
@ -27,13 +28,13 @@ func ParseIngress(cfg *config.IngressConfig) ingress.Ingress {
}
switch strings.ToLower(cfg.Plugin.Type) {
case "http":
return xingress.NewHTTPPlugin(
return ingress_plugin.NewHTTPPlugin(
cfg.Name, cfg.Plugin.Addr,
plugin.TLSConfigOption(tlsCfg),
plugin.TimeoutOption(cfg.Plugin.Timeout),
)
default:
return xingress.NewGRPCPlugin(
return ingress_plugin.NewGRPCPlugin(
cfg.Name, cfg.Plugin.Addr,
plugin.TokenOption(cfg.Plugin.Token),
plugin.TLSConfigOption(tlsCfg),

View File

@ -14,6 +14,7 @@ import (
xconn "github.com/go-gost/x/limiter/conn"
xrate "github.com/go-gost/x/limiter/rate"
xtraffic "github.com/go-gost/x/limiter/traffic"
traffic_plugin "github.com/go-gost/x/limiter/traffic/plugin"
)
func ParseTrafficLimiter(cfg *config.LimiterConfig) (lim traffic.TrafficLimiter) {
@ -31,13 +32,13 @@ func ParseTrafficLimiter(cfg *config.LimiterConfig) (lim traffic.TrafficLimiter)
}
switch strings.ToLower(cfg.Plugin.Type) {
case "http":
return xtraffic.NewHTTPPlugin(
return traffic_plugin.NewHTTPPlugin(
cfg.Name, cfg.Plugin.Addr,
plugin.TLSConfigOption(tlsCfg),
plugin.TimeoutOption(cfg.Plugin.Timeout),
)
default:
return xtraffic.NewGRPCPlugin(
return traffic_plugin.NewGRPCPlugin(
cfg.Name, cfg.Plugin.Addr,
plugin.TokenOption(cfg.Plugin.Token),
plugin.TLSConfigOption(tlsCfg),

View File

@ -0,0 +1,39 @@
package observer
import (
"crypto/tls"
"strings"
"github.com/go-gost/core/observer"
"github.com/go-gost/x/config"
"github.com/go-gost/x/internal/plugin"
observer_plugin "github.com/go-gost/x/observer/plugin"
)
func ParseObserver(cfg *config.ObserverConfig) observer.Observer {
if cfg == nil || cfg.Plugin == nil {
return nil
}
var tlsCfg *tls.Config
if cfg.Plugin.TLS != nil {
tlsCfg = &tls.Config{
ServerName: cfg.Plugin.TLS.ServerName,
InsecureSkipVerify: !cfg.Plugin.TLS.Secure,
}
}
switch strings.ToLower(cfg.Plugin.Type) {
case "http":
return observer_plugin.NewHTTPPlugin(
cfg.Name, cfg.Plugin.Addr,
plugin.TLSConfigOption(tlsCfg),
plugin.TimeoutOption(cfg.Plugin.Timeout),
)
default:
return observer_plugin.NewGRPCPlugin(
cfg.Name, cfg.Plugin.Addr,
plugin.TokenOption(cfg.Plugin.Token),
plugin.TLSConfigOption(tlsCfg),
)
}
}

View File

@ -10,6 +10,7 @@ const (
MDKeyPostUp = "postUp"
MDKeyPostDown = "postDown"
MDKeyIgnoreChain = "ignoreChain"
MDKeyEnableStats = "enableStats"
MDKeyRecorderDirection = "direction"
MDKeyRecorderTimestampFormat = "timeStampFormat"

View File

@ -8,6 +8,7 @@ import (
"github.com/go-gost/x/config"
"github.com/go-gost/x/internal/plugin"
xrecorder "github.com/go-gost/x/recorder"
recorder_plugin "github.com/go-gost/x/recorder/plugin"
)
func ParseRecorder(cfg *config.RecorderConfig) (r recorder.Recorder) {
@ -25,13 +26,13 @@ func ParseRecorder(cfg *config.RecorderConfig) (r recorder.Recorder) {
}
switch strings.ToLower(cfg.Plugin.Type) {
case "http":
return xrecorder.NewHTTPPlugin(
return recorder_plugin.NewHTTPPlugin(
cfg.Name, cfg.Plugin.Addr,
plugin.TLSConfigOption(tlsCfg),
plugin.TimeoutOption(cfg.Plugin.Timeout),
)
default:
return xrecorder.NewGRPCPlugin(
return recorder_plugin.NewGRPCPlugin(
cfg.Name, cfg.Plugin.Addr,
plugin.TokenOption(cfg.Plugin.Token),
plugin.TLSConfigOption(tlsCfg),

View File

@ -11,6 +11,7 @@ import (
"github.com/go-gost/x/internal/plugin"
"github.com/go-gost/x/registry"
xresolver "github.com/go-gost/x/resolver"
resolver_plugin "github.com/go-gost/x/resolver/plugin"
)
func ParseResolver(cfg *config.ResolverConfig) (resolver.Resolver, error) {
@ -28,13 +29,13 @@ func ParseResolver(cfg *config.ResolverConfig) (resolver.Resolver, error) {
}
switch strings.ToLower(cfg.Plugin.Type) {
case "http":
return xresolver.NewHTTPPlugin(
return resolver_plugin.NewHTTPPlugin(
cfg.Name, cfg.Plugin.Addr,
plugin.TLSConfigOption(tlsCfg),
plugin.TimeoutOption(cfg.Plugin.Timeout),
), nil
default:
return xresolver.NewGRPCPlugin(
return resolver_plugin.NewGRPCPlugin(
cfg.Name, cfg.Plugin.Addr,
plugin.TokenOption(cfg.Plugin.Token),
plugin.TLSConfigOption(tlsCfg),

View File

@ -11,6 +11,7 @@ import (
"github.com/go-gost/x/internal/loader"
"github.com/go-gost/x/internal/plugin"
xrouter "github.com/go-gost/x/router"
router_plugin "github.com/go-gost/x/router/plugin"
)
func ParseRouter(cfg *config.RouterConfig) router.Router {
@ -28,13 +29,13 @@ func ParseRouter(cfg *config.RouterConfig) router.Router {
}
switch strings.ToLower(cfg.Plugin.Type) {
case "http":
return xrouter.NewHTTPPlugin(
return router_plugin.NewHTTPPlugin(
cfg.Name, cfg.Plugin.Addr,
plugin.TLSConfigOption(tlsCfg),
plugin.TimeoutOption(cfg.Plugin.Timeout),
)
default:
return xrouter.NewGRPCPlugin(
return router_plugin.NewGRPCPlugin(
cfg.Name, cfg.Plugin.Addr,
plugin.TokenOption(cfg.Plugin.Token),
plugin.TLSConfigOption(tlsCfg),

View File

@ -7,7 +7,7 @@ import (
"github.com/go-gost/core/sd"
"github.com/go-gost/x/config"
"github.com/go-gost/x/internal/plugin"
xsd "github.com/go-gost/x/sd"
sd_plugin "github.com/go-gost/x/sd/plugin"
)
func ParseSD(cfg *config.SDConfig) sd.SD {
@ -24,13 +24,13 @@ func ParseSD(cfg *config.SDConfig) sd.SD {
}
switch strings.ToLower(cfg.Plugin.Type) {
case "http":
return xsd.NewHTTPPlugin(
return sd_plugin.NewHTTPPlugin(
cfg.Name, cfg.Plugin.Addr,
plugin.TLSConfigOption(tlsCfg),
plugin.TimeoutOption(cfg.Plugin.Timeout),
)
default:
return xsd.NewGRPCPlugin(
return sd_plugin.NewGRPCPlugin(
cfg.Name, cfg.Plugin.Addr,
plugin.TokenOption(cfg.Plugin.Token),
plugin.TLSConfigOption(tlsCfg),

View File

@ -29,6 +29,7 @@ import (
"github.com/go-gost/x/metadata"
"github.com/go-gost/x/registry"
xservice "github.com/go-gost/x/service"
"github.com/go-gost/x/stats"
)
func ParseService(cfg *config.ServiceConfig) (service.Service, error) {
@ -96,6 +97,7 @@ func ParseService(cfg *config.ServiceConfig) (service.Service, error) {
ifce := cfg.Interface
var preUp, preDown, postUp, postDown []string
var ignoreChain bool
var pStats *stats.Stats
if cfg.Metadata != nil {
md := metadata.NewMetadata(cfg.Metadata)
ppv = mdutil.GetInt(md, parsing.MDKeyProxyProtocol)
@ -112,6 +114,10 @@ func ParseService(cfg *config.ServiceConfig) (service.Service, error) {
postUp = mdutil.GetStrings(md, parsing.MDKeyPostUp)
postDown = mdutil.GetStrings(md, parsing.MDKeyPostDown)
ignoreChain = mdutil.GetBool(md, parsing.MDKeyIgnoreChain)
if mdutil.GetBool(md, parsing.MDKeyEnableStats) {
pStats = &stats.Stats{}
}
}
listenOpts := []listener.Option{
@ -125,6 +131,7 @@ func ParseService(cfg *config.ServiceConfig) (service.Service, error) {
listener.LoggerOption(listenerLogger),
listener.ServiceOption(cfg.Name),
listener.ProxyProtocolOption(ppv),
listener.StatsOption(pStats),
}
if !ignoreChain {
listenOpts = append(listenOpts,
@ -218,6 +225,7 @@ func ParseService(cfg *config.ServiceConfig) (service.Service, error) {
handler.TLSConfigOption(tlsConfig),
handler.RateLimiterOption(registry.RateLimiterRegistry().Get(cfg.RLimiter)),
handler.TrafficLimiterOption(registry.TrafficLimiterRegistry().Get(cfg.Handler.Limiter)),
handler.ObserverOption(registry.ObserverRegistry().Get(cfg.Handler.Observer)),
handler.LoggerOption(handlerLogger),
handler.ServiceOption(cfg.Name),
)
@ -249,6 +257,8 @@ func ParseService(cfg *config.ServiceConfig) (service.Service, error) {
xservice.PostUpOption(postUp),
xservice.PostDownOption(postDown),
xservice.RecordersOption(recorders...),
xservice.StatsOption(pStats),
xservice.ObserverOption(registry.ObserverRegistry().Get(cfg.Observer)),
xservice.LoggerOption(serviceLogger),
)

4
go.mod
View File

@ -7,10 +7,10 @@ require (
github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d
github.com/gin-contrib/cors v1.5.0
github.com/gin-gonic/gin v1.9.1
github.com/go-gost/core v0.0.0-20231219132306-6b5c04b5e446
github.com/go-gost/core v0.0.0-20240103125300-5a427b4eaf99
github.com/go-gost/gosocks4 v0.0.1
github.com/go-gost/gosocks5 v0.4.0
github.com/go-gost/plugin v0.0.0-20231119084331-d49a1cb23b3b
github.com/go-gost/plugin v0.0.0-20240103125338-9c84e29cb81a
github.com/go-gost/relay v0.4.1-0.20230916134211-828f314ddfe7
github.com/go-gost/tls-dissector v0.0.2-0.20220408131628-aac992c27451
github.com/go-redis/redis/v8 v8.11.5

8
go.sum
View File

@ -49,14 +49,14 @@ github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg=
github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU=
github.com/go-gost/core v0.0.0-20231219132306-6b5c04b5e446 h1:H7VyfQOOAH7smDQ41O/mMClFv8MyflVk5AO9uIp7qXg=
github.com/go-gost/core v0.0.0-20231219132306-6b5c04b5e446/go.mod h1:ndkgWVYRLwupVaFFWv8ML1Nr8tD3xhHK245PLpUDg4E=
github.com/go-gost/core v0.0.0-20240103125300-5a427b4eaf99 h1:/0hmilnQBEDlOaRcO+TlwaHH8a5ig6nc2aAsU4FGZcw=
github.com/go-gost/core v0.0.0-20240103125300-5a427b4eaf99/go.mod h1:ndkgWVYRLwupVaFFWv8ML1Nr8tD3xhHK245PLpUDg4E=
github.com/go-gost/gosocks4 v0.0.1 h1:+k1sec8HlELuQV7rWftIkmy8UijzUt2I6t+iMPlGB2s=
github.com/go-gost/gosocks4 v0.0.1/go.mod h1:3B6L47HbU/qugDg4JnoFPHgJXE43Inz8Bah1QaN9qCc=
github.com/go-gost/gosocks5 v0.4.0 h1:EIrOEkpJez4gwHrMa33frA+hHXJyevjp47thpMQsJzI=
github.com/go-gost/gosocks5 v0.4.0/go.mod h1:1G6I7HP7VFVxveGkoK8mnprnJqSqJjdcASKsdUn4Pp4=
github.com/go-gost/plugin v0.0.0-20231119084331-d49a1cb23b3b h1:ZmnYutflq+KOZK+Px5RDckorDSxTYlkT4aQbjTC8/C4=
github.com/go-gost/plugin v0.0.0-20231119084331-d49a1cb23b3b/go.mod h1:qXr2Zm9Ex2ATqnWuNUzVZqySPMnuIihvblYZt4MlZLw=
github.com/go-gost/plugin v0.0.0-20240103125338-9c84e29cb81a h1:ME7P1Brcg4C640DSPqlvQr7JuvvQfJ8QpmS3yCFlK3A=
github.com/go-gost/plugin v0.0.0-20240103125338-9c84e29cb81a/go.mod h1:qXr2Zm9Ex2ATqnWuNUzVZqySPMnuIihvblYZt4MlZLw=
github.com/go-gost/relay v0.4.1-0.20230916134211-828f314ddfe7 h1:qAG1OyjvdA5h221CfFSS3J359V3d2E7dJWyP29QoDSI=
github.com/go-gost/relay v0.4.1-0.20230916134211-828f314ddfe7/go.mod h1:lcX+23LCQ3khIeASBo+tJ/WbwXFO32/N5YN6ucuYTG8=
github.com/go-gost/tls-dissector v0.0.2-0.20220408131628-aac992c27451 h1:xj8gUZGYO3nb5+6Bjw9+tsFkA9sYynrOvDvvC4uDV2I=

View File

@ -19,13 +19,16 @@ import (
"github.com/asaskevich/govalidator"
"github.com/go-gost/core/chain"
"github.com/go-gost/core/handler"
"github.com/go-gost/core/limiter/traffic"
traffic "github.com/go-gost/core/limiter/traffic"
"github.com/go-gost/core/logger"
md "github.com/go-gost/core/metadata"
ctxvalue "github.com/go-gost/x/internal/ctx"
netpkg "github.com/go-gost/x/internal/net"
"github.com/go-gost/x/limiter/traffic/wrapper"
stats_util "github.com/go-gost/x/internal/util/stats"
traffic_wrapper "github.com/go-gost/x/limiter/traffic/wrapper"
"github.com/go-gost/x/registry"
"github.com/go-gost/x/stats"
stats_wrapper "github.com/go-gost/x/stats/wrapper"
)
func init() {
@ -36,6 +39,8 @@ type httpHandler struct {
router *chain.Router
md metadata
options handler.Options
stats *stats_util.HandlerStats
cancel context.CancelFunc
}
func NewHandler(opts ...handler.Option) handler.Handler {
@ -46,6 +51,7 @@ func NewHandler(opts ...handler.Option) handler.Handler {
return &httpHandler{
options: options,
stats: stats_util.NewHandlerStats(options.Service),
}
}
@ -59,6 +65,13 @@ func (h *httpHandler) Init(md md.Metadata) error {
h.router = chain.NewRouter(chain.LoggerRouterOption(h.options.Logger))
}
ctx, cancel := context.WithCancel(context.Background())
h.cancel = cancel
if h.options.Observer != nil {
go h.observeStats(ctx)
}
return nil
}
@ -93,6 +106,13 @@ func (h *httpHandler) Handle(ctx context.Context, conn net.Conn, opts ...handler
return h.handleRequest(ctx, conn, req, log)
}
func (h *httpHandler) Close() error {
if h.cancel != nil {
h.cancel()
}
return nil
}
func (h *httpHandler) handleRequest(ctx context.Context, conn net.Conn, req *http.Request, log logger.Logger) error {
if !req.URL.IsAbs() && govalidator.IsDNSName(req.Host) {
req.URL.Scheme = "http"
@ -221,12 +241,19 @@ func (h *httpHandler) handleRequest(ctx context.Context, conn net.Conn, req *htt
}
}
rw := wrapper.WrapReadWriter(h.options.Limiter, conn, conn.RemoteAddr().String(),
rw := traffic_wrapper.WrapReadWriter(h.options.Limiter, conn,
traffic.NetworkOption(network),
traffic.AddrOption(addr),
traffic.ClientOption(clientID),
traffic.SrcOption(conn.RemoteAddr().String()),
)
if h.options.Observer != nil {
pstats := h.stats.Stats(clientID)
pstats.Add(stats.KindTotalConns, 1)
pstats.Add(stats.KindCurrentConns, 1)
defer pstats.Add(stats.KindCurrentConns, -1)
rw = stats_wrapper.WrapReadWriter(rw, pstats)
}
start := time.Now()
log.Infof("%s <-> %s", conn.RemoteAddr(), addr)
@ -378,3 +405,21 @@ func (h *httpHandler) checkRateLimit(addr net.Addr) bool {
return true
}
func (h *httpHandler) observeStats(ctx context.Context) {
if h.options.Observer == nil {
return
}
ticker := time.NewTicker(5 * time.Second)
defer ticker.Stop()
for {
select {
case <-ticker.C:
h.options.Observer.Observe(ctx, h.stats.Events())
case <-ctx.Done():
return
}
}
}

View File

@ -26,8 +26,11 @@ import (
ctxvalue "github.com/go-gost/x/internal/ctx"
xio "github.com/go-gost/x/internal/io"
netpkg "github.com/go-gost/x/internal/net"
stats_util "github.com/go-gost/x/internal/util/stats"
"github.com/go-gost/x/limiter/traffic/wrapper"
"github.com/go-gost/x/registry"
"github.com/go-gost/x/stats"
stats_wrapper "github.com/go-gost/x/stats/wrapper"
)
func init() {
@ -38,6 +41,8 @@ type http2Handler struct {
router *chain.Router
md metadata
options handler.Options
stats *stats_util.HandlerStats
cancel context.CancelFunc
}
func NewHandler(opts ...handler.Option) handler.Handler {
@ -48,6 +53,7 @@ func NewHandler(opts ...handler.Option) handler.Handler {
return &http2Handler{
options: options,
stats: stats_util.NewHandlerStats(options.Service),
}
}
@ -61,6 +67,12 @@ func (h *http2Handler) Init(md md.Metadata) error {
h.router = chain.NewRouter(chain.LoggerRouterOption(h.options.Logger))
}
ctx, cancel := context.WithCancel(context.Background())
h.cancel = cancel
if h.options.Observer != nil {
go h.observeStats(ctx)
}
return nil
}
@ -98,6 +110,13 @@ func (h *http2Handler) Handle(ctx context.Context, conn net.Conn, opts ...handle
)
}
func (h *http2Handler) Close() error {
if h.cancel != nil {
h.cancel()
}
return nil
}
// NOTE: there is an issue (golang/go#43989) will cause the client hangs
// when server returns an non-200 status code,
// May be fixed in go1.18.
@ -204,12 +223,20 @@ func (h *http2Handler) roundTrip(ctx context.Context, w http.ResponseWriter, req
return nil
}
rw := wrapper.WrapReadWriter(h.options.Limiter, xio.NewReadWriter(req.Body, flushWriter{w}), req.RemoteAddr,
rw := wrapper.WrapReadWriter(h.options.Limiter, xio.NewReadWriter(req.Body, flushWriter{w}),
traffic.NetworkOption("tcp"),
traffic.AddrOption(addr),
traffic.ClientOption(clientID),
traffic.SrcOption(req.RemoteAddr),
)
if h.options.Observer != nil {
pstats := h.stats.Stats(clientID)
pstats.Add(stats.KindTotalConns, 1)
pstats.Add(stats.KindCurrentConns, 1)
defer pstats.Add(stats.KindCurrentConns, -1)
rw = stats_wrapper.WrapReadWriter(rw, pstats)
}
start := time.Now()
log.Infof("%s <-> %s", req.RemoteAddr, addr)
netpkg.Transport(rw, cc)
@ -386,3 +413,21 @@ func (h *http2Handler) checkRateLimit(addr net.Addr) bool {
return true
}
func (h *http2Handler) observeStats(ctx context.Context) {
if h.options.Observer == nil {
return
}
ticker := time.NewTicker(5 * time.Second)
defer ticker.Stop()
for {
select {
case <-ticker.C:
h.options.Observer.Observe(ctx, h.stats.Events())
case <-ctx.Done():
return
}
}
}

View File

@ -15,6 +15,8 @@ import (
xnet "github.com/go-gost/x/internal/net"
serial "github.com/go-gost/x/internal/util/serial"
"github.com/go-gost/x/limiter/traffic/wrapper"
"github.com/go-gost/x/stats"
stats_wrapper "github.com/go-gost/x/stats/wrapper"
)
func (h *relayHandler) handleConnect(ctx context.Context, conn net.Conn, network, address string, log logger.Logger) (err error) {
@ -105,12 +107,20 @@ func (h *relayHandler) handleConnect(ctx context.Context, conn net.Conn, network
}
}
rw := wrapper.WrapReadWriter(h.options.Limiter, conn, conn.RemoteAddr().String(),
clientID := ctxvalue.ClientIDFromContext(ctx)
rw := wrapper.WrapReadWriter(h.options.Limiter, conn,
traffic.NetworkOption(network),
traffic.AddrOption(address),
traffic.ClientOption(string(ctxvalue.ClientIDFromContext(ctx))),
traffic.ClientOption(string(clientID)),
traffic.SrcOption(conn.RemoteAddr().String()),
)
if h.options.Observer != nil {
pstats := h.stats.Stats(string(clientID))
pstats.Add(stats.KindTotalConns, 1)
pstats.Add(stats.KindCurrentConns, 1)
defer pstats.Add(stats.KindCurrentConns, -1)
rw = stats_wrapper.WrapReadWriter(rw, pstats)
}
t := time.Now()
log.Infof("%s <-> %s", conn.RemoteAddr(), address)

View File

@ -13,6 +13,8 @@ import (
ctxvalue "github.com/go-gost/x/internal/ctx"
netpkg "github.com/go-gost/x/internal/net"
"github.com/go-gost/x/limiter/traffic/wrapper"
"github.com/go-gost/x/stats"
stats_wrapper "github.com/go-gost/x/stats/wrapper"
)
func (h *relayHandler) handleForward(ctx context.Context, conn net.Conn, network string, log logger.Logger) error {
@ -87,12 +89,20 @@ func (h *relayHandler) handleForward(ctx context.Context, conn net.Conn, network
conn = rc
}
rw := wrapper.WrapReadWriter(h.options.Limiter, conn, conn.RemoteAddr().String(),
clientID := ctxvalue.ClientIDFromContext(ctx)
rw := wrapper.WrapReadWriter(h.options.Limiter, conn,
traffic.NetworkOption(network),
traffic.AddrOption(target.Addr),
traffic.ClientOption(string(ctxvalue.ClientIDFromContext(ctx))),
traffic.ClientOption(string(clientID)),
traffic.SrcOption(conn.RemoteAddr().String()),
)
if h.options.Observer != nil {
pstats := h.stats.Stats(string(clientID))
pstats.Add(stats.KindTotalConns, 1)
pstats.Add(stats.KindCurrentConns, 1)
defer pstats.Add(stats.KindCurrentConns, -1)
rw = stats_wrapper.WrapReadWriter(rw, pstats)
}
t := time.Now()
log.Debugf("%s <-> %s", conn.RemoteAddr(), target.Addr)

View File

@ -11,10 +11,10 @@ import (
"github.com/go-gost/core/handler"
"github.com/go-gost/core/hop"
md "github.com/go-gost/core/metadata"
"github.com/go-gost/core/service"
"github.com/go-gost/relay"
ctxvalue "github.com/go-gost/x/internal/ctx"
"github.com/go-gost/x/registry"
stats_util "github.com/go-gost/x/internal/util/stats"
)
var (
@ -33,7 +33,8 @@ type relayHandler struct {
router *chain.Router
md metadata
options handler.Options
ep service.Service
stats *stats_util.HandlerStats
cancel context.CancelFunc
}
func NewHandler(opts ...handler.Option) handler.Handler {
@ -44,6 +45,7 @@ func NewHandler(opts ...handler.Option) handler.Handler {
return &relayHandler{
options: options,
stats: stats_util.NewHandlerStats(options.Service),
}
}
@ -57,6 +59,13 @@ func (h *relayHandler) Init(md md.Metadata) (err error) {
h.router = chain.NewRouter(chain.LoggerRouterOption(h.options.Logger))
}
ctx, cancel := context.WithCancel(context.Background())
h.cancel = cancel
if h.options.Observer != nil {
go h.observeStats(ctx)
}
return nil
}
@ -172,8 +181,8 @@ func (h *relayHandler) Handle(ctx context.Context, conn net.Conn, opts ...handle
// Close implements io.Closer interface.
func (h *relayHandler) Close() error {
if h.ep != nil {
return h.ep.Close()
if h.cancel != nil {
h.cancel()
}
return nil
}
@ -189,3 +198,21 @@ func (h *relayHandler) checkRateLimit(addr net.Addr) bool {
return true
}
func (h *relayHandler) observeStats(ctx context.Context) {
if h.options.Observer == nil {
return
}
ticker := time.NewTicker(5 * time.Second)
defer ticker.Stop()
for {
select {
case <-ticker.C:
h.options.Observer.Observe(ctx, h.stats.Events())
case <-ctx.Done():
return
}
}
}

View File

@ -16,6 +16,9 @@ import (
netpkg "github.com/go-gost/x/internal/net"
"github.com/go-gost/x/limiter/traffic/wrapper"
"github.com/go-gost/x/registry"
stats_util "github.com/go-gost/x/internal/util/stats"
"github.com/go-gost/x/stats"
stats_wrapper "github.com/go-gost/x/stats/wrapper"
)
var (
@ -32,6 +35,8 @@ type socks4Handler struct {
router *chain.Router
md metadata
options handler.Options
stats *stats_util.HandlerStats
cancel context.CancelFunc
}
func NewHandler(opts ...handler.Option) handler.Handler {
@ -42,6 +47,7 @@ func NewHandler(opts ...handler.Option) handler.Handler {
return &socks4Handler{
options: options,
stats: stats_util.NewHandlerStats(options.Service),
}
}
@ -55,6 +61,13 @@ func (h *socks4Handler) Init(md md.Metadata) (err error) {
h.router = chain.NewRouter(chain.LoggerRouterOption(h.options.Logger))
}
ctx, cancel := context.WithCancel(context.Background())
h.cancel = cancel
if h.options.Observer != nil {
go h.observeStats(ctx)
}
return nil
}
@ -114,6 +127,13 @@ func (h *socks4Handler) Handle(ctx context.Context, conn net.Conn, opts ...handl
}
}
func (h *socks4Handler) Close() error {
if h.cancel != nil {
h.cancel()
}
return nil
}
func (h *socks4Handler) handleConnect(ctx context.Context, conn net.Conn, req *gosocks4.Request, log logger.Logger) error {
addr := req.Addr.String()
@ -151,12 +171,20 @@ func (h *socks4Handler) handleConnect(ctx context.Context, conn net.Conn, req *g
return err
}
rw := wrapper.WrapReadWriter(h.options.Limiter, conn, conn.RemoteAddr().String(),
clientID := ctxvalue.ClientIDFromContext(ctx)
rw := wrapper.WrapReadWriter(h.options.Limiter, conn,
traffic.NetworkOption("tcp"),
traffic.AddrOption(addr),
traffic.ClientOption(string(ctxvalue.ClientIDFromContext(ctx))),
traffic.ClientOption(string(clientID)),
traffic.SrcOption(conn.RemoteAddr().String()),
)
if h.options.Observer != nil {
pstats := h.stats.Stats(string(clientID))
pstats.Add(stats.KindTotalConns, 1)
pstats.Add(stats.KindCurrentConns, 1)
defer pstats.Add(stats.KindCurrentConns, -1)
rw = stats_wrapper.WrapReadWriter(rw, pstats)
}
t := time.Now()
log.Infof("%s <-> %s", conn.RemoteAddr(), addr)
@ -184,3 +212,21 @@ func (h *socks4Handler) checkRateLimit(addr net.Addr) bool {
return true
}
func (h *socks4Handler) observeStats(ctx context.Context) {
if h.options.Observer == nil {
return
}
ticker := time.NewTicker(5 * time.Second)
defer ticker.Stop()
for {
select {
case <-ticker.C:
h.options.Observer.Observe(ctx, h.stats.Events())
case <-ctx.Done():
return
}
}
}

View File

@ -12,6 +12,8 @@ import (
ctxvalue "github.com/go-gost/x/internal/ctx"
netpkg "github.com/go-gost/x/internal/net"
"github.com/go-gost/x/limiter/traffic/wrapper"
"github.com/go-gost/x/stats"
stats_wrapper "github.com/go-gost/x/stats/wrapper"
)
func (h *socks5Handler) handleConnect(ctx context.Context, conn net.Conn, network, address string, log logger.Logger) error {
@ -50,12 +52,20 @@ func (h *socks5Handler) handleConnect(ctx context.Context, conn net.Conn, networ
return err
}
rw := wrapper.WrapReadWriter(h.options.Limiter, conn, conn.RemoteAddr().String(),
clientID := ctxvalue.ClientIDFromContext(ctx)
rw := wrapper.WrapReadWriter(h.options.Limiter, conn,
traffic.NetworkOption(network),
traffic.AddrOption(address),
traffic.ClientOption(string(ctxvalue.ClientIDFromContext(ctx))),
traffic.ClientOption(string(clientID)),
traffic.SrcOption(conn.RemoteAddr().String()),
)
if h.options.Observer != nil {
pstats := h.stats.Stats(string(clientID))
pstats.Add(stats.KindTotalConns, 1)
pstats.Add(stats.KindCurrentConns, 1)
defer pstats.Add(stats.KindCurrentConns, -1)
rw = stats_wrapper.WrapReadWriter(rw, pstats)
}
t := time.Now()
log.Infof("%s <-> %s", conn.RemoteAddr(), address)

View File

@ -12,6 +12,7 @@ import (
"github.com/go-gost/gosocks5"
ctxvalue "github.com/go-gost/x/internal/ctx"
"github.com/go-gost/x/internal/util/socks"
stats_util "github.com/go-gost/x/internal/util/stats"
"github.com/go-gost/x/registry"
)
@ -29,6 +30,8 @@ type socks5Handler struct {
router *chain.Router
md metadata
options handler.Options
stats *stats_util.HandlerStats
cancel context.CancelFunc
}
func NewHandler(opts ...handler.Option) handler.Handler {
@ -39,6 +42,7 @@ func NewHandler(opts ...handler.Option) handler.Handler {
return &socks5Handler{
options: options,
stats: stats_util.NewHandlerStats(options.Service),
}
}
@ -59,6 +63,13 @@ func (h *socks5Handler) Init(md md.Metadata) (err error) {
noTLS: h.md.noTLS,
}
ctx, cancel := context.WithCancel(context.Background())
h.cancel = cancel
if h.options.Observer != nil {
go h.observeStats(ctx)
}
return
}
@ -125,6 +136,13 @@ func (h *socks5Handler) Handle(ctx context.Context, conn net.Conn, opts ...handl
}
}
func (h *socks5Handler) Close() error {
if h.cancel != nil {
h.cancel()
}
return nil
}
func (h *socks5Handler) checkRateLimit(addr net.Addr) bool {
if h.options.RateLimiter == nil {
return true
@ -136,3 +154,21 @@ func (h *socks5Handler) checkRateLimit(addr net.Addr) bool {
return true
}
func (h *socks5Handler) observeStats(ctx context.Context) {
if h.options.Observer == nil {
return
}
ticker := time.NewTicker(5 * time.Second)
defer ticker.Stop()
for {
select {
case <-ticker.C:
h.options.Observer.Observe(ctx, h.stats.Events())
case <-ctx.Done():
return
}
}
}

View File

@ -10,8 +10,11 @@ import (
"github.com/go-gost/core/logger"
"github.com/go-gost/gosocks5"
ctxvalue "github.com/go-gost/x/internal/ctx"
"github.com/go-gost/x/internal/net/udp"
"github.com/go-gost/x/internal/util/socks"
"github.com/go-gost/x/stats"
stats_wrapper "github.com/go-gost/x/stats/wrapper"
)
func (h *socks5Handler) handleUDP(ctx context.Context, conn net.Conn, log logger.Logger) error {
@ -67,7 +70,17 @@ func (h *socks5Handler) handleUDP(ctx context.Context, conn net.Conn, log logger
return err
}
r := udp.NewRelay(socks.UDPConn(cc, h.md.udpBufferSize), pc).
var lc net.PacketConn = cc
clientID := ctxvalue.ClientIDFromContext(ctx)
if h.options.Observer != nil {
pstats := h.stats.Stats(string(clientID))
pstats.Add(stats.KindTotalConns, 1)
pstats.Add(stats.KindCurrentConns, 1)
defer pstats.Add(stats.KindCurrentConns, -1)
lc = stats_wrapper.WrapPacketConn(lc, pstats)
}
r := udp.NewRelay(socks.UDPConn(lc, h.md.udpBufferSize), pc).
WithBypass(h.options.Bypass).
WithLogger(log)
r.SetBufferSize(h.md.udpBufferSize)

View File

@ -7,8 +7,11 @@ import (
"github.com/go-gost/core/logger"
"github.com/go-gost/gosocks5"
ctxvalue "github.com/go-gost/x/internal/ctx"
"github.com/go-gost/x/internal/net/udp"
"github.com/go-gost/x/internal/util/socks"
"github.com/go-gost/x/stats"
stats_wrapper "github.com/go-gost/x/stats/wrapper"
)
func (h *socks5Handler) handleUDPTun(ctx context.Context, conn net.Conn, network, address string, log logger.Logger) error {
@ -56,6 +59,15 @@ func (h *socks5Handler) handleUDPTun(ctx context.Context, conn net.Conn, network
}
log.Debugf("bind on %s OK", pc.LocalAddr())
clientID := ctxvalue.ClientIDFromContext(ctx)
if h.options.Observer != nil {
pstats := h.stats.Stats(string(clientID))
pstats.Add(stats.KindTotalConns, 1)
pstats.Add(stats.KindCurrentConns, 1)
defer pstats.Add(stats.KindCurrentConns, -1)
conn = stats_wrapper.WrapConn(conn, pstats)
}
r := udp.NewRelay(socks.UDPTunServerConn(conn), pc).
WithBypass(h.options.Bypass).
WithLogger(log)

View File

@ -100,7 +100,7 @@ func (h *tunnelHandler) handleConnect(ctx context.Context, req *relay.Request, c
req.WriteTo(cc)
}
rw := wrapper.WrapReadWriter(h.options.Limiter, conn, tunnelID.String(),
rw := wrapper.WrapReadWriter(h.options.Limiter, conn,
traffic.NetworkOption(network),
traffic.AddrOption(dstAddr),
traffic.ClientOption(string(ctxvalue.ClientIDFromContext(ctx))),

101
hop/plugin/grpc.go Normal file
View File

@ -0,0 +1,101 @@
package hop
import (
"bytes"
"context"
"encoding/json"
"io"
"github.com/go-gost/core/chain"
"github.com/go-gost/core/hop"
"github.com/go-gost/core/logger"
"github.com/go-gost/plugin/hop/proto"
"github.com/go-gost/x/config"
node_parser "github.com/go-gost/x/config/parsing/node"
ctxvalue "github.com/go-gost/x/internal/ctx"
"github.com/go-gost/x/internal/plugin"
"google.golang.org/grpc"
)
type grpcPlugin struct {
name string
conn grpc.ClientConnInterface
client proto.HopClient
log logger.Logger
}
// NewGRPCPlugin creates a Hop plugin based on gRPC.
func NewGRPCPlugin(name string, addr string, opts ...plugin.Option) hop.Hop {
var options plugin.Options
for _, opt := range opts {
opt(&options)
}
log := logger.Default().WithFields(map[string]any{
"kind": "hop",
"hop": name,
})
conn, err := plugin.NewGRPCConn(addr, &options)
if err != nil {
log.Error(err)
}
p := &grpcPlugin{
name: name,
conn: conn,
log: log,
}
if conn != nil {
p.client = proto.NewHopClient(conn)
}
return p
}
func (p *grpcPlugin) Select(ctx context.Context, opts ...hop.SelectOption) *chain.Node {
if p.client == nil {
return nil
}
var options hop.SelectOptions
for _, opt := range opts {
opt(&options)
}
r, err := p.client.Select(ctx,
&proto.SelectRequest{
Network: options.Network,
Addr: options.Addr,
Host: options.Host,
Path: options.Path,
Client: string(ctxvalue.ClientIDFromContext(ctx)),
Src: string(ctxvalue.ClientAddrFromContext(ctx)),
})
if err != nil {
p.log.Error(err)
return nil
}
if r.Node == nil {
return nil
}
var cfg config.NodeConfig
if err := json.NewDecoder(bytes.NewReader(r.Node)).Decode(&cfg); err != nil {
p.log.Error(err)
return nil
}
node, err := node_parser.ParseNode(p.name, &cfg, logger.Default())
if err != nil {
p.log.Error(err)
return nil
}
return node
}
func (p *grpcPlugin) Close() error {
if closer, ok := p.conn.(io.Closer); ok {
return closer.Close()
}
return nil
}

View File

@ -4,103 +4,17 @@ import (
"bytes"
"context"
"encoding/json"
"io"
"net/http"
"github.com/go-gost/core/chain"
"github.com/go-gost/core/hop"
"github.com/go-gost/core/logger"
"github.com/go-gost/plugin/hop/proto"
"github.com/go-gost/x/config"
node_parser "github.com/go-gost/x/config/parsing/node"
ctxvalue "github.com/go-gost/x/internal/ctx"
"github.com/go-gost/x/internal/plugin"
"google.golang.org/grpc"
)
type grpcPlugin struct {
name string
conn grpc.ClientConnInterface
client proto.HopClient
log logger.Logger
}
// NewGRPCPlugin creates a Hop plugin based on gRPC.
func NewGRPCPlugin(name string, addr string, opts ...plugin.Option) hop.Hop {
var options plugin.Options
for _, opt := range opts {
opt(&options)
}
log := logger.Default().WithFields(map[string]any{
"kind": "hop",
"hop": name,
})
conn, err := plugin.NewGRPCConn(addr, &options)
if err != nil {
log.Error(err)
}
p := &grpcPlugin{
name: name,
conn: conn,
log: log,
}
if conn != nil {
p.client = proto.NewHopClient(conn)
}
return p
}
func (p *grpcPlugin) Select(ctx context.Context, opts ...hop.SelectOption) *chain.Node {
if p.client == nil {
return nil
}
var options hop.SelectOptions
for _, opt := range opts {
opt(&options)
}
r, err := p.client.Select(ctx,
&proto.SelectRequest{
Network: options.Network,
Addr: options.Addr,
Host: options.Host,
Path: options.Path,
Client: string(ctxvalue.ClientIDFromContext(ctx)),
Src: string(ctxvalue.ClientAddrFromContext(ctx)),
})
if err != nil {
p.log.Error(err)
return nil
}
if r.Node == nil {
return nil
}
var cfg config.NodeConfig
if err := json.NewDecoder(bytes.NewReader(r.Node)).Decode(&cfg); err != nil {
p.log.Error(err)
return nil
}
node, err := node_parser.ParseNode(p.name, &cfg, logger.Default())
if err != nil {
p.log.Error(err)
return nil
}
return node
}
func (p *grpcPlugin) Close() error {
if closer, ok := p.conn.(io.Closer); ok {
return closer.Close()
}
return nil
}
type httpPluginRequest struct {
Network string `json:"network"`
Addr string `json:"addr"`

78
hosts/plugin/grpc.go Normal file
View File

@ -0,0 +1,78 @@
package hosts
import (
"context"
"io"
"net"
"github.com/go-gost/core/hosts"
"github.com/go-gost/core/logger"
"github.com/go-gost/plugin/hosts/proto"
ctxvalue "github.com/go-gost/x/internal/ctx"
"github.com/go-gost/x/internal/plugin"
"google.golang.org/grpc"
)
type grpcPlugin struct {
conn grpc.ClientConnInterface
client proto.HostMapperClient
log logger.Logger
}
// NewGRPCPlugin creates a HostMapper plugin based on gRPC.
func NewGRPCPlugin(name string, addr string, opts ...plugin.Option) hosts.HostMapper {
var options plugin.Options
for _, opt := range opts {
opt(&options)
}
log := logger.Default().WithFields(map[string]any{
"kind": "hosts",
"hosts": name,
})
conn, err := plugin.NewGRPCConn(addr, &options)
if err != nil {
log.Error(err)
}
p := &grpcPlugin{
conn: conn,
log: log,
}
if conn != nil {
p.client = proto.NewHostMapperClient(conn)
}
return p
}
func (p *grpcPlugin) Lookup(ctx context.Context, network, host string, opts ...hosts.Option) (ips []net.IP, ok bool) {
p.log.Debugf("lookup %s/%s", host, network)
if p.client == nil {
return
}
r, err := p.client.Lookup(ctx,
&proto.LookupRequest{
Network: network,
Host: host,
Client: string(ctxvalue.ClientIDFromContext(ctx)),
})
if err != nil {
p.log.Error(err)
return
}
for _, s := range r.Ips {
if ip := net.ParseIP(s); ip != nil {
ips = append(ips, ip)
}
}
ok = r.Ok
return
}
func (p *grpcPlugin) Close() error {
if closer, ok := p.conn.(io.Closer); ok {
return closer.Close()
}
return nil
}

View File

@ -4,82 +4,15 @@ import (
"bytes"
"context"
"encoding/json"
"io"
"net"
"net/http"
"github.com/go-gost/core/hosts"
"github.com/go-gost/core/logger"
"github.com/go-gost/plugin/hosts/proto"
ctxvalue "github.com/go-gost/x/internal/ctx"
"github.com/go-gost/x/internal/plugin"
"google.golang.org/grpc"
)
type grpcPlugin struct {
conn grpc.ClientConnInterface
client proto.HostMapperClient
log logger.Logger
}
// NewGRPCPlugin creates a HostMapper plugin based on gRPC.
func NewGRPCPlugin(name string, addr string, opts ...plugin.Option) hosts.HostMapper {
var options plugin.Options
for _, opt := range opts {
opt(&options)
}
log := logger.Default().WithFields(map[string]any{
"kind": "hosts",
"hosts": name,
})
conn, err := plugin.NewGRPCConn(addr, &options)
if err != nil {
log.Error(err)
}
p := &grpcPlugin{
conn: conn,
log: log,
}
if conn != nil {
p.client = proto.NewHostMapperClient(conn)
}
return p
}
func (p *grpcPlugin) Lookup(ctx context.Context, network, host string, opts ...hosts.Option) (ips []net.IP, ok bool) {
p.log.Debugf("lookup %s/%s", host, network)
if p.client == nil {
return
}
r, err := p.client.Lookup(ctx,
&proto.LookupRequest{
Network: network,
Host: host,
Client: string(ctxvalue.ClientIDFromContext(ctx)),
})
if err != nil {
p.log.Error(err)
return
}
for _, s := range r.Ips {
if ip := net.ParseIP(s); ip != nil {
ips = append(ips, ip)
}
}
ok = r.Ok
return
}
func (p *grpcPlugin) Close() error {
if closer, ok := p.conn.(io.Closer); ok {
return closer.Close()
}
return nil
}
type httpPluginRequest struct {
Network string `json:"network"`
Host string `json:"host"`

89
ingress/plugin/grpc.go Normal file
View File

@ -0,0 +1,89 @@
package ingress
import (
"context"
"io"
"github.com/go-gost/core/ingress"
"github.com/go-gost/core/logger"
"github.com/go-gost/plugin/ingress/proto"
"github.com/go-gost/x/internal/plugin"
"google.golang.org/grpc"
)
type grpcPlugin struct {
conn grpc.ClientConnInterface
client proto.IngressClient
log logger.Logger
}
// NewGRPCPlugin creates an Ingress plugin based on gRPC.
func NewGRPCPlugin(name string, addr string, opts ...plugin.Option) ingress.Ingress {
var options plugin.Options
for _, opt := range opts {
opt(&options)
}
log := logger.Default().WithFields(map[string]any{
"kind": "ingress",
"ingress": name,
})
conn, err := plugin.NewGRPCConn(addr, &options)
if err != nil {
log.Error(err)
}
p := &grpcPlugin{
conn: conn,
log: log,
}
if conn != nil {
p.client = proto.NewIngressClient(conn)
}
return p
}
func (p *grpcPlugin) GetRule(ctx context.Context, host string, opts ...ingress.Option) *ingress.Rule {
if p.client == nil {
return nil
}
r, err := p.client.GetRule(ctx,
&proto.GetRuleRequest{
Host: host,
})
if err != nil {
p.log.Error(err)
return nil
}
if r.Endpoint == "" {
return nil
}
return &ingress.Rule{
Hostname: host,
Endpoint: r.Endpoint,
}
}
func (p *grpcPlugin) SetRule(ctx context.Context, rule *ingress.Rule, opts ...ingress.Option) bool {
if p.client == nil || rule == nil {
return false
}
r, _ := p.client.SetRule(ctx, &proto.SetRuleRequest{
Host: rule.Hostname,
Endpoint: rule.Endpoint,
})
if r == nil {
return false
}
return r.Ok
}
func (p *grpcPlugin) Close() error {
if closer, ok := p.conn.(io.Closer); ok {
return closer.Close()
}
return nil
}

View File

@ -4,93 +4,13 @@ import (
"bytes"
"context"
"encoding/json"
"io"
"net/http"
"github.com/go-gost/core/ingress"
"github.com/go-gost/core/logger"
"github.com/go-gost/plugin/ingress/proto"
"github.com/go-gost/x/internal/plugin"
"google.golang.org/grpc"
)
type grpcPlugin struct {
conn grpc.ClientConnInterface
client proto.IngressClient
log logger.Logger
}
// NewGRPCPlugin creates an Ingress plugin based on gRPC.
func NewGRPCPlugin(name string, addr string, opts ...plugin.Option) ingress.Ingress {
var options plugin.Options
for _, opt := range opts {
opt(&options)
}
log := logger.Default().WithFields(map[string]any{
"kind": "ingress",
"ingress": name,
})
conn, err := plugin.NewGRPCConn(addr, &options)
if err != nil {
log.Error(err)
}
p := &grpcPlugin{
conn: conn,
log: log,
}
if conn != nil {
p.client = proto.NewIngressClient(conn)
}
return p
}
func (p *grpcPlugin) GetRule(ctx context.Context, host string, opts ...ingress.Option) *ingress.Rule {
if p.client == nil {
return nil
}
r, err := p.client.GetRule(ctx,
&proto.GetRuleRequest{
Host: host,
})
if err != nil {
p.log.Error(err)
return nil
}
if r.Endpoint == "" {
return nil
}
return &ingress.Rule{
Hostname: host,
Endpoint: r.Endpoint,
}
}
func (p *grpcPlugin) SetRule(ctx context.Context, rule *ingress.Rule, opts ...ingress.Option) bool {
if p.client == nil || rule == nil {
return false
}
r, _ := p.client.SetRule(ctx, &proto.SetRuleRequest{
Host: rule.Hostname,
Endpoint: rule.Endpoint,
})
if r == nil {
return false
}
return r.Ok
}
func (p *grpcPlugin) Close() error {
if closer, ok := p.conn.(io.Closer); ok {
return closer.Close()
}
return nil
}
type httpPluginGetRuleRequest struct {
Host string `json:"host"`
}

View File

@ -0,0 +1,62 @@
package stats
import (
"sync"
"github.com/go-gost/core/observer"
"github.com/go-gost/x/stats"
)
type HandlerStats struct {
service string
stats map[string]*stats.Stats
mu sync.RWMutex
}
func NewHandlerStats(service string) *HandlerStats {
return &HandlerStats{
service: service,
stats: make(map[string]*stats.Stats),
}
}
func (p *HandlerStats) Stats(client string) *stats.Stats {
p.mu.RLock()
pstats := p.stats[client]
p.mu.RUnlock()
if pstats != nil {
return pstats
}
p.mu.Lock()
defer p.mu.Unlock()
pstats = p.stats[client]
if pstats == nil {
pstats = &stats.Stats{}
}
p.stats[client] = pstats
return pstats
}
func (p *HandlerStats) Events() (events []observer.Event) {
p.mu.RLock()
defer p.mu.RUnlock()
for k, v := range p.stats {
if !v.IsUpdated() {
continue
}
events = append(events, stats.StatsEvent{
Kind: "handler",
Service: p.service,
Client: k,
TotalConns: v.Get(stats.KindTotalConns),
CurrentConns: v.Get(stats.KindCurrentConns),
InputBytes: v.Get(stats.KindInputBytes),
OutputBytes: v.Get(stats.KindOutputBytes),
TotalErrs: v.Get(stats.KindTotalErrs),
})
}
return
}

View File

@ -0,0 +1,102 @@
package traffic
import (
"context"
"io"
"github.com/go-gost/core/limiter/traffic"
"github.com/go-gost/core/logger"
"github.com/go-gost/plugin/limiter/traffic/proto"
"github.com/go-gost/x/internal/plugin"
xtraffic "github.com/go-gost/x/limiter/traffic"
"google.golang.org/grpc"
)
type grpcPlugin struct {
conn grpc.ClientConnInterface
client proto.LimiterClient
log logger.Logger
}
// NewGRPCPlugin creates a traffic limiter plugin based on gRPC.
func NewGRPCPlugin(name string, addr string, opts ...plugin.Option) traffic.TrafficLimiter {
var options plugin.Options
for _, opt := range opts {
opt(&options)
}
log := logger.Default().WithFields(map[string]any{
"kind": "limiter",
"limiter": name,
})
conn, err := plugin.NewGRPCConn(addr, &options)
if err != nil {
log.Error(err)
}
p := &grpcPlugin{
conn: conn,
log: log,
}
if conn != nil {
p.client = proto.NewLimiterClient(conn)
}
return p
}
func (p *grpcPlugin) In(ctx context.Context, key string, opts ...traffic.Option) traffic.Limiter {
if p.client == nil {
return nil
}
var options traffic.Options
for _, opt := range opts {
opt(&options)
}
r, err := p.client.Limit(ctx,
&proto.LimitRequest{
Network: options.Network,
Addr: options.Addr,
Client: options.Client,
Src: options.Src,
})
if err != nil {
p.log.Error(err)
return nil
}
return xtraffic.NewLimiter(int(r.In))
}
func (p *grpcPlugin) Out(ctx context.Context, key string, opts ...traffic.Option) traffic.Limiter {
if p.client == nil {
return nil
}
var options traffic.Options
for _, opt := range opts {
opt(&options)
}
r, err := p.client.Limit(ctx,
&proto.LimitRequest{
Network: options.Network,
Addr: options.Addr,
Client: options.Client,
Src: options.Src,
})
if err != nil {
p.log.Error(err)
return nil
}
return xtraffic.NewLimiter(int(r.Out))
}
func (p *grpcPlugin) Close() error {
if closer, ok := p.conn.(io.Closer); ok {
return closer.Close()
}
return nil
}

View File

@ -4,105 +4,14 @@ import (
"bytes"
"context"
"encoding/json"
"io"
"net/http"
"github.com/go-gost/core/limiter/traffic"
"github.com/go-gost/core/logger"
"github.com/go-gost/plugin/limiter/traffic/proto"
"github.com/go-gost/x/internal/plugin"
"google.golang.org/grpc"
xtraffic "github.com/go-gost/x/limiter/traffic"
)
type grpcPlugin struct {
conn grpc.ClientConnInterface
client proto.LimiterClient
log logger.Logger
}
// NewGRPCPlugin creates a traffic limiter plugin based on gRPC.
func NewGRPCPlugin(name string, addr string, opts ...plugin.Option) traffic.TrafficLimiter {
var options plugin.Options
for _, opt := range opts {
opt(&options)
}
log := logger.Default().WithFields(map[string]any{
"kind": "limiter",
"limiter": name,
})
conn, err := plugin.NewGRPCConn(addr, &options)
if err != nil {
log.Error(err)
}
p := &grpcPlugin{
conn: conn,
log: log,
}
if conn != nil {
p.client = proto.NewLimiterClient(conn)
}
return p
}
func (p *grpcPlugin) In(ctx context.Context, key string, opts ...traffic.Option) traffic.Limiter {
if p.client == nil {
return nil
}
var options traffic.Options
for _, opt := range opts {
opt(&options)
}
r, err := p.client.Limit(ctx,
&proto.LimitRequest{
Network: options.Network,
Addr: options.Addr,
Client: options.Client,
Src: options.Src,
})
if err != nil {
p.log.Error(err)
return nil
}
return NewLimiter(int(r.In))
}
func (p *grpcPlugin) Out(ctx context.Context, key string, opts ...traffic.Option) traffic.Limiter {
if p.client == nil {
return nil
}
var options traffic.Options
for _, opt := range opts {
opt(&options)
}
r, err := p.client.Limit(ctx,
&proto.LimitRequest{
Network: options.Network,
Addr: options.Addr,
Client: options.Client,
Src: options.Src,
})
if err != nil {
p.log.Error(err)
return nil
}
return NewLimiter(int(r.Out))
}
func (p *grpcPlugin) Close() error {
if closer, ok := p.conn.(io.Closer); ok {
return closer.Close()
}
return nil
}
type httpPluginRequest struct {
Network string `json:"network"`
Addr string `json:"addr"`
@ -184,7 +93,7 @@ func (p *httpPlugin) In(ctx context.Context, key string, opts ...traffic.Option)
if err := json.NewDecoder(resp.Body).Decode(&res); err != nil {
return nil
}
return NewLimiter(int(res.In))
return xtraffic.NewLimiter(int(res.In))
}
func (p *httpPlugin) Out(ctx context.Context, key string, opts ...traffic.Option) traffic.Limiter {
@ -231,5 +140,5 @@ func (p *httpPlugin) Out(ctx context.Context, key string, opts ...traffic.Option
if err := json.NewDecoder(resp.Body).Decode(&res); err != nil {
return nil
}
return NewLimiter(int(res.Out))
return xtraffic.NewLimiter(int(res.Out))
}

View File

@ -22,7 +22,7 @@ type readWriter struct {
key string
}
func WrapReadWriter(limiter limiter.TrafficLimiter, rw io.ReadWriter, key string, opts ...limiter.Option) io.ReadWriter {
func WrapReadWriter(limiter limiter.TrafficLimiter, rw io.ReadWriter, opts ...limiter.Option) io.ReadWriter {
if limiter == nil {
return rw
}

View File

@ -17,6 +17,7 @@ import (
md "github.com/go-gost/core/metadata"
metrics "github.com/go-gost/x/metrics/wrapper"
"github.com/go-gost/x/registry"
stats "github.com/go-gost/x/stats/wrapper"
"github.com/miekg/dns"
)
@ -117,6 +118,7 @@ func (l *dnsListener) Accept() (conn net.Conn, err error) {
select {
case conn = <-l.cqueue:
conn = metrics.WrapConn(l.options.Service, conn)
conn = stats.WrapConn(conn, l.options.Stats)
conn = admission.WrapConn(l.options.Admission, conn)
conn = limiter.WrapConn(l.options.TrafficLimiter, conn)
case err, ok = <-l.errChan:

View File

@ -17,6 +17,7 @@ import (
limiter "github.com/go-gost/x/limiter/traffic/wrapper"
metrics "github.com/go-gost/x/metrics/wrapper"
"github.com/go-gost/x/registry"
stats "github.com/go-gost/x/stats/wrapper"
"github.com/pion/dtls/v2"
)
@ -79,6 +80,7 @@ func (l *dtlsListener) Init(md md.Metadata) (err error) {
}
ln = proxyproto.WrapListener(l.options.ProxyProtocol, ln, 10*time.Second)
ln = metrics.WrapListener(l.options.Service, ln)
ln = stats.WrapListener(ln, l.options.Stats)
ln = admission.WrapListener(l.options.Admission, ln)
ln = limiter.WrapListener(l.options.TrafficLimiter, ln)
ln = climiter.WrapListener(l.options.ConnLimiter, ln)

View File

@ -12,6 +12,7 @@ import (
limiter "github.com/go-gost/x/limiter/traffic/wrapper"
metrics "github.com/go-gost/x/metrics/wrapper"
"github.com/go-gost/x/registry"
stats "github.com/go-gost/x/stats/wrapper"
"github.com/xtaci/tcpraw"
)
@ -52,6 +53,7 @@ func (l *ftcpListener) Init(md md.Metadata) (err error) {
return
}
conn = metrics.WrapPacketConn(l.options.Service, conn)
conn = stats.WrapPacketConn(conn, l.options.Stats)
conn = admission.WrapPacketConn(l.options.Admission, conn)
conn = limiter.WrapPacketConn(l.options.TrafficLimiter, conn)

View File

@ -16,6 +16,7 @@ import (
limiter "github.com/go-gost/x/limiter/traffic/wrapper"
metrics "github.com/go-gost/x/metrics/wrapper"
"github.com/go-gost/x/registry"
stats "github.com/go-gost/x/stats/wrapper"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
"google.golang.org/grpc/keepalive"
@ -66,6 +67,7 @@ func (l *grpcListener) Init(md md.Metadata) (err error) {
}
ln = proxyproto.WrapListener(l.options.ProxyProtocol, ln, 10*time.Second)
ln = metrics.WrapListener(l.options.Service, ln)
ln = stats.WrapListener(ln, l.options.Stats)
ln = admission.WrapListener(l.options.Admission, ln)
ln = limiter.WrapListener(l.options.TrafficLimiter, ln)
ln = climiter.WrapListener(l.options.ConnLimiter, ln)

View File

@ -19,6 +19,7 @@ import (
limiter "github.com/go-gost/x/limiter/traffic/wrapper"
metrics "github.com/go-gost/x/metrics/wrapper"
"github.com/go-gost/x/registry"
stats "github.com/go-gost/x/stats/wrapper"
"golang.org/x/net/http2"
"golang.org/x/net/http2/h2c"
)
@ -87,6 +88,7 @@ func (l *h2Listener) Init(md md.Metadata) (err error) {
l.addr = ln.Addr()
ln = proxyproto.WrapListener(l.options.ProxyProtocol, ln, 10*time.Second)
ln = metrics.WrapListener(l.options.Service, ln)
ln = stats.WrapListener(ln, l.options.Stats)
ln = admission.WrapListener(l.options.Admission, ln)
ln = limiter.WrapListener(l.options.TrafficLimiter, ln)
ln = climiter.WrapListener(l.options.ConnLimiter, ln)

View File

@ -18,6 +18,7 @@ import (
mdx "github.com/go-gost/x/metadata"
metrics "github.com/go-gost/x/metrics/wrapper"
"github.com/go-gost/x/registry"
stats "github.com/go-gost/x/stats/wrapper"
"golang.org/x/net/http2"
)
@ -76,6 +77,7 @@ func (l *http2Listener) Init(md md.Metadata) (err error) {
l.addr = ln.Addr()
ln = proxyproto.WrapListener(l.options.ProxyProtocol, ln, 10*time.Second)
ln = metrics.WrapListener(l.options.Service, ln)
ln = stats.WrapListener(ln, l.options.Stats)
ln = admission.WrapListener(l.options.Admission, ln)
ln = limiter.WrapListener(l.options.TrafficLimiter, ln)
ln = climiter.WrapListener(l.options.ConnLimiter, ln)

View File

@ -12,6 +12,7 @@ import (
limiter "github.com/go-gost/x/limiter/traffic/wrapper"
metrics "github.com/go-gost/x/metrics/wrapper"
"github.com/go-gost/x/registry"
stats "github.com/go-gost/x/stats/wrapper"
"github.com/quic-go/quic-go"
)
@ -85,6 +86,7 @@ func (l *http3Listener) Accept() (conn net.Conn, err error) {
}
conn = metrics.WrapConn(l.options.Service, conn)
conn = stats.WrapConn(conn, l.options.Stats)
conn = admission.WrapConn(l.options.Admission, conn)
conn = limiter.WrapConn(l.options.TrafficLimiter, conn)
return conn, nil

View File

@ -8,9 +8,13 @@ import (
"github.com/go-gost/core/listener"
"github.com/go-gost/core/logger"
md "github.com/go-gost/core/metadata"
admission "github.com/go-gost/x/admission/wrapper"
xnet "github.com/go-gost/x/internal/net"
wt_util "github.com/go-gost/x/internal/util/wt"
limiter "github.com/go-gost/x/limiter/traffic/wrapper"
metrics "github.com/go-gost/x/metrics/wrapper"
"github.com/go-gost/x/registry"
stats "github.com/go-gost/x/stats/wrapper"
"github.com/quic-go/quic-go"
"github.com/quic-go/quic-go/http3"
wt "github.com/quic-go/webtransport-go"
@ -95,6 +99,10 @@ func (l *wtListener) Accept() (conn net.Conn, err error) {
var ok bool
select {
case conn = <-l.cqueue:
conn = metrics.WrapConn(l.options.Service, conn)
conn = stats.WrapConn(conn, l.options.Stats)
conn = admission.WrapConn(l.options.Admission, conn)
conn = limiter.WrapConn(l.options.TrafficLimiter, conn)
case err, ok = <-l.errChan:
if !ok {
err = listener.ErrClosed

View File

@ -12,6 +12,7 @@ import (
limiter "github.com/go-gost/x/limiter/traffic/wrapper"
metrics "github.com/go-gost/x/metrics/wrapper"
"github.com/go-gost/x/registry"
stats "github.com/go-gost/x/stats/wrapper"
"github.com/quic-go/quic-go"
"golang.org/x/net/icmp"
)
@ -57,6 +58,7 @@ func (l *icmpListener) Init(md md.Metadata) (err error) {
}
conn = icmp_pkg.ServerConn(conn)
conn = metrics.WrapPacketConn(l.options.Service, conn)
conn = stats.WrapPacketConn(conn, l.options.Stats)
conn = admission.WrapPacketConn(l.options.Admission, conn)
conn = limiter.WrapPacketConn(l.options.TrafficLimiter, conn)

View File

@ -13,6 +13,7 @@ import (
limiter "github.com/go-gost/x/limiter/traffic/wrapper"
metrics "github.com/go-gost/x/metrics/wrapper"
"github.com/go-gost/x/registry"
stats "github.com/go-gost/x/stats/wrapper"
"github.com/xtaci/kcp-go/v5"
"github.com/xtaci/smux"
"github.com/xtaci/tcpraw"
@ -75,6 +76,7 @@ func (l *kcpListener) Init(md md.Metadata) (err error) {
}
conn = metrics.WrapUDPConn(l.options.Service, conn)
conn = stats.WrapUDPConn(conn, l.options.Stats)
conn = admission.WrapUDPConn(l.options.Admission, conn)
conn = limiter.WrapUDPConn(l.options.TrafficLimiter, conn)

View File

@ -16,6 +16,7 @@ import (
limiter "github.com/go-gost/x/limiter/traffic/wrapper"
metrics "github.com/go-gost/x/metrics/wrapper"
"github.com/go-gost/x/registry"
stats "github.com/go-gost/x/stats/wrapper"
)
func init() {
@ -66,6 +67,7 @@ func (l *mtcpListener) Init(md md.Metadata) (err error) {
ln = proxyproto.WrapListener(l.options.ProxyProtocol, ln, 10*time.Second)
ln = metrics.WrapListener(l.options.Service, ln)
ln = stats.WrapListener(ln, l.options.Stats)
ln = admission.WrapListener(l.options.Admission, ln)
ln = limiter.WrapListener(l.options.TrafficLimiter, ln)
ln = climiter.WrapListener(l.options.ConnLimiter, ln)

View File

@ -17,6 +17,7 @@ import (
limiter "github.com/go-gost/x/limiter/traffic/wrapper"
metrics "github.com/go-gost/x/metrics/wrapper"
"github.com/go-gost/x/registry"
stats "github.com/go-gost/x/stats/wrapper"
)
func init() {
@ -65,6 +66,7 @@ func (l *mtlsListener) Init(md md.Metadata) (err error) {
ln = proxyproto.WrapListener(l.options.ProxyProtocol, ln, 10*time.Second)
ln = metrics.WrapListener(l.options.Service, ln)
ln = stats.WrapListener(ln, l.options.Stats)
ln = admission.WrapListener(l.options.Admission, ln)
ln = limiter.WrapListener(l.options.TrafficLimiter, ln)
ln = climiter.WrapListener(l.options.ConnLimiter, ln)

View File

@ -20,6 +20,7 @@ import (
limiter "github.com/go-gost/x/limiter/traffic/wrapper"
metrics "github.com/go-gost/x/metrics/wrapper"
"github.com/go-gost/x/registry"
stats "github.com/go-gost/x/stats/wrapper"
"github.com/gorilla/websocket"
)
@ -107,6 +108,7 @@ func (l *mwsListener) Init(md md.Metadata) (err error) {
}
ln = proxyproto.WrapListener(l.options.ProxyProtocol, ln, 10*time.Second)
ln = metrics.WrapListener(l.options.Service, ln)
ln = stats.WrapListener(ln, l.options.Stats)
ln = admission.WrapListener(l.options.Admission, ln)
ln = limiter.WrapListener(l.options.TrafficLimiter, ln)
ln = climiter.WrapListener(l.options.ConnLimiter, ln)

View File

@ -9,13 +9,13 @@ import (
"github.com/go-gost/core/logger"
md "github.com/go-gost/core/metadata"
admission "github.com/go-gost/x/admission/wrapper"
xnet "github.com/go-gost/x/internal/net"
"github.com/go-gost/x/internal/net/proxyproto"
climiter "github.com/go-gost/x/limiter/conn/wrapper"
limiter "github.com/go-gost/x/limiter/traffic/wrapper"
metrics "github.com/go-gost/x/metrics/wrapper"
xnet "github.com/go-gost/x/internal/net"
"github.com/go-gost/x/internal/net/proxyproto"
"github.com/go-gost/x/registry"
stats "github.com/go-gost/x/stats/wrapper"
)
func init() {
@ -61,6 +61,7 @@ func (l *obfsListener) Init(md md.Metadata) (err error) {
}
ln = proxyproto.WrapListener(l.options.ProxyProtocol, ln, 10*time.Second)
ln = metrics.WrapListener(l.options.Service, ln)
ln = stats.WrapListener(ln, l.options.Stats)
ln = admission.WrapListener(l.options.Admission, ln)
ln = limiter.WrapListener(l.options.TrafficLimiter, ln)
ln = climiter.WrapListener(l.options.ConnLimiter, ln)

View File

@ -15,6 +15,7 @@ import (
limiter "github.com/go-gost/x/limiter/traffic/wrapper"
metrics "github.com/go-gost/x/metrics/wrapper"
"github.com/go-gost/x/registry"
stats "github.com/go-gost/x/stats/wrapper"
)
func init() {
@ -60,6 +61,7 @@ func (l *obfsListener) Init(md md.Metadata) (err error) {
}
ln = proxyproto.WrapListener(l.options.ProxyProtocol, ln, 10*time.Second)
ln = metrics.WrapListener(l.options.Service, ln)
ln = stats.WrapListener(ln, l.options.Stats)
ln = admission.WrapListener(l.options.Admission, ln)
ln = limiter.WrapListener(l.options.TrafficLimiter, ln)
ln = climiter.WrapListener(l.options.ConnLimiter, ln)

View File

@ -14,6 +14,7 @@ import (
limiter "github.com/go-gost/x/limiter/traffic/wrapper"
metrics "github.com/go-gost/x/metrics/wrapper"
"github.com/go-gost/x/registry"
stats "github.com/go-gost/x/stats/wrapper"
)
func init() {
@ -92,6 +93,7 @@ func (l *phtListener) Accept() (conn net.Conn, err error) {
return
}
conn = metrics.WrapConn(l.options.Service, conn)
conn = stats.WrapConn(conn, l.options.Stats)
conn = admission.WrapConn(l.options.Admission, conn)
conn = limiter.WrapConn(l.options.TrafficLimiter, conn)
return

View File

@ -13,6 +13,7 @@ import (
limiter "github.com/go-gost/x/limiter/traffic/wrapper"
metrics "github.com/go-gost/x/metrics/wrapper"
"github.com/go-gost/x/registry"
stats "github.com/go-gost/x/stats/wrapper"
"github.com/quic-go/quic-go"
)
@ -102,6 +103,7 @@ func (l *quicListener) Accept() (conn net.Conn, err error) {
select {
case conn = <-l.cqueue:
conn = metrics.WrapConn(l.options.Service, conn)
conn = stats.WrapConn(conn, l.options.Stats)
conn = admission.WrapConn(l.options.Admission, conn)
conn = limiter.WrapConn(l.options.TrafficLimiter, conn)
case err, ok = <-l.errChan:

View File

@ -15,6 +15,7 @@ import (
limiter "github.com/go-gost/x/limiter/traffic/wrapper"
metrics "github.com/go-gost/x/metrics/wrapper"
"github.com/go-gost/x/registry"
stats "github.com/go-gost/x/stats/wrapper"
)
func init() {
@ -65,6 +66,7 @@ func (l *redirectListener) Init(md md.Metadata) (err error) {
ln = proxyproto.WrapListener(l.options.ProxyProtocol, ln, 10*time.Second)
ln = metrics.WrapListener(l.options.Service, ln)
ln = stats.WrapListener(ln, l.options.Stats)
ln = admission.WrapListener(l.options.Admission, ln)
ln = limiter.WrapListener(l.options.TrafficLimiter, ln)
ln = climiter.WrapListener(l.options.ConnLimiter, ln)

View File

@ -10,6 +10,7 @@ import (
limiter "github.com/go-gost/x/limiter/traffic/wrapper"
metrics "github.com/go-gost/x/metrics/wrapper"
"github.com/go-gost/x/registry"
stats "github.com/go-gost/x/stats/wrapper"
)
func init() {
@ -54,6 +55,7 @@ func (l *redirectListener) Accept() (conn net.Conn, err error) {
return
}
conn = metrics.WrapConn(l.options.Service, conn)
conn = stats.WrapConn(conn, l.options.Stats)
conn = admission.WrapConn(l.options.Admission, conn)
conn = limiter.WrapConn(l.options.TrafficLimiter, conn)
return

View File

@ -15,6 +15,7 @@ import (
limiter "github.com/go-gost/x/limiter/traffic/wrapper"
metrics "github.com/go-gost/x/metrics/wrapper"
"github.com/go-gost/x/registry"
stats "github.com/go-gost/x/stats/wrapper"
)
func init() {
@ -84,6 +85,7 @@ func (l *rtcpListener) Accept() (conn net.Conn, err error) {
return nil, listener.NewAcceptError(err)
}
ln = metrics.WrapListener(l.options.Service, ln)
ln = stats.WrapListener(ln, l.options.Stats)
ln = admission.WrapListener(l.options.Admission, ln)
ln = limiter.WrapListener(l.options.TrafficLimiter, ln)
ln = climiter.WrapListener(l.options.ConnLimiter, ln)

View File

@ -14,6 +14,7 @@ import (
limiter "github.com/go-gost/x/limiter/traffic/wrapper"
metrics "github.com/go-gost/x/metrics/wrapper"
"github.com/go-gost/x/registry"
stats "github.com/go-gost/x/stats/wrapper"
)
func init() {
@ -105,6 +106,7 @@ func (l *rudpListener) Accept() (conn net.Conn, err error) {
if pc, ok := conn.(net.PacketConn); ok {
uc := metrics.WrapUDPConn(l.options.Service, pc)
uc = stats.WrapUDPConn(uc, l.options.Stats)
uc = admission.WrapUDPConn(l.options.Admission, uc)
conn = limiter.WrapUDPConn(l.options.TrafficLimiter, uc)
}

View File

@ -12,6 +12,7 @@ import (
limiter "github.com/go-gost/x/limiter/traffic/wrapper"
metrics "github.com/go-gost/x/metrics/wrapper"
"github.com/go-gost/x/registry"
stats "github.com/go-gost/x/stats/wrapper"
)
func init() {
@ -95,6 +96,7 @@ func (l *serialListener) listenLoop() {
c := serial.NewConn(port, l.addr, cancel)
c = metrics.WrapConn(l.options.Service, c)
c = stats.WrapConn(c, l.options.Stats)
c = limiter.WrapConn(l.options.TrafficLimiter, c)
l.cqueue <- c

View File

@ -17,6 +17,7 @@ import (
limiter "github.com/go-gost/x/limiter/traffic/wrapper"
metrics "github.com/go-gost/x/metrics/wrapper"
"github.com/go-gost/x/registry"
stats "github.com/go-gost/x/stats/wrapper"
"golang.org/x/crypto/ssh"
)
@ -67,6 +68,7 @@ func (l *sshListener) Init(md md.Metadata) (err error) {
ln = proxyproto.WrapListener(l.options.ProxyProtocol, ln, 10*time.Second)
ln = metrics.WrapListener(l.options.Service, ln)
ln = stats.WrapListener(ln, l.options.Stats)
ln = admission.WrapListener(l.options.Admission, ln)
ln = limiter.WrapListener(l.options.TrafficLimiter, ln)
ln = climiter.WrapListener(l.options.ConnLimiter, ln)

View File

@ -20,6 +20,7 @@ import (
metrics "github.com/go-gost/x/metrics/wrapper"
"github.com/go-gost/x/registry"
"golang.org/x/crypto/ssh"
stats "github.com/go-gost/x/stats/wrapper"
)
// Applicable SSH Request types for Port Forwarding - RFC 4254 7.X
@ -75,6 +76,7 @@ func (l *sshdListener) Init(md md.Metadata) (err error) {
ln = proxyproto.WrapListener(l.options.ProxyProtocol, ln, 10*time.Second)
ln = metrics.WrapListener(l.options.Service, ln)
ln = stats.WrapListener(ln, l.options.Stats)
ln = admission.WrapListener(l.options.Admission, ln)
ln = limiter.WrapListener(l.options.TrafficLimiter, ln)
ln = climiter.WrapListener(l.options.ConnLimiter, ln)

View File

@ -13,6 +13,7 @@ import (
mdx "github.com/go-gost/x/metadata"
metrics "github.com/go-gost/x/metrics/wrapper"
"github.com/go-gost/x/registry"
stats "github.com/go-gost/x/stats/wrapper"
)
func init() {
@ -90,6 +91,7 @@ func (l *tapListener) listenLoop() {
cancel: cancel,
}
c = metrics.WrapConn(l.options.Service, c)
c = stats.WrapConn(c, l.options.Stats)
c = limiter.WrapConn(l.options.TrafficLimiter, c)
c = withMetadata(mdx.NewMetadata(map[string]any{
"config": l.md.config,

View File

@ -15,6 +15,7 @@ import (
limiter "github.com/go-gost/x/limiter/traffic/wrapper"
metrics "github.com/go-gost/x/metrics/wrapper"
"github.com/go-gost/x/registry"
stats "github.com/go-gost/x/stats/wrapper"
)
func init() {
@ -63,6 +64,7 @@ func (l *tcpListener) Init(md md.Metadata) (err error) {
ln = proxyproto.WrapListener(l.options.ProxyProtocol, ln, 10*time.Second)
ln = metrics.WrapListener(l.options.Service, ln)
ln = stats.WrapListener(ln, l.options.Stats)
ln = admission.WrapListener(l.options.Admission, ln)
ln = limiter.WrapListener(l.options.TrafficLimiter, ln)
ln = climiter.WrapListener(l.options.ConnLimiter, ln)

View File

@ -16,6 +16,7 @@ import (
limiter "github.com/go-gost/x/limiter/traffic/wrapper"
metrics "github.com/go-gost/x/metrics/wrapper"
"github.com/go-gost/x/registry"
stats "github.com/go-gost/x/stats/wrapper"
)
func init() {
@ -61,6 +62,7 @@ func (l *tlsListener) Init(md md.Metadata) (err error) {
}
ln = proxyproto.WrapListener(l.options.ProxyProtocol, ln, 10*time.Second)
ln = metrics.WrapListener(l.options.Service, ln)
ln = stats.WrapListener(ln, l.options.Stats)
ln = admission.WrapListener(l.options.Admission, ln)
ln = limiter.WrapListener(l.options.TrafficLimiter, ln)
ln = climiter.WrapListener(l.options.ConnLimiter, ln)

View File

@ -14,6 +14,7 @@ import (
mdx "github.com/go-gost/x/metadata"
metrics "github.com/go-gost/x/metrics/wrapper"
"github.com/go-gost/x/registry"
stats "github.com/go-gost/x/stats/wrapper"
)
func init() {
@ -27,7 +28,7 @@ type tunListener struct {
logger logger.Logger
md metadata
options listener.Options
routes []*router.Route
routes []*router.Route
}
func NewListener(opts ...listener.Option) listener.Listener {
@ -91,6 +92,7 @@ func (l *tunListener) listenLoop() {
cancel: cancel,
}
c = metrics.WrapConn(l.options.Service, c)
c = stats.WrapConn(c, l.options.Stats)
c = limiter.WrapConn(l.options.TrafficLimiter, c)
c = withMetadata(mdx.NewMetadata(map[string]any{
"config": l.md.config,

View File

@ -12,6 +12,7 @@ import (
limiter "github.com/go-gost/x/limiter/traffic/wrapper"
metrics "github.com/go-gost/x/metrics/wrapper"
"github.com/go-gost/x/registry"
stats "github.com/go-gost/x/stats/wrapper"
)
func init() {
@ -56,6 +57,7 @@ func (l *udpListener) Init(md md.Metadata) (err error) {
return
}
conn = metrics.WrapPacketConn(l.options.Service, conn)
conn = stats.WrapPacketConn(conn, l.options.Stats)
conn = admission.WrapPacketConn(l.options.Admission, conn)
conn = limiter.WrapPacketConn(l.options.TrafficLimiter, conn)

View File

@ -11,6 +11,7 @@ import (
limiter "github.com/go-gost/x/limiter/traffic/wrapper"
metrics "github.com/go-gost/x/metrics/wrapper"
"github.com/go-gost/x/registry"
stats "github.com/go-gost/x/stats/wrapper"
)
func init() {
@ -46,6 +47,7 @@ func (l *unixListener) Init(md md.Metadata) (err error) {
}
ln = metrics.WrapListener(l.options.Service, ln)
ln = stats.WrapListener(ln, l.options.Stats)
ln = admission.WrapListener(l.options.Admission, ln)
ln = limiter.WrapListener(l.options.TrafficLimiter, ln)
ln = climiter.WrapListener(l.options.ConnLimiter, ln)

View File

@ -19,6 +19,7 @@ import (
limiter "github.com/go-gost/x/limiter/traffic/wrapper"
metrics "github.com/go-gost/x/metrics/wrapper"
"github.com/go-gost/x/registry"
stats "github.com/go-gost/x/stats/wrapper"
"github.com/gorilla/websocket"
)
@ -102,6 +103,7 @@ func (l *wsListener) Init(md md.Metadata) (err error) {
}
ln = proxyproto.WrapListener(l.options.ProxyProtocol, ln, 10*time.Second)
ln = metrics.WrapListener(l.options.Service, ln)
ln = stats.WrapListener(ln, l.options.Stats)
ln = admission.WrapListener(l.options.Admission, ln)
ln = limiter.WrapListener(l.options.TrafficLimiter, ln)
ln = climiter.WrapListener(l.options.ConnLimiter, ln)

98
observer/plugin/grpc.go Normal file
View File

@ -0,0 +1,98 @@
package observer
import (
"context"
"io"
"github.com/go-gost/core/logger"
"github.com/go-gost/core/observer"
"github.com/go-gost/plugin/observer/proto"
"github.com/go-gost/x/internal/plugin"
"github.com/go-gost/x/service"
"github.com/go-gost/x/stats"
"google.golang.org/grpc"
)
type grpcPlugin struct {
conn grpc.ClientConnInterface
client proto.ObserverClient
log logger.Logger
}
// NewGRPCPlugin creates an Observer plugin based on gRPC.
func NewGRPCPlugin(name string, addr string, opts ...plugin.Option) observer.Observer {
var options plugin.Options
for _, opt := range opts {
opt(&options)
}
log := logger.Default().WithFields(map[string]any{
"kind": "observer",
"observer": name,
})
conn, err := plugin.NewGRPCConn(addr, &options)
if err != nil {
log.Error(err)
}
p := &grpcPlugin{
conn: conn,
log: log,
}
if conn != nil {
p.client = proto.NewObserverClient(conn)
}
return p
}
func (p *grpcPlugin) Observe(ctx context.Context, events []observer.Event, opts ...observer.Option) error {
if p.client == nil || len(events) == 0 {
return nil
}
var req proto.ObserveRequest
for _, event := range events {
switch event.Type() {
case observer.EventStatus:
ev := event.(service.ServiceEvent)
req.Events = append(req.Events, &proto.Event{
Kind: ev.Kind,
Service: ev.Service,
Type: string(event.Type()),
Status: &proto.ServiceStatus{
State: string(ev.State),
Msg: ev.Msg,
},
})
case observer.EventStats:
ev := event.(stats.StatsEvent)
req.Events = append(req.Events, &proto.Event{
Kind: ev.Kind,
Service: ev.Service,
Client: ev.Client,
Type: string(event.Type()),
Stats: &proto.Stats{
TotalConns: ev.TotalConns,
CurrentConns: ev.CurrentConns,
InputBytes: ev.InputBytes,
OutputBytes: ev.OutputBytes,
TotalErrs: ev.TotalErrs,
},
})
}
}
_, err := p.client.Observe(ctx, &req)
if err != nil {
p.log.Error(err)
return err
}
return nil
}
func (p *grpcPlugin) Close() error {
if closer, ok := p.conn.(io.Closer); ok {
return closer.Close()
}
return nil
}

134
observer/plugin/http.go Normal file
View File

@ -0,0 +1,134 @@
package observer
import (
"bytes"
"context"
"encoding/json"
"fmt"
"net/http"
"github.com/go-gost/core/logger"
"github.com/go-gost/core/observer"
"github.com/go-gost/x/internal/plugin"
"github.com/go-gost/x/service"
"github.com/go-gost/x/stats"
)
type observeRequest struct {
Events []event `json:"events"`
}
type event struct {
Kind string `json:"kind"`
Service string `json:"service"`
Client string `json:"client,omitempty"`
Type observer.EventType `json:"type"`
Stats *statsEvent `json:"stats,omitempty"`
Status *statusEvent `json:"status,omitempty"`
}
type statsEvent struct {
TotalConns uint64 `json:"totalConns"`
CurrentConns uint64 `json:"currentConns"`
InputBytes uint64 `json:"inputBytes"`
OutputBytes uint64 `json:"outputBytes"`
TotalErrs uint64 `json:"totalErrs"`
}
type statusEvent struct {
State string `json:"state"`
Msg string `json:"msg"`
}
type observeResponse struct {
OK bool `json:"ok"`
}
type httpPlugin struct {
url string
client *http.Client
header http.Header
log logger.Logger
}
// NewHTTPPlugin creates an Observer plugin based on HTTP.
func NewHTTPPlugin(name string, url string, opts ...plugin.Option) observer.Observer {
var options plugin.Options
for _, opt := range opts {
opt(&options)
}
return &httpPlugin{
url: url,
client: plugin.NewHTTPClient(&options),
header: options.Header,
log: logger.Default().WithFields(map[string]any{
"kind": "observer",
"observer": name,
}),
}
}
func (p *httpPlugin) Observe(ctx context.Context, events []observer.Event, opts ...observer.Option) error {
if p.client == nil || len(events) == 0 {
return nil
}
var r observeRequest
for _, e := range events {
switch e.Type() {
case observer.EventStatus:
ev := e.(service.ServiceEvent)
r.Events = append(r.Events, event{
Kind: ev.Kind,
Service: ev.Service,
Type: ev.Type(),
Status: &statusEvent{
State: string(ev.State),
Msg: ev.Msg,
},
})
case observer.EventStats:
ev := e.(stats.StatsEvent)
r.Events = append(r.Events, event{
Kind: ev.Kind,
Service: ev.Service,
Client: ev.Client,
Type: ev.Type(),
Stats: &statsEvent{
TotalConns: ev.TotalConns,
CurrentConns: ev.CurrentConns,
InputBytes: ev.InputBytes,
OutputBytes: ev.OutputBytes,
TotalErrs: ev.TotalErrs,
},
})
}
}
v, err := json.Marshal(r)
if err != nil {
return err
}
req, err := http.NewRequestWithContext(ctx, http.MethodPost, p.url, bytes.NewReader(v))
if err != nil {
return err
}
if p.header != nil {
req.Header = p.header.Clone()
}
req.Header.Set("Content-Type", "application/json")
resp, err := p.client.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return fmt.Errorf(resp.Status)
}
return nil
}

76
recorder/plugin/grpc.go Normal file
View File

@ -0,0 +1,76 @@
package recorder
import (
"context"
"encoding/json"
"io"
"github.com/go-gost/core/logger"
"github.com/go-gost/core/recorder"
"github.com/go-gost/plugin/recorder/proto"
"github.com/go-gost/x/internal/plugin"
"google.golang.org/grpc"
)
type grpcPlugin struct {
conn grpc.ClientConnInterface
client proto.RecorderClient
log logger.Logger
}
// NewGRPCPlugin creates a Recorder plugin based on gRPC.
func NewGRPCPlugin(name string, addr string, opts ...plugin.Option) recorder.Recorder {
var options plugin.Options
for _, opt := range opts {
opt(&options)
}
log := logger.Default().WithFields(map[string]any{
"kind": "recorder",
"recorder": name,
})
conn, err := plugin.NewGRPCConn(addr, &options)
if err != nil {
log.Error(err)
}
p := &grpcPlugin{
conn: conn,
log: log,
}
if conn != nil {
p.client = proto.NewRecorderClient(conn)
}
return p
}
func (p *grpcPlugin) Record(ctx context.Context, b []byte, opts ...recorder.RecordOption) error {
if p.client == nil {
return nil
}
var options recorder.RecordOptions
for _, opt := range opts {
opt(&options)
}
md, _ := json.Marshal(options.Metadata)
_, err := p.client.Record(ctx,
&proto.RecordRequest{
Data: b,
Metadata: md,
})
if err != nil {
p.log.Error(err)
return err
}
return nil
}
func (p *grpcPlugin) Close() error {
if closer, ok := p.conn.(io.Closer); ok {
return closer.Close()
}
return nil
}

View File

@ -6,79 +6,13 @@ import (
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"github.com/go-gost/core/logger"
"github.com/go-gost/core/recorder"
"github.com/go-gost/plugin/recorder/proto"
"github.com/go-gost/x/internal/plugin"
"google.golang.org/grpc"
)
type grpcPlugin struct {
conn grpc.ClientConnInterface
client proto.RecorderClient
log logger.Logger
}
// NewGRPCPlugin creates a Recorder plugin based on gRPC.
func NewGRPCPlugin(name string, addr string, opts ...plugin.Option) recorder.Recorder {
var options plugin.Options
for _, opt := range opts {
opt(&options)
}
log := logger.Default().WithFields(map[string]any{
"kind": "recorder",
"recorder": name,
})
conn, err := plugin.NewGRPCConn(addr, &options)
if err != nil {
log.Error(err)
}
p := &grpcPlugin{
conn: conn,
log: log,
}
if conn != nil {
p.client = proto.NewRecorderClient(conn)
}
return p
}
func (p *grpcPlugin) Record(ctx context.Context, b []byte, opts ...recorder.RecordOption) error {
if p.client == nil {
return nil
}
var options recorder.RecordOptions
for _, opt := range opts {
opt(&options)
}
md, _ := json.Marshal(options.Metadata)
_, err := p.client.Record(ctx,
&proto.RecordRequest{
Data: b,
Metadata: md,
})
if err != nil {
p.log.Error(err)
return err
}
return nil
}
func (p *grpcPlugin) Close() error {
if closer, ok := p.conn.(io.Closer); ok {
return closer.Close()
}
return nil
}
type httpPluginRequest struct {
Data []byte `json:"data"`
Metadata []byte `json:"metadata"`

39
registry/observer.go Normal file
View File

@ -0,0 +1,39 @@
package registry
import (
"context"
"github.com/go-gost/core/observer"
)
type observerRegistry struct {
registry[observer.Observer]
}
func (r *observerRegistry) Register(name string, v observer.Observer) error {
return r.registry.Register(name, v)
}
func (r *observerRegistry) Get(name string) observer.Observer {
if name != "" {
return &observerWrapper{name: name, r: r}
}
return nil
}
func (r *observerRegistry) get(name string) observer.Observer {
return r.registry.Get(name)
}
type observerWrapper struct {
name string
r *observerRegistry
}
func (w *observerWrapper) Observe(ctx context.Context, events []observer.Event, opts ...observer.Option) error {
v := w.r.get(w.name)
if v == nil {
return nil
}
return v.Observe(ctx, events, opts...)
}

View File

@ -16,6 +16,7 @@ import (
"github.com/go-gost/core/limiter/rate"
"github.com/go-gost/core/limiter/traffic"
"github.com/go-gost/core/logger"
"github.com/go-gost/core/observer"
"github.com/go-gost/core/recorder"
reg "github.com/go-gost/core/registry"
"github.com/go-gost/core/resolver"
@ -47,9 +48,10 @@ var (
connLimiterReg reg.Registry[conn.ConnLimiter] = new(connLimiterRegistry)
rateLimiterReg reg.Registry[rate.RateLimiter] = new(rateLimiterRegistry)
ingressReg reg.Registry[ingress.Ingress] = new(ingressRegistry)
routerReg reg.Registry[router.Router] = new(routerRegistry)
sdReg reg.Registry[sd.SD] = new(sdRegistry)
ingressReg reg.Registry[ingress.Ingress] = new(ingressRegistry)
routerReg reg.Registry[router.Router] = new(routerRegistry)
sdReg reg.Registry[sd.SD] = new(sdRegistry)
observerReg reg.Registry[observer.Observer] = new(observerRegistry)
loggerReg reg.Registry[logger.Logger] = new(loggerRegistry)
)
@ -179,6 +181,10 @@ func SDRegistry() reg.Registry[sd.SD] {
return sdReg
}
func ObserverRegistry() reg.Registry[observer.Observer] {
return observerReg
}
func LoggerRegistry() reg.Registry[logger.Logger] {
return loggerReg
}

77
resolver/plugin/grpc.go Normal file
View File

@ -0,0 +1,77 @@
package resolver
import (
"context"
"io"
"net"
"github.com/go-gost/core/logger"
"github.com/go-gost/core/resolver"
"github.com/go-gost/plugin/resolver/proto"
ctxvalue "github.com/go-gost/x/internal/ctx"
"github.com/go-gost/x/internal/plugin"
"google.golang.org/grpc"
)
type grpcPlugin struct {
conn grpc.ClientConnInterface
client proto.ResolverClient
log logger.Logger
}
// NewGRPCPlugin creates a Resolver plugin based on gRPC.
func NewGRPCPlugin(name string, addr string, opts ...plugin.Option) (resolver.Resolver, error) {
var options plugin.Options
for _, opt := range opts {
opt(&options)
}
log := logger.Default().WithFields(map[string]any{
"kind": "resolver",
"resolover": name,
})
conn, err := plugin.NewGRPCConn(addr, &options)
if err != nil {
log.Error(err)
}
p := &grpcPlugin{
conn: conn,
log: log,
}
if conn != nil {
p.client = proto.NewResolverClient(conn)
}
return p, nil
}
func (p *grpcPlugin) Resolve(ctx context.Context, network, host string, opts ...resolver.Option) (ips []net.IP, err error) {
p.log.Debugf("resolve %s/%s", host, network)
if p.client == nil {
return
}
r, err := p.client.Resolve(ctx,
&proto.ResolveRequest{
Network: network,
Host: host,
Client: string(ctxvalue.ClientIDFromContext(ctx)),
})
if err != nil {
p.log.Error(err)
return
}
for _, s := range r.Ips {
if ip := net.ParseIP(s); ip != nil {
ips = append(ips, ip)
}
}
return
}
func (p *grpcPlugin) Close() error {
if closer, ok := p.conn.(io.Closer); ok {
return closer.Close()
}
return nil
}

View File

@ -6,81 +6,15 @@ import (
"encoding/json"
"errors"
"fmt"
"io"
"net"
"net/http"
"github.com/go-gost/core/logger"
"github.com/go-gost/core/resolver"
"github.com/go-gost/plugin/resolver/proto"
ctxvalue "github.com/go-gost/x/internal/ctx"
"github.com/go-gost/x/internal/plugin"
"google.golang.org/grpc"
)
type grpcPlugin struct {
conn grpc.ClientConnInterface
client proto.ResolverClient
log logger.Logger
}
// NewGRPCPlugin creates a Resolver plugin based on gRPC.
func NewGRPCPlugin(name string, addr string, opts ...plugin.Option) (resolver.Resolver, error) {
var options plugin.Options
for _, opt := range opts {
opt(&options)
}
log := logger.Default().WithFields(map[string]any{
"kind": "resolver",
"resolover": name,
})
conn, err := plugin.NewGRPCConn(addr, &options)
if err != nil {
log.Error(err)
}
p := &grpcPlugin{
conn: conn,
log: log,
}
if conn != nil {
p.client = proto.NewResolverClient(conn)
}
return p, nil
}
func (p *grpcPlugin) Resolve(ctx context.Context, network, host string, opts ...resolver.Option) (ips []net.IP, err error) {
p.log.Debugf("resolve %s/%s", host, network)
if p.client == nil {
return
}
r, err := p.client.Resolve(ctx,
&proto.ResolveRequest{
Network: network,
Host: host,
Client: string(ctxvalue.ClientIDFromContext(ctx)),
})
if err != nil {
p.log.Error(err)
return
}
for _, s := range r.Ips {
if ip := net.ParseIP(s); ip != nil {
ips = append(ips, ip)
}
}
return
}
func (p *grpcPlugin) Close() error {
if closer, ok := p.conn.(io.Closer); ok {
return closer.Close()
}
return nil
}
type httpPluginRequest struct {
Network string `json:"network"`
Host string `json:"host"`

70
router/plugin/grpc.go Normal file
View File

@ -0,0 +1,70 @@
package router
import (
"context"
"io"
"net"
"github.com/go-gost/core/logger"
"github.com/go-gost/core/router"
"github.com/go-gost/plugin/router/proto"
"github.com/go-gost/x/internal/plugin"
xrouter "github.com/go-gost/x/router"
"google.golang.org/grpc"
)
type grpcPlugin struct {
conn grpc.ClientConnInterface
client proto.RouterClient
log logger.Logger
}
// NewGRPCPlugin creates an Router plugin based on gRPC.
func NewGRPCPlugin(name string, addr string, opts ...plugin.Option) router.Router {
var options plugin.Options
for _, opt := range opts {
opt(&options)
}
log := logger.Default().WithFields(map[string]any{
"kind": "router",
"router": name,
})
conn, err := plugin.NewGRPCConn(addr, &options)
if err != nil {
log.Error(err)
}
p := &grpcPlugin{
conn: conn,
log: log,
}
if conn != nil {
p.client = proto.NewRouterClient(conn)
}
return p
}
func (p *grpcPlugin) GetRoute(ctx context.Context, dst net.IP, opts ...router.Option) *router.Route {
if p.client == nil {
return nil
}
r, err := p.client.GetRoute(ctx,
&proto.GetRouteRequest{
Dst: dst.String(),
})
if err != nil {
p.log.Error(err)
return nil
}
return xrouter.ParseRoute(r.Net, r.Gateway)
}
func (p *grpcPlugin) Close() error {
if closer, ok := p.conn.(io.Closer); ok {
return closer.Close()
}
return nil
}

View File

@ -3,73 +3,15 @@ package router
import (
"context"
"encoding/json"
"io"
"net"
"net/http"
"github.com/go-gost/core/logger"
"github.com/go-gost/core/router"
"github.com/go-gost/plugin/router/proto"
"github.com/go-gost/x/internal/plugin"
"google.golang.org/grpc"
xrouter "github.com/go-gost/x/router"
)
type grpcPlugin struct {
conn grpc.ClientConnInterface
client proto.RouterClient
log logger.Logger
}
// NewGRPCPlugin creates an Router plugin based on gRPC.
func NewGRPCPlugin(name string, addr string, opts ...plugin.Option) router.Router {
var options plugin.Options
for _, opt := range opts {
opt(&options)
}
log := logger.Default().WithFields(map[string]any{
"kind": "router",
"router": name,
})
conn, err := plugin.NewGRPCConn(addr, &options)
if err != nil {
log.Error(err)
}
p := &grpcPlugin{
conn: conn,
log: log,
}
if conn != nil {
p.client = proto.NewRouterClient(conn)
}
return p
}
func (p *grpcPlugin) GetRoute(ctx context.Context, dst net.IP, opts ...router.Option) *router.Route {
if p.client == nil {
return nil
}
r, err := p.client.GetRoute(ctx,
&proto.GetRouteRequest{
Dst: dst.String(),
})
if err != nil {
p.log.Error(err)
return nil
}
return ParseRoute(r.Net, r.Gateway)
}
func (p *grpcPlugin) Close() error {
if closer, ok := p.conn.(io.Closer); ok {
return closer.Close()
}
return nil
}
type httpPluginGetRouteRequest struct {
Dst string `json:"dst"`
}
@ -137,5 +79,5 @@ func (p *httpPlugin) GetRoute(ctx context.Context, dst net.IP, opts ...router.Op
return nil
}
return ParseRoute(res.Net, res.Gateway)
return xrouter.ParseRoute(res.Net, res.Gateway)
}

134
sd/plugin/grpc.go Normal file
View File

@ -0,0 +1,134 @@
package sd
import (
"context"
"io"
"github.com/go-gost/core/logger"
"github.com/go-gost/core/sd"
"github.com/go-gost/plugin/sd/proto"
"github.com/go-gost/x/internal/plugin"
"google.golang.org/grpc"
)
type grpcPlugin struct {
conn grpc.ClientConnInterface
client proto.SDClient
log logger.Logger
}
// NewGRPCPlugin creates an SD plugin based on gRPC.
func NewGRPCPlugin(name string, addr string, opts ...plugin.Option) sd.SD {
var options plugin.Options
for _, opt := range opts {
opt(&options)
}
log := logger.Default().WithFields(map[string]any{
"kind": "sd",
"sd": name,
})
conn, err := plugin.NewGRPCConn(addr, &options)
if err != nil {
log.Error(err)
}
p := &grpcPlugin{
conn: conn,
log: log,
}
if conn != nil {
p.client = proto.NewSDClient(conn)
}
return p
}
func (p *grpcPlugin) Register(ctx context.Context, service *sd.Service, opts ...sd.Option) error {
if p.client == nil {
return nil
}
_, err := p.client.Register(ctx,
&proto.RegisterRequest{
Service: &proto.Service{
Id: service.ID,
Name: service.Name,
Node: service.Node,
Network: service.Network,
Address: service.Address,
},
})
if err != nil {
p.log.Error(err)
return err
}
return nil
}
func (p *grpcPlugin) Deregister(ctx context.Context, service *sd.Service) error {
if p.client == nil {
return nil
}
_, err := p.client.Deregister(ctx, &proto.DeregisterRequest{
Service: &proto.Service{
Id: service.ID,
Name: service.Name,
Node: service.Node,
Network: service.Network,
Address: service.Address,
},
})
return err
}
func (p *grpcPlugin) Renew(ctx context.Context, service *sd.Service) error {
if p.client == nil {
return nil
}
_, err := p.client.Renew(ctx, &proto.RenewRequest{
Service: &proto.Service{
Id: service.ID,
Name: service.Name,
Node: service.Node,
Network: service.Network,
Address: service.Address,
},
})
return err
}
func (p *grpcPlugin) Get(ctx context.Context, name string) ([]*sd.Service, error) {
if p.client == nil {
return nil, nil
}
r, err := p.client.Get(ctx, &proto.GetServiceRequest{
Name: name,
})
if err != nil {
return nil, err
}
var services []*sd.Service
for _, v := range r.Services {
if v == nil {
continue
}
services = append(services, &sd.Service{
Node: v.Node,
Name: v.Name,
Network: v.Network,
Address: v.Address,
})
}
return services, nil
}
func (p *grpcPlugin) Close() error {
if closer, ok := p.conn.(io.Closer); ok {
return closer.Close()
}
return nil
}

View File

@ -1,142 +1,17 @@
package ingress
package sd
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"github.com/go-gost/core/logger"
"github.com/go-gost/core/sd"
"github.com/go-gost/plugin/sd/proto"
"github.com/go-gost/x/internal/plugin"
"google.golang.org/grpc"
)
type grpcPlugin struct {
conn grpc.ClientConnInterface
client proto.SDClient
log logger.Logger
}
// NewGRPCPlugin creates an SD plugin based on gRPC.
func NewGRPCPlugin(name string, addr string, opts ...plugin.Option) sd.SD {
var options plugin.Options
for _, opt := range opts {
opt(&options)
}
log := logger.Default().WithFields(map[string]any{
"kind": "sd",
"sd": name,
})
conn, err := plugin.NewGRPCConn(addr, &options)
if err != nil {
log.Error(err)
}
p := &grpcPlugin{
conn: conn,
log: log,
}
if conn != nil {
p.client = proto.NewSDClient(conn)
}
return p
}
func (p *grpcPlugin) Register(ctx context.Context, service *sd.Service, opts ...sd.Option) error {
if p.client == nil {
return nil
}
_, err := p.client.Register(ctx,
&proto.RegisterRequest{
Service: &proto.Service{
Id: service.ID,
Name: service.Name,
Node: service.Node,
Network: service.Network,
Address: service.Address,
},
})
if err != nil {
p.log.Error(err)
return err
}
return nil
}
func (p *grpcPlugin) Deregister(ctx context.Context, service *sd.Service) error {
if p.client == nil {
return nil
}
_, err := p.client.Deregister(ctx, &proto.DeregisterRequest{
Service: &proto.Service{
Id: service.ID,
Name: service.Name,
Node: service.Node,
Network: service.Network,
Address: service.Address,
},
})
return err
}
func (p *grpcPlugin) Renew(ctx context.Context, service *sd.Service) error {
if p.client == nil {
return nil
}
_, err := p.client.Renew(ctx, &proto.RenewRequest{
Service: &proto.Service{
Id: service.ID,
Name: service.Name,
Node: service.Node,
Network: service.Network,
Address: service.Address,
},
})
return err
}
func (p *grpcPlugin) Get(ctx context.Context, name string) ([]*sd.Service, error) {
if p.client == nil {
return nil, nil
}
r, err := p.client.Get(ctx, &proto.GetServiceRequest{
Name: name,
})
if err != nil {
return nil, err
}
var services []*sd.Service
for _, v := range r.Services {
if v == nil {
continue
}
services = append(services, &sd.Service{
Node: v.Node,
Name: v.Name,
Network: v.Network,
Address: v.Address,
})
}
return services, nil
}
func (p *grpcPlugin) Close() error {
if closer, ok := p.conn.(io.Closer); ok {
return closer.Close()
}
return nil
}
type sdService struct {
ID string `json:"id"`
Name string `json:"name"`

View File

@ -2,6 +2,7 @@ package service
import (
"context"
"fmt"
"io"
"net"
"os/exec"
@ -13,10 +14,12 @@ import (
"github.com/go-gost/core/listener"
"github.com/go-gost/core/logger"
"github.com/go-gost/core/metrics"
"github.com/go-gost/core/observer"
"github.com/go-gost/core/recorder"
"github.com/go-gost/core/service"
ctxvalue "github.com/go-gost/x/internal/ctx"
xmetrics "github.com/go-gost/x/metrics"
"github.com/go-gost/x/stats"
"github.com/rs/xid"
)
@ -27,6 +30,8 @@ type options struct {
postUp []string
preDown []string
postDown []string
stats *stats.Stats
observer observer.Observer
logger logger.Logger
}
@ -68,6 +73,18 @@ func PostDownOption(cmds []string) Option {
}
}
func StatsOption(stats *stats.Stats) Option {
return func(opts *options) {
opts.stats = stats
}
}
func ObserverOption(observer observer.Observer) Option {
return func(opts *options) {
opts.observer = observer
}
}
func LoggerOption(logger logger.Logger) Option {
return func(opts *options) {
opts.logger = logger
@ -78,6 +95,7 @@ type defaultService struct {
name string
listener listener.Listener
handler handler.Handler
status *Status
options options
}
@ -91,7 +109,13 @@ func NewService(name string, ln listener.Listener, h handler.Handler, opts ...Op
listener: ln,
handler: h,
options: options,
status: &Status{
createTime: time.Now(),
events: make([]Event, 0, MaxEventSize),
stats: options.stats,
},
}
s.setState(StateRunning)
s.execCmds("pre-up", s.options.preUp)
@ -104,6 +128,14 @@ func (s *defaultService) Addr() net.Addr {
func (s *defaultService) Serve() error {
s.execCmds("post-up", s.options.postUp)
s.setState(StateReady)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
if s.status.Stats() != nil {
go s.observeStats(ctx)
}
if v := xmetrics.GetGauge(
xmetrics.MetricServicesGauge,
@ -126,14 +158,25 @@ func (s *defaultService) Serve() error {
if max := 5 * time.Second; tempDelay > max {
tempDelay = max
}
s.setState(StateFailed)
s.options.logger.Warnf("accept: %v, retrying in %v", e, tempDelay)
time.Sleep(tempDelay)
continue
}
s.setState(StateClosed)
s.options.logger.Errorf("accept: %v", e)
return e
}
tempDelay = 0
if tempDelay > 0 {
tempDelay = 0
s.setState(StateReady)
}
s.status.stats.Add(stats.KindTotalConns, 1)
clientAddr := conn.RemoteAddr().String()
clientIP := clientAddr
@ -141,7 +184,7 @@ func (s *defaultService) Serve() error {
clientIP = h
}
ctx := ctxvalue.ContextWithSid(context.Background(), ctxvalue.Sid(xid.New().String()))
ctx := ctxvalue.ContextWithSid(ctx, ctxvalue.Sid(xid.New().String()))
ctx = ctxvalue.ContextWithClientAddr(ctx, ctxvalue.ClientAddr(clientAddr))
ctx = ctxvalue.ContextWithHash(ctx, &ctxvalue.Hash{Source: clientIP})
@ -161,6 +204,9 @@ func (s *defaultService) Serve() error {
}
go func() {
s.status.stats.Add(stats.KindCurrentConns, 1)
defer s.status.stats.Add(stats.KindCurrentConns, -1)
if v := xmetrics.GetCounter(xmetrics.MetricServiceRequestsCounter,
metrics.Labels{"service": s.name, "client": clientIP}); v != nil {
v.Inc()
@ -186,11 +232,16 @@ func (s *defaultService) Serve() error {
metrics.Labels{"service": s.name, "client": clientIP}); v != nil {
v.Inc()
}
s.status.stats.Add(stats.KindTotalErrs, 1)
}
}()
}
}
func (s *defaultService) Status() *Status {
return s.status
}
func (s *defaultService) Close() error {
s.execCmds("pre-down", s.options.preDown)
defer s.execCmds("post-down", s.options.postDown)
@ -214,3 +265,64 @@ func (s *defaultService) execCmds(phase string, cmds []string) {
}
}
}
func (s *defaultService) setState(state State) {
s.status.setState(state)
msg := fmt.Sprintf("service %s is %s", s.name, state)
s.status.addEvent(Event{
Time: time.Now(),
Message: msg,
})
if obs := s.options.observer; obs != nil {
obs.Observe(context.Background(), []observer.Event{ServiceEvent{
Kind: "service",
Service: s.name,
State: state,
Msg: msg,
}})
}
}
func (s *defaultService) observeStats(ctx context.Context) {
if s.options.observer == nil {
return
}
ticker := time.NewTicker(5 * time.Second)
defer ticker.Stop()
for {
select {
case <-ticker.C:
st := s.status.Stats()
if !st.IsUpdated() {
break
}
s.options.observer.Observe(ctx, []observer.Event{
stats.StatsEvent{
Kind: "service",
Service: s.name,
TotalConns: st.Get(stats.KindTotalConns),
CurrentConns: st.Get(stats.KindCurrentConns),
InputBytes: st.Get(stats.KindInputBytes),
OutputBytes: st.Get(stats.KindOutputBytes),
},
})
case <-ctx.Done():
return
}
}
}
type ServiceEvent struct {
Kind string
Service string
State State
Msg string
}
func (ServiceEvent) Type() observer.EventType {
return observer.EventStatus
}

76
service/status.go Normal file
View File

@ -0,0 +1,76 @@
package service
import (
"sync"
"time"
"github.com/go-gost/x/stats"
)
const (
MaxEventSize = 20
)
type State string
const (
StateRunning State = "running"
StateReady State = "ready"
StateFailed State = "failed"
StateClosed State = "closed"
)
type Event struct {
Time time.Time
Message string
}
type Status struct {
createTime time.Time
state State
events []Event
stats *stats.Stats
mu sync.RWMutex
}
func (p *Status) CreateTime() time.Time {
return p.createTime
}
func (p *Status) State() State {
p.mu.RLock()
defer p.mu.RUnlock()
return p.state
}
func (p *Status) setState(state State) {
p.mu.Lock()
defer p.mu.Unlock()
p.state = state
}
func (p *Status) Events() []Event {
events := make([]Event, MaxEventSize)
p.mu.RLock()
defer p.mu.RUnlock()
copy(events, p.events)
return events
}
func (p *Status) addEvent(event Event) {
p.mu.Lock()
defer p.mu.Unlock()
if len(p.events) == MaxEventSize {
events := make([]Event, MaxEventSize-1, MaxEventSize)
copy(events, p.events[1:])
p.events = events
}
p.events = append(p.events, event)
}
func (p *Status) Stats() *stats.Stats {
return p.stats
}

93
stats/stats.go Normal file
View File

@ -0,0 +1,93 @@
package stats
import (
"sync/atomic"
"github.com/go-gost/core/observer"
)
type Kind int
const (
KindTotalConns Kind = 1
KindCurrentConns Kind = 2
KindInputBytes Kind = 3
KindOutputBytes Kind = 4
KindTotalErrs Kind = 5
)
type Stats struct {
updated atomic.Bool
totalConns atomic.Uint64
currentConns atomic.Int64
inputBytes atomic.Uint64
outputBytes atomic.Uint64
totalErrs atomic.Uint64
}
func (s *Stats) Add(kind Kind, n int64) {
if s == nil {
return
}
switch kind {
case KindTotalConns:
if n > 0 {
s.totalConns.Add(uint64(n))
}
case KindCurrentConns:
s.currentConns.Add(n)
case KindInputBytes:
if n > 0 {
s.inputBytes.Add(uint64(n))
}
case KindOutputBytes:
if n > 0 {
s.outputBytes.Add(uint64(n))
}
case KindTotalErrs:
if n > 0 {
s.totalErrs.Add(uint64(n))
}
}
s.updated.Store(true)
}
func (s *Stats) Get(kind Kind) uint64 {
if s == nil {
return 0
}
switch kind {
case KindTotalConns:
return s.totalConns.Load()
case KindCurrentConns:
return uint64(s.currentConns.Load())
case KindInputBytes:
return s.inputBytes.Load()
case KindOutputBytes:
return s.outputBytes.Load()
case KindTotalErrs:
return s.totalErrs.Load()
}
return 0
}
func (s *Stats) IsUpdated() bool {
return s.updated.Swap(false)
}
type StatsEvent struct {
Kind string
Service string
Client string
TotalConns uint64
CurrentConns uint64
InputBytes uint64
OutputBytes uint64
TotalErrs uint64
}
func (StatsEvent) Type() observer.EventType {
return observer.EventStats
}

222
stats/wrapper/conn.go Normal file
View File

@ -0,0 +1,222 @@
package wrapper
import (
"errors"
"io"
"net"
"syscall"
"github.com/go-gost/core/metadata"
xnet "github.com/go-gost/x/internal/net"
"github.com/go-gost/x/internal/net/udp"
"github.com/go-gost/x/stats"
)
var (
errUnsupport = errors.New("unsupported operation")
)
type conn struct {
net.Conn
stats *stats.Stats
}
func WrapConn(c net.Conn, stats *stats.Stats) net.Conn {
if stats == nil {
return c
}
return &conn{
Conn: c,
stats: stats,
}
}
func (c *conn) Read(b []byte) (n int, err error) {
n, err = c.Conn.Read(b)
c.stats.Add(stats.KindInputBytes, int64(n))
return
}
func (c *conn) Write(b []byte) (n int, err error) {
n, err = c.Conn.Write(b)
c.stats.Add(stats.KindOutputBytes, int64(n))
return
}
func (c *conn) SyscallConn() (rc syscall.RawConn, err error) {
if sc, ok := c.Conn.(syscall.Conn); ok {
rc, err = sc.SyscallConn()
return
}
err = errUnsupport
return
}
func (c *conn) Metadata() metadata.Metadata {
if md, ok := c.Conn.(metadata.Metadatable); ok {
return md.Metadata()
}
return nil
}
type packetConn struct {
net.PacketConn
stats *stats.Stats
}
func WrapPacketConn(pc net.PacketConn, stats *stats.Stats) net.PacketConn {
if stats == nil {
return pc
}
return &packetConn{
PacketConn: pc,
stats: stats,
}
}
func (c *packetConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) {
n, addr, err = c.PacketConn.ReadFrom(p)
c.stats.Add(stats.KindInputBytes, int64(n))
return
}
func (c *packetConn) WriteTo(p []byte, addr net.Addr) (n int, err error) {
n, err = c.PacketConn.WriteTo(p, addr)
c.stats.Add(stats.KindOutputBytes, int64(n))
return
}
func (c *packetConn) Metadata() metadata.Metadata {
if md, ok := c.PacketConn.(metadata.Metadatable); ok {
return md.Metadata()
}
return nil
}
type udpConn struct {
net.PacketConn
stats *stats.Stats
}
func WrapUDPConn(pc net.PacketConn, stats *stats.Stats) udp.Conn {
return &udpConn{
PacketConn: pc,
stats: stats,
}
}
func (c *udpConn) RemoteAddr() net.Addr {
if nc, ok := c.PacketConn.(xnet.RemoteAddr); ok {
return nc.RemoteAddr()
}
return nil
}
func (c *udpConn) SetReadBuffer(n int) error {
if nc, ok := c.PacketConn.(xnet.SetBuffer); ok {
return nc.SetReadBuffer(n)
}
return errUnsupport
}
func (c *udpConn) SetWriteBuffer(n int) error {
if nc, ok := c.PacketConn.(xnet.SetBuffer); ok {
return nc.SetWriteBuffer(n)
}
return errUnsupport
}
func (c *udpConn) Read(b []byte) (n int, err error) {
if nc, ok := c.PacketConn.(io.Reader); ok {
n, err = nc.Read(b)
c.stats.Add(stats.KindInputBytes, int64(n))
return
}
err = errUnsupport
return
}
func (c *udpConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) {
n, addr, err = c.PacketConn.ReadFrom(p)
c.stats.Add(stats.KindInputBytes, int64(n))
return
}
func (c *udpConn) ReadFromUDP(b []byte) (n int, addr *net.UDPAddr, err error) {
if nc, ok := c.PacketConn.(udp.ReadUDP); ok {
n, addr, err = nc.ReadFromUDP(b)
c.stats.Add(stats.KindInputBytes, int64(n))
return
}
err = errUnsupport
return
}
func (c *udpConn) ReadMsgUDP(b, oob []byte) (n, oobn, flags int, addr *net.UDPAddr, err error) {
if nc, ok := c.PacketConn.(udp.ReadUDP); ok {
n, oobn, flags, addr, err = nc.ReadMsgUDP(b, oob)
c.stats.Add(stats.KindInputBytes, int64(n))
return
}
err = errUnsupport
return
}
func (c *udpConn) Write(b []byte) (n int, err error) {
if nc, ok := c.PacketConn.(io.Writer); ok {
n, err = nc.Write(b)
c.stats.Add(stats.KindOutputBytes, int64(n))
return
}
err = errUnsupport
return
}
func (c *udpConn) WriteTo(p []byte, addr net.Addr) (n int, err error) {
n, err = c.PacketConn.WriteTo(p, addr)
c.stats.Add(stats.KindOutputBytes, int64(n))
return
}
func (c *udpConn) WriteToUDP(b []byte, addr *net.UDPAddr) (n int, err error) {
if nc, ok := c.PacketConn.(udp.WriteUDP); ok {
n, err = nc.WriteToUDP(b, addr)
c.stats.Add(stats.KindOutputBytes, int64(n))
return
}
err = errUnsupport
return
}
func (c *udpConn) WriteMsgUDP(b, oob []byte, addr *net.UDPAddr) (n, oobn int, err error) {
if nc, ok := c.PacketConn.(udp.WriteUDP); ok {
n, oobn, err = nc.WriteMsgUDP(b, oob, addr)
c.stats.Add(stats.KindOutputBytes, int64(n))
return
}
err = errUnsupport
return
}
func (c *udpConn) SyscallConn() (rc syscall.RawConn, err error) {
if nc, ok := c.PacketConn.(syscall.Conn); ok {
return nc.SyscallConn()
}
err = errUnsupport
return
}
func (c *udpConn) SetDSCP(n int) error {
if nc, ok := c.PacketConn.(xnet.SetDSCP); ok {
return nc.SetDSCP(n)
}
return nil
}
func (c *udpConn) Metadata() metadata.Metadata {
if md, ok := c.PacketConn.(metadata.Metadatable); ok {
return md.Metadata()
}
return nil
}

38
stats/wrapper/io.go Normal file
View File

@ -0,0 +1,38 @@
package wrapper
import (
"io"
"github.com/go-gost/x/stats"
)
// readWriter is an io.ReadWriter with Stats.
type readWriter struct {
io.ReadWriter
stats *stats.Stats
}
func WrapReadWriter(rw io.ReadWriter, stats *stats.Stats) io.ReadWriter {
if stats == nil {
return rw
}
return &readWriter{
ReadWriter: rw,
stats: stats,
}
}
func (p *readWriter) Read(b []byte) (n int, err error) {
n, err = p.ReadWriter.Read(b)
p.stats.Add(stats.KindInputBytes, int64(n))
return
}
func (p *readWriter) Write(b []byte) (n int, err error) {
n, err = p.ReadWriter.Write(b)
p.stats.Add(stats.KindOutputBytes, int64(n))
return
}

32
stats/wrapper/listener.go Normal file
View File

@ -0,0 +1,32 @@
package wrapper
import (
"net"
"github.com/go-gost/x/stats"
)
type listener struct {
stats *stats.Stats
net.Listener
}
func WrapListener(ln net.Listener, stats *stats.Stats) net.Listener {
if stats == nil {
return ln
}
return &listener{
stats: stats,
Listener: ln,
}
}
func (ln *listener) Accept() (net.Conn, error) {
c, err := ln.Listener.Accept()
if err != nil {
return nil, err
}
return WrapConn(c, ln.stats), nil
}