fix(config): stop mutating shared config structs during parsing

Prevent data loss and nil panics across 5 parse functions:
- chain: nil-guard hop entries to avoid deref on empty YAML lists
- hop: build merged metadata map instead of writing inherited values
  into the original NodeConfig; swap+restore around ParseNode call
- node: use local connCfg/dialCfg instead of overwriting cfg.Connector
  and cfg.Dialer fields; only fill in Type when empty
- parser: preserve existing config file fields when env vars override
  Level/Addr (was replacing the entire struct)
- router: only update ipNet from dst CIDR if parse succeeds, keeping
  the route.Net fallback instead of overwriting with nil
This commit is contained in:
ginuerzh
2026-05-24 12:56:54 +08:00
parent 8390ccadab
commit 9febb23bc9
5 changed files with 60 additions and 54 deletions
+4
View File
@@ -33,6 +33,10 @@ func ParseChain(cfg *config.ChainConfig, log logger.Logger) (chain.Chainer, erro
) )
for _, ch := range cfg.Hops { for _, ch := range cfg.Hops {
if ch == nil {
continue
}
var hop hop.Hop var hop hop.Hop
var err error var err error
+17 -19
View File
@@ -78,12 +78,13 @@ func ParseHop(cfg *config.HopConfig, log logger.Logger) (hop.Hop, error) {
continue continue
} }
m := v.Metadata // Build a merged metadata map for inheritance without mutating
if m == nil { // the original node config.
m = map[string]any{} merged := make(map[string]any)
v.Metadata = m for k, val := range v.Metadata {
merged[k] = val
} }
md := metadata.NewMetadata(m) md := metadata.NewMetadata(merged)
if v.Resolver == "" { if v.Resolver == "" {
v.Resolver = cfg.Resolver v.Resolver = cfg.Resolver
@@ -93,37 +94,30 @@ func ParseHop(cfg *config.HopConfig, log logger.Logger) (hop.Hop, error) {
} }
if !md.IsExists(parsing.MDKeyInterface) { if !md.IsExists(parsing.MDKeyInterface) {
// inherit from hop
if ifce != "" { if ifce != "" {
m[parsing.MDKeyInterface] = ifce merged[parsing.MDKeyInterface] = ifce
} }
// node level
if v.Interface != "" { if v.Interface != "" {
m[parsing.MDKeyInterface] = v.Interface merged[parsing.MDKeyInterface] = v.Interface
} }
} }
if !md.IsExists(parsing.MDKeySoMark) { if !md.IsExists(parsing.MDKeySoMark) {
// inherit from hop
if soMark != 0 { if soMark != 0 {
m[parsing.MDKeySoMark] = soMark merged[parsing.MDKeySoMark] = soMark
} }
// node level
if v.SockOpts != nil && v.SockOpts.Mark != 0 { if v.SockOpts != nil && v.SockOpts.Mark != 0 {
m[parsing.MDKeySoMark] = v.SockOpts.Mark merged[parsing.MDKeySoMark] = v.SockOpts.Mark
} }
} }
if !md.IsExists(parsing.MDKeyProxyProtocol) && ppv > 0 { if !md.IsExists(parsing.MDKeyProxyProtocol) && ppv > 0 {
// inherit from hop merged[parsing.MDKeyProxyProtocol] = ppv
m[parsing.MDKeyProxyProtocol] = ppv
} }
if !md.IsExists(parsing.MDKeyNetns) { if !md.IsExists(parsing.MDKeyNetns) {
// inherit from hop
if netns != "" { if netns != "" {
m[parsing.MDKeyNetns] = netns merged[parsing.MDKeyNetns] = netns
} }
// node level
if v.Netns != "" { if v.Netns != "" {
m[parsing.MDKeyNetns] = v.Netns merged[parsing.MDKeyNetns] = v.Netns
} }
} }
@@ -141,7 +135,11 @@ func ParseHop(cfg *config.HopConfig, log logger.Logger) (hop.Hop, error) {
v.Dialer.Type = "tcp" v.Dialer.Type = "tcp"
} }
// Temporarily swap merged metadata so ParseNode sees inherited values.
origMeta := v.Metadata
v.Metadata = merged
node, err := node_parser.ParseNode(cfg.Name, v, log) node, err := node_parser.ParseNode(cfg.Name, v, log)
v.Metadata = origMeta
if err != nil { if err != nil {
return nil, err return nil, err
} }
+24 -26
View File
@@ -29,29 +29,33 @@ func ParseNode(hop string, cfg *config.NodeConfig, log logger.Logger) (*chain.No
return nil, nil return nil, nil
} }
if cfg.Connector == nil { connCfg := cfg.Connector
cfg.Connector = &config.ConnectorConfig{ if connCfg == nil {
Type: "http", connCfg = &config.ConnectorConfig{}
} }
if connCfg.Type == "" {
connCfg.Type = "http"
} }
if cfg.Dialer == nil { dialCfg := cfg.Dialer
cfg.Dialer = &config.DialerConfig{ if dialCfg == nil {
Type: "tcp", dialCfg = &config.DialerConfig{}
} }
if dialCfg.Type == "" {
dialCfg.Type = "tcp"
} }
nodeLogger := log.WithFields(map[string]any{ nodeLogger := log.WithFields(map[string]any{
"hop": hop, "hop": hop,
"kind": "node", "kind": "node",
"node": cfg.Name, "node": cfg.Name,
"connector": cfg.Connector.Type, "connector": connCfg.Type,
"dialer": cfg.Dialer.Type, "dialer": dialCfg.Type,
}) })
serverName, _, _ := net.SplitHostPort(cfg.Addr) serverName, _, _ := net.SplitHostPort(cfg.Addr)
tlsCfg := cfg.Connector.TLS tlsCfg := connCfg.TLS
if tlsCfg == nil { if tlsCfg == nil {
tlsCfg = &config.TLSConfig{} tlsCfg = &config.TLSConfig{}
} }
@@ -68,25 +72,22 @@ func ParseNode(hop string, cfg *config.NodeConfig, log logger.Logger) (*chain.No
"kind": "connector", "kind": "connector",
}) })
var cr connector.Connector var cr connector.Connector
if rf := registry.ConnectorRegistry().Get(cfg.Connector.Type); rf != nil { if rf := registry.ConnectorRegistry().Get(connCfg.Type); rf != nil {
cr = rf( cr = rf(
connector.AuthOption(auth_parser.Info(cfg.Connector.Auth)), connector.AuthOption(auth_parser.Info(connCfg.Auth)),
connector.TLSConfigOption(tlsConfig), connector.TLSConfigOption(tlsConfig),
connector.LoggerOption(connectorLogger), connector.LoggerOption(connectorLogger),
) )
} else { } else {
return nil, fmt.Errorf("unregistered connector: %s", cfg.Connector.Type) return nil, fmt.Errorf("unregistered connector: %s", connCfg.Type)
} }
if cfg.Connector.Metadata == nil { if err := cr.Init(mdx.NewMetadata(connCfg.Metadata)); err != nil {
cfg.Connector.Metadata = make(map[string]any)
}
if err := cr.Init(mdx.NewMetadata(cfg.Connector.Metadata)); err != nil {
connectorLogger.Error("init: ", err) connectorLogger.Error("init: ", err)
return nil, err return nil, err
} }
tlsCfg = cfg.Dialer.TLS tlsCfg = dialCfg.TLS
if tlsCfg == nil { if tlsCfg == nil {
tlsCfg = &config.TLSConfig{} tlsCfg = &config.TLSConfig{}
} }
@@ -106,21 +107,18 @@ func ParseNode(hop string, cfg *config.NodeConfig, log logger.Logger) (*chain.No
}) })
var d dialer.Dialer var d dialer.Dialer
if rf := registry.DialerRegistry().Get(cfg.Dialer.Type); rf != nil { if rf := registry.DialerRegistry().Get(dialCfg.Type); rf != nil {
d = rf( d = rf(
dialer.AuthOption(auth_parser.Info(cfg.Dialer.Auth)), dialer.AuthOption(auth_parser.Info(dialCfg.Auth)),
dialer.TLSConfigOption(tlsConfig), dialer.TLSConfigOption(tlsConfig),
dialer.LoggerOption(dialerLogger), dialer.LoggerOption(dialerLogger),
dialer.ProxyProtocolOption(mdutil.GetInt(md, parsing.MDKeyProxyProtocol)), dialer.ProxyProtocolOption(mdutil.GetInt(md, parsing.MDKeyProxyProtocol)),
) )
} else { } else {
return nil, fmt.Errorf("unregistered dialer: %s", cfg.Dialer.Type) return nil, fmt.Errorf("unregistered dialer: %s", dialCfg.Type)
} }
if cfg.Dialer.Metadata == nil { if err := d.Init(mdx.NewMetadata(dialCfg.Metadata)); err != nil {
cfg.Dialer.Metadata = make(map[string]any)
}
if err := d.Init(mdx.NewMetadata(cfg.Dialer.Metadata)); err != nil {
dialerLogger.Error("init: ", err) dialerLogger.Error("init: ", err)
return nil, err return nil, err
} }
+12 -8
View File
@@ -84,24 +84,28 @@ func (p *parser) Parse() (*config.Config, error) {
} }
if v := os.Getenv("GOST_LOGGER_LEVEL"); v != "" { if v := os.Getenv("GOST_LOGGER_LEVEL"); v != "" {
cfg.Log = &config.LogConfig{ if cfg.Log == nil {
Level: v, cfg.Log = &config.LogConfig{}
} }
cfg.Log.Level = v
} }
if v := os.Getenv("GOST_API"); v != "" { if v := os.Getenv("GOST_API"); v != "" {
cfg.API = &config.APIConfig{ if cfg.API == nil {
Addr: v, cfg.API = &config.APIConfig{}
} }
cfg.API.Addr = v
} }
if v := os.Getenv("GOST_METRICS"); v != "" { if v := os.Getenv("GOST_METRICS"); v != "" {
cfg.Metrics = &config.MetricsConfig{ if cfg.Metrics == nil {
Addr: v, cfg.Metrics = &config.MetricsConfig{}
} }
cfg.Metrics.Addr = v
} }
if v := os.Getenv("GOST_PROFILING"); v != "" { if v := os.Getenv("GOST_PROFILING"); v != "" {
cfg.Profiling = &config.ProfilingConfig{ if cfg.Profiling == nil {
Addr: v, cfg.Profiling = &config.ProfilingConfig{}
} }
cfg.Profiling.Addr = v
} }
if p.args.Debug || p.args.Trace { if p.args.Debug || p.args.Trace {
+3 -1
View File
@@ -51,7 +51,9 @@ func ParseRouter(cfg *config.RouterConfig) router.Router {
_, ipNet, _ := net.ParseCIDR(route.Net) _, ipNet, _ := net.ParseCIDR(route.Net)
dst := route.Dst dst := route.Dst
if dst != "" { if dst != "" {
_, ipNet, _ = net.ParseCIDR(dst) if _, parsed, _ := net.ParseCIDR(dst); parsed != nil {
ipNet = parsed
}
} else { } else {
if ipNet != nil { if ipNet != nil {
dst = ipNet.String() dst = ipNet.String()