feat: 安卓恢复进程规则,可通过enable-process开关,默认true

This commit is contained in:
adlyq 2022-05-19 20:43:41 +08:00
parent fe25ae83df
commit cc1c1340a3
11 changed files with 280 additions and 29 deletions

View file

@ -6,13 +6,14 @@ import (
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
"net" "net"
"net/netip" "net/netip"
"runtime"
) )
var ( var (
ErrInvalidNetwork = errors.New("invalid network") ErrInvalidNetwork = errors.New("invalid network")
ErrPlatformNotSupport = errors.New("not support on this platform") ErrPlatformNotSupport = errors.New("not support on this platform")
ErrNotFound = errors.New("process not found") ErrNotFound = errors.New("process not found")
enableFindProcess = true
) )
const ( const (
@ -20,7 +21,11 @@ const (
UDP = "udp" UDP = "udp"
) )
func FindProcessName(network string, srcIP netip.Addr, srcPort int) (string, error) { func EnableFindProcess(e bool) {
enableFindProcess = e
}
func FindProcessName(network string, srcIP netip.Addr, srcPort int) (int32, string, error) {
return findProcessName(network, srcIP, srcPort) return findProcessName(network, srcIP, srcPort)
} }
@ -33,10 +38,7 @@ func FindUid(network string, srcIP netip.Addr, srcPort int) (int32, error) {
} }
func ShouldFindProcess(metadata *C.Metadata) bool { func ShouldFindProcess(metadata *C.Metadata) bool {
if runtime.GOOS == "android" { if !enableFindProcess || metadata.Process != "" || metadata.ProcessPath != "" {
return false
}
if metadata.Process != "" || metadata.ProcessPath != "" {
return false return false
} }
for _, ip := range localIPs { for _, ip := range localIPs {

View file

@ -0,0 +1,228 @@
package process
import (
"bytes"
"encoding/binary"
"fmt"
"net"
"net/netip"
"os"
"path"
"path/filepath"
"strings"
"syscall"
"unicode"
"unsafe"
"github.com/Dreamacro/clash/common/pool"
)
// from https://github.com/vishvananda/netlink/blob/bca67dfc8220b44ef582c9da4e9172bf1c9ec973/nl/nl_linux.go#L52-L62
var nativeEndian = func() binary.ByteOrder {
var x uint32 = 0x01020304
if *(*byte)(unsafe.Pointer(&x)) == 0x01 {
return binary.BigEndian
}
return binary.LittleEndian
}()
const (
sizeOfSocketDiagRequest = syscall.SizeofNlMsghdr + 8 + 48
socketDiagByFamily = 20
pathProc = "/proc"
)
func findProcessName(network string, ip netip.Addr, srcPort int) (int32, string, error) {
inode, uid, err := resolveSocketByNetlink(network, ip, srcPort)
if err != nil {
return -1, "", err
}
pp, err := resolveProcessNameByProcSearch(inode, uid)
return uid, pp, err
}
func resolveSocketByNetlink(network string, ip netip.Addr, srcPort int) (int32, int32, error) {
var family byte
var protocol byte
switch network {
case TCP:
protocol = syscall.IPPROTO_TCP
case UDP:
protocol = syscall.IPPROTO_UDP
default:
return 0, 0, ErrInvalidNetwork
}
if ip.Is4() {
family = syscall.AF_INET
} else {
family = syscall.AF_INET6
}
req := packSocketDiagRequest(family, protocol, ip, uint16(srcPort))
socket, err := syscall.Socket(syscall.AF_NETLINK, syscall.SOCK_DGRAM, syscall.NETLINK_INET_DIAG)
if err != nil {
return 0, 0, fmt.Errorf("dial netlink: %w", err)
}
defer syscall.Close(socket)
_ = syscall.SetsockoptTimeval(socket, syscall.SOL_SOCKET, syscall.SO_SNDTIMEO, &syscall.Timeval{Usec: 100})
_ = syscall.SetsockoptTimeval(socket, syscall.SOL_SOCKET, syscall.SO_RCVTIMEO, &syscall.Timeval{Usec: 100})
if err := syscall.Connect(socket, &syscall.SockaddrNetlink{
Family: syscall.AF_NETLINK,
Pad: 0,
Pid: 0,
Groups: 0,
}); err != nil {
return 0, 0, err
}
if _, err := syscall.Write(socket, req); err != nil {
return 0, 0, fmt.Errorf("write request: %w", err)
}
rb := pool.Get(pool.RelayBufferSize)
defer pool.Put(rb)
n, err := syscall.Read(socket, rb)
if err != nil {
return 0, 0, fmt.Errorf("read response: %w", err)
}
messages, err := syscall.ParseNetlinkMessage(rb[:n])
if err != nil {
return 0, 0, fmt.Errorf("parse netlink message: %w", err)
} else if len(messages) == 0 {
return 0, 0, fmt.Errorf("unexcepted netlink response")
}
message := messages[0]
if message.Header.Type&syscall.NLMSG_ERROR != 0 {
return 0, 0, fmt.Errorf("netlink message: NLMSG_ERROR")
}
uid, inode := unpackSocketDiagResponse(&messages[0])
if uid < 0 || inode < 0 {
return 0, 0, fmt.Errorf("invalid uid(%d) or inode(%d)", uid, inode)
}
return uid, inode, nil
}
func packSocketDiagRequest(family, protocol byte, source netip.Addr, sourcePort uint16) []byte {
s := make([]byte, 16)
copy(s, source.AsSlice())
buf := make([]byte, sizeOfSocketDiagRequest)
nativeEndian.PutUint32(buf[0:4], sizeOfSocketDiagRequest)
nativeEndian.PutUint16(buf[4:6], socketDiagByFamily)
nativeEndian.PutUint16(buf[6:8], syscall.NLM_F_REQUEST|syscall.NLM_F_DUMP)
nativeEndian.PutUint32(buf[8:12], 0)
nativeEndian.PutUint32(buf[12:16], 0)
buf[16] = family
buf[17] = protocol
buf[18] = 0
buf[19] = 0
nativeEndian.PutUint32(buf[20:24], 0xFFFFFFFF)
binary.BigEndian.PutUint16(buf[24:26], sourcePort)
binary.BigEndian.PutUint16(buf[26:28], 0)
copy(buf[28:44], s)
copy(buf[44:60], net.IPv6zero)
nativeEndian.PutUint32(buf[60:64], 0)
nativeEndian.PutUint64(buf[64:72], 0xFFFFFFFFFFFFFFFF)
return buf
}
func unpackSocketDiagResponse(msg *syscall.NetlinkMessage) (inode, uid int32) {
if len(msg.Data) < 72 {
return 0, 0
}
data := msg.Data
uid = int32(nativeEndian.Uint32(data[64:68]))
inode = int32(nativeEndian.Uint32(data[68:72]))
return
}
func resolveProcessNameByProcSearch(inode, uid int32) (string, error) {
files, err := os.ReadDir(pathProc)
if err != nil {
return "", err
}
buffer := make([]byte, syscall.PathMax)
socket := []byte(fmt.Sprintf("socket:[%d]", inode))
for _, f := range files {
if !f.IsDir() || !isPid(f.Name()) {
continue
}
info, err := f.Info()
if err != nil {
return "", err
}
if info.Sys().(*syscall.Stat_t).Uid != uint32(uid) {
continue
}
processPath := path.Join(pathProc, f.Name())
fdPath := path.Join(processPath, "fd")
fds, err := os.ReadDir(fdPath)
if err != nil {
continue
}
for _, fd := range fds {
n, err := syscall.Readlink(path.Join(fdPath, fd.Name()), buffer)
if err != nil {
continue
}
if bytes.Equal(buffer[:n], socket) {
cmdline, err := os.ReadFile(path.Join(processPath, "cmdline"))
if err != nil {
return "", err
}
return splitCmdline(cmdline), nil
}
}
}
return "", fmt.Errorf("process of uid(%d),inode(%d) not found", uid, inode)
}
func splitCmdline(cmdline []byte) string {
cmdline = bytes.Trim(cmdline, " ")
idx := bytes.IndexFunc(cmdline, func(r rune) bool {
return unicode.IsControl(r) || unicode.IsSpace(r)
})
if idx == -1 {
return filepath.Base(string(cmdline))
}
return filepath.Base(string(cmdline[:idx]))
}
func isPid(s string) bool {
return strings.IndexFunc(s, func(r rune) bool {
return !unicode.IsDigit(r)
}) == -1
}

View file

@ -21,7 +21,7 @@ func resolveSocketByNetlink(network string, ip netip.Addr, srcPort int) (int32,
return 0, 0, ErrPlatformNotSupport return 0, 0, ErrPlatformNotSupport
} }
func findProcessName(network string, ip netip.Addr, port int) (string, error) { func findProcessName(network string, ip netip.Addr, port int) (int32, string, error) {
var spath string var spath string
switch network { switch network {
case TCP: case TCP:
@ -29,14 +29,14 @@ func findProcessName(network string, ip netip.Addr, port int) (string, error) {
case UDP: case UDP:
spath = "net.inet.udp.pcblist_n" spath = "net.inet.udp.pcblist_n"
default: default:
return "", ErrInvalidNetwork return -1, "", ErrInvalidNetwork
} }
isIPv4 := ip.Is4() isIPv4 := ip.Is4()
value, err := syscall.Sysctl(spath) value, err := syscall.Sysctl(spath)
if err != nil { if err != nil {
return "", err return -1, "", err
} }
buf := []byte(value) buf := []byte(value)
@ -81,10 +81,11 @@ func findProcessName(network string, ip netip.Addr, port int) (string, error) {
// xsocket_n.so_last_pid // xsocket_n.so_last_pid
pid := readNativeUint32(buf[so+68 : so+72]) pid := readNativeUint32(buf[so+68 : so+72])
return getExecPathFromPID(pid) pp, err := getExecPathFromPID(pid)
return -1, pp, err
} }
return "", ErrNotFound return -1, "", ErrNotFound
} }
func getExecPathFromPID(pid uint32) (string, error) { func getExecPathFromPID(pid uint32) (string, error) {

View file

@ -25,7 +25,7 @@ func resolveSocketByNetlink(network string, ip netip.Addr, srcPort int) (int32,
return 0, 0, ErrPlatformNotSupport return 0, 0, ErrPlatformNotSupport
} }
func findProcessName(network string, ip netip.Addr, srcPort int) (string, error) { func findProcessName(network string, ip netip.Addr, srcPort int) (int32, string, error) {
once.Do(func() { once.Do(func() {
if err := initSearcher(); err != nil { if err := initSearcher(); err != nil {
log.Errorln("Initialize PROCESS-NAME failed: %s", err.Error()) log.Errorln("Initialize PROCESS-NAME failed: %s", err.Error())
@ -35,7 +35,7 @@ func findProcessName(network string, ip netip.Addr, srcPort int) (string, error)
}) })
if defaultSearcher == nil { if defaultSearcher == nil {
return "", ErrPlatformNotSupport return -1, "", ErrPlatformNotSupport
} }
var spath string var spath string
@ -46,21 +46,22 @@ func findProcessName(network string, ip netip.Addr, srcPort int) (string, error)
case UDP: case UDP:
spath = "net.inet.udp.pcblist" spath = "net.inet.udp.pcblist"
default: default:
return "", ErrInvalidNetwork return -1, "", ErrInvalidNetwork
} }
value, err := syscall.Sysctl(spath) value, err := syscall.Sysctl(spath)
if err != nil { if err != nil {
return "", err return -1, "", err
} }
buf := []byte(value) buf := []byte(value)
pid, err := defaultSearcher.Search(buf, ip, uint16(srcPort), isTCP) pid, err := defaultSearcher.Search(buf, ip, uint16(srcPort), isTCP)
if err != nil { if err != nil {
return "", err return -1, "", err
} }
return getExecPathFromPID(pid) pp, err := getExecPathFromPID(pid)
return -1, pp, err
} }
func getExecPathFromPID(pid uint32) (string, error) { func getExecPathFromPID(pid uint32) (string, error) {

View file

@ -1,3 +1,5 @@
//go:build !android
package process package process
import ( import (
@ -32,12 +34,13 @@ const (
pathProc = "/proc" pathProc = "/proc"
) )
func findProcessName(network string, ip netip.Addr, srcPort int) (string, error) { func findProcessName(network string, ip netip.Addr, srcPort int) (int32, string, error) {
inode, uid, err := resolveSocketByNetlink(network, ip, srcPort) inode, uid, err := resolveSocketByNetlink(network, ip, srcPort)
if err != nil { if err != nil {
return "", err return -1, "", err
} }
return resolveProcessNameByProcSearch(inode, uid) pp, err := resolveProcessNameByProcSearch(inode, uid)
return uid, pp, err
} }
func resolveSocketByNetlink(network string, ip netip.Addr, srcPort int) (int32, int32, error) { func resolveSocketByNetlink(network string, ip netip.Addr, srcPort int) (int32, int32, error) {

View file

@ -4,8 +4,8 @@ package process
import "net/netip" import "net/netip"
func findProcessName(network string, ip netip.Addr, srcPort int) (string, error) { func findProcessName(network string, ip netip.Addr, srcPort int) (int32, string, error) {
return "", ErrPlatformNotSupport return -1, "", ErrPlatformNotSupport
} }
func resolveSocketByNetlink(network string, ip netip.Addr, srcPort int) (int32, int32, error) { func resolveSocketByNetlink(network string, ip netip.Addr, srcPort int) (int32, int32, error) {

View file

@ -62,7 +62,7 @@ func initWin32API() error {
return nil return nil
} }
func findProcessName(network string, ip netip.Addr, srcPort int) (string, error) { func findProcessName(network string, ip netip.Addr, srcPort int) (int32, string, error) {
once.Do(func() { once.Do(func() {
err := initWin32API() err := initWin32API()
if err != nil { if err != nil {
@ -86,21 +86,22 @@ func findProcessName(network string, ip netip.Addr, srcPort int) (string, error)
fn = getExUDPTable fn = getExUDPTable
class = udpTablePid class = udpTablePid
default: default:
return "", ErrInvalidNetwork return -1, "", ErrInvalidNetwork
} }
buf, err := getTransportTable(fn, family, class) buf, err := getTransportTable(fn, family, class)
if err != nil { if err != nil {
return "", err return -1, "", err
} }
s := newSearcher(family == windows.AF_INET, network == TCP) s := newSearcher(family == windows.AF_INET, network == TCP)
pid, err := s.Search(buf, ip, uint16(srcPort)) pid, err := s.Search(buf, ip, uint16(srcPort))
if err != nil { if err != nil {
return "", err return -1, "", err
} }
return getExecPathFromPID(pid) pp, err := getExecPathFromPID(pid)
return -1, pp, err
} }
type searcher struct { type searcher struct {

View file

@ -52,6 +52,7 @@ type General struct {
GeodataMode bool `json:"geodata-mode"` GeodataMode bool `json:"geodata-mode"`
GeodataLoader string `json:"geodata-loader"` GeodataLoader string `json:"geodata-loader"`
TCPConcurrent bool `json:"tcp-concurrent"` TCPConcurrent bool `json:"tcp-concurrent"`
EnableProcess bool `json:"enable-process"`
Tun Tun `json:"tun"` Tun Tun `json:"tun"`
} }
@ -206,6 +207,7 @@ type RawConfig struct {
GeodataMode bool `yaml:"geodata-mode"` GeodataMode bool `yaml:"geodata-mode"`
GeodataLoader string `yaml:"geodata-loader"` GeodataLoader string `yaml:"geodata-loader"`
TCPConcurrent bool `yaml:"tcp-concurrent" json:"tcp-concurrent"` TCPConcurrent bool `yaml:"tcp-concurrent" json:"tcp-concurrent"`
EnableProcess bool `yaml:"enable-process" json:"enable-process"`
Sniffer RawSniffer `yaml:"sniffer"` Sniffer RawSniffer `yaml:"sniffer"`
ProxyProvider map[string]map[string]any `yaml:"proxy-providers"` ProxyProvider map[string]map[string]any `yaml:"proxy-providers"`
@ -255,6 +257,7 @@ func UnmarshalRawConfig(buf []byte) (*RawConfig, error) {
Proxy: []map[string]any{}, Proxy: []map[string]any{},
ProxyGroup: []map[string]any{}, ProxyGroup: []map[string]any{},
TCPConcurrent: false, TCPConcurrent: false,
EnableProcess: true,
Tun: RawTun{ Tun: RawTun{
Enable: false, Enable: false,
Device: "", Device: "",
@ -410,6 +413,7 @@ func parseGeneral(cfg *RawConfig) (*General, error) {
GeodataMode: cfg.GeodataMode, GeodataMode: cfg.GeodataMode,
GeodataLoader: cfg.GeodataLoader, GeodataLoader: cfg.GeodataLoader,
TCPConcurrent: cfg.TCPConcurrent, TCPConcurrent: cfg.TCPConcurrent,
EnableProcess: cfg.EnableProcess,
}, nil }, nil
} }

View file

@ -2,6 +2,7 @@ package executor
import ( import (
"fmt" "fmt"
"github.com/Dreamacro/clash/component/process"
"github.com/Dreamacro/clash/listener/inner" "github.com/Dreamacro/clash/listener/inner"
"net/netip" "net/netip"
"os" "os"
@ -270,6 +271,7 @@ func updateSniffer(sniffer *config.Sniffer) {
func updateGeneral(general *config.General, force bool) { func updateGeneral(general *config.General, force bool) {
log.SetLevel(general.LogLevel) log.SetLevel(general.LogLevel)
process.EnableFindProcess(general.EnableProcess)
tunnel.SetMode(general.Mode) tunnel.SetMode(general.Mode)
dialer.DisableIPv6 = !general.IPv6 dialer.DisableIPv6 = !general.IPv6
if !dialer.DisableIPv6 { if !dialer.DisableIPv6 {

View file

@ -3,9 +3,11 @@ package rules
import ( import (
"fmt" "fmt"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/log"
RC "github.com/Dreamacro/clash/rule/common" RC "github.com/Dreamacro/clash/rule/common"
"github.com/Dreamacro/clash/rule/logic" "github.com/Dreamacro/clash/rule/logic"
RP "github.com/Dreamacro/clash/rule/provider" RP "github.com/Dreamacro/clash/rule/provider"
"runtime"
) )
func ParseRule(tp, payload, target string, params []string) (C.Rule, error) { func ParseRule(tp, payload, target string, params []string) (C.Rule, error) {
@ -42,7 +44,11 @@ func ParseRule(tp, payload, target string, params []string) (C.Rule, error) {
case "NETWORK": case "NETWORK":
parsed, parseErr = RC.NewNetworkType(payload, target) parsed, parseErr = RC.NewNetworkType(payload, target)
case "UID": case "UID":
if runtime.GOOS == "linux" || runtime.GOOS == "android" {
parsed, parseErr = RC.NewUid(payload, target) parsed, parseErr = RC.NewUid(payload, target)
} else {
log.Warnln("uid rule not support this platform")
}
case "AND": case "AND":
parsed, parseErr = logic.NewAND(payload, target) parsed, parseErr = logic.NewAND(payload, target)
case "OR": case "OR":

View file

@ -170,7 +170,7 @@ func preHandleMetadata(metadata *C.Metadata) error {
// pre resolve process name // pre resolve process name
srcPort, err := strconv.Atoi(metadata.SrcPort) srcPort, err := strconv.Atoi(metadata.SrcPort)
if err == nil && P.ShouldFindProcess(metadata) { if err == nil && P.ShouldFindProcess(metadata) {
path, err := P.FindProcessName(metadata.NetWork.String(), metadata.SrcIP, srcPort) uid, path, err := P.FindProcessName(metadata.NetWork.String(), metadata.SrcIP, srcPort)
if err != nil { if err != nil {
if failTotal < 20 { if failTotal < 20 {
log.Debugln("[Process] find process %s: %v", metadata.String(), err) log.Debugln("[Process] find process %s: %v", metadata.String(), err)
@ -179,6 +179,9 @@ func preHandleMetadata(metadata *C.Metadata) error {
} else { } else {
metadata.Process = filepath.Base(path) metadata.Process = filepath.Base(path)
metadata.ProcessPath = path metadata.ProcessPath = path
if uid != -1 {
metadata.Uid = &uid
}
if procesCache != metadata.Process { if procesCache != metadata.Process {
log.Debugln("[Process] %s from process %s", metadata.String(), path) log.Debugln("[Process] %s from process %s", metadata.String(), path)
} }