Browse Source

Add android package rules support in tun routing

世界 3 years ago
parent
commit
cd5e7055d2

+ 1 - 0
adapter/router.go

@@ -37,6 +37,7 @@ type Router interface {
 	DefaultMark() int
 	NetworkMonitor() tun.NetworkUpdateMonitor
 	InterfaceMonitor() tun.DefaultInterfaceMonitor
+	PackageManager() tun.PackageManager
 	Rules() []Rule
 	SetTrafficController(controller TrafficController)
 }

+ 7 - 0
common/process/searcher.go

@@ -4,6 +4,8 @@ import (
 	"context"
 	"net/netip"
 
+	"github.com/sagernet/sing-box/log"
+	"github.com/sagernet/sing-tun"
 	E "github.com/sagernet/sing/common/exceptions"
 )
 
@@ -13,6 +15,11 @@ type Searcher interface {
 
 var ErrNotFound = E.New("process not found")
 
+type Config struct {
+	Logger         log.ContextLogger
+	PackageManager tun.PackageManager
+}
+
 type Info struct {
 	ProcessPath string
 	PackageName string

+ 7 - 140
common/process/searcher_android.go

@@ -2,81 +2,19 @@ package process
 
 import (
 	"context"
-	"encoding/xml"
-	"io"
 	"net/netip"
-	"os"
-	"strconv"
 
-	"github.com/sagernet/sing-box/log"
-	"github.com/sagernet/sing/common"
-	E "github.com/sagernet/sing/common/exceptions"
-
-	"github.com/fsnotify/fsnotify"
+	tun "github.com/sagernet/sing-tun"
 )
 
 var _ Searcher = (*androidSearcher)(nil)
 
 type androidSearcher struct {
-	logger        log.ContextLogger
-	watcher       *fsnotify.Watcher
-	userMap       map[string]int32
-	packageMap    map[int32]string
-	sharedUserMap map[int32]string
-}
-
-func NewSearcher(logger log.ContextLogger) (Searcher, error) {
-	return &androidSearcher{logger: logger}, nil
+	packageManager tun.PackageManager
 }
 
-func (s *androidSearcher) Start() error {
-	err := s.updatePackages()
-	if err != nil {
-		return E.Cause(err, "read packages list")
-	}
-	err = s.startWatcher()
-	if err != nil {
-		s.logger.Warn("create fsnotify watcher: ", err)
-	}
-	return nil
-}
-
-func (s *androidSearcher) startWatcher() error {
-	watcher, err := fsnotify.NewWatcher()
-	if err != nil {
-		return err
-	}
-	err = watcher.Add("/data/system/packages.xml")
-	if err != nil {
-		return err
-	}
-	s.watcher = watcher
-	go s.loopUpdate()
-	return nil
-}
-
-func (s *androidSearcher) loopUpdate() {
-	for {
-		select {
-		case _, ok := <-s.watcher.Events:
-			if !ok {
-				return
-			}
-			err := s.updatePackages()
-			if err != nil {
-				s.logger.Error(E.Cause(err, "update packages list"))
-			}
-		case err, ok := <-s.watcher.Errors:
-			if !ok {
-				return
-			}
-			s.logger.Error(E.Cause(err, "fsnotify error"))
-		}
-	}
-}
-
-func (s *androidSearcher) Close() error {
-	return common.Close(common.PtrOrNil(s.watcher))
+func NewSearcher(config Config) (Searcher, error) {
+	return &androidSearcher{config.PackageManager}, nil
 }
 
 func (s *androidSearcher) FindProcessInfo(ctx context.Context, network string, srcIP netip.Addr, srcPort int) (*Info, error) {
@@ -84,13 +22,13 @@ func (s *androidSearcher) FindProcessInfo(ctx context.Context, network string, s
 	if err != nil {
 		return nil, err
 	}
-	if sharedUser, loaded := s.sharedUserMap[uid]; loaded {
+	if sharedPackage, loaded := s.packageManager.SharedPackageByID(uint32(uid)); loaded {
 		return &Info{
 			UserId:      uid,
-			PackageName: sharedUser,
+			PackageName: sharedPackage,
 		}, nil
 	}
-	if packageName, loaded := s.packageMap[uid]; loaded {
+	if packageName, loaded := s.packageManager.PackageByID(uint32(uid)); loaded {
 		return &Info{
 			UserId:      uid,
 			PackageName: packageName,
@@ -98,74 +36,3 @@ func (s *androidSearcher) FindProcessInfo(ctx context.Context, network string, s
 	}
 	return &Info{UserId: uid}, nil
 }
-
-func (s *androidSearcher) updatePackages() error {
-	userMap := make(map[string]int32)
-	packageMap := make(map[int32]string)
-	sharedUserMap := make(map[int32]string)
-	packagesData, err := os.Open("/data/system/packages.xml")
-	if err != nil {
-		return err
-	}
-	decoder := xml.NewDecoder(packagesData)
-	var token xml.Token
-	for {
-		token, err = decoder.Token()
-		if err == io.EOF {
-			break
-		} else if err != nil {
-			return err
-		}
-
-		element, isStart := token.(xml.StartElement)
-		if !isStart {
-			continue
-		}
-
-		switch element.Name.Local {
-		case "package":
-			var name string
-			var userID int64
-			for _, attr := range element.Attr {
-				switch attr.Name.Local {
-				case "name":
-					name = attr.Value
-				case "userId", "sharedUserId":
-					userID, err = strconv.ParseInt(attr.Value, 10, 32)
-					if err != nil {
-						return err
-					}
-				}
-			}
-			if userID == 0 && name == "" {
-				continue
-			}
-			userMap[name] = int32(userID)
-			packageMap[int32(userID)] = name
-		case "shared-user":
-			var name string
-			var userID int64
-			for _, attr := range element.Attr {
-				switch attr.Name.Local {
-				case "name":
-					name = attr.Value
-				case "userId":
-					userID, err = strconv.ParseInt(attr.Value, 10, 32)
-					if err != nil {
-						return err
-					}
-					packageMap[int32(userID)] = name
-				}
-			}
-			if userID == 0 && name == "" {
-				continue
-			}
-			sharedUserMap[int32(userID)] = name
-		}
-	}
-	s.logger.Info("updated packages list: ", len(packageMap), " packages, ", len(sharedUserMap), " shared users")
-	s.userMap = userMap
-	s.packageMap = packageMap
-	s.sharedUserMap = sharedUserMap
-	return nil
-}

+ 1 - 2
common/process/searcher_darwin.go

@@ -8,7 +8,6 @@ import (
 	"syscall"
 	"unsafe"
 
-	"github.com/sagernet/sing-box/log"
 	N "github.com/sagernet/sing/common/network"
 
 	"golang.org/x/sys/unix"
@@ -18,7 +17,7 @@ var _ Searcher = (*darwinSearcher)(nil)
 
 type darwinSearcher struct{}
 
-func NewSearcher(logger log.ContextLogger) (Searcher, error) {
+func NewSearcher(_ Config) (Searcher, error) {
 	return &darwinSearcher{}, nil
 }
 

+ 2 - 2
common/process/searcher_linux.go

@@ -15,8 +15,8 @@ type linuxSearcher struct {
 	logger log.ContextLogger
 }
 
-func NewSearcher(logger log.ContextLogger) (Searcher, error) {
-	return &linuxSearcher{logger}, nil
+func NewSearcher(config Config) (Searcher, error) {
+	return &linuxSearcher{config.Logger}, nil
 }
 
 func (s *linuxSearcher) FindProcessInfo(ctx context.Context, network string, srcIP netip.Addr, srcPort int) (*Info, error) {

+ 1 - 3
common/process/searcher_stub.go

@@ -4,10 +4,8 @@ package process
 
 import (
 	"os"
-
-	"github.com/sagernet/sing-box/log"
 )
 
-func NewSearcher(logger log.ContextLogger) (Searcher, error) {
+func NewSearcher(_ Config) (Searcher, error) {
 	return nil, os.ErrInvalid
 }

+ 1 - 2
common/process/searcher_windows.go

@@ -8,7 +8,6 @@ import (
 	"syscall"
 	"unsafe"
 
-	"github.com/sagernet/sing-box/log"
 	E "github.com/sagernet/sing/common/exceptions"
 	N "github.com/sagernet/sing/common/network"
 
@@ -19,7 +18,7 @@ var _ Searcher = (*windowsSearcher)(nil)
 
 type windowsSearcher struct{}
 
-func NewSearcher(logger log.ContextLogger) (Searcher, error) {
+func NewSearcher(_ Config) (Searcher, error) {
 	err := initWin32API()
 	if err != nil {
 		return nil, E.Cause(err, "init win32 api")

+ 1 - 1
docs/changelog.md

@@ -1,6 +1,6 @@
 #### 2022/08/15
 
-* Add uid and android user rules support in [Tun](/configuration/inbound/tun) routing.
+* Add uid, android user and package rules support in [Tun](/configuration/inbound/tun) routing.
 
 #### 2022/08/13
 

+ 25 - 11
docs/configuration/inbound/tun.md

@@ -28,10 +28,6 @@
           99999
         ]
       ],
-      "include_android_user": [
-        0,
-        10
-      ],
       "exclude_uid": [
         1000
       ],
@@ -39,6 +35,16 @@
         1000,
         99999
       ],
+      "include_android_user": [
+        0,
+        10
+      ],
+      "include_package": [
+        "com.android.chrome"
+      ],
+      "exclude_package": [
+        "com.android.captiveportallogin"
+      ],
       
       "sniff": true,
       "sniff_override_destination": false,
@@ -111,7 +117,7 @@ TCP/IP stack.
 
 !!! error ""
 
-    UID and android user rules are only supported on Linux and require auto_route.
+    UID rules are only supported on Linux and require auto_route.
 
 Limit users in route. Not limited by default.
 
@@ -119,11 +125,19 @@ Limit users in route. Not limited by default.
 
 Limit users in route, but in range.
 
+#### exclude_uid
+
+Exclude users in route.
+
+#### exclude_uid_range
+
+Exclude users in route, but in range.
+
 #### include_android_user
 
-!!! warning ""
+!!! error ""
 
-    Only supported on Android
+    Android user and package rules are only supported on Android and require auto_route.
 
 Limit android users in route.
 
@@ -132,13 +146,13 @@ Limit android users in route.
 | Main         | 0   |
 | Work Profile | 10  |
 
-#### exclude_uid
+#### include_package
 
-Exclude users in route.
+Limit android packages in route.
 
-#### exclude_uid_range
+#### exclude_package
 
-Exclude users in route, but in range.
+Exclude android packages in route.
 
 ### Listen Fields
 

+ 1 - 1
go.mod

@@ -18,7 +18,7 @@ require (
 	github.com/sagernet/sing v0.0.0-20220814164830-4f2b872a8cbf
 	github.com/sagernet/sing-dns v0.0.0-20220813025814-e656c9dbf3ae
 	github.com/sagernet/sing-shadowsocks v0.0.0-20220812082714-484a11603b48
-	github.com/sagernet/sing-tun v0.0.0-20220815014658-b828f0164333
+	github.com/sagernet/sing-tun v0.0.0-20220815033412-1407eae46bd7
 	github.com/sagernet/sing-vmess v0.0.0-20220811135656-4f3f07acf9c4
 	github.com/sagernet/smux v0.0.0-20220812084127-e2d085ee3939
 	github.com/spf13/cobra v1.5.0

+ 2 - 2
go.sum

@@ -159,8 +159,8 @@ github.com/sagernet/sing-dns v0.0.0-20220813025814-e656c9dbf3ae h1:xOpbvgizvIbKK
 github.com/sagernet/sing-dns v0.0.0-20220813025814-e656c9dbf3ae/go.mod h1:T77zZdE2Cm6VqnFumrpwsq+kxYsbq+vWDhmjtdSl/oM=
 github.com/sagernet/sing-shadowsocks v0.0.0-20220812082714-484a11603b48 h1:NlcTFKldteZvYBDyr+V9MjZEI0rAWCSFCyLgPvc5n/Y=
 github.com/sagernet/sing-shadowsocks v0.0.0-20220812082714-484a11603b48/go.mod h1:EX3RbZvrwAkPI2nuGa78T2iQXmrkT+/VQtskjou42xM=
-github.com/sagernet/sing-tun v0.0.0-20220815014658-b828f0164333 h1:fJj7jCPkGkbhY/UNwebi7kKq8Yxc6qeD3Jzh9Wk9tPw=
-github.com/sagernet/sing-tun v0.0.0-20220815014658-b828f0164333/go.mod h1:+JztVFWrBR8bbf1fWPCyc4KJ/a1bPejmmoEBj9rI6HQ=
+github.com/sagernet/sing-tun v0.0.0-20220815033412-1407eae46bd7 h1:KwUTQUPvdcJtrZR3WImygB0fINaGIr4X42TnDIDJ9sU=
+github.com/sagernet/sing-tun v0.0.0-20220815033412-1407eae46bd7/go.mod h1:+mJ/s6hO3CZyD7CpHbEuZmIVyRkTYLRl4iTr5a57mG0=
 github.com/sagernet/sing-vmess v0.0.0-20220811135656-4f3f07acf9c4 h1:2hLETh97+S4WnfMR27XyC7QVU1SH7FTNoCznP229YJU=
 github.com/sagernet/sing-vmess v0.0.0-20220811135656-4f3f07acf9c4/go.mod h1:82O6gzbxLha/W/jxSVQbsqf2lVdRTjMIgyLug0lpJps=
 github.com/sagernet/smux v0.0.0-20220812084127-e2d085ee3939 h1:pB1Dh1NbwVrLhQhotr4O4Hs3yhiBzmg3AvnUyYjL4x4=

+ 6 - 1
inbound/tun.go

@@ -81,8 +81,10 @@ func NewTun(ctx context.Context, router adapter.Router, logger log.ContextLogger
 			Inet6Address:       options.Inet6Address.Build(),
 			AutoRoute:          options.AutoRoute,
 			IncludeUID:         includeUID,
-			IncludeAndroidUser: options.IncludeAndroidUser,
 			ExcludeUID:         excludeUID,
+			IncludeAndroidUser: options.IncludeAndroidUser,
+			IncludePackage:     options.IncludePackage,
+			ExcludePackage:     options.ExcludePackage,
 		},
 		endpointIndependentNat: options.EndpointIndependentNat,
 		udpTimeout:             udpTimeout,
@@ -131,6 +133,9 @@ func (t *Tun) Tag() string {
 }
 
 func (t *Tun) Start() error {
+	if C.IsAndroid {
+		t.tunOptions.BuildAndroidRules(t.router.PackageManager(), t)
+	}
 	tunIf, err := tun.Open(t.tunOptions)
 	if err != nil {
 		return E.Cause(err, "configure tun interface")

+ 2 - 0
option/tun.go

@@ -11,6 +11,8 @@ type TunInboundOptions struct {
 	ExcludeUID             Listable[uint32] `json:"exclude_uid,omitempty"`
 	ExcludeUIDRange        Listable[string] `json:"exclude_uid_range,omitempty"`
 	IncludeAndroidUser     Listable[int]    `json:"android_user,omitempty"`
+	IncludePackage         Listable[string] `json:"include_package,omitempty"`
+	ExcludePackage         Listable[string] `json:"exclude_package,omitempty"`
 	EndpointIndependentNat bool             `json:"endpoint_independent_nat,omitempty"`
 	UDPTimeout             int64            `json:"udp_timeout,omitempty"`
 	Stack                  string           `json:"stack,omitempty"`

+ 30 - 10
route/router.go

@@ -90,6 +90,7 @@ type Router struct {
 	defaultMark                        int
 	networkMonitor                     tun.NetworkUpdateMonitor
 	interfaceMonitor                   tun.DefaultInterfaceMonitor
+	packageManager                     tun.PackageManager
 	trafficController                  adapter.TrafficController
 	processSearcher                    process.Searcher
 }
@@ -260,8 +261,22 @@ func NewRouter(ctx context.Context, logger log.ContextLogger, dnsLogger log.Cont
 		router.interfaceMonitor = interfaceMonitor
 	}
 
-	if hasRule(options.Rules, isProcessRule) || hasDNSRule(dnsOptions.Rules, isProcessDNSRule) || options.FindProcess {
-		searcher, err := process.NewSearcher(logger)
+	needFindProcess := hasRule(options.Rules, isProcessRule) || hasDNSRule(dnsOptions.Rules, isProcessDNSRule) || options.FindProcess
+	needPackageManager := C.IsAndroid && (needFindProcess || common.Any(inbounds, func(inbound option.Inbound) bool {
+		return len(inbound.TunOptions.IncludePackage) > 0 || len(inbound.TunOptions.ExcludePackage) > 0
+	}))
+	if needPackageManager {
+		packageManager, err := tun.NewPackageManager(router)
+		if err != nil {
+			return nil, E.Cause(err, "create package manager")
+		}
+		router.packageManager = packageManager
+	}
+	if needFindProcess {
+		searcher, err := process.NewSearcher(process.Config{
+			Logger:         logger,
+			PackageManager: router.packageManager,
+		})
 		if err != nil {
 			if err != os.ErrInvalid {
 				logger.Warn(E.Cause(err, "create process searcher"))
@@ -425,13 +440,10 @@ func (r *Router) Start() error {
 			return err
 		}
 	}
-	if r.processSearcher != nil {
-		if starter, isStarter := r.processSearcher.(common.Starter); isStarter {
-			err := starter.Start()
-			if err != nil {
-				r.logger.Error(E.Cause(err, "initialize process searcher"))
-				r.processSearcher = nil
-			}
+	if r.packageManager != nil {
+		err := r.packageManager.Start()
+		if err != nil {
+			return err
 		}
 	}
 	return nil
@@ -454,7 +466,7 @@ func (r *Router) Close() error {
 		common.PtrOrNil(r.geoIPReader),
 		r.interfaceMonitor,
 		r.networkMonitor,
-		r.processSearcher,
+		r.packageManager,
 	)
 }
 
@@ -679,6 +691,10 @@ func (r *Router) InterfaceMonitor() tun.DefaultInterfaceMonitor {
 	return r.interfaceMonitor
 }
 
+func (r *Router) PackageManager() tun.PackageManager {
+	return r.packageManager
+}
+
 func (r *Router) SetTrafficController(controller adapter.TrafficController) {
 	r.trafficController = controller
 }
@@ -912,6 +928,10 @@ func (r *Router) downloadGeositeDatabase(savePath string) error {
 	return err
 }
 
+func (r *Router) OnPackagesUpdated(packages int, sharedUsers int) {
+	r.logger.Info("updated packages list: ", packages, " packages, ", sharedUsers, " shared users")
+}
+
 func (r *Router) NewError(ctx context.Context, err error) {
 	common.Close(err)
 	if E.IsClosedOrCanceled(err) {

+ 2 - 2
test/go.mod

@@ -14,7 +14,7 @@ require (
 	github.com/sagernet/sing-shadowsocks v0.0.0-20220812082714-484a11603b48
 	github.com/spyzhov/ajson v0.7.1
 	github.com/stretchr/testify v1.8.0
-	golang.org/x/net v0.0.0-20220811182439-13a9a731de15
+	golang.org/x/net v0.0.0-20220812174116-3211cb980234
 )
 
 require (
@@ -54,7 +54,7 @@ require (
 	github.com/pmezard/go-difflib v1.0.0 // indirect
 	github.com/sagernet/netlink v0.0.0-20220803045538-bdac49abf805 // indirect
 	github.com/sagernet/sing-dns v0.0.0-20220813025814-e656c9dbf3ae // indirect
-	github.com/sagernet/sing-tun v0.0.0-20220815014658-b828f0164333 // indirect
+	github.com/sagernet/sing-tun v0.0.0-20220815033412-1407eae46bd7 // indirect
 	github.com/sagernet/sing-vmess v0.0.0-20220811135656-4f3f07acf9c4 // indirect
 	github.com/sagernet/smux v0.0.0-20220812084127-e2d085ee3939 // indirect
 	github.com/sirupsen/logrus v1.8.1 // indirect

+ 4 - 4
test/go.sum

@@ -181,8 +181,8 @@ github.com/sagernet/sing-dns v0.0.0-20220813025814-e656c9dbf3ae h1:xOpbvgizvIbKK
 github.com/sagernet/sing-dns v0.0.0-20220813025814-e656c9dbf3ae/go.mod h1:T77zZdE2Cm6VqnFumrpwsq+kxYsbq+vWDhmjtdSl/oM=
 github.com/sagernet/sing-shadowsocks v0.0.0-20220812082714-484a11603b48 h1:NlcTFKldteZvYBDyr+V9MjZEI0rAWCSFCyLgPvc5n/Y=
 github.com/sagernet/sing-shadowsocks v0.0.0-20220812082714-484a11603b48/go.mod h1:EX3RbZvrwAkPI2nuGa78T2iQXmrkT+/VQtskjou42xM=
-github.com/sagernet/sing-tun v0.0.0-20220815014658-b828f0164333 h1:fJj7jCPkGkbhY/UNwebi7kKq8Yxc6qeD3Jzh9Wk9tPw=
-github.com/sagernet/sing-tun v0.0.0-20220815014658-b828f0164333/go.mod h1:+JztVFWrBR8bbf1fWPCyc4KJ/a1bPejmmoEBj9rI6HQ=
+github.com/sagernet/sing-tun v0.0.0-20220815033412-1407eae46bd7 h1:KwUTQUPvdcJtrZR3WImygB0fINaGIr4X42TnDIDJ9sU=
+github.com/sagernet/sing-tun v0.0.0-20220815033412-1407eae46bd7/go.mod h1:+mJ/s6hO3CZyD7CpHbEuZmIVyRkTYLRl4iTr5a57mG0=
 github.com/sagernet/sing-vmess v0.0.0-20220811135656-4f3f07acf9c4 h1:2hLETh97+S4WnfMR27XyC7QVU1SH7FTNoCznP229YJU=
 github.com/sagernet/sing-vmess v0.0.0-20220811135656-4f3f07acf9c4/go.mod h1:82O6gzbxLha/W/jxSVQbsqf2lVdRTjMIgyLug0lpJps=
 github.com/sagernet/smux v0.0.0-20220812084127-e2d085ee3939 h1:pB1Dh1NbwVrLhQhotr4O4Hs3yhiBzmg3AvnUyYjL4x4=
@@ -275,8 +275,8 @@ golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwY
 golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
 golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
 golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
-golang.org/x/net v0.0.0-20220811182439-13a9a731de15 h1:cik0bxZUSJVDyaHf1hZPSDsU8SZHGQZQMeueXCE7yBQ=
-golang.org/x/net v0.0.0-20220811182439-13a9a731de15/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
+golang.org/x/net v0.0.0-20220812174116-3211cb980234 h1:RDqmgfe7SvlMWoqC3xwQ2blLO3fcWcxMa3eBLRdRW7E=
+golang.org/x/net v0.0.0-20220812174116-3211cb980234/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
 golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
 golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=