github.com/inazumav/sing-box@v0.0.0-20230926072359-ab51429a14f1/common/process/searcher_linux_shared.go (about) 1 //go:build linux 2 3 package process 4 5 import ( 6 "bytes" 7 "encoding/binary" 8 "fmt" 9 "net" 10 "net/netip" 11 "os" 12 "path" 13 "strings" 14 "syscall" 15 "unicode" 16 "unsafe" 17 18 "github.com/sagernet/sing/common/buf" 19 E "github.com/sagernet/sing/common/exceptions" 20 N "github.com/sagernet/sing/common/network" 21 ) 22 23 // from https://github.com/vishvananda/netlink/blob/bca67dfc8220b44ef582c9da4e9172bf1c9ec973/nl/nl_linux.go#L52-L62 24 var nativeEndian = func() binary.ByteOrder { 25 var x uint32 = 0x01020304 26 if *(*byte)(unsafe.Pointer(&x)) == 0x01 { 27 return binary.BigEndian 28 } 29 30 return binary.LittleEndian 31 }() 32 33 const ( 34 sizeOfSocketDiagRequest = syscall.SizeofNlMsghdr + 8 + 48 35 socketDiagByFamily = 20 36 pathProc = "/proc" 37 ) 38 39 func resolveSocketByNetlink(network string, source netip.AddrPort, destination netip.AddrPort) (inode, uid uint32, err error) { 40 var family uint8 41 var protocol uint8 42 43 switch network { 44 case N.NetworkTCP: 45 protocol = syscall.IPPROTO_TCP 46 case N.NetworkUDP: 47 protocol = syscall.IPPROTO_UDP 48 default: 49 return 0, 0, os.ErrInvalid 50 } 51 52 if source.Addr().Is4() { 53 family = syscall.AF_INET 54 } else { 55 family = syscall.AF_INET6 56 } 57 58 req := packSocketDiagRequest(family, protocol, source) 59 60 socket, err := syscall.Socket(syscall.AF_NETLINK, syscall.SOCK_DGRAM, syscall.NETLINK_INET_DIAG) 61 if err != nil { 62 return 0, 0, E.Cause(err, "dial netlink") 63 } 64 defer syscall.Close(socket) 65 66 syscall.SetsockoptTimeval(socket, syscall.SOL_SOCKET, syscall.SO_SNDTIMEO, &syscall.Timeval{Usec: 100}) 67 syscall.SetsockoptTimeval(socket, syscall.SOL_SOCKET, syscall.SO_RCVTIMEO, &syscall.Timeval{Usec: 100}) 68 69 err = syscall.Connect(socket, &syscall.SockaddrNetlink{ 70 Family: syscall.AF_NETLINK, 71 Pad: 0, 72 Pid: 0, 73 Groups: 0, 74 }) 75 if err != nil { 76 return 77 } 78 79 _, err = syscall.Write(socket, req) 80 if err != nil { 81 return 0, 0, E.Cause(err, "write netlink request") 82 } 83 84 buffer := buf.New() 85 defer buffer.Release() 86 87 n, err := syscall.Read(socket, buffer.FreeBytes()) 88 if err != nil { 89 return 0, 0, E.Cause(err, "read netlink response") 90 } 91 92 buffer.Truncate(n) 93 94 messages, err := syscall.ParseNetlinkMessage(buffer.Bytes()) 95 if err != nil { 96 return 0, 0, E.Cause(err, "parse netlink message") 97 } else if len(messages) == 0 { 98 return 0, 0, E.New("unexcepted netlink response") 99 } 100 101 message := messages[0] 102 if message.Header.Type&syscall.NLMSG_ERROR != 0 { 103 return 0, 0, E.New("netlink message: NLMSG_ERROR") 104 } 105 106 inode, uid = unpackSocketDiagResponse(&messages[0]) 107 return 108 } 109 110 func packSocketDiagRequest(family, protocol byte, source netip.AddrPort) []byte { 111 s := make([]byte, 16) 112 copy(s, source.Addr().AsSlice()) 113 114 buf := make([]byte, sizeOfSocketDiagRequest) 115 116 nativeEndian.PutUint32(buf[0:4], sizeOfSocketDiagRequest) 117 nativeEndian.PutUint16(buf[4:6], socketDiagByFamily) 118 nativeEndian.PutUint16(buf[6:8], syscall.NLM_F_REQUEST|syscall.NLM_F_DUMP) 119 nativeEndian.PutUint32(buf[8:12], 0) 120 nativeEndian.PutUint32(buf[12:16], 0) 121 122 buf[16] = family 123 buf[17] = protocol 124 buf[18] = 0 125 buf[19] = 0 126 nativeEndian.PutUint32(buf[20:24], 0xFFFFFFFF) 127 128 binary.BigEndian.PutUint16(buf[24:26], source.Port()) 129 binary.BigEndian.PutUint16(buf[26:28], 0) 130 131 copy(buf[28:44], s) 132 copy(buf[44:60], net.IPv6zero) 133 134 nativeEndian.PutUint32(buf[60:64], 0) 135 nativeEndian.PutUint64(buf[64:72], 0xFFFFFFFFFFFFFFFF) 136 137 return buf 138 } 139 140 func unpackSocketDiagResponse(msg *syscall.NetlinkMessage) (inode, uid uint32) { 141 if len(msg.Data) < 72 { 142 return 0, 0 143 } 144 145 data := msg.Data 146 147 uid = nativeEndian.Uint32(data[64:68]) 148 inode = nativeEndian.Uint32(data[68:72]) 149 150 return 151 } 152 153 func resolveProcessNameByProcSearch(inode, uid uint32) (string, error) { 154 files, err := os.ReadDir(pathProc) 155 if err != nil { 156 return "", err 157 } 158 159 buffer := make([]byte, syscall.PathMax) 160 socket := []byte(fmt.Sprintf("socket:[%d]", inode)) 161 162 for _, f := range files { 163 if !f.IsDir() || !isPid(f.Name()) { 164 continue 165 } 166 167 info, err := f.Info() 168 if err != nil { 169 return "", err 170 } 171 if info.Sys().(*syscall.Stat_t).Uid != uid { 172 continue 173 } 174 175 processPath := path.Join(pathProc, f.Name()) 176 fdPath := path.Join(processPath, "fd") 177 178 fds, err := os.ReadDir(fdPath) 179 if err != nil { 180 continue 181 } 182 183 for _, fd := range fds { 184 n, err := syscall.Readlink(path.Join(fdPath, fd.Name()), buffer) 185 if err != nil { 186 continue 187 } 188 189 if bytes.Equal(buffer[:n], socket) { 190 return os.Readlink(path.Join(processPath, "exe")) 191 } 192 } 193 } 194 195 return "", fmt.Errorf("process of uid(%d),inode(%d) not found", uid, inode) 196 } 197 198 func isPid(s string) bool { 199 return strings.IndexFunc(s, func(r rune) bool { 200 return !unicode.IsDigit(r) 201 }) == -1 202 }