diff --git a/api/config_hop.go b/api/config_hop.go new file mode 100644 index 0000000..5981e6c --- /dev/null +++ b/api/config_hop.go @@ -0,0 +1,175 @@ +package api + +import ( + "net/http" + + "github.com/gin-gonic/gin" + "github.com/go-gost/x/config" + "github.com/go-gost/x/config/parsing" + "github.com/go-gost/x/registry" +) + +// swagger:parameters createHopRequest +type createHopRequest struct { + // in: body + Data config.HopConfig `json:"data"` +} + +// successful operation. +// swagger:response createHopResponse +type createHopResponse struct { + Data Response +} + +func createHop(ctx *gin.Context) { + // swagger:route POST /config/hops Hop createHopRequest + // + // Create a new hop, the name of hop must be unique in hop list. + // + // Security: + // basicAuth: [] + // + // Responses: + // 200: createHopResponse + + var req createHopRequest + ctx.ShouldBindJSON(&req.Data) + + if req.Data.Name == "" { + writeError(ctx, ErrInvalid) + return + } + + v, err := parsing.ParseHop(&req.Data) + if err != nil { + writeError(ctx, ErrCreate) + return + } + + if err := registry.HopRegistry().Register(req.Data.Name, v); err != nil { + writeError(ctx, ErrDup) + return + } + + cfg := config.Global() + cfg.Hops = append(cfg.Hops, &req.Data) + config.SetGlobal(cfg) + + ctx.JSON(http.StatusOK, Response{ + Msg: "OK", + }) +} + +// swagger:parameters updateHopRequest +type updateHopRequest struct { + // in: path + // required: true + // hop name + Hop string `uri:"hop" json:"hop"` + // in: body + Data config.HopConfig `json:"data"` +} + +// successful operation. +// swagger:response updateHopResponse +type updateHopResponse struct { + Data Response +} + +func updateHop(ctx *gin.Context) { + // swagger:route PUT /config/hops/{hop} Hop updateHopRequest + // + // Update hop by name, the hop must already exist. + // + // Security: + // basicAuth: [] + // + // Responses: + // 200: updateHopResponse + + var req updateHopRequest + ctx.ShouldBindUri(&req) + ctx.ShouldBindJSON(&req.Data) + + if !registry.HopRegistry().IsRegistered(req.Hop) { + writeError(ctx, ErrNotFound) + return + } + + req.Data.Name = req.Hop + + v, err := parsing.ParseHop(&req.Data) + if err != nil { + writeError(ctx, ErrCreate) + return + } + + registry.HopRegistry().Unregister(req.Hop) + + if err := registry.HopRegistry().Register(req.Hop, v); err != nil { + writeError(ctx, ErrDup) + return + } + + cfg := config.Global() + for i := range cfg.Hops { + if cfg.Hops[i].Name == req.Hop { + cfg.Hops[i] = &req.Data + break + } + } + config.SetGlobal(cfg) + + ctx.JSON(http.StatusOK, Response{ + Msg: "OK", + }) +} + +// swagger:parameters deleteHopRequest +type deleteHopRequest struct { + // in: path + // required: true + Hop string `uri:"hop" json:"hop"` +} + +// successful operation. +// swagger:response deleteHopResponse +type deleteHopResponse struct { + Data Response +} + +func deleteHop(ctx *gin.Context) { + // swagger:route DELETE /config/hops/{hop} Hop deleteHopRequest + // + // Delete hop by name. + // + // Security: + // basicAuth: [] + // + // Responses: + // 200: deleteHopResponse + + var req deleteHopRequest + ctx.ShouldBindUri(&req) + + if !registry.HopRegistry().IsRegistered(req.Hop) { + writeError(ctx, ErrNotFound) + return + } + registry.HopRegistry().Unregister(req.Hop) + + cfg := config.Global() + hops := cfg.Hops + cfg.Hops = nil + for _, s := range hops { + if s.Name == req.Hop { + continue + } + cfg.Hops = append(cfg.Hops, s) + } + config.SetGlobal(cfg) + + ctx.JSON(http.StatusOK, Response{ + Msg: "OK", + }) +} diff --git a/api/service.go b/api/service.go index 81ad192..07208a3 100644 --- a/api/service.go +++ b/api/service.go @@ -110,6 +110,10 @@ func registerConfig(config *gin.RouterGroup) { config.PUT("/chains/:chain", updateChain) config.DELETE("/chains/:chain", deleteChain) + config.POST("/hops", createHop) + config.PUT("/hops/:hop", updateHop) + config.DELETE("/hops/:hop", deleteHop) + config.POST("/authers", createAuther) config.PUT("/authers/:auther", updateAuther) config.DELETE("/authers/:auther", deleteAuther) diff --git a/api/swagger.yaml b/api/swagger.yaml index dfcaab9..f702498 100644 --- a/api/swagger.yaml +++ b/api/swagger.yaml @@ -24,6 +24,8 @@ definitions: properties: file: $ref: '#/definitions/FileLoader' + http: + $ref: '#/definitions/HTTPLoader' matchers: items: type: string @@ -64,6 +66,8 @@ definitions: x-go-name: Auths file: $ref: '#/definitions/FileLoader' + http: + $ref: '#/definitions/HTTPLoader' name: type: string x-go-name: Name @@ -77,6 +81,8 @@ definitions: properties: file: $ref: '#/definitions/FileLoader' + http: + $ref: '#/definitions/HTTPLoader' matchers: items: type: string @@ -101,6 +107,9 @@ definitions: ChainConfig: properties: hops: + description: |- + REMOVED since beta.6 + Selector *SelectorConfig `yaml:",omitempty" json:"selector,omitempty"` items: $ref: '#/definitions/HopConfig' type: array @@ -112,8 +121,6 @@ definitions: name: type: string x-go-name: Name - selector: - $ref: '#/definitions/SelectorConfig' type: object x-go-package: github.com/go-gost/x/config ChainGroupConfig: @@ -156,6 +163,11 @@ definitions: $ref: '#/definitions/LimiterConfig' type: array x-go-name: CLimiters + hops: + items: + $ref: '#/definitions/HopConfig' + type: array + x-go-name: Hops hosts: items: $ref: '#/definitions/HostsConfig' @@ -251,11 +263,32 @@ definitions: x-go-name: Sep type: object x-go-package: github.com/go-gost/x/config + ForwardNodeConfig: + properties: + addr: + type: string + x-go-name: Addr + bypass: + type: string + x-go-name: Bypass + bypasses: + items: + type: string + type: array + x-go-name: Bypasses + name: + type: string + x-go-name: Name + type: object + x-go-package: github.com/go-gost/x/config ForwarderConfig: properties: + name: + type: string + x-go-name: Name nodes: items: - $ref: '#/definitions/NodeConfig' + $ref: '#/definitions/ForwardNodeConfig' type: array x-go-name: Nodes selector: @@ -268,6 +301,15 @@ definitions: x-go-name: Targets type: object x-go-package: github.com/go-gost/x/config + HTTPLoader: + properties: + timeout: + $ref: '#/definitions/Duration' + url: + type: string + x-go-name: URL + type: object + x-go-package: github.com/go-gost/x/config HandlerConfig: properties: auth: @@ -352,6 +394,8 @@ definitions: properties: file: $ref: '#/definitions/FileLoader' + http: + $ref: '#/definitions/HTTPLoader' mappings: items: $ref: '#/definitions/HostMappingConfig' @@ -370,6 +414,8 @@ definitions: properties: file: $ref: '#/definitions/FileLoader' + http: + $ref: '#/definitions/HTTPLoader' limits: items: type: string @@ -1025,6 +1071,64 @@ paths: summary: Update conn limiter by name, the limiter must already exist. tags: - Limiter + /config/hops: + post: + operationId: createHopRequest + parameters: + - in: body + name: data + schema: + $ref: '#/definitions/HopConfig' + x-go-name: Data + responses: + "200": + $ref: '#/responses/createHopResponse' + security: + - basicAuth: + - '[]' + summary: Create a new hop, the name of hop must be unique in hop list. + tags: + - Hop + /config/hops/{hop}: + delete: + operationId: deleteHopRequest + parameters: + - in: path + name: hop + required: true + type: string + x-go-name: Hop + responses: + "200": + $ref: '#/responses/deleteHopResponse' + security: + - basicAuth: + - '[]' + summary: Delete hop by name. + tags: + - Hop + put: + operationId: updateHopRequest + parameters: + - in: path + name: hop + required: true + type: string + x-go-name: Hop + - in: body + name: data + schema: + $ref: '#/definitions/HopConfig' + x-go-name: Data + responses: + "200": + $ref: '#/responses/updateHopResponse' + security: + - basicAuth: + - '[]' + summary: Update hop by name, the hop must already exist. + tags: + - Hop /config/hosts: post: operationId: createHostsRequest @@ -1348,6 +1452,12 @@ responses: Data: {} schema: $ref: '#/definitions/Response' + createHopResponse: + description: successful operation. + headers: + Data: {} + schema: + $ref: '#/definitions/Response' createHostsResponse: description: successful operation. headers: @@ -1408,6 +1518,12 @@ responses: Data: {} schema: $ref: '#/definitions/Response' + deleteHopResponse: + description: successful operation. + headers: + Data: {} + schema: + $ref: '#/definitions/Response' deleteHostsResponse: description: successful operation. headers: @@ -1480,6 +1596,12 @@ responses: Data: {} schema: $ref: '#/definitions/Response' + updateHopResponse: + description: successful operation. + headers: + Data: {} + schema: + $ref: '#/definitions/Response' updateHostsResponse: description: successful operation. headers: diff --git a/chain/chain.go b/chain/chain.go index b572197..0ff02dc 100644 --- a/chain/chain.go +++ b/chain/chain.go @@ -4,6 +4,7 @@ import ( "context" "github.com/go-gost/core/chain" + "github.com/go-gost/core/logger" "github.com/go-gost/core/metadata" "github.com/go-gost/core/selector" ) @@ -12,6 +13,25 @@ var ( _ chain.Chainer = (*chainGroup)(nil) ) +type ChainOptions struct { + Metadata metadata.Metadata + Logger logger.Logger +} + +type ChainOption func(*ChainOptions) + +func MetadataChainOption(md metadata.Metadata) ChainOption { + return func(opts *ChainOptions) { + opts.Metadata = md + } +} + +func LoggerChainOption(logger logger.Logger) ChainOption { + return func(opts *ChainOptions) { + opts.Logger = logger + } +} + type chainNamer interface { Name() string } @@ -21,13 +41,22 @@ type Chain struct { hops []chain.Hop marker selector.Marker metadata metadata.Metadata + logger logger.Logger } -func NewChain(name string, hops ...chain.Hop) *Chain { +func NewChain(name string, opts ...ChainOption) *Chain { + var options ChainOptions + for _, opt := range opts { + if opt != nil { + opt(&options) + } + } + return &Chain{ - name: name, - hops: hops, - marker: selector.NewFailMarker(), + name: name, + metadata: options.Metadata, + marker: selector.NewFailMarker(), + logger: options.Logger, } } @@ -35,10 +64,6 @@ func (c *Chain) AddHop(hop chain.Hop) { c.hops = append(c.hops, hop) } -func (c *Chain) WithMetadata(md metadata.Metadata) { - c.metadata = md -} - // Metadata implements metadata.Metadatable interface. func (c *Chain) Metadata() metadata.Metadata { return c.metadata diff --git a/chain/hop.go b/chain/hop.go index a57e0e4..54c608c 100644 --- a/chain/hop.go +++ b/chain/hop.go @@ -2,25 +2,17 @@ package chain import ( "context" - "io" - "sync" - "time" "github.com/go-gost/core/bypass" "github.com/go-gost/core/chain" "github.com/go-gost/core/logger" "github.com/go-gost/core/selector" - "github.com/go-gost/x/internal/loader" ) type HopOptions struct { - bypass bypass.Bypass - selector selector.Selector[*chain.Node] - fileLoader loader.Loader - httpLoader loader.Loader - redisLoader loader.Loader - period time.Duration - logger logger.Logger + bypass bypass.Bypass + selector selector.Selector[*chain.Node] + logger logger.Logger } type HopOption func(*HopOptions) @@ -37,30 +29,6 @@ func SelectorHopOption(s selector.Selector[*chain.Node]) HopOption { } } -func FileLoaderHopOption(fileLoader loader.Loader) HopOption { - return func(opts *HopOptions) { - opts.fileLoader = fileLoader - } -} - -func RedisLoaderHopOption(redisLoader loader.Loader) HopOption { - return func(opts *HopOptions) { - opts.redisLoader = redisLoader - } -} - -func HTTPLoaderHopOption(httpLoader loader.Loader) HopOption { - return func(opts *HopOptions) { - opts.httpLoader = httpLoader - } -} - -func ReloadPeriodHopOption(period time.Duration) HopOption { - return func(opts *HopOptions) { - opts.period = period - } -} - func LoggerHopOption(logger logger.Logger) HopOption { return func(opts *HopOptions) { opts.logger = logger @@ -68,10 +36,8 @@ func LoggerHopOption(logger logger.Logger) HopOption { } type chainHop struct { - nodes []*chain.Node - options HopOptions - cancelFunc context.CancelFunc - mu sync.RWMutex + nodes []*chain.Node + options HopOptions } func NewChainHop(nodes []*chain.Node, opts ...HopOption) chain.Hop { @@ -82,18 +48,9 @@ func NewChainHop(nodes []*chain.Node, opts ...HopOption) chain.Hop { } } - ctx, cancel := context.WithCancel(context.TODO()) - hop := &chainHop{ - nodes: nodes, - options: options, - cancelFunc: cancel, - } - if err := hop.reload(ctx); err != nil { - options.logger.Warnf("reload: %v", err) - } - if hop.options.period > 0 { - go hop.periodReload(ctx) + nodes: nodes, + options: options, } return hop @@ -138,61 +95,3 @@ func (p *chainHop) Select(ctx context.Context, opts ...chain.SelectOption) *chai } return nodes[0] } - -func (p *chainHop) 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 - } - case <-ctx.Done(): - return ctx.Err() - } - } -} - -func (p *chainHop) reload(ctx context.Context) error { - _, err := p.load(ctx) - if err != nil { - return err - } - - return nil -} - -func (p *chainHop) load(ctx context.Context) (data []byte, err error) { - if p.options.fileLoader != nil { - r, er := p.options.fileLoader.Load(ctx) - if er != nil { - p.options.logger.Warnf("file loader: %v", er) - } - return io.ReadAll(r) - } - - if p.options.redisLoader != nil { - r, er := p.options.redisLoader.Load(ctx) - if er != nil { - p.options.logger.Warnf("redis loader: %v", er) - } - return io.ReadAll(r) - } - - if p.options.httpLoader != nil { - r, er := p.options.redisLoader.Load(ctx) - if er != nil { - p.options.logger.Warnf("http loader: %v", er) - } - return io.ReadAll(r) - } - - return -} diff --git a/config/config.go b/config/config.go index 4f8eb07..53ac032 100644 --- a/config/config.go +++ b/config/config.go @@ -226,10 +226,11 @@ type HandlerConfig struct { } type ForwarderConfig struct { - // DEPRECATED by nodes since beta.4 - Targets []string `yaml:",omitempty" json:"targets,omitempty"` - Nodes []*ForwardNodeConfig `json:"nodes"` + Name string `yaml:",omitempty" json:"name,omitempty"` Selector *SelectorConfig `yaml:",omitempty" json:"selector,omitempty"` + Nodes []*ForwardNodeConfig `json:"nodes"` + // DEPRECATED by nodes since beta.4 + Targets []string `yaml:",omitempty" json:"targets,omitempty"` } type ForwardNodeConfig struct { @@ -281,10 +282,11 @@ type ServiceConfig struct { } type ChainConfig struct { - Name string `json:"name"` - Selector *SelectorConfig `yaml:",omitempty" json:"selector,omitempty"` - Hops []*HopConfig `json:"hops"` - Metadata map[string]any `yaml:",omitempty" json:"metadata,omitempty"` + Name string `json:"name"` + // REMOVED since beta.6 + // Selector *SelectorConfig `yaml:",omitempty" json:"selector,omitempty"` + Hops []*HopConfig `json:"hops"` + Metadata map[string]any `yaml:",omitempty" json:"metadata,omitempty"` } type ChainGroupConfig struct { @@ -301,7 +303,7 @@ type HopConfig struct { Bypasses []string `yaml:",omitempty" json:"bypasses,omitempty"` Resolver string `yaml:",omitempty" json:"resolver,omitempty"` Hosts string `yaml:",omitempty" json:"hosts,omitempty"` - Nodes []*NodeConfig `json:"nodes"` + Nodes []*NodeConfig `yaml:",omitempty" json:"nodes,omitempty"` } type NodeConfig struct { @@ -321,6 +323,7 @@ type NodeConfig struct { type Config struct { Services []*ServiceConfig `json:"services"` Chains []*ChainConfig `yaml:",omitempty" json:"chains,omitempty"` + Hops []*HopConfig `yaml:",omitempty" json:"hops,omitempty"` Authers []*AutherConfig `yaml:",omitempty" json:"authers,omitempty"` Admissions []*AdmissionConfig `yaml:",omitempty" json:"admissions,omitempty"` Bypasses []*BypassConfig `yaml:",omitempty" json:"bypasses,omitempty"` diff --git a/config/parsing/chain.go b/config/parsing/chain.go index 52e34c0..445c1c9 100644 --- a/config/parsing/chain.go +++ b/config/parsing/chain.go @@ -28,153 +28,191 @@ func ParseChain(cfg *config.ChainConfig) (chain.Chainer, error) { "chain": cfg.Name, }) - c := xchain.NewChain(cfg.Name) + var md metadata.Metadata if cfg.Metadata != nil { - c.WithMetadata(mdx.NewMetadata(cfg.Metadata)) + md = mdx.NewMetadata(cfg.Metadata) } - sel := parseNodeSelector(cfg.Selector) - for _, hop := range cfg.Hops { - var nodes []*chain.Node - for _, v := range hop.Nodes { - nodeLogger := chainLogger.WithFields(map[string]any{ - "kind": "node", - "connector": v.Connector.Type, - "dialer": v.Dialer.Type, - "hop": hop.Name, - "node": v.Name, - }) - connectorLogger := nodeLogger.WithFields(map[string]any{ - "kind": "connector", - }) + c := xchain.NewChain(cfg.Name, + xchain.MetadataChainOption(md), + xchain.LoggerChainOption(chainLogger), + ) - tlsCfg := v.Connector.TLS - if tlsCfg == nil { - tlsCfg = &config.TLSConfig{} - } - tlsConfig, err := tls_util.LoadClientConfig( - tlsCfg.CertFile, tlsCfg.KeyFile, tlsCfg.CAFile, - tlsCfg.Secure, tlsCfg.ServerName) - if err != nil { - chainLogger.Error(err) + for _, ch := range cfg.Hops { + var hop chain.Hop + var err error + + if len(ch.Nodes) > 0 { + if hop, err = ParseHop(ch); err != nil { return nil, err } - - var nm metadata.Metadata - if v.Metadata != nil { - nm = mdx.NewMetadata(v.Metadata) - } - - var cr connector.Connector - if rf := registry.ConnectorRegistry().Get(v.Connector.Type); rf != nil { - cr = rf( - connector.AuthOption(parseAuth(v.Connector.Auth)), - connector.TLSConfigOption(tlsConfig), - connector.LoggerOption(connectorLogger), - ) - } else { - return nil, fmt.Errorf("unregistered connector: %s", v.Connector.Type) - } - - if v.Connector.Metadata == nil { - v.Connector.Metadata = make(map[string]any) - } - if err := cr.Init(mdx.NewMetadata(v.Connector.Metadata)); err != nil { - connectorLogger.Error("init: ", err) - return nil, err - } - - dialerLogger := nodeLogger.WithFields(map[string]any{ - "kind": "dialer", - }) - - tlsCfg = v.Dialer.TLS - if tlsCfg == nil { - tlsCfg = &config.TLSConfig{} - } - tlsConfig, err = tls_util.LoadClientConfig( - tlsCfg.CertFile, tlsCfg.KeyFile, tlsCfg.CAFile, - tlsCfg.Secure, tlsCfg.ServerName) - if err != nil { - chainLogger.Error(err) - return nil, err - } - - var ppv int - if nm != nil { - ppv = mdutil.GetInt(nm, mdKeyProxyProtocol) - } - - var d dialer.Dialer - if rf := registry.DialerRegistry().Get(v.Dialer.Type); rf != nil { - d = rf( - dialer.AuthOption(parseAuth(v.Dialer.Auth)), - dialer.TLSConfigOption(tlsConfig), - dialer.LoggerOption(dialerLogger), - dialer.ProxyProtocolOption(ppv), - ) - } else { - return nil, fmt.Errorf("unregistered dialer: %s", v.Dialer.Type) - } - - if v.Dialer.Metadata == nil { - v.Dialer.Metadata = make(map[string]any) - } - if err := d.Init(mdx.NewMetadata(v.Dialer.Metadata)); err != nil { - dialerLogger.Error("init: ", err) - return nil, err - } - - if v.Resolver == "" { - v.Resolver = hop.Resolver - } - if v.Hosts == "" { - v.Hosts = hop.Hosts - } - if v.Interface == "" { - v.Interface = hop.Interface - } - if v.SockOpts == nil { - v.SockOpts = hop.SockOpts - } - - var sockOpts *chain.SockOpts - if v.SockOpts != nil { - sockOpts = &chain.SockOpts{ - Mark: v.SockOpts.Mark, - } - } - - tr := chain.NewTransport(d, cr, - chain.AddrTransportOption(v.Addr), - chain.InterfaceTransportOption(v.Interface), - chain.SockOptsTransportOption(sockOpts), - chain.TimeoutTransportOption(10*time.Second), - ) - - node := chain.NewNode(v.Name, v.Addr, - chain.TransportNodeOption(tr), - chain.BypassNodeOption(bypass.BypassGroup(bypassList(v.Bypass, v.Bypasses...)...)), - chain.ResoloverNodeOption(registry.ResolverRegistry().Get(v.Resolver)), - chain.HostMapperNodeOption(registry.HostsRegistry().Get(v.Hosts)), - chain.MetadataNodeOption(nm), - ) - nodes = append(nodes, node) + } else { + hop = registry.HopRegistry().Get(ch.Name) } - - sl := sel - if s := parseNodeSelector(hop.Selector); s != nil { - sl = s + if hop != nil { + c.AddHop(hop) } - if sl == nil { - sl = defaultNodeSelector() - } - - c.AddHop(xchain.NewChainHop(nodes, - xchain.SelectorHopOption(sl), - xchain.BypassHopOption(bypass.BypassGroup(bypassList(hop.Bypass, hop.Bypasses...)...))), - ) } return c, nil } + +func ParseHop(cfg *config.HopConfig) (chain.Hop, error) { + if cfg == nil { + return nil, nil + } + + hopLogger := logger.Default().WithFields(map[string]any{ + "kind": "hop", + "hop": cfg.Name, + }) + + var nodes []*chain.Node + for _, v := range cfg.Nodes { + if v == nil { + continue + } + + if v.Connector == nil { + v.Connector = &config.ConnectorConfig{ + Type: "http", + } + v.Dialer = &config.DialerConfig{ + Type: "tcp", + } + } + + nodeLogger := hopLogger.WithFields(map[string]any{ + "kind": "node", + "node": v.Name, + "connector": v.Connector.Type, + "dialer": v.Dialer.Type, + }) + + tlsCfg := v.Connector.TLS + if tlsCfg == nil { + tlsCfg = &config.TLSConfig{} + } + tlsConfig, err := tls_util.LoadClientConfig( + tlsCfg.CertFile, tlsCfg.KeyFile, tlsCfg.CAFile, + tlsCfg.Secure, tlsCfg.ServerName) + if err != nil { + hopLogger.Error(err) + return nil, err + } + + var nm metadata.Metadata + if v.Metadata != nil { + nm = mdx.NewMetadata(v.Metadata) + } + + connectorLogger := nodeLogger.WithFields(map[string]any{ + "kind": "connector", + }) + var cr connector.Connector + if rf := registry.ConnectorRegistry().Get(v.Connector.Type); rf != nil { + cr = rf( + connector.AuthOption(parseAuth(v.Connector.Auth)), + connector.TLSConfigOption(tlsConfig), + connector.LoggerOption(connectorLogger), + ) + } else { + return nil, fmt.Errorf("unregistered connector: %s", v.Connector.Type) + } + + if v.Connector.Metadata == nil { + v.Connector.Metadata = make(map[string]any) + } + if err := cr.Init(mdx.NewMetadata(v.Connector.Metadata)); err != nil { + connectorLogger.Error("init: ", err) + return nil, err + } + + tlsCfg = v.Dialer.TLS + if tlsCfg == nil { + tlsCfg = &config.TLSConfig{} + } + tlsConfig, err = tls_util.LoadClientConfig( + tlsCfg.CertFile, tlsCfg.KeyFile, tlsCfg.CAFile, + tlsCfg.Secure, tlsCfg.ServerName) + if err != nil { + hopLogger.Error(err) + return nil, err + } + + var ppv int + if nm != nil { + ppv = mdutil.GetInt(nm, mdKeyProxyProtocol) + } + + dialerLogger := nodeLogger.WithFields(map[string]any{ + "kind": "dialer", + }) + + var d dialer.Dialer + if rf := registry.DialerRegistry().Get(v.Dialer.Type); rf != nil { + d = rf( + dialer.AuthOption(parseAuth(v.Dialer.Auth)), + dialer.TLSConfigOption(tlsConfig), + dialer.LoggerOption(dialerLogger), + dialer.ProxyProtocolOption(ppv), + ) + } else { + return nil, fmt.Errorf("unregistered dialer: %s", v.Dialer.Type) + } + + if v.Dialer.Metadata == nil { + v.Dialer.Metadata = make(map[string]any) + } + if err := d.Init(mdx.NewMetadata(v.Dialer.Metadata)); err != nil { + dialerLogger.Error("init: ", err) + return nil, err + } + + if v.Resolver == "" { + v.Resolver = cfg.Resolver + } + if v.Hosts == "" { + v.Hosts = cfg.Hosts + } + if v.Interface == "" { + v.Interface = cfg.Interface + } + if v.SockOpts == nil { + v.SockOpts = cfg.SockOpts + } + + var sockOpts *chain.SockOpts + if v.SockOpts != nil { + sockOpts = &chain.SockOpts{ + Mark: v.SockOpts.Mark, + } + } + + tr := chain.NewTransport(d, cr, + chain.AddrTransportOption(v.Addr), + chain.InterfaceTransportOption(v.Interface), + chain.SockOptsTransportOption(sockOpts), + chain.TimeoutTransportOption(10*time.Second), + ) + + node := chain.NewNode(v.Name, v.Addr, + chain.TransportNodeOption(tr), + chain.BypassNodeOption(bypass.BypassGroup(bypassList(v.Bypass, v.Bypasses...)...)), + chain.ResoloverNodeOption(registry.ResolverRegistry().Get(v.Resolver)), + chain.HostMapperNodeOption(registry.HostsRegistry().Get(v.Hosts)), + chain.MetadataNodeOption(nm), + ) + nodes = append(nodes, node) + } + + sel := parseNodeSelector(cfg.Selector) + if sel == nil { + sel = defaultNodeSelector() + } + return xchain.NewChainHop(nodes, + xchain.SelectorHopOption(sel), + xchain.BypassHopOption(bypass.BypassGroup(bypassList(cfg.Bypass, cfg.Bypasses...)...)), + xchain.LoggerHopOption(hopLogger), + ), nil +} diff --git a/config/parsing/service.go b/config/parsing/service.go index 2c0e46d..1183af7 100644 --- a/config/parsing/service.go +++ b/config/parsing/service.go @@ -185,7 +185,11 @@ func ParseService(cfg *config.ServiceConfig) (service.Service, error) { } if forwarder, ok := h.(handler.Forwarder); ok { - forwarder.Forward(parseForwarder(cfg.Forwarder)) + hop, err := parseForwarder(cfg.Forwarder) + if err != nil { + return nil, err + } + forwarder.Forward(hop) } if cfg.Handler.Metadata == nil { @@ -205,36 +209,45 @@ func ParseService(cfg *config.ServiceConfig) (service.Service, error) { return s, nil } -func parseForwarder(cfg *config.ForwarderConfig) chain.Hop { - if cfg == nil || - (len(cfg.Targets) == 0 && len(cfg.Nodes) == 0) { - return nil +func parseForwarder(cfg *config.ForwarderConfig) (chain.Hop, error) { + if cfg == nil { + return nil, nil } - var nodes []*chain.Node + hc := config.HopConfig{ + Name: cfg.Name, + Selector: cfg.Selector, + } if len(cfg.Nodes) > 0 { for _, node := range cfg.Nodes { if node != nil { - nodes = append(nodes, - chain.NewNode(node.Name, node.Addr, - chain.BypassNodeOption(bypass.BypassGroup(bypassList(node.Bypass, node.Bypasses...)...)), - ), + hc.Nodes = append(hc.Nodes, + &config.NodeConfig{ + Name: node.Name, + Addr: node.Addr, + Bypass: node.Bypass, + Bypasses: node.Bypasses, + }, ) } } } else { for _, target := range cfg.Targets { if v := strings.TrimSpace(target); v != "" { - nodes = append(nodes, chain.NewNode(target, target)) + hc.Nodes = append(hc.Nodes, + &config.NodeConfig{ + Name: target, + Addr: target, + }, + ) } } } - sel := parseNodeSelector(cfg.Selector) - if sel == nil { - sel = defaultNodeSelector() + if len(hc.Nodes) > 0 { + return ParseHop(&hc) } - return xchain.NewChainHop(nodes, xchain.SelectorHopOption(sel)) + return registry.HopRegistry().Get(hc.Name), nil } func bypassList(name string, names ...string) []bypass.Bypass { diff --git a/config/parsing/tls.go b/config/parsing/tls.go index 9dffc1f..88ceee3 100644 --- a/config/parsing/tls.go +++ b/config/parsing/tls.go @@ -38,9 +38,9 @@ func BuildDefaultTLSConfig(cfg *config.TLSConfig) { tlsConfig = &tls.Config{ Certificates: []tls.Certificate{cert}, } - log.Warn("load global TLS certificate files failed, use random generated certificate") + log.Debug("load global TLS certificate files failed, use random generated certificate") } else { - log.Info("load global TLS certificate files OK") + log.Debug("load global TLS certificate files OK") } defaultTLSConfig = tlsConfig } diff --git a/go.mod b/go.mod index 40a0e82..81e06dd 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,7 @@ require ( github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d github.com/gin-contrib/cors v1.3.1 github.com/gin-gonic/gin v1.7.7 - github.com/go-gost/core v0.0.0-20220920034830-41ff9835a66d + github.com/go-gost/core v0.0.0-20220914115321-50d443049f3b 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 af51ae0..ae5c28f 100644 --- a/go.sum +++ b/go.sum @@ -98,6 +98,8 @@ github.com/gin-gonic/gin v1.7.7/go.mod h1:axIBovoeJpVj8S3BwE0uPMTeReE4+AfFtqpqaZ 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-20220914115321-50d443049f3b h1:fWUPYFp0W/6GEhL0wrURGPQN2AQHhf4IZKiALJJOJh8= +github.com/go-gost/core v0.0.0-20220914115321-50d443049f3b/go.mod h1:bHVbCS9da6XtKNYMkMUVcck5UqDDUkyC37erVfs4GXQ= github.com/go-gost/core v0.0.0-20220920034830-41ff9835a66d h1:UFn21xIJgWE/te12rzQA7Ymwbo+MaxOcp38K41L+Yck= github.com/go-gost/core v0.0.0-20220920034830-41ff9835a66d/go.mod h1:bHVbCS9da6XtKNYMkMUVcck5UqDDUkyC37erVfs4GXQ= github.com/go-gost/gosocks4 v0.0.1 h1:+k1sec8HlELuQV7rWftIkmy8UijzUt2I6t+iMPlGB2s= diff --git a/registry/hop.go b/registry/hop.go new file mode 100644 index 0000000..fdf2c24 --- /dev/null +++ b/registry/hop.go @@ -0,0 +1,51 @@ +package registry + +import ( + "context" + + "github.com/go-gost/core/chain" +) + +type hopRegistry struct { + registry +} + +func (r *hopRegistry) Register(name string, v chain.Hop) error { + return r.registry.Register(name, v) +} + +func (r *hopRegistry) Get(name string) chain.Hop { + if name != "" { + return &hopWrapper{name: name, r: r} + } + return nil +} + +func (r *hopRegistry) get(name string) chain.Hop { + if v := r.registry.Get(name); v != nil { + return v.(chain.Hop) + } + return nil +} + +type hopWrapper struct { + name string + r *hopRegistry +} + +func (w *hopWrapper) Nodes() []*chain.Node { + v := w.r.get(w.name) + if v == nil { + return nil + } + return v.Nodes() +} + +func (w *hopWrapper) Select(ctx context.Context, opts ...chain.SelectOption) *chain.Node { + v := w.r.get(w.name) + if v == nil { + return nil + } + + return v.Select(ctx, opts...) +} diff --git a/registry/registry.go b/registry/registry.go index 9d2ef45..0ab9ae3 100644 --- a/registry/registry.go +++ b/registry/registry.go @@ -30,6 +30,7 @@ var ( serviceReg Registry[service.Service] = &serviceRegistry{} chainReg Registry[chain.Chainer] = &chainRegistry{} + hopReg Registry[chain.Hop] = &hopRegistry{} autherReg Registry[auth.Authenticator] = &autherRegistry{} admissionReg Registry[admission.Admission] = &admissionRegistry{} bypassReg Registry[bypass.Bypass] = &bypassRegistry{} @@ -110,6 +111,10 @@ func ChainRegistry() Registry[chain.Chainer] { return chainReg } +func HopRegistry() Registry[chain.Hop] { + return hopReg +} + func AutherRegistry() Registry[auth.Authenticator] { return autherReg }