diff --git a/component/process/process_linux.go b/component/process/process_linux.go index 158a353c..1edd486c 100644 --- a/component/process/process_linux.go +++ b/component/process/process_linux.go @@ -6,31 +6,57 @@ import ( "fmt" "net" "os" - "path" + "path/filepath" "strings" "syscall" "unicode" "unsafe" - "github.com/Dreamacro/clash/common/pool" + "github.com/mdlayher/netlink" + "golang.org/x/sys/unix" ) -// 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" + SOCK_DIAG_BY_FAMILY = 20 + inetDiagRequestSize = int(unsafe.Sizeof(inetDiagRequest{})) + inetDiagResponseSize = int(unsafe.Sizeof(inetDiagResponse{})) ) +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 findProcessName(network string, ip net.IP, srcPort int) (string, error) { inode, uid, err := resolveSocketByNetlink(network, ip, srcPort) if err != nil { @@ -40,132 +66,73 @@ func findProcessName(network string, ip net.IP, srcPort int) (string, error) { return resolveProcessNameByProcSearch(inode, uid) } -func resolveSocketByNetlink(network string, ip net.IP, 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 +func resolveSocketByNetlink(network string, ip net.IP, srcPort int) (uint32, uint32, error) { + request := &inetDiagRequest{ + States: 0xffffffff, + Cookie: [2]uint32{0xffffffff, 0xffffffff}, } if ip.To4() != nil { - family = syscall.AF_INET + request.Family = unix.AF_INET } else { - family = syscall.AF_INET6 + request.Family = unix.AF_INET6 } - req := packSocketDiagRequest(family, protocol, ip, uint16(srcPort)) + if strings.HasPrefix(network, "tcp") { + request.Protocol = unix.IPPROTO_TCP + } else if strings.HasPrefix(network, "udp") { + request.Protocol = unix.IPPROTO_UDP + } else { + return 0, 0, ErrInvalidNetwork + } - socket, err := syscall.Socket(syscall.AF_NETLINK, syscall.SOCK_DGRAM, syscall.NETLINK_INET_DIAG) + if v4 := ip.To4(); v4 != nil { + copy(request.Src[:], v4) + } else { + copy(request.Src[:], ip) + } + + binary.BigEndian.PutUint16(request.SrcPort[:], uint16(srcPort)) + + conn, err := netlink.Dial(unix.NETLINK_INET_DIAG, nil) if err != nil { - return 0, 0, fmt.Errorf("dial netlink: %w", err) + return 0, 0, err } - defer syscall.Close(socket) + defer conn.Close() - 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}) + message := netlink.Message{ + Header: netlink.Header{ + Type: SOCK_DIAG_BY_FAMILY, + Flags: netlink.Request | netlink.Dump, + }, + Data: (*(*[inetDiagRequestSize]byte)(unsafe.Pointer(request)))[:], + } - if err := syscall.Connect(socket, &syscall.SockaddrNetlink{ - Family: syscall.AF_NETLINK, - Pad: 0, - Pid: 0, - Groups: 0, - }); err != nil { + messages, err := conn.Execute(message) + if err != nil { return 0, 0, err } - if _, err := syscall.Write(socket, req); err != nil { - return 0, 0, fmt.Errorf("write request: %w", err) + for _, msg := range messages { + if len(msg.Data) < inetDiagResponseSize { + continue + } + + response := (*inetDiagResponse)(unsafe.Pointer(&msg.Data[0])) + + return response.INode, response.UID, nil } - 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") - } - - inode, uid := unpackSocketDiagResponse(&messages[0]) - if inode < 0 || uid < 0 { - return 0, 0, fmt.Errorf("invalid inode(%d) or uid(%d)", inode, uid) - } - - return inode, uid, nil + return 0, 0, ErrNotFound } -func packSocketDiagRequest(family, protocol byte, source net.IP, sourcePort uint16) []byte { - s := make([]byte, 16) - - if v4 := source.To4(); v4 != nil { - copy(s, v4) - } else { - copy(s, source) - } - - 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) +func resolveProcessNameByProcSearch(inode, uid uint32) (string, error) { + files, err := os.ReadDir("/proc") if err != nil { return "", err } - buffer := make([]byte, syscall.PathMax) + buffer := make([]byte, unix.PathMax) socket := fmt.Appendf(nil, "socket:[%d]", inode) for _, f := range files { @@ -177,12 +144,12 @@ func resolveProcessNameByProcSearch(inode, uid int32) (string, error) { if err != nil { return "", err } - if info.Sys().(*syscall.Stat_t).Uid != uint32(uid) { + if info.Sys().(*syscall.Stat_t).Uid != uid { continue } - processPath := path.Join(pathProc, f.Name()) - fdPath := path.Join(processPath, "fd") + processPath := filepath.Join("/proc", f.Name()) + fdPath := filepath.Join(processPath, "fd") fds, err := os.ReadDir(fdPath) if err != nil { @@ -190,13 +157,13 @@ func resolveProcessNameByProcSearch(inode, uid int32) (string, error) { } for _, fd := range fds { - n, err := syscall.Readlink(path.Join(fdPath, fd.Name()), buffer) + n, err := unix.Readlink(filepath.Join(fdPath, fd.Name()), buffer) if err != nil { continue } if bytes.Equal(buffer[:n], socket) { - return os.Readlink(path.Join(processPath, "exe")) + return os.Readlink(filepath.Join(processPath, "exe")) } } } diff --git a/go.mod b/go.mod index a6fe15e0..84f78766 100644 --- a/go.mod +++ b/go.mod @@ -9,6 +9,7 @@ require ( github.com/gofrs/uuid v4.2.0+incompatible github.com/gorilla/websocket v1.5.0 github.com/insomniacslk/dhcp v0.0.0-20220822114210-de18a9d48e84 + github.com/mdlayher/netlink v1.6.0 github.com/miekg/dns v1.1.50 github.com/oschwald/geoip2-golang v1.8.0 github.com/sirupsen/logrus v1.9.0 @@ -26,7 +27,10 @@ require ( require ( github.com/ajg/form v1.5.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect + github.com/google/go-cmp v0.5.7 // indirect + github.com/josharian/native v1.0.0 // indirect github.com/kr/text v0.2.0 // indirect + github.com/mdlayher/socket v0.1.1 // indirect github.com/oschwald/maxminddb-golang v1.10.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/u-root/uio v0.0.0-20210528114334-82958018845c // indirect diff --git a/go.sum b/go.sum index a86cdaa7..4dc43b1f 100644 --- a/go.sum +++ b/go.sum @@ -18,12 +18,17 @@ github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o= +github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/hugelgupf/socketpair v0.0.0-20190730060125-05d35a94e714/go.mod h1:2Goc3h8EklBH5mspfHFxBnEoURQCGzQQH1ga9Myjvis= github.com/insomniacslk/dhcp v0.0.0-20220822114210-de18a9d48e84 h1:MJTy6H+EpXLeAn0P5WAWeLk6dJA3V0ik6S3VJfUyQuI= github.com/insomniacslk/dhcp v0.0.0-20220822114210-de18a9d48e84/go.mod h1:h+MxyHxRg9NH3terB1nfRIUaQEcI0XOVkdR9LNBlp8E= +github.com/josharian/native v1.0.0 h1:Ts/E8zCSEsG17dUqv7joXJFybuMLjQfWE04tsBODTxk= +github.com/josharian/native v1.0.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w= github.com/jsimonetti/rtnetlink v0.0.0-20190606172950-9527aa82566a/go.mod h1:Oz+70psSo5OFh8DBl0Zv2ACw7Esh6pPUphlvZG9x7uw= github.com/jsimonetti/rtnetlink v0.0.0-20200117123717-f846d4f6c1f4/go.mod h1:WGuG/smIU4J/54PblvSbh+xvCZmpJnFgr3ds6Z55XMQ= github.com/jsimonetti/rtnetlink v0.0.0-20201009170750-9c6f07d100c1/go.mod h1:hqoO/u39cqLeBLebZ8fWdE96O7FxrAsRYhnVOdgHxok= @@ -37,8 +42,12 @@ github.com/mdlayher/netlink v0.0.0-20190409211403-11939a169225/go.mod h1:eQB3mZE github.com/mdlayher/netlink v1.0.0/go.mod h1:KxeJAFOFLG6AjpyDkQ/iIhxygIUKD+vcwqcnu43w/+M= github.com/mdlayher/netlink v1.1.0/go.mod h1:H4WCitaheIsdF9yOYu8CFmCgQthAPIWZmcKp9uZHgmY= github.com/mdlayher/netlink v1.1.1/go.mod h1:WTYpFb/WTvlRJAyKhZL5/uy69TDDpHHu2VZmb2XgV7o= +github.com/mdlayher/netlink v1.6.0 h1:rOHX5yl7qnlpiVkFWoqccueppMtXzeziFjWAjLg6sz0= +github.com/mdlayher/netlink v1.6.0/go.mod h1:0o3PlBmGst1xve7wQ7j/hwpNaFaH4qCRyWCdcZk8/vA= github.com/mdlayher/raw v0.0.0-20190606142536-fef19f00fc18/go.mod h1:7EpbotpCmVZcu+KCX4g9WaRNuu11uyhiW7+Le1dKawg= github.com/mdlayher/raw v0.0.0-20191009151244-50f2db8cc065/go.mod h1:7EpbotpCmVZcu+KCX4g9WaRNuu11uyhiW7+Le1dKawg= +github.com/mdlayher/socket v0.1.1 h1:q3uOGirUPfAV2MUoaC7BavjQ154J7+JOkTWyiV+intI= +github.com/mdlayher/socket v0.1.1/go.mod h1:mYV5YIZAfHh4dzDVzI8x8tWLWCliuX8Mon5Awbj+qDs= github.com/miekg/dns v1.1.50 h1:DQUfb9uc6smULcREF09Uc+/Gd46YWqJd5DbpPE9xkcA= github.com/miekg/dns v1.1.50/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME= github.com/oschwald/geoip2-golang v1.8.0 h1:KfjYB8ojCEn/QLqsDU0AzrJ3R5Qa9vFlx3z6SLNcKTs= @@ -87,6 +96,8 @@ golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwY golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210928044308-7d9f5e0b762b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220822230855-b0a4917ee28c h1:JVAXQ10yGGVbSyoer5VILysz6YKjdNT2bsvlayjqhes= golang.org/x/net v0.0.0-20220822230855-b0a4917ee28c/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -111,11 +122,16 @@ golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210525143221-35b2ab0089ea/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220825204002-c680a09ffe64 h1:UiNENfZ8gDvpiWw7IpOMQ27spWmThO1RwwdQVbJahJM= golang.org/x/sys v0.0.0-20220825204002-c680a09ffe64/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=