diff --git a/auth/auth.go b/auth/auth.go index b8a3d9e..4373b00 100644 --- a/auth/auth.go +++ b/auth/auth.go @@ -180,11 +180,11 @@ func (p *authenticator) parseAuths(r io.Reader) (auths map[string]string, err er scanner := bufio.NewScanner(r) for scanner.Scan() { - line := scanner.Text() - if n := strings.IndexByte(line, '#'); n >= 0 { - line = line[:n] + line := strings.TrimSpace(scanner.Text()) + if n := strings.IndexByte(line, '#'); n == 0 { + continue } - sp := strings.SplitN(strings.TrimSpace(line), " ", 2) + sp := strings.SplitN(line, " ", 2) if len(sp) == 1 { if k := strings.TrimSpace(sp[0]); k != "" { auths[k] = "" diff --git a/config/config.go b/config/config.go index 2a061e2..dc1ffd6 100644 --- a/config/config.go +++ b/config/config.go @@ -115,7 +115,7 @@ type FileLoader struct { } type RedisLoader struct { - Addr string `yaml:",omitempty" json:"addr,omitempty"` + Addr string `json:"addr"` DB int `yaml:",omitempty" json:"db,omitempty"` Password string `yaml:",omitempty" json:"password,omitempty"` Key string `yaml:",omitempty" json:"key,omitempty"` @@ -132,9 +132,7 @@ type NameserverConfig struct { } type ResolverConfig struct { - Name string `json:"name"` - // inline, file, etc. - Type string `yaml:",omitempty" json:"type,omitempty"` + Name string `json:"name"` Nameservers []*NameserverConfig `json:"nameservers"` } @@ -145,12 +143,34 @@ type HostMappingConfig struct { } type HostsConfig struct { - Name string `json:"name"` - // inline, file, etc. - Type string `yaml:",omitempty" json:"type,omitempty"` + Name string `json:"name"` Mappings []*HostMappingConfig `json:"mappings"` } +type RecorderConfig struct { + Name string `json:"name"` + File *FileRecorder `yaml:",omitempty" json:"file,omitempty"` + Redis *RedisRecorder `yaml:",omitempty" json:"redis,omitempty"` +} + +type FileRecorder struct { + Path string `json:"path"` + Sep string `yaml:",omitempty" json:"sep,omitempty"` +} + +type RedisRecorder struct { + Addr string `json:"addr"` + DB int `yaml:",omitempty" json:"db,omitempty"` + Password string `yaml:",omitempty" json:"password,omitempty"` + Key string `yaml:",omitempty" json:"key,omitempty"` + Type string `yaml:",omitempty" json:"type,omitempty"` +} + +type RecorderObject struct { + Name string `json:"name"` + Record string `json:"record"` +} + type ListenerConfig struct { Type string `json:"type"` Chain string `yaml:",omitempty" json:"chain,omitempty"` @@ -194,17 +214,18 @@ type SockOptsConfig struct { } type ServiceConfig struct { - Name string `json:"name"` - Addr string `yaml:",omitempty" json:"addr,omitempty"` - Interface string `yaml:",omitempty" json:"interface,omitempty"` - SockOpts *SockOptsConfig `yaml:"sockopts,omitempty" json:"sockopts,omitempty"` - Admission string `yaml:",omitempty" json:"admission,omitempty"` - Bypass string `yaml:",omitempty" json:"bypass,omitempty"` - Resolver string `yaml:",omitempty" json:"resolver,omitempty"` - Hosts string `yaml:",omitempty" json:"hosts,omitempty"` - Handler *HandlerConfig `yaml:",omitempty" json:"handler,omitempty"` - Listener *ListenerConfig `yaml:",omitempty" json:"listener,omitempty"` - Forwarder *ForwarderConfig `yaml:",omitempty" json:"forwarder,omitempty"` + Name string `json:"name"` + Addr string `yaml:",omitempty" json:"addr,omitempty"` + Interface string `yaml:",omitempty" json:"interface,omitempty"` + SockOpts *SockOptsConfig `yaml:"sockopts,omitempty" json:"sockopts,omitempty"` + Admission string `yaml:",omitempty" json:"admission,omitempty"` + Bypass string `yaml:",omitempty" json:"bypass,omitempty"` + Resolver string `yaml:",omitempty" json:"resolver,omitempty"` + Hosts string `yaml:",omitempty" json:"hosts,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"` } type ChainConfig struct { @@ -244,6 +265,7 @@ type Config struct { Bypasses []*BypassConfig `yaml:",omitempty" json:"bypasses,omitempty"` Resolvers []*ResolverConfig `yaml:",omitempty" json:"resolvers,omitempty"` Hosts []*HostsConfig `yaml:",omitempty" json:"hosts,omitempty"` + Recorders []*RecorderConfig `yaml:",omitempty" json:"recorders,omitempty"` TLS *TLSConfig `yaml:",omitempty" json:"tls,omitempty"` Log *LogConfig `yaml:",omitempty" json:"log,omitempty"` Profiling *ProfilingConfig `yaml:",omitempty" json:"profiling,omitempty"` diff --git a/config/parsing/parse.go b/config/parsing/parse.go index 221f2cc..f138484 100644 --- a/config/parsing/parse.go +++ b/config/parsing/parse.go @@ -10,6 +10,7 @@ import ( "github.com/go-gost/core/chain" "github.com/go-gost/core/hosts" "github.com/go-gost/core/logger" + "github.com/go-gost/core/recorder" "github.com/go-gost/core/resolver" admission_impl "github.com/go-gost/x/admission" auth_impl "github.com/go-gost/x/auth" @@ -17,6 +18,7 @@ import ( "github.com/go-gost/x/config" hosts_impl "github.com/go-gost/x/hosts" "github.com/go-gost/x/internal/loader" + recorder_impl "github.com/go-gost/x/recorder" "github.com/go-gost/x/registry" resolver_impl "github.com/go-gost/x/resolver" ) @@ -211,3 +213,35 @@ func ParseHosts(cfg *config.HostsConfig) hosts.HostMapper { } return hosts } + +func ParseRecorder(cfg *config.RecorderConfig) (r recorder.Recorder) { + if cfg == nil { + return nil + } + + if cfg.File != nil && cfg.File.Path != "" { + return recorder_impl.FileRecorder(cfg.File.Path, + recorder_impl.SepRecorderOption(cfg.File.Sep)) + } + + if cfg.Redis != nil && + cfg.Redis.Addr != "" && + cfg.Redis.Key != "" { + switch cfg.Redis.Type { + case "list": // redis list + return recorder_impl.RedisListRecorder(cfg.Redis.Addr, + recorder_impl.DBRedisRecorderOption(cfg.Redis.DB), + recorder_impl.KeyRedisRecorderOption(cfg.Redis.Key), + recorder_impl.PasswordRedisRecorderOption(cfg.Redis.Password), + ) + default: // redis set + return recorder_impl.RedisSetRecorder(cfg.Redis.Addr, + recorder_impl.DBRedisRecorderOption(cfg.Redis.DB), + recorder_impl.KeyRedisRecorderOption(cfg.Redis.Key), + recorder_impl.PasswordRedisRecorderOption(cfg.Redis.Password), + ) + } + } + + return +} diff --git a/config/parsing/service.go b/config/parsing/service.go index 5f0186d..7927934 100644 --- a/config/parsing/service.go +++ b/config/parsing/service.go @@ -7,6 +7,7 @@ import ( "github.com/go-gost/core/handler" "github.com/go-gost/core/listener" "github.com/go-gost/core/logger" + "github.com/go-gost/core/recorder" "github.com/go-gost/core/service" "github.com/go-gost/x/config" tls_util "github.com/go-gost/x/internal/util/tls" @@ -104,6 +105,13 @@ func ParseService(cfg *config.ServiceConfig) (service.Service, error) { } } + var recorders []recorder.RecorderObject + for _, r := range cfg.Recorders { + recorders = append(recorders, recorder.RecorderObject{ + Recorder: registry.RecorderRegistry().Get(r.Name), + Record: r.Record, + }) + } router := (&chain.Router{}). WithRetries(cfg.Handler.Retries). // WithTimeout(timeout time.Duration). @@ -112,6 +120,7 @@ func ParseService(cfg *config.ServiceConfig) (service.Service, error) { WithChain(registry.ChainRegistry().Get(cfg.Handler.Chain)). WithResolver(registry.ResolverRegistry().Get(cfg.Resolver)). WithHosts(registry.HostsRegistry().Get(cfg.Hosts)). + WithRecorder(recorders...). WithLogger(handlerLogger) h := registry.HandlerRegistry().Get(cfg.Handler.Type)( diff --git a/go.mod b/go.mod index 35287e9..64384ac 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,7 @@ require ( github.com/docker/libcontainer v2.2.1+incompatible github.com/gin-contrib/cors v1.3.1 github.com/gin-gonic/gin v1.7.7 - github.com/go-gost/core v0.0.0-20220408131722-a117222cde09 + github.com/go-gost/core v0.0.0-20220411145302-03988fee0b9a github.com/go-gost/gosocks4 v0.0.1 github.com/go-gost/gosocks5 v0.3.1-0.20211109033403-d894d75b7f09 github.com/go-gost/relay v0.1.1-0.20211123134818-8ef7fd81ffd7 diff --git a/go.sum b/go.sum index 8fd9e46..665d036 100644 --- a/go.sum +++ b/go.sum @@ -121,8 +121,8 @@ github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-gost/core v0.0.0-20220408131722-a117222cde09 h1:NJJ2AXxnEBEYGL+GFVQ3dLZvYkHDFhxQIpcdNMw0gI4= -github.com/go-gost/core v0.0.0-20220408131722-a117222cde09/go.mod h1:bHVbCS9da6XtKNYMkMUVcck5UqDDUkyC37erVfs4GXQ= +github.com/go-gost/core v0.0.0-20220411145302-03988fee0b9a h1:GYdVcUmEoFVzkPNDavMsDnGA6AM+jgORE4n3/tIiKqI= +github.com/go-gost/core v0.0.0-20220411145302-03988fee0b9a/go.mod h1:bHVbCS9da6XtKNYMkMUVcck5UqDDUkyC37erVfs4GXQ= 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.3.1-0.20211109033403-d894d75b7f09 h1:A95M6UWcfZgOuJkQ7QLfG0Hs5peWIUSysCDNz4pfe04= diff --git a/internal/loader/file.go b/internal/loader/file.go index fecfc72..bd8be11 100644 --- a/internal/loader/file.go +++ b/internal/loader/file.go @@ -11,6 +11,7 @@ type fileLoader struct { filename string } +// FileLoader loads data from file. func FileLoader(filename string) Loader { return &fileLoader{ filename: filename, diff --git a/internal/loader/redis.go b/internal/loader/redis.go index 6f4c1d5..38dac60 100644 --- a/internal/loader/redis.go +++ b/internal/loader/redis.go @@ -45,7 +45,7 @@ type redisSetLoader struct { key string } -// RedisSetLoader loads values from redis set. +// RedisSetLoader loads data from redis set. func RedisSetLoader(addr string, opts ...RedisLoaderOption) Loader { var options redisLoaderOptions for _, opt := range opts { @@ -84,7 +84,7 @@ type redisHashLoader struct { key string } -// RedisHashLoader loads values from redis hash. +// RedisHashLoader loads data from redis hash. func RedisHashLoader(addr string, opts ...RedisLoaderOption) Loader { var options redisLoaderOptions for _, opt := range opts { diff --git a/recorder/file.go b/recorder/file.go new file mode 100644 index 0000000..c0651c9 --- /dev/null +++ b/recorder/file.go @@ -0,0 +1,59 @@ +package recorder + +import ( + "context" + "os" + + "github.com/go-gost/core/recorder" +) + +type fileRecorderOptions struct { + sep string +} + +type FileRecorderOption func(opts *fileRecorderOptions) + +func SepRecorderOption(sep string) FileRecorderOption { + return func(opts *fileRecorderOptions) { + opts.sep = sep + } +} + +type fileRecorder struct { + filename string + sep string +} + +// FileRecorder records data to file. +func FileRecorder(filename string, opts ...FileRecorderOption) recorder.Recorder { + var options fileRecorderOptions + for _, opt := range opts { + opt(&options) + } + + return &fileRecorder{ + filename: filename, + sep: options.sep, + } +} + +func (r *fileRecorder) Record(ctx context.Context, b []byte) error { + f, err := os.OpenFile(r.filename, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) + if err != nil { + return err + } + defer f.Close() + + if _, err = f.Write(b); err != nil { + return err + } + if r.sep != "" { + _, err := f.WriteString(r.sep) + return err + } + return nil +} + +func (r *fileRecorder) Close() error { + return nil +} diff --git a/recorder/redis.go b/recorder/redis.go new file mode 100644 index 0000000..9157140 --- /dev/null +++ b/recorder/redis.go @@ -0,0 +1,101 @@ +package recorder + +import ( + "context" + + "github.com/go-gost/core/recorder" + "github.com/go-redis/redis/v8" +) + +type redisRecorderOptions struct { + db int + password string + key string +} +type RedisRecorderOption func(opts *redisRecorderOptions) + +func DBRedisRecorderOption(db int) RedisRecorderOption { + return func(opts *redisRecorderOptions) { + opts.db = db + } +} + +func PasswordRedisRecorderOption(password string) RedisRecorderOption { + return func(opts *redisRecorderOptions) { + opts.password = password + } +} + +func KeyRedisRecorderOption(key string) RedisRecorderOption { + return func(opts *redisRecorderOptions) { + opts.key = key + } +} + +type redisSetRecorder struct { + client *redis.Client + key string +} + +// RedisSetRecorder records data to a redis set. +func RedisSetRecorder(addr string, opts ...RedisRecorderOption) recorder.Recorder { + var options redisRecorderOptions + for _, opt := range opts { + opt(&options) + } + + return &redisSetRecorder{ + client: redis.NewClient(&redis.Options{ + Addr: addr, + Password: options.password, + DB: options.db, + }), + key: options.key, + } +} + +func (r *redisSetRecorder) Record(ctx context.Context, b []byte) error { + if r.key == "" { + return nil + } + + return r.client.SAdd(ctx, r.key, b).Err() +} + +func (r *redisSetRecorder) Close() error { + return r.client.Close() +} + +type redisListRecorder struct { + client *redis.Client + key string +} + +// RedisListRecorder records data to a redis list. +func RedisListRecorder(addr string, opts ...RedisRecorderOption) recorder.Recorder { + var options redisRecorderOptions + for _, opt := range opts { + opt(&options) + } + + return &redisListRecorder{ + client: redis.NewClient(&redis.Options{ + Addr: addr, + Password: options.password, + DB: options.db, + }), + key: options.key, + } +} + +func (r *redisListRecorder) Record(ctx context.Context, b []byte) error { + if r.key == "" { + return nil + } + + return r.client.LPush(ctx, r.key, b).Err() +} + +func (r *redisListRecorder) Close() error { + return r.client.Close() +} diff --git a/registry/recorder.go b/registry/recorder.go new file mode 100644 index 0000000..56ab418 --- /dev/null +++ b/registry/recorder.go @@ -0,0 +1,42 @@ +package registry + +import ( + "context" + + "github.com/go-gost/core/recorder" +) + +type recorderRegistry struct { + registry +} + +func (r *recorderRegistry) Register(name string, v recorder.Recorder) error { + return r.registry.Register(name, v) +} + +func (r *recorderRegistry) Get(name string) recorder.Recorder { + if name != "" { + return &recorderWrapper{name: name, r: r} + } + return nil +} + +func (r *recorderRegistry) get(name string) recorder.Recorder { + if v := r.registry.Get(name); v != nil { + return v.(recorder.Recorder) + } + return nil +} + +type recorderWrapper struct { + name string + r *recorderRegistry +} + +func (w *recorderWrapper) Record(ctx context.Context, b []byte) error { + v := w.r.get(w.name) + if v == nil { + return nil + } + return v.Record(ctx, b) +} diff --git a/registry/registry.go b/registry/registry.go index 877ba1e..f9aa5b4 100644 --- a/registry/registry.go +++ b/registry/registry.go @@ -10,6 +10,7 @@ import ( "github.com/go-gost/core/bypass" "github.com/go-gost/core/chain" "github.com/go-gost/core/hosts" + "github.com/go-gost/core/recorder" "github.com/go-gost/core/resolver" "github.com/go-gost/core/service" ) @@ -31,6 +32,7 @@ var ( bypassReg Registry[bypass.Bypass] = &bypassRegistry{} resolverReg Registry[resolver.Resolver] = &resolverRegistry{} hostsReg Registry[hosts.HostMapper] = &hostsRegistry{} + recorderReg Registry[recorder.Recorder] = &recorderRegistry{} ) type Registry[T any] interface { @@ -120,3 +122,7 @@ func ResolverRegistry() Registry[resolver.Resolver] { func HostsRegistry() Registry[hosts.HostMapper] { return hostsReg } + +func RecorderRegistry() Registry[recorder.Recorder] { + return recorderReg +}