package service import ( "fmt" "runtime" "strings" "time" "github.com/go-gost/core/auth" "github.com/go-gost/core/chain" "github.com/go-gost/core/handler" "github.com/go-gost/core/hop" "github.com/go-gost/core/listener" "github.com/go-gost/core/logger" "github.com/go-gost/core/observer/stats" "github.com/go-gost/core/recorder" "github.com/go-gost/core/selector" "github.com/go-gost/core/service" xadmission "github.com/go-gost/x/admission" xauth "github.com/go-gost/x/auth" xbypass "github.com/go-gost/x/bypass" xchain "github.com/go-gost/x/chain" "github.com/go-gost/x/config" "github.com/go-gost/x/config/parsing" admission_parser "github.com/go-gost/x/config/parsing/admission" auth_parser "github.com/go-gost/x/config/parsing/auth" bypass_parser "github.com/go-gost/x/config/parsing/bypass" hop_parser "github.com/go-gost/x/config/parsing/hop" logger_parser "github.com/go-gost/x/config/parsing/logger" selector_parser "github.com/go-gost/x/config/parsing/selector" tls_util "github.com/go-gost/x/internal/util/tls" quota_wrapper "github.com/go-gost/x/limiter/quota/wrapper" cache_limiter "github.com/go-gost/x/limiter/traffic/cache" "github.com/go-gost/x/metadata" mdutil "github.com/go-gost/x/metadata/util" xstats "github.com/go-gost/x/observer/stats" xrecorder "github.com/go-gost/x/recorder" "github.com/go-gost/x/registry" xservice "github.com/go-gost/x/service" "github.com/vishvananda/netns" ) // plaintextListeners are listener types that never terminate TLS on their // accepted connections, so any certFile/keyFile/caFile configured for them is // silently ignored. This catches the common footgun where a user writes e.g. // `tls+mws://...?certFile=...` expecting TLS: the "tls+" prefix is parsed as // the handler scheme (see x/config/cmd/cmd.go buildServiceConfig), not a TLS // wrapper, so the underlying mws listener stays plaintext. Extend this set when // new plaintext listeners are added. var plaintextListeners = map[string]bool{ "tcp": true, "udp": true, "ws": true, "mws": true, "redirect": true, "tproxy": true, "rtcp": true, "rudp": true, "unix": true, "runix": true, "serial": true, "stdio": true, } // ParseService constructs a fully-wired service.Service from a ServiceConfig. // It defaults the listener to "tcp" and the handler to "auto", resolves named // components from the registry (authers, admissions, bypasses, resolvers, // hosts, chains, hops, limiters, recorders, observers), composes TLS settings, // and applies metadata-driven options (proxy protocol, netns, sockopts, // pre/post hooks, stats, etc.). It returns an error if the listener or handler // type is unknown or if construction of any sub-component fails. func ParseService(cfg *config.ServiceConfig) (service.Service, error) { if cfg.Listener == nil { cfg.Listener = &config.ListenerConfig{} } if strings.TrimSpace(cfg.Listener.Type) == "" { cfg.Listener.Type = "tcp" } if cfg.Handler == nil { cfg.Handler = &config.HandlerConfig{} } if strings.TrimSpace(cfg.Handler.Type) == "" { cfg.Handler.Type = "auto" } log := logger.Default() if loggers := logger_parser.List(cfg.Logger, cfg.Loggers...); len(loggers) > 0 { log = logger.LoggerGroup(loggers...) } serviceLogger := log.WithFields(map[string]any{ "kind": "service", "service": cfg.Name, "listener": cfg.Listener.Type, "handler": cfg.Handler.Type, }) tlsCfg := cfg.Listener.TLS if tlsCfg == nil { tlsCfg = &config.TLSConfig{} } tlsConfig, err := tls_util.LoadServerConfig(tlsCfg) if err != nil { serviceLogger.Error(err) return nil, err } if tlsConfig == nil { tlsConfig = parsing.DefaultTLSConfig().Clone() tls_util.SetTLSOptions(tlsConfig, tlsCfg.Options) tls_util.RejectUnknownSNIConfig(tlsConfig, tlsCfg.RejectUnknownSNI, tlsCfg.ServerNames) } if (tlsCfg.CertFile != "" || tlsCfg.KeyFile != "" || tlsCfg.CAFile != "") && plaintextListeners[cfg.Listener.Type] { serviceLogger.Warnf("TLS certificate options are configured but the %q listener does not use TLS and will ignore them; the connection will be plaintext. Use a TLS-capable listener (e.g. mwss, wss, tls, quic, grpc, http2, http3) to enable TLS.", cfg.Listener.Type) } authers := auth_parser.List(cfg.Listener.Auther, cfg.Listener.Authers...) if len(authers) == 0 { if auther := auth_parser.ParseAutherFromAuth(cfg.Listener.Auth); auther != nil { authers = append(authers, auther) } } var auther auth.Authenticator if len(authers) > 0 { auther = xauth.AuthenticatorGroup(authers...) } admissions := admission_parser.List(cfg.Admission, cfg.Admissions...) var sockOpts *chain.SockOpts if cfg.SockOpts != nil { sockOpts = &chain.SockOpts{ Mark: cfg.SockOpts.Mark, } } var ppv int ifce := cfg.Interface var preUp, preDown, postUp, postDown []string var ignoreChain bool var pStats stats.Stats var observerPeriod time.Duration var netnsIn, netnsOut string var dialTimeout time.Duration var labels map[string]string var limiterRefreshInterval time.Duration var limiterCleanupInterval time.Duration var limiterScope string if cfg.Metadata != nil { md := metadata.NewMetadata(cfg.Metadata) ppv = mdutil.GetInt(md, parsing.MDKeyProxyProtocol) if v := mdutil.GetString(md, parsing.MDKeyInterface); v != "" { ifce = v } if v := mdutil.GetInt(md, parsing.MDKeySoMark); v > 0 { sockOpts = &chain.SockOpts{ Mark: v, } } preUp = mdutil.GetStrings(md, parsing.MDKeyPreUp) preDown = mdutil.GetStrings(md, parsing.MDKeyPreDown) postUp = mdutil.GetStrings(md, parsing.MDKeyPostUp) postDown = mdutil.GetStrings(md, parsing.MDKeyPostDown) ignoreChain = mdutil.GetBool(md, parsing.MDKeyIgnoreChain) if mdutil.GetBool(md, parsing.MDKeyEnableStats) { pStats = xstats.NewStats(mdutil.GetBool(md, parsing.MDKeyObserverResetTraffic)) } observerPeriod = mdutil.GetDuration(md, parsing.MDKeyObserverPeriod, "observePeriod") netnsIn = mdutil.GetString(md, parsing.MDKeyNetns) netnsOut = mdutil.GetString(md, parsing.MDKeyNetnsOut) dialTimeout = mdutil.GetDuration(md, parsing.MDKeyDialTimeout) limiterRefreshInterval = mdutil.GetDuration(md, parsing.MDKeyLimiterRefreshInterval) limiterCleanupInterval = mdutil.GetDuration(md, parsing.MDKeyLimiterCleanupInterval) limiterScope = mdutil.GetString(md, parsing.MDKeyLimiterScope) labels = mdutil.GetStringMapString(md, parsing.MDKeyLabels) } if len(labels) > 0 { serviceLogger = serviceLogger.WithFields(map[string]any{ "labels": labels, }) } listenerLogger := serviceLogger.WithFields(map[string]any{ "kind": "listener", }) routerOpts := []chain.RouterOption{ chain.TimeoutRouterOption(dialTimeout), chain.InterfaceRouterOption(ifce), chain.NetnsRouterOption(netnsOut), chain.SockOptsRouterOption(sockOpts), chain.ResolverRouterOption(registry.ResolverRegistry().Get(cfg.Resolver)), chain.HostMapperRouterOption(registry.HostsRegistry().Get(cfg.Hosts)), chain.LoggerRouterOption(listenerLogger), } if !ignoreChain { routerOpts = append(routerOpts, chain.ChainRouterOption(chainGroup(cfg.Listener.Chain, cfg.Listener.ChainGroup)), ) } listenOpts := []listener.Option{ listener.AddrOption(cfg.Addr), listener.RouterOption(xchain.NewRouter(routerOpts...)), listener.AutherOption(auther), listener.AuthOption(auth_parser.Info(cfg.Listener.Auth)), listener.TLSConfigOption(tlsConfig), listener.AdmissionOption(xadmission.AdmissionGroup(admissions...)), listener.TrafficLimiterOption( cache_limiter.NewCachedTrafficLimiter( registry.TrafficLimiterRegistry().Get(cfg.Limiter), cache_limiter.RefreshIntervalOption(limiterRefreshInterval), cache_limiter.CleanupIntervalOption(limiterCleanupInterval), cache_limiter.ScopeOption(limiterScope), ), ), listener.ConnLimiterOption(registry.ConnLimiterRegistry().Get(cfg.CLimiter)), listener.ServiceOption(cfg.Name), listener.ProxyProtocolOption(ppv), listener.StatsOption(pStats), listener.NetnsOption(netnsIn), listener.LoggerOption(listenerLogger), } if netnsIn != "" { runtime.LockOSThread() defer runtime.UnlockOSThread() originNs, err := netns.Get() if err != nil { return nil, fmt.Errorf("netns.Get(): %v", err) } defer netns.Set(originNs) var ns netns.NsHandle if strings.HasPrefix(netnsIn, "/") { ns, err = netns.GetFromPath(netnsIn) } else { ns, err = netns.GetFromName(netnsIn) } if err != nil { return nil, fmt.Errorf("netns.Get(%s): %v", netnsIn, err) } defer ns.Close() if err := netns.Set(ns); err != nil { return nil, fmt.Errorf("netns.Set(%s): %v", netnsIn, err) } } var ln listener.Listener if rf := registry.ListenerRegistry().Get(cfg.Listener.Type); rf != nil { ln = rf(listenOpts...) } else { return nil, fmt.Errorf("unknown listener: %s", cfg.Listener.Type) } if cfg.Listener.Metadata == nil { cfg.Listener.Metadata = make(map[string]any) } listenerLogger.Debugf("metadata: %v", cfg.Listener.Metadata) if err := ln.Init(metadata.NewMetadata(cfg.Listener.Metadata)); err != nil { listenerLogger.Error("init: ", err) return nil, err } for _, qname := range cfg.Quotas { ln = quota_wrapper.WrapListener(ln, strings.TrimSpace(qname)) } handlerLogger := serviceLogger.WithFields(map[string]any{ "kind": "handler", }) tlsCfg = cfg.Handler.TLS if tlsCfg == nil { tlsCfg = &config.TLSConfig{} } tlsConfig, err = tls_util.LoadServerConfig(tlsCfg) if err != nil { handlerLogger.Error(err) return nil, err } if tlsConfig == nil { tlsConfig = parsing.DefaultTLSConfig().Clone() tls_util.SetTLSOptions(tlsConfig, tlsCfg.Options) tls_util.RejectUnknownSNIConfig(tlsConfig, tlsCfg.RejectUnknownSNI, tlsCfg.ServerNames) } authers = auth_parser.List(cfg.Handler.Auther, cfg.Handler.Authers...) if len(authers) == 0 { if auther := auth_parser.ParseAutherFromAuth(cfg.Handler.Auth); auther != nil { authers = append(authers, auther) } } auther = nil if len(authers) > 0 { auther = xauth.AuthenticatorGroup(authers...) } var recorders []recorder.RecorderObject for _, r := range cfg.Recorders { md := metadata.NewMetadata(r.Metadata) rec := registry.RecorderRegistry().Get(r.Name) if r.Metadata != nil { rec = &xrecorder.MetadataRecorder{ Recorder: rec, Metadata: r.Metadata, } } recorders = append(recorders, recorder.RecorderObject{ Recorder: rec, Record: r.Record, Options: &recorder.Options{ Direction: mdutil.GetBool(md, parsing.MDKeyRecorderDirection), TimestampFormat: mdutil.GetString(md, parsing.MDKeyRecorderTimestampFormat), Hexdump: mdutil.GetBool(md, parsing.MDKeyRecorderHexdump), HTTPBody: mdutil.GetBool(md, parsing.MDKeyRecorderHTTPBody), MaxBodySize: mdutil.GetInt(md, parsing.MDKeyRecorderHTTPMaxBodySize), }, Metadata: r.Metadata, }) } routerOpts = []chain.RouterOption{ chain.RetriesRouterOption(cfg.Handler.Retries), chain.TimeoutRouterOption(dialTimeout), chain.InterfaceRouterOption(ifce), chain.NetnsRouterOption(netnsOut), chain.SockOptsRouterOption(sockOpts), chain.ResolverRouterOption(registry.ResolverRegistry().Get(cfg.Resolver)), chain.HostMapperRouterOption(registry.HostsRegistry().Get(cfg.Hosts)), chain.RecordersRouterOption(recorders...), chain.LoggerRouterOption(handlerLogger), } if !ignoreChain { routerOpts = append(routerOpts, chain.ChainRouterOption(chainGroup(cfg.Handler.Chain, cfg.Handler.ChainGroup)), ) } var h handler.Handler if rf := registry.HandlerRegistry().Get(cfg.Handler.Type); rf != nil { h = rf( handler.RouterOption(xchain.NewRouter(routerOpts...)), handler.AutherOption(auther), handler.AuthOption(auth_parser.Info(cfg.Handler.Auth)), handler.BypassOption(xbypass.BypassGroup(bypass_parser.List(cfg.Bypass, cfg.Bypasses...)...)), handler.TLSConfigOption(tlsConfig), handler.RateLimiterOption(registry.RateLimiterRegistry().Get(cfg.RLimiter)), handler.TrafficLimiterOption(registry.TrafficLimiterRegistry().Get(cfg.Handler.Limiter)), handler.ObserverOption(registry.ObserverRegistry().Get(cfg.Handler.Observer)), handler.RecordersOption(recorders...), handler.LoggerOption(handlerLogger), handler.ServiceOption(cfg.Name), handler.NetnsOption(netnsIn), ) } else { return nil, fmt.Errorf("unknown handler: %s", cfg.Handler.Type) } if forwarder, ok := h.(handler.Forwarder); ok { hop, err := parseForwarder(cfg.Forwarder, log) if err != nil { return nil, err } forwarder.Forward(hop) } if cfg.Handler.Metadata == nil { cfg.Handler.Metadata = make(map[string]any) } handlerLogger.Debugf("metadata: %v", cfg.Handler.Metadata) if err := h.Init(metadata.NewMetadata(cfg.Handler.Metadata)); err != nil { handlerLogger.Error("init: ", err) return nil, err } s := xservice.NewService(cfg.Name, ln, h, xservice.AdmissionOption(xadmission.AdmissionGroup(admissions...)), xservice.PreUpOption(preUp), xservice.PreDownOption(preDown), xservice.PostUpOption(postUp), xservice.PostDownOption(postDown), xservice.RecordersOption(recorders...), xservice.StatsOption(pStats), xservice.ObserverOption(registry.ObserverRegistry().Get(cfg.Observer)), xservice.ObserverPeriodOption(observerPeriod), xservice.LoggerOption(serviceLogger), xservice.LabelsOption(labels), ) serviceLogger.Infof("listening on %s/%s", s.Addr().String(), s.Addr().Network()) return s, nil } func parseForwarder(cfg *config.ForwarderConfig, log logger.Logger) (hop.Hop, error) { if cfg == nil { return nil, nil } hopName := cfg.Hop if hopName == "" { hopName = cfg.Name } if hopName != "" { return registry.HopRegistry().Get(hopName), nil } hc := config.HopConfig{ Name: cfg.Name, Selector: cfg.Selector, } for _, node := range cfg.Nodes { if node == nil { continue } filter := node.Filter if filter == nil { if node.Protocol != "" || node.Host != "" || node.Path != "" { filter = &config.NodeFilterConfig{ Protocol: node.Protocol, Host: node.Host, Path: node.Path, } } } httpCfg := node.HTTP if node.Auth != nil { if httpCfg == nil { httpCfg = &config.HTTPNodeConfig{} } if httpCfg.Auth == nil { httpCfg.Auth = node.Auth } } hc.Nodes = append(hc.Nodes, &config.NodeConfig{ Name: node.Name, Addr: node.Addr, Network: node.Network, Bypass: node.Bypass, Bypasses: node.Bypasses, Filter: filter, Matcher: node.Matcher, HTTP: httpCfg, TLS: node.TLS, Metadata: node.Metadata, }) } return hop_parser.ParseHop(&hc, log) } func chainGroup(name string, group *config.ChainGroupConfig) chain.Chainer { var chains []chain.Chainer var sel selector.Selector[chain.Chainer] if c := registry.ChainRegistry().Get(name); c != nil { chains = append(chains, c) } if group != nil { for _, s := range group.Chains { if c := registry.ChainRegistry().Get(s); c != nil { chains = append(chains, c) } } sel = selector_parser.ParseChainSelector(group.Selector) } if len(chains) == 0 { return nil } if sel == nil { sel = selector_parser.DefaultChainSelector() } return xchain.NewChainGroup(chains...). WithSelector(sel) }