github.com/Asutorufa/yuhaiin@v0.3.6-0.20240502055049-7984da7023a0/pkg/net/netlink/netlink_linux.go (about)

     1  package netlink
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"net"
     7  	"os"
     8  
     9  	"github.com/Asutorufa/yuhaiin/pkg/utils/pool"
    10  	"github.com/vishvananda/netlink"
    11  	"golang.org/x/sys/unix"
    12  )
    13  
    14  /*
    15  https://stackoverflow.com/questions/10996242/how-to-get-the-pid-of-a-process-that-is-listening-on-a-certain-port-programmatic
    16  
    17  Like netstat, you should read /proc/net/tcp.
    18  
    19  Interpreting it:
    20  
    21  	The second field, titled local_address, is the IP and port. 00000000:0050 would be HTTP (the port number is in hex).
    22  	The 4th field, titled st, is the state. A is TCP_LISTEN.
    23  	The 10th field, titled inode is the inode number (decimal this time).
    24  	For each process, /proc/pid/fd/ contains an entry for each open file descriptor. ls -l for socket descriptors shows that it's a link to socket:[nnnnnn]. The number nnnnnn should match the inode number from /proc/net/tcp.
    25  
    26  This makes finding the process quite tiresome, but possible.
    27  Finding the right line in /proc/net/tcp isn't difficult, and then you can get the inode number.
    28  Finding the process requires you to scan all processes, looking for one which refers this inode number. I know no better way.
    29  */
    30  
    31  func FindProcessName(network string, ip net.IP, srcPort uint16, to net.IP, toPort uint16) (string, error) {
    32  	var addr net.Addr
    33  	var remote []net.Addr
    34  
    35  	if len(network) < 3 {
    36  		return "", fmt.Errorf("ErrInvalidNetwork: %s", network)
    37  	}
    38  
    39  	network = network[0:3]
    40  
    41  	switch network {
    42  	case "tcp":
    43  		addr = &net.TCPAddr{IP: ip, Port: int(srcPort)}
    44  		remote = []net.Addr{&net.TCPAddr{IP: to, Port: int(toPort)}}
    45  	case "udp":
    46  		addr = &net.UDPAddr{IP: ip, Port: int(srcPort)}
    47  		remote = []net.Addr{
    48  			// &net.UDPAddr{IP: to, Port: int(toPort)},
    49  			&net.UDPAddr{IP: net.IPv6zero, Port: 0},
    50  			&net.UDPAddr{IP: net.IPv4zero, Port: 0},
    51  		}
    52  	default:
    53  		return "", fmt.Errorf("ErrInvalidNetwork: %s", network)
    54  	}
    55  
    56  	var st *netlink.Socket
    57  	var err error
    58  
    59  	for _, r := range remote {
    60  		st, err = netlink.SocketGet(addr, r)
    61  		if err == nil {
    62  			break
    63  		}
    64  	}
    65  
    66  	if st == nil {
    67  		return "", err
    68  	}
    69  
    70  	return resolveProcessNameByProcSearch(st.INode, st.UID)
    71  }
    72  
    73  func resolveProcessNameByProcSearch(inode, uid uint32) (string, error) {
    74  	procDir, err := os.Open("/proc")
    75  	if err != nil {
    76  		return "", err
    77  	}
    78  	defer procDir.Close()
    79  
    80  	pids, err := procDir.Readdirnames(-1)
    81  	if err != nil {
    82  		return "", err
    83  	}
    84  
    85  	expectedSocketName := fmt.Appendf(nil, "socket:[%d]", inode)
    86  
    87  	pathBuffer := pool.GetBuffer()
    88  	defer pool.PutBuffer(pathBuffer)
    89  
    90  	readlinkBuffer := pool.GetBytesBuffer(32)
    91  	defer readlinkBuffer.Free()
    92  
    93  	pathBuffer.WriteString("/proc/")
    94  
    95  	for _, pid := range pids {
    96  		if !isPid(pid) {
    97  			continue
    98  		}
    99  
   100  		pathBuffer.Truncate(len("/proc/"))
   101  		pathBuffer.WriteString(pid)
   102  
   103  		stat := &unix.Stat_t{}
   104  		err = unix.Stat(pathBuffer.String(), stat)
   105  		if err != nil {
   106  			continue
   107  		}
   108  
   109  		if stat.Uid != uid {
   110  			continue
   111  		}
   112  
   113  		pathBuffer.WriteString("/fd/")
   114  		fdsPrefixLength := pathBuffer.Len()
   115  
   116  		fdDir, err := os.Open(pathBuffer.String())
   117  		if err != nil {
   118  			continue
   119  		}
   120  
   121  		fds, err := fdDir.Readdirnames(-1)
   122  		fdDir.Close()
   123  		if err != nil {
   124  			continue
   125  		}
   126  
   127  		for _, fd := range fds {
   128  			pathBuffer.Truncate(fdsPrefixLength)
   129  			pathBuffer.WriteString(fd)
   130  
   131  			n, err := unix.Readlink(pathBuffer.String(), readlinkBuffer.Bytes())
   132  			if err != nil {
   133  				continue
   134  			}
   135  
   136  			if bytes.Equal(readlinkBuffer.Bytes()[:n], expectedSocketName) {
   137  				return os.Readlink("/proc/" + pid + "/exe")
   138  			}
   139  		}
   140  	}
   141  
   142  	return "", fmt.Errorf("inode %d of uid %d not found", inode, uid)
   143  }
   144  
   145  func isPid(name string) bool {
   146  	for _, c := range name {
   147  		if c < '0' || c > '9' {
   148  			return false
   149  		}
   150  	}
   151  
   152  	return true
   153  }