Skip to content

Commit

Permalink
feat: 安卓恢复进程规则,可通过enable-process开关,默认true
Browse files Browse the repository at this point in the history
  • Loading branch information
adlyq committed May 19, 2022
1 parent fe25ae8 commit cc1c134
Show file tree
Hide file tree
Showing 11 changed files with 280 additions and 29 deletions.
14 changes: 8 additions & 6 deletions component/process/process.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,26 @@ import (
C "github.com/Dreamacro/clash/constant"
"net"
"net/netip"
"runtime"
)

var (
ErrInvalidNetwork = errors.New("invalid network")
ErrPlatformNotSupport = errors.New("not support on this platform")
ErrNotFound = errors.New("process not found")

enableFindProcess = true
)

const (
TCP = "tcp"
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)
}

Expand All @@ -33,10 +38,7 @@ func FindUid(network string, srcIP netip.Addr, srcPort int) (int32, error) {
}

func ShouldFindProcess(metadata *C.Metadata) bool {
if runtime.GOOS == "android" {
return false
}
if metadata.Process != "" || metadata.ProcessPath != "" {
if !enableFindProcess || metadata.Process != "" || metadata.ProcessPath != "" {
return false
}
for _, ip := range localIPs {
Expand Down
228 changes: 228 additions & 0 deletions component/process/process_android.go
Original file line number Diff line number Diff line change
@@ -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
}
11 changes: 6 additions & 5 deletions component/process/process_darwin.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,22 +21,22 @@ func resolveSocketByNetlink(network string, ip netip.Addr, srcPort int) (int32,
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
switch network {
case TCP:
spath = "net.inet.tcp.pcblist_n"
case UDP:
spath = "net.inet.udp.pcblist_n"
default:
return "", ErrInvalidNetwork
return -1, "", ErrInvalidNetwork
}

isIPv4 := ip.Is4()

value, err := syscall.Sysctl(spath)
if err != nil {
return "", err
return -1, "", err
}

buf := []byte(value)
Expand Down Expand Up @@ -81,10 +81,11 @@ func findProcessName(network string, ip netip.Addr, port int) (string, error) {

// xsocket_n.so_last_pid
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) {
Expand Down
13 changes: 7 additions & 6 deletions component/process/process_freebsd_amd64.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ func resolveSocketByNetlink(network string, ip netip.Addr, srcPort int) (int32,
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() {
if err := initSearcher(); err != nil {
log.Errorln("Initialize PROCESS-NAME failed: %s", err.Error())
Expand All @@ -35,7 +35,7 @@ func findProcessName(network string, ip netip.Addr, srcPort int) (string, error)
})

if defaultSearcher == nil {
return "", ErrPlatformNotSupport
return -1, "", ErrPlatformNotSupport
}

var spath string
Expand All @@ -46,21 +46,22 @@ func findProcessName(network string, ip netip.Addr, srcPort int) (string, error)
case UDP:
spath = "net.inet.udp.pcblist"
default:
return "", ErrInvalidNetwork
return -1, "", ErrInvalidNetwork
}

value, err := syscall.Sysctl(spath)
if err != nil {
return "", err
return -1, "", err
}

buf := []byte(value)
pid, err := defaultSearcher.Search(buf, ip, uint16(srcPort), isTCP)
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) {
Expand Down
9 changes: 6 additions & 3 deletions component/process/process_linux.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
//go:build !android

package process

import (
Expand Down Expand Up @@ -32,12 +34,13 @@ const (
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)
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) {
Expand Down
4 changes: 2 additions & 2 deletions component/process/process_other.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ package process

import "net/netip"

func findProcessName(network string, ip netip.Addr, srcPort int) (string, error) {
return "", ErrPlatformNotSupport
func findProcessName(network string, ip netip.Addr, srcPort int) (int32, string, error) {
return -1, "", ErrPlatformNotSupport
}

func resolveSocketByNetlink(network string, ip netip.Addr, srcPort int) (int32, int32, error) {
Expand Down
Loading

0 comments on commit cc1c134

Please sign in to comment.