diff --git a/Makefile b/Makefile index 43f93c8c..a98faeac 100644 --- a/Makefile +++ b/Makefile @@ -17,6 +17,10 @@ build: export GOTOOLCHAIN=local && \ go build $(MAIN_PARAMS) $(MAIN) +race: + export GOTOOLCHAIN=local && \ + go build -race $(MAIN_PARAMS) $(MAIN) + ci_build: export GOTOOLCHAIN=local && \ go build $(PARAMS) $(MAIN) && \ diff --git a/common/tls/ech.go b/common/tls/ech.go index d264de9b..5e9fba6d 100644 --- a/common/tls/ech.go +++ b/common/tls/ech.go @@ -81,6 +81,19 @@ func parseECHServerConfig(ctx context.Context, options option.InboundTLSOptions, return nil } +func (c *STDServerConfig) setECHServerConfig(echKey []byte) error { + echKeys, err := parseECHKeys(echKey) + if err != nil { + return err + } + c.access.Lock() + config := c.config.Clone() + config.EncryptedClientHelloKeys = echKeys + c.config = config + c.access.Unlock() + return nil +} + func parseECHKeys(echKey []byte) ([]tls.EncryptedClientHelloKey, error) { block, _ := pem.Decode(echKey) if block == nil || block.Type != "ECH KEYS" { diff --git a/common/tls/ech_stub.go b/common/tls/ech_stub.go index 3b6ffc23..357466c0 100644 --- a/common/tls/ech_stub.go +++ b/common/tls/ech_stub.go @@ -18,6 +18,6 @@ func parseECHServerConfig(ctx context.Context, options option.InboundTLSOptions, return E.New("ECH requires go1.24, please recompile your binary.") } -func reloadECHKeys(echKeyPath string, tlsConfig *tls.Config) error { - return E.New("ECH requires go1.24, please recompile your binary.") +func (c *STDServerConfig) setECHServerConfig(echKey []byte) error { + panic("unreachable") } diff --git a/common/tls/std_server.go b/common/tls/std_server.go index 541818fa..94774179 100644 --- a/common/tls/std_server.go +++ b/common/tls/std_server.go @@ -163,15 +163,10 @@ func (c *STDServerConfig) certificateUpdated(path string) error { if err != nil { return E.Cause(err, "reload ECH keys from ", c.echKeyPath) } - echKeys, err := parseECHKeys(echKey) + err = c.setECHServerConfig(echKey) if err != nil { return err } - c.access.Lock() - config := c.config.Clone() - config.EncryptedClientHelloKeys = echKeys - c.config = config - c.access.Unlock() c.logger.Info("reloaded ECH keys") } return nil diff --git a/common/urltest/urltest.go b/common/urltest/urltest.go index cfe1e532..ee8624fd 100644 --- a/common/urltest/urltest.go +++ b/common/urltest/urltest.go @@ -47,15 +47,15 @@ func (s *HistoryStorage) LoadURLTestHistory(tag string) *adapter.URLTestHistory func (s *HistoryStorage) DeleteURLTestHistory(tag string) { s.access.Lock() delete(s.delayHistory, tag) - s.access.Unlock() s.notifyUpdated() + s.access.Unlock() } func (s *HistoryStorage) StoreURLTestHistory(tag string, history *adapter.URLTestHistory) { s.access.Lock() s.delayHistory[tag] = history - s.access.Unlock() s.notifyUpdated() + s.access.Unlock() } func (s *HistoryStorage) notifyUpdated() { @@ -69,6 +69,8 @@ func (s *HistoryStorage) notifyUpdated() { } func (s *HistoryStorage) Close() error { + s.access.Lock() + defer s.access.Unlock() s.updateHook = nil return nil } diff --git a/go.mod b/go.mod index de248fb2..71aba29e 100644 --- a/go.mod +++ b/go.mod @@ -27,13 +27,13 @@ require ( github.com/sagernet/gomobile v0.1.8 github.com/sagernet/gvisor v0.0.0-20250325023245-7a9c0f5725fb github.com/sagernet/quic-go v0.52.0-beta.1 - github.com/sagernet/sing v0.7.8 + github.com/sagernet/sing v0.7.10 github.com/sagernet/sing-mux v0.3.3 github.com/sagernet/sing-quic v0.5.2-0.20250909083218-00a55617c0fb github.com/sagernet/sing-shadowsocks v0.2.8 github.com/sagernet/sing-shadowsocks2 v0.2.1 github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11 - github.com/sagernet/sing-tun v0.7.1 + github.com/sagernet/sing-tun v0.7.2 github.com/sagernet/sing-vmess v0.2.7 github.com/sagernet/smux v1.5.34-mod.2 github.com/sagernet/tailscale v1.80.3-sing-box-1.12-mod.1 diff --git a/go.sum b/go.sum index 3e2a8d83..34aad7a1 100644 --- a/go.sum +++ b/go.sum @@ -167,8 +167,8 @@ github.com/sagernet/nftables v0.3.0-beta.4/go.mod h1:OQXAjvjNGGFxaTgVCSTRIhYB5/l github.com/sagernet/quic-go v0.52.0-beta.1 h1:hWkojLg64zjV+MJOvJU/kOeWndm3tiEfBLx5foisszs= github.com/sagernet/quic-go v0.52.0-beta.1/go.mod h1:OV+V5kEBb8kJS7k29MzDu6oj9GyMc7HA07sE1tedxz4= github.com/sagernet/sing v0.6.9/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= -github.com/sagernet/sing v0.7.8 h1:i3JBTzeEOqMRtYcyNV17LKvxkb3mr2Y/omM5ldvhCYo= -github.com/sagernet/sing v0.7.8/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= +github.com/sagernet/sing v0.7.10 h1:2yPhZFx+EkyHPH8hXNezgyRSHyGY12CboId7CtwLROw= +github.com/sagernet/sing v0.7.10/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= github.com/sagernet/sing-mux v0.3.3 h1:YFgt9plMWzH994BMZLmyKL37PdIVaIilwP0Jg+EcLfw= github.com/sagernet/sing-mux v0.3.3/go.mod h1:pht8iFY4c9Xltj7rhVd208npkNaeCxzyXCgulDPLUDA= github.com/sagernet/sing-quic v0.5.2-0.20250909083218-00a55617c0fb h1:5Wx3XeTiKrrrcrAky7Hc1bO3CGxrvho2Vu5b/adlEIM= @@ -179,8 +179,8 @@ github.com/sagernet/sing-shadowsocks2 v0.2.1 h1:dWV9OXCeFPuYGHb6IRqlSptVnSzOelnq github.com/sagernet/sing-shadowsocks2 v0.2.1/go.mod h1:RnXS0lExcDAovvDeniJ4IKa2IuChrdipolPYWBv9hWQ= github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11 h1:tK+75l64tm9WvEFrYRE1t0YxoFdWQqw/h7Uhzj0vJ+w= github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11/go.mod h1:sWqKnGlMipCHaGsw1sTTlimyUpgzP4WP3pjhCsYt9oA= -github.com/sagernet/sing-tun v0.7.1 h1:bgoe9ixY9G+y8FN5y2szD/0Kr1wQqYuuiCzsXefNDBE= -github.com/sagernet/sing-tun v0.7.1/go.mod h1:pUEjh9YHQ2gJT6Lk0TYDklh3WJy7lz+848vleGM3JPM= +github.com/sagernet/sing-tun v0.7.2 h1:uJkAZM0KBqIYzrq077QGqdvj/+4i/pMOx6Pnx0jYqAs= +github.com/sagernet/sing-tun v0.7.2/go.mod h1:pUEjh9YHQ2gJT6Lk0TYDklh3WJy7lz+848vleGM3JPM= github.com/sagernet/sing-vmess v0.2.7 h1:2ee+9kO0xW5P4mfe6TYVWf9VtY8k1JhNysBqsiYj0sk= github.com/sagernet/sing-vmess v0.2.7/go.mod h1:5aYoOtYksAyS0NXDm0qKeTYW1yoE1bJVcv+XLcVoyJs= github.com/sagernet/smux v1.5.34-mod.2 h1:gkmBjIjlJ2zQKpLigOkFur5kBKdV6bNRoFu2WkltRQ4= diff --git a/route/rule/rule_set_local.go b/route/rule/rule_set_local.go index 0ef15fc7..bda7c5ab 100644 --- a/route/rule/rule_set_local.go +++ b/route/rule/rule_set_local.go @@ -27,16 +27,16 @@ import ( var _ adapter.RuleSet = (*LocalRuleSet)(nil) type LocalRuleSet struct { - ctx context.Context - logger logger.Logger - tag string - rules []adapter.HeadlessRule - metadata adapter.RuleSetMetadata - fileFormat string - watcher *fswatch.Watcher - callbackAccess sync.Mutex - callbacks list.List[adapter.RuleSetUpdateCallback] - refs atomic.Int32 + ctx context.Context + logger logger.Logger + tag string + access sync.RWMutex + rules []adapter.HeadlessRule + metadata adapter.RuleSetMetadata + fileFormat string + watcher *fswatch.Watcher + callbacks list.List[adapter.RuleSetUpdateCallback] + refs atomic.Int32 } func NewLocalRuleSet(ctx context.Context, logger logger.Logger, options option.RuleSet) (*LocalRuleSet, error) { @@ -141,11 +141,11 @@ func (s *LocalRuleSet) reloadRules(headlessRules []option.HeadlessRule) error { metadata.ContainsProcessRule = hasHeadlessRule(headlessRules, isProcessHeadlessRule) metadata.ContainsWIFIRule = hasHeadlessRule(headlessRules, isWIFIHeadlessRule) metadata.ContainsIPCIDRRule = hasHeadlessRule(headlessRules, isIPCIDRHeadlessRule) + s.access.Lock() s.rules = rules s.metadata = metadata - s.callbackAccess.Lock() callbacks := s.callbacks.Array() - s.callbackAccess.Unlock() + s.access.Unlock() for _, callback := range callbacks { callback(s) } @@ -157,10 +157,14 @@ func (s *LocalRuleSet) PostStart() error { } func (s *LocalRuleSet) Metadata() adapter.RuleSetMetadata { + s.access.RLock() + defer s.access.RUnlock() return s.metadata } func (s *LocalRuleSet) ExtractIPSet() []*netipx.IPSet { + s.access.RLock() + defer s.access.RUnlock() return common.FlatMap(s.rules, extractIPSetFromRule) } @@ -181,14 +185,14 @@ func (s *LocalRuleSet) Cleanup() { } func (s *LocalRuleSet) RegisterCallback(callback adapter.RuleSetUpdateCallback) *list.Element[adapter.RuleSetUpdateCallback] { - s.callbackAccess.Lock() - defer s.callbackAccess.Unlock() + s.access.Lock() + defer s.access.Unlock() return s.callbacks.PushBack(callback) } func (s *LocalRuleSet) UnregisterCallback(element *list.Element[adapter.RuleSetUpdateCallback]) { - s.callbackAccess.Lock() - defer s.callbackAccess.Unlock() + s.access.Lock() + defer s.access.Unlock() s.callbacks.Remove(element) } diff --git a/route/rule/rule_set_remote.go b/route/rule/rule_set_remote.go index 133e575a..73e46f4f 100644 --- a/route/rule/rule_set_remote.go +++ b/route/rule/rule_set_remote.go @@ -40,16 +40,16 @@ type RemoteRuleSet struct { logger logger.ContextLogger outbound adapter.OutboundManager options option.RuleSet - metadata adapter.RuleSetMetadata updateInterval time.Duration dialer N.Dialer + access sync.RWMutex rules []adapter.HeadlessRule + metadata adapter.RuleSetMetadata lastUpdated time.Time lastEtag string updateTicker *time.Ticker cacheFile adapter.CacheFile pauseManager pause.Manager - callbackAccess sync.Mutex callbacks list.List[adapter.RuleSetUpdateCallback] refs atomic.Int32 } @@ -120,10 +120,14 @@ func (s *RemoteRuleSet) PostStart() error { } func (s *RemoteRuleSet) Metadata() adapter.RuleSetMetadata { + s.access.RLock() + defer s.access.RUnlock() return s.metadata } func (s *RemoteRuleSet) ExtractIPSet() []*netipx.IPSet { + s.access.RLock() + defer s.access.RUnlock() return common.FlatMap(s.rules, extractIPSetFromRule) } @@ -144,14 +148,14 @@ func (s *RemoteRuleSet) Cleanup() { } func (s *RemoteRuleSet) RegisterCallback(callback adapter.RuleSetUpdateCallback) *list.Element[adapter.RuleSetUpdateCallback] { - s.callbackAccess.Lock() - defer s.callbackAccess.Unlock() + s.access.Lock() + defer s.access.Unlock() return s.callbacks.PushBack(callback) } func (s *RemoteRuleSet) UnregisterCallback(element *list.Element[adapter.RuleSetUpdateCallback]) { - s.callbackAccess.Lock() - defer s.callbackAccess.Unlock() + s.access.Lock() + defer s.access.Unlock() s.callbacks.Remove(element) } @@ -185,13 +189,13 @@ func (s *RemoteRuleSet) loadBytes(content []byte) error { return E.Cause(err, "parse rule_set.rules.[", i, "]") } } + s.access.Lock() s.metadata.ContainsProcessRule = hasHeadlessRule(plainRuleSet.Rules, isProcessHeadlessRule) s.metadata.ContainsWIFIRule = hasHeadlessRule(plainRuleSet.Rules, isWIFIHeadlessRule) s.metadata.ContainsIPCIDRRule = hasHeadlessRule(plainRuleSet.Rules, isIPCIDRHeadlessRule) s.rules = rules - s.callbackAccess.Lock() callbacks := s.callbacks.Array() - s.callbackAccess.Unlock() + s.access.Unlock() for _, callback := range callbacks { callback(s) }