add file and redis loader

This commit is contained in:
ginuerzh
2022-04-11 00:03:04 +08:00
parent 3bc2524068
commit d6f8ec5116
14 changed files with 805 additions and 86 deletions

View File

@ -1,19 +1,61 @@
package admission package admission
import ( import (
"bufio"
"context"
"io"
"net" "net"
"strings"
"sync"
"time"
admission_pkg "github.com/go-gost/core/admission" admission_pkg "github.com/go-gost/core/admission"
"github.com/go-gost/core/logger" "github.com/go-gost/core/logger"
"github.com/go-gost/x/internal/util/matcher" "github.com/go-gost/x/internal/loader"
"github.com/go-gost/x/internal/matcher"
) )
type options struct { type options struct {
reverse bool
matchers []string
fileLoader loader.Loader
redisLoader loader.Loader
period time.Duration
logger logger.Logger logger logger.Logger
} }
type Option func(opts *options) type Option func(opts *options)
func ReverseOption(reverse bool) Option {
return func(opts *options) {
opts.reverse = reverse
}
}
func MatchersOption(matchers []string) Option {
return func(opts *options) {
opts.matchers = matchers
}
}
func ReloadPeriodOption(period time.Duration) Option {
return func(opts *options) {
opts.period = period
}
}
func FileLoaderOption(fileLoader loader.Loader) Option {
return func(opts *options) {
opts.fileLoader = fileLoader
}
}
func RedisLoaderOption(redisLoader loader.Loader) Option {
return func(opts *options) {
opts.redisLoader = redisLoader
}
}
func LoggerOption(logger logger.Logger) Option { func LoggerOption(logger logger.Logger) Option {
return func(opts *options) { return func(opts *options) {
opts.logger = logger opts.logger = logger
@ -23,18 +65,81 @@ func LoggerOption(logger logger.Logger) Option {
type admission struct { type admission struct {
ipMatcher matcher.Matcher ipMatcher matcher.Matcher
cidrMatcher matcher.Matcher cidrMatcher matcher.Matcher
reversed bool mu sync.RWMutex
cancelFunc context.CancelFunc
options options options options
} }
// NewAdmissionPatterns creates and initializes a new Admission using matcher patterns as its match rules. // NewAdmission creates and initializes a new Admission using matcher patterns as its match rules.
// The rules will be reversed if the reverse is true. // The rules will be reversed if the reverse is true.
func NewAdmission(reversed bool, patterns []string, opts ...Option) admission_pkg.Admission { func NewAdmission(opts ...Option) admission_pkg.Admission {
var options options var options options
for _, opt := range opts { for _, opt := range opts {
opt(&options) opt(&options)
} }
ctx, cancel := context.WithCancel(context.TODO())
p := &admission{
cancelFunc: cancel,
options: options,
}
if err := p.reload(ctx); err != nil {
options.logger.Warnf("reload: %v", err)
}
if p.options.period > 0 {
go p.periodReload(ctx)
}
return p
}
func (p *admission) Admit(addr string) bool {
if addr == "" || p == nil {
return false
}
// try to strip the port
if host, _, _ := net.SplitHostPort(addr); host != "" {
addr = host
}
matched := p.matched(addr)
b := !p.options.reverse && matched ||
p.options.reverse && !matched
return b
}
func (p *admission) periodReload(ctx context.Context) error {
period := p.options.period
if period < time.Second {
period = time.Second
}
ticker := time.NewTicker(period)
defer ticker.Stop()
for {
select {
case <-ticker.C:
if err := p.reload(ctx); err != nil {
p.options.logger.Warnf("reload: %v", err)
// return err
}
p.options.logger.Debugf("admission reload done")
case <-ctx.Done():
return ctx.Err()
}
}
}
func (p *admission) reload(ctx context.Context) error {
v, err := p.load(ctx)
if err != nil {
return err
}
patterns := append(p.options.matchers, v...)
var ips []net.IP var ips []net.IP
var inets []*net.IPNet var inets []*net.IPNet
for _, pattern := range patterns { for _, pattern := range patterns {
@ -47,36 +152,75 @@ func NewAdmission(reversed bool, patterns []string, opts ...Option) admission_pk
continue continue
} }
} }
return &admission{
reversed: reversed, p.mu.Lock()
options: options, defer p.mu.Unlock()
ipMatcher: matcher.IPMatcher(ips),
cidrMatcher: matcher.CIDRMatcher(inets), p.ipMatcher = matcher.IPMatcher(ips)
} p.cidrMatcher = matcher.CIDRMatcher(inets)
return nil
} }
func (p *admission) Admit(addr string) bool { func (p *admission) load(ctx context.Context) (patterns []string, err error) {
if addr == "" || p == nil { if p.options.fileLoader != nil {
p.options.logger.Debugf("admission: %v is denied", addr) r, er := p.options.fileLoader.Load(ctx)
return false if er != nil {
p.options.logger.Warnf("file loader: %v", er)
}
if v, _ := p.parsePatterns(r); v != nil {
patterns = append(patterns, v...)
}
}
if p.options.redisLoader != nil {
r, er := p.options.redisLoader.Load(ctx)
if er != nil {
p.options.logger.Warnf("redis loader: %v", er)
}
if v, _ := p.parsePatterns(r); v != nil {
patterns = append(patterns, v...)
}
} }
// try to strip the port return
if host, _, _ := net.SplitHostPort(addr); host != "" { }
addr = host
func (p *admission) parsePatterns(r io.Reader) (patterns []string, err error) {
if r == nil {
return
} }
matched := p.matched(addr) scanner := bufio.NewScanner(r)
for scanner.Scan() {
b := !p.reversed && matched || line := scanner.Text()
p.reversed && !matched if n := strings.IndexByte(line, '#'); n >= 0 {
if !b { line = line[:n]
p.options.logger.Debugf("admission: %v is denied", addr)
} }
return b line = strings.TrimSpace(line)
if line != "" {
patterns = append(patterns, line)
}
}
err = scanner.Err()
return
} }
func (p *admission) matched(addr string) bool { func (p *admission) matched(addr string) bool {
p.mu.RLock()
defer p.mu.RUnlock()
return p.ipMatcher.Match(addr) || return p.ipMatcher.Match(addr) ||
p.cidrMatcher.Match(addr) p.cidrMatcher.Match(addr)
} }
func (p *admission) Close() error {
p.cancelFunc()
if p.options.fileLoader != nil {
p.options.fileLoader.Close()
}
if p.options.redisLoader != nil {
p.options.redisLoader.Close()
}
return nil
}

View File

@ -1,27 +1,213 @@
package auth package auth
import ( import (
"bufio"
"context"
"io"
"strings"
"sync"
"time"
"github.com/go-gost/core/auth" "github.com/go-gost/core/auth"
"github.com/go-gost/core/logger"
"github.com/go-gost/x/internal/loader"
) )
type options struct {
auths map[string]string
fileLoader loader.Loader
redisLoader loader.Loader
period time.Duration
logger logger.Logger
}
type Option func(opts *options)
func AuthsPeriodOption(auths map[string]string) Option {
return func(opts *options) {
opts.auths = auths
}
}
func ReloadPeriodOption(period time.Duration) Option {
return func(opts *options) {
opts.period = period
}
}
func FileLoaderOption(fileLoader loader.Loader) Option {
return func(opts *options) {
opts.fileLoader = fileLoader
}
}
func RedisLoaderOption(redisLoader loader.Loader) Option {
return func(opts *options) {
opts.redisLoader = redisLoader
}
}
func LoggerOption(logger logger.Logger) Option {
return func(opts *options) {
opts.logger = logger
}
}
// authenticator is an Authenticator that authenticates client by key-value pairs. // authenticator is an Authenticator that authenticates client by key-value pairs.
type authenticator struct { type authenticator struct {
kvs map[string]string kvs map[string]string
mu sync.RWMutex
cancelFunc context.CancelFunc
options options
} }
// NewAuthenticator creates an Authenticator that authenticates client by pre-defined user mapping. // NewAuthenticator creates an Authenticator that authenticates client by pre-defined user mapping.
func NewAuthenticator(kvs map[string]string) auth.Authenticator { func NewAuthenticator(opts ...Option) auth.Authenticator {
return &authenticator{ var options options
kvs: kvs, for _, opt := range opts {
opt(&options)
} }
ctx, cancel := context.WithCancel(context.TODO())
p := &authenticator{
kvs: make(map[string]string),
cancelFunc: cancel,
options: options,
}
if err := p.reload(ctx); err != nil {
options.logger.Warnf("reload: %v", err)
}
if p.options.period > 0 {
go p.periodReload(ctx)
}
return p
} }
// Authenticate checks the validity of the provided user-password pair. // Authenticate checks the validity of the provided user-password pair.
func (au *authenticator) Authenticate(user, password string) bool { func (p *authenticator) Authenticate(user, password string) bool {
if au == nil || len(au.kvs) == 0 { if p == nil || len(p.kvs) == 0 {
return true return true
} }
v, ok := au.kvs[user] p.mu.RLock()
defer p.mu.RUnlock()
v, ok := p.kvs[user]
return ok && (v == "" || password == v) return ok && (v == "" || password == v)
} }
func (p *authenticator) periodReload(ctx context.Context) error {
period := p.options.period
if period < time.Second {
period = time.Second
}
ticker := time.NewTicker(period)
defer ticker.Stop()
for {
select {
case <-ticker.C:
if err := p.reload(ctx); err != nil {
p.options.logger.Warnf("reload: %v", err)
// return err
}
p.options.logger.Debugf("auther reload done")
case <-ctx.Done():
return ctx.Err()
}
}
}
func (p *authenticator) reload(ctx context.Context) error {
kvs := make(map[string]string)
for k, v := range p.options.auths {
kvs[k] = v
}
m, err := p.load(ctx)
if err != nil {
return err
}
for k, v := range m {
kvs[k] = v
}
p.mu.Lock()
defer p.mu.Unlock()
p.kvs = kvs
return nil
}
func (p *authenticator) load(ctx context.Context) (m map[string]string, err error) {
m = make(map[string]string)
if p.options.fileLoader != nil {
r, er := p.options.fileLoader.Load(ctx)
if er != nil {
p.options.logger.Warnf("file loader: %v", er)
}
if auths, _ := p.parseAuths(r); auths != nil {
for k, v := range auths {
m[k] = v
}
}
}
if p.options.redisLoader != nil {
r, er := p.options.redisLoader.Load(ctx)
if er != nil {
p.options.logger.Warnf("redis loader: %v", er)
}
if auths, _ := p.parseAuths(r); auths != nil {
for k, v := range auths {
m[k] = v
}
}
}
return
}
func (p *authenticator) parseAuths(r io.Reader) (auths map[string]string, err error) {
if r == nil {
return
}
auths = make(map[string]string)
scanner := bufio.NewScanner(r)
for scanner.Scan() {
line := scanner.Text()
if n := strings.IndexByte(line, '#'); n >= 0 {
line = line[:n]
}
sp := strings.SplitN(strings.TrimSpace(line), " ", 2)
if len(sp) == 1 {
if k := strings.TrimSpace(sp[0]); k != "" {
auths[k] = ""
}
}
if len(sp) == 2 {
if k := strings.TrimSpace(sp[0]); k != "" {
auths[k] = strings.TrimSpace(sp[1])
}
}
}
err = scanner.Err()
return
}
func (p *authenticator) Close() error {
p.cancelFunc()
if p.options.fileLoader != nil {
p.options.fileLoader.Close()
}
if p.options.redisLoader != nil {
p.options.redisLoader.Close()
}
return nil
}

View File

@ -1,20 +1,61 @@
package bypass package bypass
import ( import (
"bufio"
"context"
"io"
"net" "net"
"strings" "strings"
"sync"
"time"
bypass_pkg "github.com/go-gost/core/bypass" bypass_pkg "github.com/go-gost/core/bypass"
"github.com/go-gost/core/logger" "github.com/go-gost/core/logger"
"github.com/go-gost/x/internal/util/matcher" "github.com/go-gost/x/internal/loader"
"github.com/go-gost/x/internal/matcher"
) )
type options struct { type options struct {
reverse bool
matchers []string
fileLoader loader.Loader
redisLoader loader.Loader
period time.Duration
logger logger.Logger logger logger.Logger
} }
type Option func(opts *options) type Option func(opts *options)
func ReverseOption(reverse bool) Option {
return func(opts *options) {
opts.reverse = reverse
}
}
func MatchersOption(matchers []string) Option {
return func(opts *options) {
opts.matchers = matchers
}
}
func ReloadPeriodOption(period time.Duration) Option {
return func(opts *options) {
opts.period = period
}
}
func FileLoaderOption(fileLoader loader.Loader) Option {
return func(opts *options) {
opts.fileLoader = fileLoader
}
}
func RedisLoaderOption(redisLoader loader.Loader) Option {
return func(opts *options) {
opts.redisLoader = redisLoader
}
}
func LoggerOption(logger logger.Logger) Option { func LoggerOption(logger logger.Logger) Option {
return func(opts *options) { return func(opts *options) {
opts.logger = logger opts.logger = logger
@ -26,18 +67,65 @@ type bypass struct {
cidrMatcher matcher.Matcher cidrMatcher matcher.Matcher
domainMatcher matcher.Matcher domainMatcher matcher.Matcher
wildcardMatcher matcher.Matcher wildcardMatcher matcher.Matcher
reversed bool mu sync.RWMutex
cancelFunc context.CancelFunc
options options options options
} }
// NewBypassPatterns creates and initializes a new Bypass using matcher patterns as its match rules. // NewBypass creates and initializes a new Bypass.
// The rules will be reversed if the reverse is true. // The rules will be reversed if the reverse option is true.
func NewBypass(reversed bool, patterns []string, opts ...Option) bypass_pkg.Bypass { func NewBypass(opts ...Option) bypass_pkg.Bypass {
var options options var options options
for _, opt := range opts { for _, opt := range opts {
opt(&options) opt(&options)
} }
ctx, cancel := context.WithCancel(context.TODO())
bp := &bypass{
cancelFunc: cancel,
options: options,
}
if err := bp.reload(ctx); err != nil {
options.logger.Warnf("reload: %v", err)
}
if bp.options.period > 0 {
go bp.periodReload(ctx)
}
return bp
}
func (bp *bypass) periodReload(ctx context.Context) error {
period := bp.options.period
if period < time.Second {
period = time.Second
}
ticker := time.NewTicker(period)
defer ticker.Stop()
for {
select {
case <-ticker.C:
if err := bp.reload(ctx); err != nil {
bp.options.logger.Warnf("reload: %v", err)
// return err
}
bp.options.logger.Debugf("bypass reload done")
case <-ctx.Done():
return ctx.Err()
}
}
}
func (bp *bypass) reload(ctx context.Context) error {
v, err := bp.load(ctx)
if err != nil {
return err
}
patterns := append(bp.options.matchers, v...)
var ips []net.IP var ips []net.IP
var inets []*net.IPNet var inets []*net.IPNet
var domains []string var domains []string
@ -56,16 +144,61 @@ func NewBypass(reversed bool, patterns []string, opts ...Option) bypass_pkg.Bypa
continue continue
} }
domains = append(domains, pattern) domains = append(domains, pattern)
}
bp.mu.Lock()
defer bp.mu.Unlock()
bp.ipMatcher = matcher.IPMatcher(ips)
bp.cidrMatcher = matcher.CIDRMatcher(inets)
bp.domainMatcher = matcher.DomainMatcher(domains)
bp.wildcardMatcher = matcher.WildcardMatcher(wildcards)
return nil
}
func (bp *bypass) load(ctx context.Context) (patterns []string, err error) {
if bp.options.fileLoader != nil {
r, er := bp.options.fileLoader.Load(ctx)
if er != nil {
bp.options.logger.Warnf("file loader: %v", er)
} }
return &bypass{ if v, _ := bp.parsePatterns(r); v != nil {
reversed: reversed, patterns = append(patterns, v...)
options: options,
ipMatcher: matcher.IPMatcher(ips),
cidrMatcher: matcher.CIDRMatcher(inets),
domainMatcher: matcher.DomainMatcher(domains),
wildcardMatcher: matcher.WildcardMatcher(wildcards),
} }
}
if bp.options.redisLoader != nil {
r, er := bp.options.redisLoader.Load(ctx)
if er != nil {
bp.options.logger.Warnf("redis loader: %v", er)
}
if v, _ := bp.parsePatterns(r); v != nil {
patterns = append(patterns, v...)
}
}
return
}
func (bp *bypass) parsePatterns(r io.Reader) (patterns []string, err error) {
if r == nil {
return
}
scanner := bufio.NewScanner(r)
for scanner.Scan() {
line := scanner.Text()
if n := strings.IndexByte(line, '#'); n >= 0 {
line = line[:n]
}
line = strings.TrimSpace(line)
if line != "" {
patterns = append(patterns, line)
}
}
err = scanner.Err()
return
} }
func (bp *bypass) Contains(addr string) bool { func (bp *bypass) Contains(addr string) bool {
@ -80,8 +213,8 @@ func (bp *bypass) Contains(addr string) bool {
matched := bp.matched(addr) matched := bp.matched(addr)
b := !bp.reversed && matched || b := !bp.options.reverse && matched ||
bp.reversed && !matched bp.options.reverse && !matched
if b { if b {
bp.options.logger.Debugf("bypass: %s", addr) bp.options.logger.Debugf("bypass: %s", addr)
} }
@ -89,6 +222,9 @@ func (bp *bypass) Contains(addr string) bool {
} }
func (bp *bypass) matched(addr string) bool { func (bp *bypass) matched(addr string) bool {
bp.mu.RLock()
defer bp.mu.RUnlock()
if ip := net.ParseIP(addr); ip != nil { if ip := net.ParseIP(addr); ip != nil {
return bp.ipMatcher.Match(addr) || return bp.ipMatcher.Match(addr) ||
bp.cidrMatcher.Match(addr) bp.cidrMatcher.Match(addr)
@ -97,3 +233,14 @@ func (bp *bypass) matched(addr string) bool {
return bp.domainMatcher.Match(addr) || return bp.domainMatcher.Match(addr) ||
bp.wildcardMatcher.Match(addr) bp.wildcardMatcher.Match(addr)
} }
func (bp *bypass) Close() error {
bp.cancelFunc()
if bp.options.fileLoader != nil {
bp.options.fileLoader.Close()
}
if bp.options.redisLoader != nil {
bp.options.redisLoader.Close()
}
return nil
}

View File

@ -75,10 +75,10 @@ type TLSConfig struct {
type AutherConfig struct { type AutherConfig struct {
Name string `json:"name"` Name string `json:"name"`
// inline, file, redis, etc.
Type string `yaml:",omitempty" json:"type,omitempty"`
Auths []*AuthConfig `yaml:",omitempty" json:"auths"` Auths []*AuthConfig `yaml:",omitempty" json:"auths"`
// File string `yaml:",omitempty" json:"file"` Reload time.Duration `yaml:",omitempty" json:"reload,omitempty"`
File *FileLoader `yaml:",omitempty" json:"file,omitempty"`
Redis *RedisLoader `yaml:",omitempty" json:"redis,omitempty"`
} }
type AuthConfig struct { type AuthConfig struct {
@ -94,18 +94,31 @@ type SelectorConfig struct {
type AdmissionConfig struct { type AdmissionConfig struct {
Name string `json:"name"` Name string `json:"name"`
// inline, file, etc.
Type string `yaml:",omitempty" json:"type,omitempty"`
Reverse bool `yaml:",omitempty" json:"reverse,omitempty"` Reverse bool `yaml:",omitempty" json:"reverse,omitempty"`
Matchers []string `json:"matchers"` Matchers []string `json:"matchers"`
Reload time.Duration `yaml:",omitempty" json:"reload,omitempty"`
File *FileLoader `yaml:",omitempty" json:"file,omitempty"`
Redis *RedisLoader `yaml:",omitempty" json:"redis,omitempty"`
} }
type BypassConfig struct { type BypassConfig struct {
Name string `json:"name"` Name string `json:"name"`
// inline, file, etc.
Type string `yaml:",omitempty" json:"type,omitempty"`
Reverse bool `yaml:",omitempty" json:"reverse,omitempty"` Reverse bool `yaml:",omitempty" json:"reverse,omitempty"`
Matchers []string `json:"matchers"` Matchers []string `json:"matchers"`
Reload time.Duration `yaml:",omitempty" json:"reload,omitempty"`
File *FileLoader `yaml:",omitempty" json:"file,omitempty"`
Redis *RedisLoader `yaml:",omitempty" json:"redis,omitempty"`
}
type FileLoader struct {
Path string `json:"path"`
}
type RedisLoader struct {
Addr string `yaml:",omitempty" json:"addr,omitempty"`
DB int `yaml:",omitempty" json:"db,omitempty"`
Password string `yaml:",omitempty" json:"password,omitempty"`
Key string `yaml:",omitempty" json:"key,omitempty"`
} }
type NameserverConfig struct { type NameserverConfig struct {

View File

@ -16,6 +16,7 @@ import (
bypass_impl "github.com/go-gost/x/bypass" bypass_impl "github.com/go-gost/x/bypass"
"github.com/go-gost/x/config" "github.com/go-gost/x/config"
hosts_impl "github.com/go-gost/x/hosts" hosts_impl "github.com/go-gost/x/hosts"
"github.com/go-gost/x/internal/loader"
"github.com/go-gost/x/registry" "github.com/go-gost/x/registry"
resolver_impl "github.com/go-gost/x/resolver" resolver_impl "github.com/go-gost/x/resolver"
) )
@ -34,19 +35,39 @@ func ParseAuther(cfg *config.AutherConfig) auth.Authenticator {
m[user.Username] = user.Password m[user.Username] = user.Password
} }
if len(m) == 0 { opts := []auth_impl.Option{
return nil auth_impl.AuthsPeriodOption(m),
auth_impl.ReloadPeriodOption(cfg.Reload),
auth_impl.LoggerOption(logger.Default().WithFields(map[string]any{
"kind": "auther",
"auther": cfg.Name,
})),
} }
return auth_impl.NewAuthenticator(m) if cfg.File != nil && cfg.File.Path != "" {
opts = append(opts, auth_impl.FileLoaderOption(loader.FileLoader(cfg.File.Path)))
}
if cfg.Redis != nil && cfg.Redis.Addr != "" {
opts = append(opts, auth_impl.RedisLoaderOption(loader.RedisHashLoader(
cfg.Redis.Addr,
loader.DBRedisLoaderOption(cfg.Redis.DB),
loader.PasswordRedisLoaderOption(cfg.Redis.Password),
loader.KeyRedisLoaderOption(cfg.Redis.Key),
)))
}
return auth_impl.NewAuthenticator(opts...)
} }
func ParseAutherFromAuth(au *config.AuthConfig) auth.Authenticator { func ParseAutherFromAuth(au *config.AuthConfig) auth.Authenticator {
if au == nil || au.Username == "" { if au == nil || au.Username == "" {
return nil return nil
} }
return auth_impl.NewAuthenticator(map[string]string{ return auth_impl.NewAuthenticator(
auth_impl.AuthsPeriodOption(
map[string]string{
au.Username: au.Password, au.Username: au.Password,
}) },
))
} }
func parseAuth(cfg *config.AuthConfig) *url.Userinfo { func parseAuth(cfg *config.AuthConfig) *url.Userinfo {
@ -88,28 +109,55 @@ func ParseAdmission(cfg *config.AdmissionConfig) admission.Admission {
if cfg == nil { if cfg == nil {
return nil return nil
} }
return admission_impl.NewAdmission( opts := []admission_impl.Option{
cfg.Reverse, admission_impl.MatchersOption(cfg.Matchers),
cfg.Matchers, admission_impl.ReverseOption(cfg.Reverse),
admission_impl.ReloadPeriodOption(cfg.Reload),
admission_impl.LoggerOption(logger.Default().WithFields(map[string]any{ admission_impl.LoggerOption(logger.Default().WithFields(map[string]any{
"kind": "admission", "kind": "admission",
"admission": cfg.Name, "admission": cfg.Name,
})), })),
) }
if cfg.File != nil && cfg.File.Path != "" {
opts = append(opts, admission_impl.FileLoaderOption(loader.FileLoader(cfg.File.Path)))
}
if cfg.Redis != nil && cfg.Redis.Addr != "" {
opts = append(opts, admission_impl.RedisLoaderOption(loader.RedisSetLoader(
cfg.Redis.Addr,
loader.DBRedisLoaderOption(cfg.Redis.DB),
loader.PasswordRedisLoaderOption(cfg.Redis.Password),
loader.KeyRedisLoaderOption(cfg.Redis.Key),
)))
}
return admission_impl.NewAdmission(opts...)
} }
func ParseBypass(cfg *config.BypassConfig) bypass.Bypass { func ParseBypass(cfg *config.BypassConfig) bypass.Bypass {
if cfg == nil { if cfg == nil {
return nil return nil
} }
return bypass_impl.NewBypass(
cfg.Reverse, opts := []bypass_impl.Option{
cfg.Matchers, bypass_impl.MatchersOption(cfg.Matchers),
bypass_impl.ReverseOption(cfg.Reverse),
bypass_impl.ReloadPeriodOption(cfg.Reload),
bypass_impl.LoggerOption(logger.Default().WithFields(map[string]any{ bypass_impl.LoggerOption(logger.Default().WithFields(map[string]any{
"kind": "bypass", "kind": "bypass",
"bypass": cfg.Name, "bypass": cfg.Name,
})), })),
) }
if cfg.File != nil && cfg.File.Path != "" {
opts = append(opts, bypass_impl.FileLoaderOption(loader.FileLoader(cfg.File.Path)))
}
if cfg.Redis != nil && cfg.Redis.Addr != "" {
opts = append(opts, bypass_impl.RedisLoaderOption(loader.RedisSetLoader(
cfg.Redis.Addr,
loader.DBRedisLoaderOption(cfg.Redis.DB),
loader.PasswordRedisLoaderOption(cfg.Redis.Password),
loader.KeyRedisLoaderOption(cfg.Redis.Key),
)))
}
return bypass_impl.NewBypass(opts...)
} }
func ParseResolver(cfg *config.ResolverConfig) (resolver.Resolver, error) { func ParseResolver(cfg *config.ResolverConfig) (resolver.Resolver, error) {

View File

@ -94,19 +94,23 @@ func (c *relayConnector) Connect(ctx context.Context, conn net.Conn, network, ad
if _, err := req.WriteTo(conn); err != nil { if _, err := req.WriteTo(conn); err != nil {
return nil, err return nil, err
} }
// drain the response
if err := readResponse(conn); err != nil {
return nil, err
}
} }
switch network { switch network {
case "tcp", "tcp4", "tcp6": case "tcp", "tcp4", "tcp6":
if !c.md.noDelay {
cc := &tcpConn{ cc := &tcpConn{
Conn: conn, Conn: conn,
} }
if !c.md.noDelay {
if _, err := req.WriteTo(&cc.wbuf); err != nil { if _, err := req.WriteTo(&cc.wbuf); err != nil {
return nil, err return nil, err
} }
}
conn = cc conn = cc
}
case "udp", "udp4", "udp6": case "udp", "udp4", "udp6":
cc := &udpConn{ cc := &udpConn{
Conn: conn, Conn: conn,

2
go.mod
View File

@ -12,6 +12,7 @@ require (
github.com/go-gost/gosocks5 v0.3.1-0.20211109033403-d894d75b7f09 github.com/go-gost/gosocks5 v0.3.1-0.20211109033403-d894d75b7f09
github.com/go-gost/relay v0.1.1-0.20211123134818-8ef7fd81ffd7 github.com/go-gost/relay v0.1.1-0.20211123134818-8ef7fd81ffd7
github.com/go-gost/tls-dissector v0.0.2-0.20220408131628-aac992c27451 github.com/go-gost/tls-dissector v0.0.2-0.20220408131628-aac992c27451
github.com/go-redis/redis/v8 v8.11.5
github.com/gobwas/glob v0.2.3 github.com/gobwas/glob v0.2.3
github.com/golang/snappy v0.0.4 github.com/golang/snappy v0.0.4
github.com/gorilla/websocket v1.5.0 github.com/gorilla/websocket v1.5.0
@ -42,6 +43,7 @@ require (
github.com/cespare/xxhash/v2 v2.1.2 // indirect github.com/cespare/xxhash/v2 v2.1.2 // indirect
github.com/cheekybits/genny v1.0.0 // indirect github.com/cheekybits/genny v1.0.0 // indirect
github.com/coreos/go-iptables v0.5.0 // indirect github.com/coreos/go-iptables v0.5.0 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/fsnotify/fsnotify v1.5.1 // indirect github.com/fsnotify/fsnotify v1.5.1 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect github.com/gin-contrib/sse v0.1.0 // indirect
github.com/go-playground/locales v0.14.0 // indirect github.com/go-playground/locales v0.14.0 // indirect

6
go.sum
View File

@ -89,6 +89,8 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
github.com/docker/libcontainer v2.2.1+incompatible h1:++SbbkCw+X8vAd4j2gOCzZ2Nn7s2xFALTf7LZKmM1/0= github.com/docker/libcontainer v2.2.1+incompatible h1:++SbbkCw+X8vAd4j2gOCzZ2Nn7s2xFALTf7LZKmM1/0=
github.com/docker/libcontainer v2.2.1+incompatible/go.mod h1:osvj61pYsqhNCMLGX31xr7klUBhHb/ZBuXS0o1Fvwbw= github.com/docker/libcontainer v2.2.1+incompatible/go.mod h1:osvj61pYsqhNCMLGX31xr7klUBhHb/ZBuXS0o1Fvwbw=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
@ -148,6 +150,8 @@ github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl
github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4= github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4=
github.com/go-playground/validator/v10 v10.10.1 h1:uA0+amWMiglNZKZ9FJRKUAe9U3RX91eVn1JYXMWt7ig= github.com/go-playground/validator/v10 v10.10.1 h1:uA0+amWMiglNZKZ9FJRKUAe9U3RX91eVn1JYXMWt7ig=
github.com/go-playground/validator/v10 v10.10.1/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4dMGDBiPU55YFDl0WbKdWU= github.com/go-playground/validator/v10 v10.10.1/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4dMGDBiPU55YFDl0WbKdWU=
github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI=
github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I=
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
@ -327,8 +331,8 @@ github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
github.com/onsi/gomega v1.13.0 h1:7lLHu94wT9Ij0o6EWWclhu0aOh32VxhkwEJvzuWPeak=
github.com/onsi/gomega v1.13.0/go.mod h1:lRk9szgn8TxENtWd0Tp4c3wjlRfMTMH27I+3Je41yGY= github.com/onsi/gomega v1.13.0/go.mod h1:lRk9szgn8TxENtWd0Tp4c3wjlRfMTMH27I+3Je41yGY=
github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE=
github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8= github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8=
github.com/pelletier/go-toml v1.9.4 h1:tjENF6MfZAg8e4ZmZTeWaWiT2vXtsoO6+iuOjFhECwM= github.com/pelletier/go-toml v1.9.4 h1:tjENF6MfZAg8e4ZmZTeWaWiT2vXtsoO6+iuOjFhECwM=
github.com/pelletier/go-toml v1.9.4/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/pelletier/go-toml v1.9.4/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=

View File

@ -68,17 +68,17 @@ func (h *relayHandler) handleConnect(ctx context.Context, conn net.Conn, network
} }
conn = rc conn = rc
default: default:
if !h.md.noDelay {
rc := &tcpConn{ rc := &tcpConn{
Conn: conn, Conn: conn,
} }
if !h.md.noDelay {
// cache the header // cache the header
if _, err := resp.WriteTo(&rc.wbuf); err != nil { if _, err := resp.WriteTo(&rc.wbuf); err != nil {
return err return err
} }
}
conn = rc conn = rc
} }
}
t := time.Now() t := time.Now()
log.Infof("%s <-> %s", conn.RemoteAddr(), address) log.Infof("%s <-> %s", conn.RemoteAddr(), address)

30
internal/loader/file.go Normal file
View File

@ -0,0 +1,30 @@
package loader
import (
"bytes"
"context"
"io"
"os"
)
type fileLoader struct {
filename string
}
func FileLoader(filename string) Loader {
return &fileLoader{
filename: filename,
}
}
func (l *fileLoader) Load(ctx context.Context) (io.Reader, error) {
data, err := os.ReadFile(l.filename)
if err != nil {
return nil, err
}
return bytes.NewReader(data), nil
}
func (l *fileLoader) Close() error {
return nil
}

11
internal/loader/loader.go Normal file
View File

@ -0,0 +1,11 @@
package loader
import (
"context"
"io"
)
type Loader interface {
Load(context.Context) (io.Reader, error)
Close() error
}

124
internal/loader/redis.go Normal file
View File

@ -0,0 +1,124 @@
package loader
import (
"bytes"
"context"
"fmt"
"io"
"strings"
"github.com/go-redis/redis/v8"
)
const (
DefaultRedisKey = "gost"
)
type redisLoaderOptions struct {
db int
password string
key string
}
type RedisLoaderOption func(opts *redisLoaderOptions)
func DBRedisLoaderOption(db int) RedisLoaderOption {
return func(opts *redisLoaderOptions) {
opts.db = db
}
}
func PasswordRedisLoaderOption(password string) RedisLoaderOption {
return func(opts *redisLoaderOptions) {
opts.password = password
}
}
func KeyRedisLoaderOption(key string) RedisLoaderOption {
return func(opts *redisLoaderOptions) {
opts.key = key
}
}
type redisSetLoader struct {
client *redis.Client
key string
}
// RedisSetLoader loads values from redis set.
func RedisSetLoader(addr string, opts ...RedisLoaderOption) Loader {
var options redisLoaderOptions
for _, opt := range opts {
opt(&options)
}
key := options.key
if key == "" {
key = DefaultRedisKey
}
return &redisSetLoader{
client: redis.NewClient(&redis.Options{
Addr: addr,
Password: options.password,
DB: options.db,
}),
key: key,
}
}
func (p *redisSetLoader) Load(ctx context.Context) (io.Reader, error) {
v, err := p.client.SMembers(ctx, p.key).Result()
if err != nil {
return nil, err
}
return bytes.NewReader([]byte(strings.Join(v, "\n"))), nil
}
func (p *redisSetLoader) Close() error {
return p.client.Close()
}
type redisHashLoader struct {
client *redis.Client
key string
}
// RedisHashLoader loads values from redis hash.
func RedisHashLoader(addr string, opts ...RedisLoaderOption) Loader {
var options redisLoaderOptions
for _, opt := range opts {
opt(&options)
}
key := options.key
if key == "" {
key = DefaultRedisKey
}
return &redisHashLoader{
client: redis.NewClient(&redis.Options{
Addr: addr,
Password: options.password,
DB: options.db,
}),
key: key,
}
}
func (p *redisHashLoader) Load(ctx context.Context) (io.Reader, error) {
m, err := p.client.HGetAll(ctx, p.key).Result()
if err != nil {
return nil, err
}
var b strings.Builder
for k, v := range m {
fmt.Fprintf(&b, "%s %s\n", k, v)
}
return bytes.NewBufferString(b.String()), nil
}
func (p *redisHashLoader) Close() error {
return p.client.Close()
}

View File

@ -2,6 +2,7 @@ package registry
import ( import (
"errors" "errors"
"io"
"sync" "sync"
"github.com/go-gost/core/admission" "github.com/go-gost/core/admission"
@ -55,7 +56,12 @@ func (r *registry) Register(name string, v any) error {
} }
func (r *registry) Unregister(name string) { func (r *registry) Unregister(name string) {
if v, ok := r.m.Load(name); ok {
if closer, ok := v.(io.Closer); ok {
closer.Close()
}
r.m.Delete(name) r.m.Delete(name)
}
} }
func (r *registry) IsRegistered(name string) bool { func (r *registry) IsRegistered(name string) bool {