mirror of https://github.com/Dreamacro/clash.git
233 lines
4.6 KiB
Go
233 lines
4.6 KiB
Go
package process
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/binary"
|
|
"fmt"
|
|
"net"
|
|
"net/netip"
|
|
"os"
|
|
"unsafe"
|
|
|
|
"github.com/Dreamacro/clash/common/pool"
|
|
|
|
"github.com/mdlayher/netlink"
|
|
"golang.org/x/sys/unix"
|
|
)
|
|
|
|
type inetDiagRequest struct {
|
|
Family byte
|
|
Protocol byte
|
|
Ext byte
|
|
Pad byte
|
|
States uint32
|
|
|
|
SrcPort [2]byte
|
|
DstPort [2]byte
|
|
Src [16]byte
|
|
Dst [16]byte
|
|
If uint32
|
|
Cookie [2]uint32
|
|
}
|
|
|
|
type inetDiagResponse struct {
|
|
Family byte
|
|
State byte
|
|
Timer byte
|
|
ReTrans byte
|
|
|
|
SrcPort [2]byte
|
|
DstPort [2]byte
|
|
Src [16]byte
|
|
Dst [16]byte
|
|
If uint32
|
|
Cookie [2]uint32
|
|
|
|
Expires uint32
|
|
RQueue uint32
|
|
WQueue uint32
|
|
UID uint32
|
|
INode uint32
|
|
}
|
|
|
|
func findProcessPath(network string, from netip.AddrPort, to netip.AddrPort) (string, error) {
|
|
inode, uid, err := resolveSocketByNetlink(network, from, to)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
return resolveProcessPathByProcSearch(inode, uid)
|
|
}
|
|
|
|
func resolveSocketByNetlink(network string, from netip.AddrPort, to netip.AddrPort) (inode uint32, uid uint32, err error) {
|
|
var families []byte
|
|
if from.Addr().Unmap().Is4() {
|
|
families = []byte{unix.AF_INET, unix.AF_INET6}
|
|
} else {
|
|
families = []byte{unix.AF_INET6, unix.AF_INET}
|
|
}
|
|
|
|
var protocol byte
|
|
switch network {
|
|
case TCP:
|
|
protocol = unix.IPPROTO_TCP
|
|
case UDP:
|
|
protocol = unix.IPPROTO_UDP
|
|
default:
|
|
return 0, 0, ErrInvalidNetwork
|
|
}
|
|
|
|
if protocol == unix.IPPROTO_UDP {
|
|
// Swap from & to for udp
|
|
// See also https://www.mail-archive.com/netdev@vger.kernel.org/msg248638.html
|
|
from, to = to, from
|
|
}
|
|
|
|
for _, family := range families {
|
|
inode, uid, err = resolveSocketByNetlinkExact(family, protocol, from, to, netlink.Request)
|
|
if err == nil {
|
|
return inode, uid, err
|
|
}
|
|
}
|
|
|
|
return 0, 0, ErrNotFound
|
|
}
|
|
|
|
func resolveSocketByNetlinkExact(family byte, protocol byte, from netip.AddrPort, to netip.AddrPort, flags netlink.HeaderFlags) (inode uint32, uid uint32, err error) {
|
|
request := &inetDiagRequest{
|
|
Family: family,
|
|
Protocol: protocol,
|
|
States: 0xffffffff,
|
|
Cookie: [2]uint32{0xffffffff, 0xffffffff},
|
|
}
|
|
|
|
var (
|
|
fromAddr []byte
|
|
toAddr []byte
|
|
)
|
|
if family == unix.AF_INET {
|
|
fromAddr = net.IP(from.Addr().AsSlice()).To4()
|
|
toAddr = net.IP(to.Addr().AsSlice()).To4()
|
|
} else {
|
|
fromAddr = net.IP(from.Addr().AsSlice()).To16()
|
|
toAddr = net.IP(to.Addr().AsSlice()).To16()
|
|
}
|
|
|
|
copy(request.Src[:], fromAddr)
|
|
copy(request.Dst[:], toAddr)
|
|
|
|
binary.BigEndian.PutUint16(request.SrcPort[:], from.Port())
|
|
binary.BigEndian.PutUint16(request.DstPort[:], to.Port())
|
|
|
|
conn, err := netlink.Dial(unix.NETLINK_INET_DIAG, nil)
|
|
if err != nil {
|
|
return 0, 0, err
|
|
}
|
|
defer conn.Close()
|
|
|
|
message := netlink.Message{
|
|
Header: netlink.Header{
|
|
Type: 20, // SOCK_DIAG_BY_FAMILY
|
|
Flags: flags,
|
|
},
|
|
Data: (*(*[unsafe.Sizeof(*request)]byte)(unsafe.Pointer(request)))[:],
|
|
}
|
|
|
|
messages, err := conn.Execute(message)
|
|
if err != nil {
|
|
return 0, 0, err
|
|
}
|
|
|
|
for _, msg := range messages {
|
|
if len(msg.Data) < int(unsafe.Sizeof(inetDiagResponse{})) {
|
|
continue
|
|
}
|
|
|
|
response := (*inetDiagResponse)(unsafe.Pointer(&msg.Data[0]))
|
|
|
|
return response.INode, response.UID, nil
|
|
}
|
|
|
|
return 0, 0, ErrNotFound
|
|
}
|
|
|
|
func resolveProcessPathByProcSearch(inode, uid uint32) (string, error) {
|
|
procDir, err := os.Open("/proc")
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
defer procDir.Close()
|
|
|
|
pids, err := procDir.Readdirnames(-1)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
expectedSocketName := fmt.Appendf(nil, "socket:[%d]", inode)
|
|
|
|
pathBuffer := pool.Get(64)
|
|
defer pool.Put(pathBuffer)
|
|
|
|
readlinkBuffer := pool.Get(32)
|
|
defer pool.Put(readlinkBuffer)
|
|
|
|
copy(pathBuffer, "/proc/")
|
|
|
|
for _, pid := range pids {
|
|
if !isPid(pid) {
|
|
continue
|
|
}
|
|
|
|
pathBuffer = append(pathBuffer[:len("/proc/")], pid...)
|
|
|
|
stat := &unix.Stat_t{}
|
|
err = unix.Stat(string(pathBuffer), stat)
|
|
if err != nil {
|
|
continue
|
|
} else if stat.Uid != uid {
|
|
continue
|
|
}
|
|
|
|
pathBuffer = append(pathBuffer, "/fd/"...)
|
|
fdsPrefixLength := len(pathBuffer)
|
|
|
|
fdDir, err := os.Open(string(pathBuffer))
|
|
if err != nil {
|
|
continue
|
|
}
|
|
|
|
fds, err := fdDir.Readdirnames(-1)
|
|
fdDir.Close()
|
|
if err != nil {
|
|
continue
|
|
}
|
|
|
|
for _, fd := range fds {
|
|
pathBuffer = pathBuffer[:fdsPrefixLength]
|
|
|
|
pathBuffer = append(pathBuffer, fd...)
|
|
|
|
n, err := unix.Readlink(string(pathBuffer), readlinkBuffer)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
|
|
if bytes.Equal(readlinkBuffer[:n], expectedSocketName) {
|
|
return os.Readlink("/proc/" + pid + "/exe")
|
|
}
|
|
}
|
|
}
|
|
|
|
return "", fmt.Errorf("inode %d of uid %d not found", inode, uid)
|
|
}
|
|
|
|
func isPid(name string) bool {
|
|
for _, c := range name {
|
|
if c < '0' || c > '9' {
|
|
return false
|
|
}
|
|
}
|
|
|
|
return true
|
|
}
|