Compare commits

...

13 Commits

Author SHA1 Message Date
ginuerzh
30cc928705 add observer/stats 2024-07-04 23:03:22 +08:00
ginuerzh
4e831b95e8 fix timeout for router 2024-06-25 20:37:08 +08:00
ginuerzh
ca340b1bf1 add netns option for handler and listener 2024-06-24 21:13:23 +08:00
ginuerzh
5aede9a2b3 add support for linux network namespace 2024-06-21 23:34:12 +08:00
ginuerzh
8d554ddcf7 add filter node option 2024-05-08 21:20:29 +08:00
ginuerzh
5d6c2115fa update go.mod 2024-04-24 23:31:55 +08:00
ginuerzh
f06f3bb46a
Merge pull request #6 from cgroschupp/feature/whitelist
feat: change whitelist bypass behaviour
2024-04-24 22:49:14 +08:00
Christian Groschupp
ed7f2dcdfc feat: change whitelist bypass behaviour 2024-04-16 20:58:50 +02:00
kist
fea73cf682 Update recorder.go
Changed functions name to MetadataRecordOption
2024-03-09 20:45:15 +08:00
ginuerzh
a06608ccaf added http url rewrite setting for forwarder node 2024-01-31 23:17:24 +08:00
ginuerzh
04314fa084 added auther option for node http settings 2024-01-27 21:06:04 +08:00
ginuerzh
5a427b4eaf add observer 2024-01-03 20:53:00 +08:00
ginuerzh
6b5c04b5e4 added logger group 2023-12-19 21:23:06 +08:00
14 changed files with 395 additions and 76 deletions

View File

@ -1,6 +1,9 @@
package bypass package bypass
import "context" import (
"context"
"slices"
)
type Options struct { type Options struct {
Host string Host string
@ -24,6 +27,7 @@ func WithPathOption(path string) Option {
// Bypass is a filter of address (IP or domain). // Bypass is a filter of address (IP or domain).
type Bypass interface { type Bypass interface {
// Contains reports whether the bypass includes addr. // Contains reports whether the bypass includes addr.
IsWhitelist() bool
Contains(ctx context.Context, network, addr string, opts ...Option) bool Contains(ctx context.Context, network, addr string, opts ...Option) bool
} }
@ -38,10 +42,33 @@ func BypassGroup(bypasses ...Bypass) Bypass {
} }
func (p *bypassGroup) Contains(ctx context.Context, network, addr string, opts ...Option) bool { func (p *bypassGroup) Contains(ctx context.Context, network, addr string, opts ...Option) bool {
var whitelist, blacklist []bool
for _, bypass := range p.bypasses { for _, bypass := range p.bypasses {
if bypass != nil && bypass.Contains(ctx, network, addr, opts...) { result := bypass.Contains(ctx, network, addr, opts...)
return true if bypass.IsWhitelist() {
whitelist = append(whitelist, result)
} else {
blacklist = append(blacklist, result)
} }
} }
status := false
if len(whitelist) > 0 {
if slices.Contains(whitelist, false) {
status = false
} else {
status = true
}
}
if !status && len(blacklist) > 0 {
if slices.Contains(blacklist, true) {
status = true
} else {
status = false
}
}
return status
}
func (p *bypassGroup) IsWhitelist() bool {
return false return false
} }

View File

@ -1,6 +1,8 @@
package chain package chain
import ( import (
"regexp"
"github.com/go-gost/core/auth" "github.com/go-gost/core/auth"
"github.com/go-gost/core/bypass" "github.com/go-gost/core/bypass"
"github.com/go-gost/core/hosts" "github.com/go-gost/core/hosts"
@ -9,9 +11,22 @@ import (
"github.com/go-gost/core/selector" "github.com/go-gost/core/selector"
) )
type NodeFilterSettings struct {
Protocol string
Host string
Path string
}
type HTTPURLRewriteSetting struct {
Pattern *regexp.Regexp
Replacement string
}
type HTTPNodeSettings struct { type HTTPNodeSettings struct {
Host string Host string
Header map[string]string Header map[string]string
Auther auth.Authenticator
Rewrite []HTTPURLRewriteSetting
} }
type TLSNodeSettings struct { type TLSNodeSettings struct {
@ -25,18 +40,15 @@ type TLSNodeSettings struct {
} }
type NodeOptions struct { type NodeOptions struct {
Network string
Transport *Transport Transport *Transport
Bypass bypass.Bypass Bypass bypass.Bypass
Resolver resolver.Resolver Resolver resolver.Resolver
HostMapper hosts.HostMapper HostMapper hosts.HostMapper
Metadata metadata.Metadata Filter *NodeFilterSettings
Host string
Network string
Protocol string
Path string
HTTP *HTTPNodeSettings HTTP *HTTPNodeSettings
TLS *TLSNodeSettings TLS *TLSNodeSettings
Auther auth.Authenticator Metadata metadata.Metadata
} }
type NodeOption func(*NodeOptions) type NodeOption func(*NodeOptions)
@ -65,33 +77,15 @@ func HostMapperNodeOption(m hosts.HostMapper) NodeOption {
} }
} }
func HostNodeOption(host string) NodeOption {
return func(o *NodeOptions) {
o.Host = host
}
}
func NetworkNodeOption(network string) NodeOption { func NetworkNodeOption(network string) NodeOption {
return func(o *NodeOptions) { return func(o *NodeOptions) {
o.Network = network o.Network = network
} }
} }
func ProtocolNodeOption(protocol string) NodeOption { func NodeFilterOption(filter *NodeFilterSettings) NodeOption {
return func(o *NodeOptions) { return func(o *NodeOptions) {
o.Protocol = protocol o.Filter = filter
}
}
func PathNodeOption(path string) NodeOption {
return func(o *NodeOptions) {
o.Path = path
}
}
func MetadataNodeOption(md metadata.Metadata) NodeOption {
return func(o *NodeOptions) {
o.Metadata = md
} }
} }
@ -107,9 +101,9 @@ func TLSNodeOption(tlsSettings *TLSNodeSettings) NodeOption {
} }
} }
func AutherNodeOption(auther auth.Authenticator) NodeOption { func MetadataNodeOption(md metadata.Metadata) NodeOption {
return func(o *NodeOptions) { return func(o *NodeOptions) {
o.Auther = auther o.Metadata = md
} }
} }

View File

@ -36,8 +36,8 @@ func (*route) Dial(ctx context.Context, network, address string, opts ...DialOpt
} }
netd := dialer.NetDialer{ netd := dialer.NetDialer{
Timeout: options.Timeout,
Interface: options.Interface, Interface: options.Interface,
Netns: options.Netns,
Logger: options.Logger, Logger: options.Logger,
} }
if options.SockOpts != nil { if options.SockOpts != nil {
@ -93,26 +93,26 @@ func (r *route) Nodes() []*Node {
} }
type DialOptions struct { type DialOptions struct {
Timeout time.Duration
Interface string Interface string
Netns string
SockOpts *SockOpts SockOpts *SockOpts
Logger logger.Logger Logger logger.Logger
} }
type DialOption func(opts *DialOptions) type DialOption func(opts *DialOptions)
func TimeoutDialOption(d time.Duration) DialOption {
return func(opts *DialOptions) {
opts.Timeout = d
}
}
func InterfaceDialOption(ifName string) DialOption { func InterfaceDialOption(ifName string) DialOption {
return func(opts *DialOptions) { return func(opts *DialOptions) {
opts.Interface = ifName opts.Interface = ifName
} }
} }
func NetnsDialOption(netns string) DialOption {
return func(opts *DialOptions) {
opts.Netns = netns
}
}
func SockOptsDialOption(so *SockOpts) DialOption { func SockOptsDialOption(so *SockOpts) DialOption {
return func(opts *DialOptions) { return func(opts *DialOptions) {
opts.SockOpts = so opts.SockOpts = so

View File

@ -21,6 +21,7 @@ type RouterOptions struct {
Retries int Retries int
Timeout time.Duration Timeout time.Duration
IfceName string IfceName string
Netns string
SockOpts *SockOpts SockOpts *SockOpts
Chain Chainer Chain Chainer
Resolver resolver.Resolver Resolver resolver.Resolver
@ -37,6 +38,12 @@ func InterfaceRouterOption(ifceName string) RouterOption {
} }
} }
func NetnsRouterOption(netns string) RouterOption {
return func(o *RouterOptions) {
o.Netns = netns
}
}
func SockOptsRouterOption(so *SockOpts) RouterOption { func SockOptsRouterOption(so *SockOpts) RouterOption {
return func(o *RouterOptions) { return func(o *RouterOptions) {
o.SockOpts = so o.SockOpts = so
@ -96,6 +103,10 @@ func NewRouter(opts ...RouterOption) *Router {
opt(&r.options) opt(&r.options)
} }
} }
if r.options.Timeout == 0 {
r.options.Timeout = 15 * time.Second
}
if r.options.Logger == nil { if r.options.Logger == nil {
r.options.Logger = logger.Default().WithFields(map[string]any{"kind": "router"}) r.options.Logger = logger.Default().WithFields(map[string]any{"kind": "router"})
} }
@ -110,6 +121,12 @@ func (r *Router) Options() *RouterOptions {
} }
func (r *Router) Dial(ctx context.Context, network, address string) (conn net.Conn, err error) { func (r *Router) Dial(ctx context.Context, network, address string) (conn net.Conn, err error) {
if r.options.Timeout > 0 {
var cancel context.CancelFunc
ctx, cancel = context.WithTimeout(ctx, r.options.Timeout)
defer cancel()
}
host := address host := address
if h, _, _ := net.SplitHostPort(address); h != "" { if h, _, _ := net.SplitHostPort(address); h != "" {
host = h host = h
@ -181,9 +198,9 @@ func (r *Router) dial(ctx context.Context, network, address string) (conn net.Co
} }
conn, err = route.Dial(ctx, network, ipAddr, conn, err = route.Dial(ctx, network, ipAddr,
InterfaceDialOption(r.options.IfceName), InterfaceDialOption(r.options.IfceName),
NetnsDialOption(r.options.Netns),
SockOptsDialOption(r.options.SockOpts), SockOptsDialOption(r.options.SockOpts),
LoggerDialOption(r.options.Logger), LoggerDialOption(r.options.Logger),
TimeoutDialOption(r.options.Timeout),
) )
if err == nil { if err == nil {
break break
@ -195,6 +212,12 @@ func (r *Router) dial(ctx context.Context, network, address string) (conn net.Co
} }
func (r *Router) Bind(ctx context.Context, network, address string, opts ...BindOption) (ln net.Listener, err error) { func (r *Router) Bind(ctx context.Context, network, address string, opts ...BindOption) (ln net.Listener, err error) {
if r.options.Timeout > 0 {
var cancel context.CancelFunc
ctx, cancel = context.WithTimeout(ctx, r.options.Timeout)
defer cancel()
}
count := r.options.Retries + 1 count := r.options.Retries + 1
if count <= 0 { if count <= 0 {
count = 1 count = 1

View File

@ -3,7 +3,6 @@ package chain
import ( import (
"context" "context"
"net" "net"
"time"
net_dialer "github.com/go-gost/core/common/net/dialer" net_dialer "github.com/go-gost/core/common/net/dialer"
"github.com/go-gost/core/connector" "github.com/go-gost/core/connector"
@ -13,9 +12,9 @@ import (
type TransportOptions struct { type TransportOptions struct {
Addr string Addr string
IfceName string IfceName string
Netns string
SockOpts *SockOpts SockOpts *SockOpts
Route Route Route Route
Timeout time.Duration
} }
type TransportOption func(*TransportOptions) type TransportOption func(*TransportOptions)
@ -32,6 +31,12 @@ func InterfaceTransportOption(ifceName string) TransportOption {
} }
} }
func NetnsTransportOption(netns string) TransportOption {
return func(o *TransportOptions) {
o.Netns = netns
}
}
func SockOptsTransportOption(so *SockOpts) TransportOption { func SockOptsTransportOption(so *SockOpts) TransportOption {
return func(o *TransportOptions) { return func(o *TransportOptions) {
o.SockOpts = so o.SockOpts = so
@ -44,12 +49,6 @@ func RouteTransportOption(route Route) TransportOption {
} }
} }
func TimeoutTransportOption(timeout time.Duration) TransportOption {
return func(o *TransportOptions) {
o.Timeout = timeout
}
}
type Transport struct { type Transport struct {
dialer dialer.Dialer dialer dialer.Dialer
connector connector.Connector connector connector.Connector
@ -73,7 +72,7 @@ func NewTransport(d dialer.Dialer, c connector.Connector, opts ...TransportOptio
func (tr *Transport) Dial(ctx context.Context, addr string) (net.Conn, error) { func (tr *Transport) Dial(ctx context.Context, addr string) (net.Conn, error) {
netd := &net_dialer.NetDialer{ netd := &net_dialer.NetDialer{
Interface: tr.options.IfceName, Interface: tr.options.IfceName,
Timeout: tr.options.Timeout, Netns: tr.options.Netns,
} }
if tr.options.SockOpts != nil { if tr.options.SockOpts != nil {
netd.Mark = tr.options.SockOpts.Mark netd.Mark = tr.options.SockOpts.Mark
@ -108,7 +107,7 @@ func (tr *Transport) Handshake(ctx context.Context, conn net.Conn) (net.Conn, er
func (tr *Transport) Connect(ctx context.Context, conn net.Conn, network, address string) (net.Conn, error) { func (tr *Transport) Connect(ctx context.Context, conn net.Conn, network, address string) (net.Conn, error) {
netd := &net_dialer.NetDialer{ netd := &net_dialer.NetDialer{
Interface: tr.options.IfceName, Interface: tr.options.IfceName,
Timeout: tr.options.Timeout, Netns: tr.options.Netns,
} }
if tr.options.SockOpts != nil { if tr.options.SockOpts != nil {
netd.Mark = tr.options.SockOpts.Mark netd.Mark = tr.options.SockOpts.Mark

View File

@ -4,12 +4,14 @@ import (
"context" "context"
"fmt" "fmt"
"net" "net"
"runtime"
"strings" "strings"
"syscall" "syscall"
"time" "time"
xnet "github.com/go-gost/core/common/net" xnet "github.com/go-gost/core/common/net"
"github.com/go-gost/core/logger" "github.com/go-gost/core/logger"
"github.com/vishvananda/netns"
) )
const ( const (
@ -22,8 +24,8 @@ var (
type NetDialer struct { type NetDialer struct {
Interface string Interface string
Netns string
Mark int Mark int
Timeout time.Duration
DialFunc func(ctx context.Context, network, addr string) (net.Conn, error) DialFunc func(ctx context.Context, network, addr string) (net.Conn, error)
Logger logger.Logger Logger logger.Logger
} }
@ -33,20 +35,36 @@ func (d *NetDialer) Dial(ctx context.Context, network, addr string) (conn net.Co
d = DefaultNetDialer d = DefaultNetDialer
} }
timeout := d.Timeout log := d.Logger
if timeout <= 0 { if log == nil {
timeout = DefaultTimeout log = logger.Default()
}
if d.Netns != "" {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
originNs, err := netns.Get()
if err != nil {
return nil, fmt.Errorf("netns.Get(): %v", err)
}
defer netns.Set(originNs)
ns, err := netns.GetFromName(d.Netns)
if err != nil {
return nil, fmt.Errorf("netns.GetFromName(%s): %v", d.Netns, err)
}
defer ns.Close()
if err := netns.Set(ns); err != nil {
return nil, fmt.Errorf("netns.Set(%s): %v", d.Netns, err)
}
} }
if d.DialFunc != nil { if d.DialFunc != nil {
return d.DialFunc(ctx, network, addr) return d.DialFunc(ctx, network, addr)
} }
log := d.Logger
if log == nil {
log = logger.Default()
}
switch network { switch network {
case "unix": case "unix":
netd := net.Dialer{} netd := net.Dialer{}
@ -54,7 +72,6 @@ func (d *NetDialer) Dial(ctx context.Context, network, addr string) (conn net.Co
default: default:
} }
deadline := time.Now().Add(timeout)
ifces := strings.Split(d.Interface, ",") ifces := strings.Split(d.Interface, ",")
for _, ifce := range ifces { for _, ifce := range ifces {
strict := strings.HasSuffix(ifce, "!") strict := strings.HasSuffix(ifce, "!")
@ -67,7 +84,7 @@ func (d *NetDialer) Dial(ctx context.Context, network, addr string) (conn net.Co
} }
for _, ifAddr := range ifAddrs { for _, ifAddr := range ifAddrs {
conn, err = d.dialOnce(ctx, network, addr, ifceName, ifAddr, deadline, log) conn, err = d.dialOnce(ctx, network, addr, ifceName, ifAddr, log)
if err == nil { if err == nil {
return return
} }
@ -79,17 +96,13 @@ func (d *NetDialer) Dial(ctx context.Context, network, addr string) (conn net.Co
!strings.Contains(err.Error(), "mismatched local address type") { !strings.Contains(err.Error(), "mismatched local address type") {
return return
} }
if time.Until(deadline) < 0 {
return
}
} }
} }
return return
} }
func (d *NetDialer) dialOnce(ctx context.Context, network, addr, ifceName string, ifAddr net.Addr, deadline time.Time, log logger.Logger) (net.Conn, error) { func (d *NetDialer) dialOnce(ctx context.Context, network, addr, ifceName string, ifAddr net.Addr, log logger.Logger) (net.Conn, error) {
if ifceName != "" { if ifceName != "" {
log.Debugf("interface: %s %v/%s", ifceName, ifAddr, network) log.Debugf("interface: %s %v/%s", ifceName, ifAddr, network)
} }
@ -133,7 +146,6 @@ func (d *NetDialer) dialOnce(ctx context.Context, network, addr, ifceName string
return nil, fmt.Errorf("dial: unsupported network %s", network) return nil, fmt.Errorf("dial: unsupported network %s", network)
} }
netd := net.Dialer{ netd := net.Dialer{
Deadline: deadline,
LocalAddr: ifAddr, LocalAddr: ifAddr,
Control: func(network, address string, c syscall.RawConn) error { Control: func(network, address string, c syscall.RawConn) error {
return c.Control(func(fd uintptr) { return c.Control(func(fd uintptr) {
@ -150,5 +162,10 @@ func (d *NetDialer) dialOnce(ctx context.Context, network, addr, ifceName string
}) })
}, },
} }
if d.Netns != "" {
// https://github.com/golang/go/issues/44922#issuecomment-796645858
netd.FallbackDelay = -1
}
return netd.DialContext(ctx, network, addr) return netd.DialContext(ctx, network, addr)
} }

9
go.mod
View File

@ -1,5 +1,10 @@
module github.com/go-gost/core module github.com/go-gost/core
go 1.18 go 1.22
require golang.org/x/sys v0.12.0 toolchain go1.22.2
require (
github.com/vishvananda/netns v0.0.4
golang.org/x/sys v0.21.0
)

6
go.sum
View File

@ -1,2 +1,4 @@
golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= github.com/vishvananda/netns v0.0.4 h1:Oeaw1EM2JMxD51g9uhtC0D7erkIjgmj8+JZc26m1YX8=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= github.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=

View File

@ -11,6 +11,7 @@ import (
"github.com/go-gost/core/limiter/traffic" "github.com/go-gost/core/limiter/traffic"
"github.com/go-gost/core/logger" "github.com/go-gost/core/logger"
"github.com/go-gost/core/metadata" "github.com/go-gost/core/metadata"
"github.com/go-gost/core/observer"
) )
type Options struct { type Options struct {
@ -22,7 +23,9 @@ type Options struct {
Limiter traffic.TrafficLimiter Limiter traffic.TrafficLimiter
TLSConfig *tls.Config TLSConfig *tls.Config
Logger logger.Logger Logger logger.Logger
Observer observer.Observer
Service string Service string
Netns string
} }
type Option func(opts *Options) type Option func(opts *Options)
@ -75,12 +78,24 @@ func LoggerOption(logger logger.Logger) Option {
} }
} }
func ObserverOption(observer observer.Observer) Option {
return func(opts *Options) {
opts.Observer = observer
}
}
func ServiceOption(service string) Option { func ServiceOption(service string) Option {
return func(opts *Options) { return func(opts *Options) {
opts.Service = service opts.Service = service
} }
} }
func NetnsOption(netns string) Option {
return func(opts *Options) {
opts.Netns = netns
}
}
type HandleOptions struct { type HandleOptions struct {
Metadata metadata.Metadata Metadata metadata.Metadata
} }

View File

@ -10,6 +10,7 @@ import (
"github.com/go-gost/core/limiter/conn" "github.com/go-gost/core/limiter/conn"
"github.com/go-gost/core/limiter/traffic" "github.com/go-gost/core/limiter/traffic"
"github.com/go-gost/core/logger" "github.com/go-gost/core/logger"
"github.com/go-gost/core/observer/stats"
) )
type Options struct { type Options struct {
@ -21,9 +22,12 @@ type Options struct {
TrafficLimiter traffic.TrafficLimiter TrafficLimiter traffic.TrafficLimiter
ConnLimiter conn.ConnLimiter ConnLimiter conn.ConnLimiter
Chain chain.Chainer Chain chain.Chainer
Stats *stats.Stats
Logger logger.Logger Logger logger.Logger
Service string Service string
ProxyProtocol int ProxyProtocol int
Netns string
Router *chain.Router
} }
type Option func(opts *Options) type Option func(opts *Options)
@ -70,9 +74,9 @@ func ConnLimiterOption(limiter conn.ConnLimiter) Option {
} }
} }
func ChainOption(chain chain.Chainer) Option { func StatsOption(stats *stats.Stats) Option {
return func(opts *Options) { return func(opts *Options) {
opts.Chain = chain opts.Stats = stats
} }
} }
@ -93,3 +97,15 @@ func ProxyProtocolOption(ppv int) Option {
opts.ProxyProtocol = ppv opts.ProxyProtocol = ppv
} }
} }
func NetnsOption(netns string) Option {
return func(opts *Options) {
opts.Netns = netns
}
}
func RouterOption(router *chain.Router) Option {
return func(opts *Options) {
opts.Router = router
}
}

View File

@ -55,3 +55,109 @@ func Default() Logger {
func SetDefault(logger Logger) { func SetDefault(logger Logger) {
defaultLogger = logger defaultLogger = logger
} }
type loggerGroup struct {
loggers []Logger
}
func LoggerGroup(loggers ...Logger) Logger {
return &loggerGroup{
loggers: loggers,
}
}
func (l *loggerGroup) WithFields(m map[string]any) Logger {
lg := &loggerGroup{}
for i := range l.loggers {
lg.loggers = append(lg.loggers, l.loggers[i].WithFields(m))
}
return lg
}
func (l *loggerGroup) Trace(args ...any) {
for _, lg := range l.loggers {
lg.Trace(args...)
}
}
func (l *loggerGroup) Tracef(format string, args ...any) {
for _, lg := range l.loggers {
lg.Tracef(format, args...)
}
}
func (l *loggerGroup) Debug(args ...any) {
for _, lg := range l.loggers {
lg.Debug(args...)
}
}
func (l *loggerGroup) Debugf(format string, args ...any) {
for _, lg := range l.loggers {
lg.Debugf(format, args...)
}
}
func (l *loggerGroup) Info(args ...any) {
for _, lg := range l.loggers {
lg.Info(args...)
}
}
func (l *loggerGroup) Infof(format string, args ...any) {
for _, lg := range l.loggers {
lg.Infof(format, args...)
}
}
func (l *loggerGroup) Warn(args ...any) {
for _, lg := range l.loggers {
lg.Warn(args...)
}
}
func (l *loggerGroup) Warnf(format string, args ...any) {
for _, lg := range l.loggers {
lg.Warnf(format, args...)
}
}
func (l *loggerGroup) Error(args ...any) {
for _, lg := range l.loggers {
lg.Error(args...)
}
}
func (l *loggerGroup) Errorf(format string, args ...any) {
for _, lg := range l.loggers {
lg.Errorf(format, args...)
}
}
func (l *loggerGroup) Fatal(args ...any) {
for _, lg := range l.loggers {
lg.Fatal(args...)
}
}
func (l *loggerGroup) Fatalf(format string, args ...any) {
for _, lg := range l.loggers {
lg.Fatalf(format, args...)
}
}
func (l *loggerGroup) GetLevel() LogLevel {
for _, lg := range l.loggers {
return lg.GetLevel()
}
return InfoLevel
}
func (l *loggerGroup) IsLevelEnabled(level LogLevel) bool {
for _, lg := range l.loggers {
if lg.IsLevelEnabled(level) {
return true
}
}
return false
}

22
observer/observer.go Normal file
View File

@ -0,0 +1,22 @@
package observer
import "context"
type Options struct{}
type Option func(opts *Options)
type Observer interface {
Observe(ctx context.Context, events []Event, opts ...Option) error
}
type EventType string
const (
EventStatus EventType = "status"
EventStats EventType = "stats"
)
type Event interface {
Type() EventType
}

93
observer/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
}

View File

@ -10,7 +10,7 @@ type RecordOptions struct {
type RecordOption func(opts *RecordOptions) type RecordOption func(opts *RecordOptions)
func MetadataReocrdOption(md any) RecordOption { func MetadataRecordOption(md any) RecordOption {
return func(opts *RecordOptions) { return func(opts *RecordOptions) {
opts.Metadata = md opts.Metadata = md
} }