世界 1 год назад
Родитель
Сommit
d66d5cd457

+ 3 - 0
cmd/sing-box/cmd.go

@@ -7,8 +7,10 @@ import (
 	"strconv"
 	"time"
 
+	"github.com/sagernet/sing-box/experimental/deprecated"
 	_ "github.com/sagernet/sing-box/include"
 	"github.com/sagernet/sing-box/log"
+	"github.com/sagernet/sing/service"
 	"github.com/sagernet/sing/service/filemanager"
 
 	"github.com/spf13/cobra"
@@ -65,4 +67,5 @@ func preRun(cmd *cobra.Command, args []string) {
 	if len(configPaths) == 0 && len(configDirectories) == 0 {
 		configPaths = append(configPaths, "config.json")
 	}
+	globalCtx = service.ContextWith(globalCtx, deprecated.NewEnvManager(log.StdLogger()))
 }

+ 86 - 0
experimental/deprecated/constants.go

@@ -0,0 +1,86 @@
+package deprecated
+
+import (
+	C "github.com/sagernet/sing-box/constant"
+	F "github.com/sagernet/sing/common/format"
+
+	"golang.org/x/mod/semver"
+)
+
+type Note struct {
+	Name              string
+	Description       string
+	DeprecatedVersion string
+	ScheduledVersion  string
+	EnvName           string
+	MigrationLink     string
+}
+
+func (n Note) Impending() bool {
+	if n.ScheduledVersion == "" {
+		return false
+	}
+	if !semver.IsValid("v" + C.Version) {
+		return false
+	}
+	versionMinor := semver.Compare(semver.MajorMinor("v"+C.Version), "v"+n.ScheduledVersion)
+	if versionMinor < 0 {
+		panic("invalid deprecated note: " + n.Name)
+	}
+	return versionMinor <= 1
+}
+
+func (n Note) Message() string {
+	return F.ToString(
+		n.Description, " is deprecated in sing-box ", n.DeprecatedVersion,
+		" and will be removed in sing-box ", n.ScheduledVersion, ", please checkout documentation for migration.",
+	)
+}
+
+func (n Note) MessageWithLink() string {
+	return F.ToString(
+		n.Description, " is deprecated in sing-box ", n.DeprecatedVersion,
+		" and will be removed in sing-box ", n.ScheduledVersion, ", checkout documentation for migration: ", n.MigrationLink,
+	)
+}
+
+var OptionBadMatchSource = Note{
+	Name:              "bad-match-source",
+	Description:       "legacy match source rule item",
+	DeprecatedVersion: "1.10.0",
+	ScheduledVersion:  "1.11.0",
+	MigrationLink:     "https://sing-box.sagernet.org/deprecated/#match-source-rule-items-are-renamed",
+}
+
+var OptionGEOIP = Note{
+	Name:              "geoip",
+	Description:       "geoip database",
+	DeprecatedVersion: "1.8.0",
+	ScheduledVersion:  "1.12.0",
+	EnvName:           "GEOIP",
+	MigrationLink:     "https://sing-box.sagernet.org/migration/#migrate-geoip-to-rule-sets",
+}
+
+var OptionGEOSITE = Note{
+	Name:              "geosite",
+	Description:       "geosite database",
+	DeprecatedVersion: "1.8.0",
+	ScheduledVersion:  "1.12.0",
+	EnvName:           "GEOSITE",
+	MigrationLink:     "https://sing-box.sagernet.org/migration/#migrate-geosite-to-rule-sets",
+}
+
+var OptionTUNAddressX = Note{
+	Name:              "tun-address-x",
+	Description:       "legacy tun address fields",
+	DeprecatedVersion: "1.10.0",
+	ScheduledVersion:  "1.12.0",
+	MigrationLink:     "https://sing-box.sagernet.org/migration/#tun-address-fields-are-merged",
+}
+
+var Options = []Note{
+	OptionBadMatchSource,
+	OptionGEOIP,
+	OptionGEOSITE,
+	OptionTUNAddressX,
+}

+ 30 - 0
experimental/deprecated/env.go

@@ -0,0 +1,30 @@
+package deprecated
+
+import (
+	"os"
+	"strconv"
+
+	"github.com/sagernet/sing/common/logger"
+)
+
+type envManager struct {
+	logger logger.Logger
+}
+
+func NewEnvManager(logger logger.Logger) Manager {
+	return &envManager{logger: logger}
+}
+
+func (f *envManager) ReportDeprecated(feature Note) {
+	if !feature.Impending() {
+		f.logger.Warn(feature.MessageWithLink())
+		return
+	}
+	enable, enableErr := strconv.ParseBool(os.Getenv("ENABLE_DEPRECATED_" + feature.EnvName))
+	if enableErr == nil && enable {
+		f.logger.Warn(feature.MessageWithLink())
+		return
+	}
+	f.logger.Error(feature.MessageWithLink())
+	f.logger.Fatal("to continuing using this feature, set ENABLE_DEPRECATED_" + feature.EnvName + "=true")
+}

+ 19 - 0
experimental/deprecated/manager.go

@@ -0,0 +1,19 @@
+package deprecated
+
+import (
+	"context"
+
+	"github.com/sagernet/sing/service"
+)
+
+type Manager interface {
+	ReportDeprecated(feature Note)
+}
+
+func Report(ctx context.Context, feature Note) {
+	manager := service.FromContext[Manager](ctx)
+	if manager == nil {
+		return
+	}
+	manager.ReportDeprecated(feature)
+}

+ 1 - 0
experimental/libbox/command.go

@@ -16,4 +16,5 @@ const (
 	CommandSetSystemProxyEnabled
 	CommandConnections
 	CommandCloseConnection
+	CommandGetDeprecatedNotes
 )

+ 4 - 0
experimental/libbox/command_close_connection.go

@@ -18,6 +18,10 @@ func (c *CommandClient) CloseConnection(connId string) error {
 		return err
 	}
 	defer conn.Close()
+	err = binary.Write(conn, binary.BigEndian, uint8(CommandCloseConnection))
+	if err != nil {
+		return err
+	}
 	writer := bufio.NewWriter(conn)
 	err = varbin.Write(writer, binary.BigEndian, connId)
 	if err != nil {

+ 46 - 0
experimental/libbox/command_deprecated_report.go

@@ -0,0 +1,46 @@
+package libbox
+
+import (
+	"encoding/binary"
+	"net"
+
+	"github.com/sagernet/sing-box/experimental/deprecated"
+	"github.com/sagernet/sing/common"
+	E "github.com/sagernet/sing/common/exceptions"
+	"github.com/sagernet/sing/common/varbin"
+	"github.com/sagernet/sing/service"
+)
+
+func (c *CommandClient) GetDeprecatedNotes() (DeprecatedNoteIterator, error) {
+	conn, err := c.directConnect()
+	if err != nil {
+		return nil, err
+	}
+	defer conn.Close()
+	err = binary.Write(conn, binary.BigEndian, uint8(CommandGetDeprecatedNotes))
+	if err != nil {
+		return nil, err
+	}
+	err = readError(conn)
+	if err != nil {
+		return nil, err
+	}
+	var features []deprecated.Note
+	err = varbin.Read(conn, binary.BigEndian, &features)
+	if err != nil {
+		return nil, err
+	}
+	return newIterator(common.Map(features, func(it deprecated.Note) *DeprecatedNote { return (*DeprecatedNote)(&it) })), nil
+}
+
+func (s *CommandServer) handleGetDeprecatedNotes(conn net.Conn) error {
+	boxService := s.service
+	if boxService == nil {
+		return writeError(conn, E.New("service not ready"))
+	}
+	err := writeError(conn, nil)
+	if err != nil {
+		return err
+	}
+	return varbin.Write(conn, binary.BigEndian, service.FromContext[deprecated.Manager](boxService.ctx).(*deprecatedManager).Get())
+}

+ 2 - 0
experimental/libbox/command_server.go

@@ -174,6 +174,8 @@ func (s *CommandServer) handleConnection(conn net.Conn) error {
 		return s.handleConnectionsConn(conn)
 	case CommandCloseConnection:
 		return s.handleCloseConnection(conn)
+	case CommandGetDeprecatedNotes:
+		return s.handleGetDeprecatedNotes(conn)
 	default:
 		return E.New("unknown command: ", command)
 	}

+ 56 - 0
experimental/libbox/deprecated.go

@@ -0,0 +1,56 @@
+package libbox
+
+import (
+	"sync"
+
+	"github.com/sagernet/sing-box/experimental/deprecated"
+)
+
+var _ deprecated.Manager = (*deprecatedManager)(nil)
+
+type deprecatedManager struct {
+	access   sync.Mutex
+	features []deprecated.Note
+}
+
+func (m *deprecatedManager) ReportDeprecated(feature deprecated.Note) {
+	m.access.Lock()
+	defer m.access.Unlock()
+	m.features = append(m.features, feature)
+}
+
+func (m *deprecatedManager) Get() []deprecated.Note {
+	m.access.Lock()
+	defer m.access.Unlock()
+	features := m.features
+	m.features = nil
+	return features
+}
+
+var _ = deprecated.Note(DeprecatedNote{})
+
+type DeprecatedNote struct {
+	Name              string
+	Description       string
+	DeprecatedVersion string
+	ScheduledVersion  string
+	EnvName           string
+	MigrationLink     string
+}
+
+func (n DeprecatedNote) Impending() bool {
+	return deprecated.Note(n).Impending()
+}
+
+func (n DeprecatedNote) Message() string {
+	return deprecated.Note(n).Message()
+}
+
+func (n DeprecatedNote) MessageWithLink() string {
+	return deprecated.Note(n).MessageWithLink()
+}
+
+type DeprecatedNoteIterator interface {
+	HasNext() bool
+	Next() *DeprecatedNote
+}

+ 2 - 0
experimental/libbox/service.go

@@ -14,6 +14,7 @@ import (
 	"github.com/sagernet/sing-box/common/process"
 	"github.com/sagernet/sing-box/common/urltest"
 	C "github.com/sagernet/sing-box/constant"
+	"github.com/sagernet/sing-box/experimental/deprecated"
 	"github.com/sagernet/sing-box/experimental/libbox/internal/procfs"
 	"github.com/sagernet/sing-box/experimental/libbox/platform"
 	"github.com/sagernet/sing-box/log"
@@ -49,6 +50,7 @@ func NewService(configContent string, platformInterface PlatformInterface) (*Box
 	ctx = filemanager.WithDefault(ctx, sWorkingPath, sTempPath, sUserID, sGroupID)
 	urlTestHistoryStorage := urltest.NewHistoryStorage()
 	ctx = service.ContextWithPtr(ctx, urlTestHistoryStorage)
+	ctx = service.ContextWith[deprecated.Manager](ctx, new(deprecatedManager))
 	platformWrapper := &platformInterfaceWrapper{iif: platformInterface, useProcFS: platformInterface.UseProcFS()}
 	instance, err := box.New(box.Options{
 		Context:           ctx,

+ 1 - 1
go.mod

@@ -46,6 +46,7 @@ require (
 	go4.org/netipx v0.0.0-20231129151722-fdeea329fbba
 	golang.org/x/crypto v0.25.0
 	golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56
+	golang.org/x/mod v0.19.0
 	golang.org/x/net v0.27.0
 	golang.org/x/sys v0.25.0
 	golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6
@@ -89,7 +90,6 @@ require (
 	github.com/vishvananda/netns v0.0.4 // indirect
 	github.com/zeebo/blake3 v0.2.3 // indirect
 	go.uber.org/multierr v1.11.0 // indirect
-	golang.org/x/mod v0.19.0 // indirect
 	golang.org/x/sync v0.8.0 // indirect
 	golang.org/x/text v0.18.0 // indirect
 	golang.org/x/time v0.5.0 // indirect

+ 12 - 0
inbound/tun.go

@@ -13,6 +13,7 @@ import (
 	"github.com/sagernet/sing-box/adapter"
 	"github.com/sagernet/sing-box/common/taskmonitor"
 	C "github.com/sagernet/sing-box/constant"
+	"github.com/sagernet/sing-box/experimental/deprecated"
 	"github.com/sagernet/sing-box/experimental/libbox/platform"
 	"github.com/sagernet/sing-box/log"
 	"github.com/sagernet/sing-box/option"
@@ -54,15 +55,18 @@ type Tun struct {
 
 func NewTun(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.TunInboundOptions, platformInterface platform.Interface) (*Tun, error) {
 	address := options.Address
+	var deprecatedAddressUsed bool
 	//nolint:staticcheck
 	//goland:noinspection GoDeprecation
 	if len(options.Inet4Address) > 0 {
 		address = append(address, options.Inet4Address...)
+		deprecatedAddressUsed = true
 	}
 	//nolint:staticcheck
 	//goland:noinspection GoDeprecation
 	if len(options.Inet6Address) > 0 {
 		address = append(address, options.Inet6Address...)
+		deprecatedAddressUsed = true
 	}
 	inet4Address := common.Filter(address, func(it netip.Prefix) bool {
 		return it.Addr().Is4()
@@ -76,11 +80,13 @@ func NewTun(ctx context.Context, router adapter.Router, logger log.ContextLogger
 	//goland:noinspection GoDeprecation
 	if len(options.Inet4RouteAddress) > 0 {
 		routeAddress = append(routeAddress, options.Inet4RouteAddress...)
+		deprecatedAddressUsed = true
 	}
 	//nolint:staticcheck
 	//goland:noinspection GoDeprecation
 	if len(options.Inet6RouteAddress) > 0 {
 		routeAddress = append(routeAddress, options.Inet6RouteAddress...)
+		deprecatedAddressUsed = true
 	}
 	inet4RouteAddress := common.Filter(routeAddress, func(it netip.Prefix) bool {
 		return it.Addr().Is4()
@@ -94,11 +100,13 @@ func NewTun(ctx context.Context, router adapter.Router, logger log.ContextLogger
 	//goland:noinspection GoDeprecation
 	if len(options.Inet4RouteExcludeAddress) > 0 {
 		routeExcludeAddress = append(routeExcludeAddress, options.Inet4RouteExcludeAddress...)
+		deprecatedAddressUsed = true
 	}
 	//nolint:staticcheck
 	//goland:noinspection GoDeprecation
 	if len(options.Inet6RouteExcludeAddress) > 0 {
 		routeExcludeAddress = append(routeExcludeAddress, options.Inet6RouteExcludeAddress...)
+		deprecatedAddressUsed = true
 	}
 	inet4RouteExcludeAddress := common.Filter(routeExcludeAddress, func(it netip.Prefix) bool {
 		return it.Addr().Is4()
@@ -107,6 +115,10 @@ func NewTun(ctx context.Context, router adapter.Router, logger log.ContextLogger
 		return it.Addr().Is6()
 	})
 
+	if deprecatedAddressUsed {
+		deprecated.Report(ctx, deprecated.OptionTUNAddressX)
+	}
+
 	tunMTU := options.MTU
 	if tunMTU == 0 {
 		tunMTU = 9000

+ 1 - 17
option/rule.go

@@ -64,7 +64,7 @@ func (r Rule) IsValid() bool {
 	}
 }
 
-type _DefaultRule struct {
+type DefaultRule struct {
 	Inbound                  Listable[string] `json:"inbound,omitempty"`
 	IPVersion                int              `json:"ip_version,omitempty"`
 	Network                  Listable[string] `json:"network,omitempty"`
@@ -104,22 +104,6 @@ type _DefaultRule struct {
 	Deprecated_RulesetIPCIDRMatchSource bool `json:"rule_set_ipcidr_match_source,omitempty"`
 }
 
-type DefaultRule _DefaultRule
-
-func (r *DefaultRule) UnmarshalJSON(bytes []byte) error {
-	err := json.Unmarshal(bytes, (*_DefaultRule)(r))
-	if err != nil {
-		return err
-	}
-	//nolint:staticcheck
-	//goland:noinspection GoDeprecation
-	if r.Deprecated_RulesetIPCIDRMatchSource {
-		r.Deprecated_RulesetIPCIDRMatchSource = false
-		r.RuleSetIPCIDRMatchSource = true
-	}
-	return nil
-}
-
 func (r *DefaultRule) IsValid() bool {
 	var defaultValue DefaultRule
 	defaultValue.Invert = r.Invert

+ 1 - 17
option/rule_dns.go

@@ -64,7 +64,7 @@ func (r DNSRule) IsValid() bool {
 	}
 }
 
-type _DefaultDNSRule struct {
+type DefaultDNSRule struct {
 	Inbound                  Listable[string]       `json:"inbound,omitempty"`
 	IPVersion                int                    `json:"ip_version,omitempty"`
 	QueryType                Listable[DNSQueryType] `json:"query_type,omitempty"`
@@ -109,22 +109,6 @@ type _DefaultDNSRule struct {
 	Deprecated_RulesetIPCIDRMatchSource bool `json:"rule_set_ipcidr_match_source,omitempty"`
 }
 
-type DefaultDNSRule _DefaultDNSRule
-
-func (r *DefaultDNSRule) UnmarshalJSON(bytes []byte) error {
-	err := json.UnmarshalDisallowUnknownFields(bytes, (*_DefaultDNSRule)(r))
-	if err != nil {
-		return err
-	}
-	//nolint:staticcheck
-	//goland:noinspection GoDeprecation
-	if r.Deprecated_RulesetIPCIDRMatchSource {
-		r.Deprecated_RulesetIPCIDRMatchSource = false
-		r.RuleSetIPCIDRMatchSource = true
-	}
-	return nil
-}
-
 func (r *DefaultDNSRule) IsValid() bool {
 	var defaultValue DefaultDNSRule
 	defaultValue.Invert = r.Invert

+ 2 - 2
route/router.go

@@ -153,14 +153,14 @@ func NewRouter(
 		Logger: router.dnsLogger,
 	})
 	for i, ruleOptions := range options.Rules {
-		routeRule, err := NewRule(router, router.logger, ruleOptions, true)
+		routeRule, err := NewRule(ctx, router, router.logger, ruleOptions, true)
 		if err != nil {
 			return nil, E.Cause(err, "parse rule[", i, "]")
 		}
 		router.rules = append(router.rules, routeRule)
 	}
 	for i, dnsRuleOptions := range dnsOptions.Rules {
-		dnsRule, err := NewDNSRule(router, router.logger, dnsRuleOptions, true)
+		dnsRule, err := NewDNSRule(ctx, router, router.logger, dnsRuleOptions, true)
 		if err != nil {
 			return nil, E.Cause(err, "parse dns rule[", i, "]")
 		}

+ 4 - 1
route/router_geo_resources.go

@@ -12,6 +12,7 @@ import (
 	"github.com/sagernet/sing-box/common/geoip"
 	"github.com/sagernet/sing-box/common/geosite"
 	C "github.com/sagernet/sing-box/constant"
+	"github.com/sagernet/sing-box/experimental/deprecated"
 	E "github.com/sagernet/sing/common/exceptions"
 	M "github.com/sagernet/sing/common/metadata"
 	"github.com/sagernet/sing/common/rw"
@@ -31,7 +32,7 @@ func (r *Router) LoadGeosite(code string) (adapter.Rule, error) {
 	if err != nil {
 		return nil, err
 	}
-	rule, err = NewDefaultRule(r, nil, geosite.Compile(items))
+	rule, err = NewDefaultRule(r.ctx, r, nil, geosite.Compile(items))
 	if err != nil {
 		return nil, err
 	}
@@ -40,6 +41,7 @@ func (r *Router) LoadGeosite(code string) (adapter.Rule, error) {
 }
 
 func (r *Router) prepareGeoIPDatabase() error {
+	deprecated.Report(r.ctx, deprecated.OptionGEOIP)
 	var geoPath string
 	if r.geoIPOptions.Path != "" {
 		geoPath = r.geoIPOptions.Path
@@ -86,6 +88,7 @@ func (r *Router) prepareGeoIPDatabase() error {
 }
 
 func (r *Router) prepareGeositeDatabase() error {
+	deprecated.Report(r.ctx, deprecated.OptionGEOSITE)
 	var geoPath string
 	if r.geositeOptions.Path != "" {
 		geoPath = r.geositeOptions.Path

+ 19 - 7
route/rule_default.go

@@ -1,14 +1,17 @@
 package route
 
 import (
+	"context"
+
 	"github.com/sagernet/sing-box/adapter"
 	C "github.com/sagernet/sing-box/constant"
+	"github.com/sagernet/sing-box/experimental/deprecated"
 	"github.com/sagernet/sing-box/log"
 	"github.com/sagernet/sing-box/option"
 	E "github.com/sagernet/sing/common/exceptions"
 )
 
-func NewRule(router adapter.Router, logger log.ContextLogger, options option.Rule, checkOutbound bool) (adapter.Rule, error) {
+func NewRule(ctx context.Context, router adapter.Router, logger log.ContextLogger, options option.Rule, checkOutbound bool) (adapter.Rule, error) {
 	switch options.Type {
 	case "", C.RuleTypeDefault:
 		if !options.DefaultOptions.IsValid() {
@@ -17,7 +20,7 @@ func NewRule(router adapter.Router, logger log.ContextLogger, options option.Rul
 		if options.DefaultOptions.Outbound == "" && checkOutbound {
 			return nil, E.New("missing outbound field")
 		}
-		return NewDefaultRule(router, logger, options.DefaultOptions)
+		return NewDefaultRule(ctx, router, logger, options.DefaultOptions)
 	case C.RuleTypeLogical:
 		if !options.LogicalOptions.IsValid() {
 			return nil, E.New("missing conditions")
@@ -25,7 +28,7 @@ func NewRule(router adapter.Router, logger log.ContextLogger, options option.Rul
 		if options.LogicalOptions.Outbound == "" && checkOutbound {
 			return nil, E.New("missing outbound field")
 		}
-		return NewLogicalRule(router, logger, options.LogicalOptions)
+		return NewLogicalRule(ctx, router, logger, options.LogicalOptions)
 	default:
 		return nil, E.New("unknown rule type: ", options.Type)
 	}
@@ -42,7 +45,7 @@ type RuleItem interface {
 	String() string
 }
 
-func NewDefaultRule(router adapter.Router, logger log.ContextLogger, options option.DefaultRule) (*DefaultRule, error) {
+func NewDefaultRule(ctx context.Context, router adapter.Router, logger log.ContextLogger, options option.DefaultRule) (*DefaultRule, error) {
 	rule := &DefaultRule{
 		abstractDefaultRule{
 			invert:   options.Invert,
@@ -218,7 +221,16 @@ func NewDefaultRule(router adapter.Router, logger log.ContextLogger, options opt
 		rule.allItems = append(rule.allItems, item)
 	}
 	if len(options.RuleSet) > 0 {
-		item := NewRuleSetItem(router, options.RuleSet, options.RuleSetIPCIDRMatchSource, false)
+		var matchSource bool
+		if options.RuleSetIPCIDRMatchSource {
+			matchSource = true
+		} else
+		//nolint:staticcheck
+		if options.Deprecated_RulesetIPCIDRMatchSource {
+			matchSource = true
+			deprecated.Report(ctx, deprecated.OptionBadMatchSource)
+		}
+		item := NewRuleSetItem(router, options.RuleSet, matchSource, false)
 		rule.items = append(rule.items, item)
 		rule.allItems = append(rule.allItems, item)
 	}
@@ -231,7 +243,7 @@ type LogicalRule struct {
 	abstractLogicalRule
 }
 
-func NewLogicalRule(router adapter.Router, logger log.ContextLogger, options option.LogicalRule) (*LogicalRule, error) {
+func NewLogicalRule(ctx context.Context, router adapter.Router, logger log.ContextLogger, options option.LogicalRule) (*LogicalRule, error) {
 	r := &LogicalRule{
 		abstractLogicalRule{
 			rules:    make([]adapter.HeadlessRule, len(options.Rules)),
@@ -248,7 +260,7 @@ func NewLogicalRule(router adapter.Router, logger log.ContextLogger, options opt
 		return nil, E.New("unknown logical mode: ", options.Mode)
 	}
 	for i, subRule := range options.Rules {
-		rule, err := NewRule(router, logger, subRule, false)
+		rule, err := NewRule(ctx, router, logger, subRule, false)
 		if err != nil {
 			return nil, E.Cause(err, "sub rule[", i, "]")
 		}

+ 18 - 7
route/rule_dns.go

@@ -1,17 +1,19 @@
 package route
 
 import (
+	"context"
 	"net/netip"
 
 	"github.com/sagernet/sing-box/adapter"
 	C "github.com/sagernet/sing-box/constant"
+	"github.com/sagernet/sing-box/experimental/deprecated"
 	"github.com/sagernet/sing-box/log"
 	"github.com/sagernet/sing-box/option"
 	"github.com/sagernet/sing/common"
 	E "github.com/sagernet/sing/common/exceptions"
 )
 
-func NewDNSRule(router adapter.Router, logger log.ContextLogger, options option.DNSRule, checkServer bool) (adapter.DNSRule, error) {
+func NewDNSRule(ctx context.Context, router adapter.Router, logger log.ContextLogger, options option.DNSRule, checkServer bool) (adapter.DNSRule, error) {
 	switch options.Type {
 	case "", C.RuleTypeDefault:
 		if !options.DefaultOptions.IsValid() {
@@ -20,7 +22,7 @@ func NewDNSRule(router adapter.Router, logger log.ContextLogger, options option.
 		if options.DefaultOptions.Server == "" && checkServer {
 			return nil, E.New("missing server field")
 		}
-		return NewDefaultDNSRule(router, logger, options.DefaultOptions)
+		return NewDefaultDNSRule(ctx, router, logger, options.DefaultOptions)
 	case C.RuleTypeLogical:
 		if !options.LogicalOptions.IsValid() {
 			return nil, E.New("missing conditions")
@@ -28,7 +30,7 @@ func NewDNSRule(router adapter.Router, logger log.ContextLogger, options option.
 		if options.LogicalOptions.Server == "" && checkServer {
 			return nil, E.New("missing server field")
 		}
-		return NewLogicalDNSRule(router, logger, options.LogicalOptions)
+		return NewLogicalDNSRule(ctx, router, logger, options.LogicalOptions)
 	default:
 		return nil, E.New("unknown rule type: ", options.Type)
 	}
@@ -43,7 +45,7 @@ type DefaultDNSRule struct {
 	clientSubnet *netip.Prefix
 }
 
-func NewDefaultDNSRule(router adapter.Router, logger log.ContextLogger, options option.DefaultDNSRule) (*DefaultDNSRule, error) {
+func NewDefaultDNSRule(ctx context.Context, router adapter.Router, logger log.ContextLogger, options option.DefaultDNSRule) (*DefaultDNSRule, error) {
 	rule := &DefaultDNSRule{
 		abstractDefaultRule: abstractDefaultRule{
 			invert:   options.Invert,
@@ -227,7 +229,16 @@ func NewDefaultDNSRule(router adapter.Router, logger log.ContextLogger, options
 		rule.allItems = append(rule.allItems, item)
 	}
 	if len(options.RuleSet) > 0 {
-		item := NewRuleSetItem(router, options.RuleSet, options.RuleSetIPCIDRMatchSource, options.RuleSetIPCIDRAcceptEmpty)
+		var matchSource bool
+		if options.RuleSetIPCIDRMatchSource {
+			matchSource = true
+		} else
+		//nolint:staticcheck
+		if options.Deprecated_RulesetIPCIDRMatchSource {
+			matchSource = true
+			deprecated.Report(ctx, deprecated.OptionBadMatchSource)
+		}
+		item := NewRuleSetItem(router, options.RuleSet, matchSource, options.RuleSetIPCIDRAcceptEmpty)
 		rule.items = append(rule.items, item)
 		rule.allItems = append(rule.allItems, item)
 	}
@@ -283,7 +294,7 @@ type LogicalDNSRule struct {
 	clientSubnet *netip.Prefix
 }
 
-func NewLogicalDNSRule(router adapter.Router, logger log.ContextLogger, options option.LogicalDNSRule) (*LogicalDNSRule, error) {
+func NewLogicalDNSRule(ctx context.Context, router adapter.Router, logger log.ContextLogger, options option.LogicalDNSRule) (*LogicalDNSRule, error) {
 	r := &LogicalDNSRule{
 		abstractLogicalRule: abstractLogicalRule{
 			rules:    make([]adapter.HeadlessRule, len(options.Rules)),
@@ -303,7 +314,7 @@ func NewLogicalDNSRule(router adapter.Router, logger log.ContextLogger, options
 		return nil, E.New("unknown logical mode: ", options.Mode)
 	}
 	for i, subRule := range options.Rules {
-		rule, err := NewDNSRule(router, logger, subRule, false)
+		rule, err := NewDNSRule(ctx, router, logger, subRule, false)
 		if err != nil {
 			return nil, E.Cause(err, "sub rule[", i, "]")
 		}