如何在同一台机器上以编程方式获取通过 AF_INET 套接字连接到我的代理的进程的 PID?

Posted

技术标签:

【中文标题】如何在同一台机器上以编程方式获取通过 AF_INET 套接字连接到我的代理的进程的 PID?【英文标题】:How to programmatically get PID of process connecting to my proxy via AF_INET sockets, on the same machine? 【发布时间】:2019-04-28 04:34:26 【问题描述】:

我正在 Linux 机器上编写一个小型 http proxy 服务器(用 C 语言),具体来说是 Ubuntu 18.04.1,我一直在尝试找到一种方法来获取 连接到它的进程的pid。

值得一提的是,代理仅用于为同一台机器上运行的进程代理连接,所以我想这应该使这项任务成为可能。

服务器使用 AF_INET 系列套接字以及读/写操作来完成它的工作;我之所以提到这一点,是因为经过一些研究,我确实遇到了有关“辅助数据”的线程,例如:Is there a way to get the uid of the other end of a unix socket connection

辅助数据包含连接套接字的凭据(例如 PID),但仅适用于 AF_UNIX 套接字,用于本地 IPC,并且需要我们在双方显式发送/接收它(客户端/服务器)。就我而言,尽管正如我所提到的,服务器只会代理与服务器相同的机器上的流量,但我需要使用 AF_INET 套接字,因此每个人(例如网络浏览器)都能够连接到它。

性能不是那么重要;所以任何建议(包括使用系统调用等的解决方法)都非常受欢迎。

【问题讨论】:

通过套接字的连接可以来自任何机器。如果你能得到连接进程的PID,我会很惊讶。 为什么你需要删除进程的PID?您试图解决的真正的原始问题是什么? 此代理实际上将成为更大过滤器框架中的网关,并且需要记录 PID 以用于代理以外的其他目的。 @PaulOgilvie,我同意你的观点,我不需要远程机器上进程的 PID,实际上正如我在问题中提到的那样,这个代理只注定要为在同一台机器上运行的进程执行它的任务机器本身。我需要那些 pids 根据我之前的评论,套接字不提供获取连接进程 PID 的方法。因此你不能。甚至对于本地进程也不行。 【参考方案1】:

我们可以使用netstat -nptW 输出来查看哪些本地进程的TCP 连接。由于输出可能是安全敏感的,因此需要超级用户权限才能查看属于所有用户的进程。

由于没有理由以提升的权限运行代理服务(可能是CAP_NET_BIND_SERVICE),因此需要一个特权帮助程序。

我思考了一个合适的安全模型,并得出结论,检查给它的连接套接字(如标准输入)并仅输出对等 PID 的助手将是最安全的:滥用它会非常困难,即使可能,也只会显示对等进程 ID。

这是示例帮助程序,tcp-peer-pids.c

#define _POSIX_C_SOURCE  200809L
#define _GNU_SOURCE
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <netdb.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>

#define   EXITCODE_OK               0
#define   EXITCODE_STDIN_INVALID    1
#define   EXITCODE_UNKNOWN_ADDRESS  2
#define   EXITCODE_NETSTAT          3
#define   EXITCODE_NETSTAT_OUTPUT   4
#define   EXITCODE_WRITE_ERROR      5
#define   EXITCODE_PRIVILEGES       6

static pid_t        *pids = NULL;
static size_t    num_pids = 0;
static size_t    max_pids = 0;

static int add_pid(const pid_t p)

    size_t  i;

    /* Check if already listed. */
    for (i = 0; i < num_pids; i++)
        if (pids[i] == p)
            return 0;

    /* Ensure enough room in pids array. */
    if (num_pids >= max_pids) 
        const size_t  max_temp = (num_pids | 1023) + 1025 - 8;
        pid_t            *temp;

        temp = realloc(pids, max_temp * sizeof pids[0]);
        if (!temp)
            return ENOMEM;

        pids     = temp;
        max_pids = max_temp;
    

    pids[num_pids++] = p;

    return 0;


int main(void)

    struct sockaddr_storage  sock_addr;
    socklen_t                sock_addrlen = sizeof sock_addr;
    char                     sock_match[128], sock_host[64], sock_port[32];

    struct sockaddr_storage  peer_addr;
    socklen_t                peer_addrlen = sizeof peer_addr;
    char                     peer_match[128], peer_host[64], peer_port[32];

    FILE                    *cmd;
    char                    *line = NULL;
    size_t                   size = 0;
    ssize_t                  len;

    int                      status;

    /* Socket address is *remote*, and peer address is *local*.
       This is because the variables are named after their matching netstat lines. */
    if (getsockname(STDIN_FILENO, (struct sockaddr *)&sock_addr, &sock_addrlen) == -1) 
        fprintf(stderr, "Standard input is not a valid socket.\n");
        exit(EXITCODE_STDIN_INVALID);
    
    if (getpeername(STDIN_FILENO, (struct sockaddr *)&peer_addr, &peer_addrlen) == -1) 
        fprintf(stderr, "Standard input is not a connected socket.\n");
        exit(EXITCODE_STDIN_INVALID);
    
    if ((sock_addr.ss_family != AF_INET && sock_addr.ss_family != AF_INET6) ||
        (peer_addr.ss_family != AF_INET && peer_addr.ss_family != AF_INET6)) 
        fprintf(stderr, "Standard input is not an IP socket.\n");
        exit(EXITCODE_STDIN_INVALID);
    

    /* For security, we close the standard input descriptor, */
    close(STDIN_FILENO);

    /* and redirect it from /dev/null, if possible. */
    
        int  fd = open("/dev/null", O_RDONLY);
        if (fd != -1 && fd != STDIN_FILENO) 
            dup2(fd, STDIN_FILENO);
            close(fd);
        
    

    /* Convert sockets to numerical host and port strings. */
    if (getnameinfo((const struct sockaddr *)&sock_addr, sock_addrlen,
                    sock_host, sizeof sock_host, sock_port, sizeof sock_port,
                    NI_NUMERICHOST | NI_NUMERICSERV)) 
        fprintf(stderr, "Unknown socket address.\n");
        exit(EXITCODE_UNKNOWN_ADDRESS);
    
    if (getnameinfo((const struct sockaddr *)&peer_addr, peer_addrlen,
                    peer_host, sizeof peer_host, peer_port, sizeof peer_port,
                    NI_NUMERICHOST | NI_NUMERICSERV)) 
        fprintf(stderr, "Unknown peer address.\n");
        exit(EXITCODE_UNKNOWN_ADDRESS);
    

    /* Combine to the host:port format netstat uses. */
    snprintf(sock_match, sizeof sock_match, "%s:%s", sock_host, sock_port);
    snprintf(peer_match, sizeof peer_match, "%s:%s", peer_host, peer_port);

    /* Switch to privileged user, if installed as setuid. */
    
        uid_t  real_uid = getuid();
        gid_t  real_gid = getgid();
        uid_t  effective_uid = geteuid();
        gid_t  effective_gid = getegid();
        if (real_gid != effective_gid || real_uid != effective_uid) 
            /* SetUID or SetGID in effect. Switch privileges. */
            if (setresgid(effective_gid, effective_gid, effective_gid) == -1 ||
                setresuid(effective_uid, effective_uid, effective_uid) == -1) 
                fprintf(stderr, "Error in privileges: %s.\n", strerror(errno));
                exit(EXITCODE_PRIVILEGES);
            
        
    

    /* Run netstat to obtain the data; redirect standard error to standard output. */
    cmd = popen("LANG=C LC_ALL=C /bin/netstat -nptW 2>&1", "r");
    if (!cmd) 
        fprintf(stderr, "Cannot run netstat.\n");
        exit(EXITCODE_NETSTAT);
    

    /* Input line loop. */
    while (1) 
        char *field[8], *ends;
        long  val;
        pid_t p;

        len = getline(&line, &size, cmd);
        if (len < 1)
            break;

        /* Split each line into fields. */
        field[0] = strtok(line, "\t\n\v\f\r ");  /* Protocol */

        /* We are only interested in tcp ("tcp" and "tcp6" protocols). */
        if (strcmp(field[0], "tcp") && strcmp(field[0], "tcp6"))
            continue;

        field[1] = strtok(NULL, "\t\n\v\f\r ");  /* Recv-Q */
        field[2] = strtok(NULL, "\t\n\v\f\r ");  /* Send-Q */
        field[3] = strtok(NULL, "\t\n\v\f\r ");  /* Local address (peer) */
        field[4] = strtok(NULL, "\t\n\v\f\r ");  /* Remote address (sock) */
        field[5] = strtok(NULL, "\t\n\v\f\r ");  /* State */
        field[6] = strtok(NULL, "\t\n\v\f\r /"); /* PID */
        field[7] = strtok(NULL, "\t\n\v\f\r ");  /* Process name */

        /* Local address must match peer_match, and foreign/remote sock_match. */
        if (strcmp(field[3], peer_match) || strcmp(field[4], sock_match))
            continue;

        /* This line corresponds to the process we are looking for. */

        /* Missing PID field is an error at this point. */
        if (!field[6])
            break;

        /* Parse the PID. Parsing errors are fatal. */
        ends = field[6];
        errno = 0;
        val = strtol(field[6], &ends, 10);
        if (errno || ends == field[6] || *ends != '\0' || val < 1)
            break;
        p = (pid_t)val;
        if ((long)p != val)
            break;

        /* Add the pid to the known pids list. */
        if (add_pid(p))
            break;
    

    /* The line buffer is no longer needed. */
    free(line);

    /* I/O error? */
    if (!feof(cmd) || ferror(cmd)) 
        fprintf(stderr, "Error reading netstat output.\n");
        exit(EXITCODE_NETSTAT_OUTPUT);
    

    /* Reap the netstat process. */
    status = pclose(cmd);
    if (status == -1) 
        fprintf(stderr, "Error reading netstat output: %s.\n", strerror(errno));
        exit(EXITCODE_NETSTAT_OUTPUT);
                   
    if (!WIFEXITED(status)) 
        fprintf(stderr, "Netstat died unexpectedly.\n");
        exit(EXITCODE_NETSTAT_OUTPUT);
    
    if (WEXITSTATUS(status)) 
        fprintf(stderr, "Netstat failed with exit status %d.\n", WEXITSTATUS(status));
        exit(EXITCODE_NETSTAT_OUTPUT);
            

    /* Output the array of pids as binary data. */
    if (num_pids > 0) 
        const char        *head = (const char *)pids;
        const char *const  ends = (const char *)(pids + num_pids);
        ssize_t            n;

        while (head < ends) 
            n = write(STDOUT_FILENO, head, (size_t)(ends - head));
            if (n > 0)
                head += n;
            else
            if (n != -1)
                exit(EXITCODE_WRITE_ERROR);
            else
            if (errno != EINTR)
                exit(EXITCODE_WRITE_ERROR);
        
    

    /* Discard the pids array. */
    free(pids);
    exit(EXITCODE_OK);

它可以使用普通用户权限(在这种情况下它只知道该用户拥有的进程)、root 权限或 setuid root 运行。

如果与sudo 一起使用,请确保使用规则proxyuser ALL = NOPASSWD: /path/to/helper,因为sudo 无法在此处询问密码。我可能只是在/usr/lib/yourproxy/tcp-peer-pid 以setuid root 身份安装帮助程序,所有者root,将您的代理服务组分组,并且不能访问其他用户(root:proxygroup -r-sr-x---)。

帮助程序与netstat -nptW 输出格式紧密耦合,但确实明确设置了 C 语言环境以避免获得本地化输出。

与 netstat 输出中的“本地地址”和“外国地址”相匹配的比较 address:port 字符串分别由​​ getpeername()getsockname() 返回的地址构成,使用 [getnameinfo()(http://man7.org/linux/man-pages/man3/getnameinfo.3.html ) 以数字形式(使用NI_NUMERICHOST | NI_NUMERICSERV 标志)。


帮助程序以二进制形式将 PID 提供给服务器,否则服务器代码将太长而无法放入此处的单个帖子中。

这里是一个示例 TCP 服务,server.c,它使用上面的帮助程序来找出本地计算机上套接字对端的 PID。 (为避免拒绝服务攻击,您应该设置一个 IP 过滤器,拒绝从计算机外部访问您的代理服务端口。)

#define _POSIX_C_SOURCE  200809L
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/select.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <netdb.h>
#include <signal.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>

#ifndef  HELPER_PATH
#define  HELPER_PATH  "./tcp-peer-pids"
#endif
#ifndef  HELPER_NAME
#define  HELPER_NAME  "tcp-peer-pids"
#endif

#ifndef  SUDO_PATH
#define  SUDO_PATH    "/usr/bin/sudo"
#endif
#ifndef  SUDO_NAME
#define  SUDO_NAME    "sudo"
#endif

/*
 * Signal handler, to detect INT (Ctrl+C), HUP, and TERM signals.
*/

static volatile sig_atomic_t  done = 0;

static void handle_done(int signum)

    /* In Linux, all signals have signum > 0. */
    __atomic_store_n(&done, (sig_atomic_t)signum, __ATOMIC_SEQ_CST);


static int install_done(int signum)

    struct sigaction  act;

    memset(&act, 0, sizeof act);
    sigemptyset(&act.sa_mask);
    act.sa_flags = SA_RESTART;  /* Do not interrupt slow syscalls. */
    act.sa_handler = handle_done;
    if (sigaction(signum, &act, NULL) == -1)
        return -1; /* errno set by getpeername() */

    return 0;


/* Helper function: Move descriptors away from STDIN/STDOUT/STDERR.
   Returns 0 if successful, -1 with errno set if an error occurs. */
static inline int normalfds(int fd[], const size_t n)

    unsigned int  closemask = 0;
    int           err = 0;
    size_t        i;    
    int           newfd;

    for (i = 0; i < n; i++)
        while (fd[i] == STDIN_FILENO || fd[i] == STDOUT_FILENO || fd[i] == STDERR_FILENO) 
            newfd = dup(fd[i]);
            if (newfd == -1) 
                err = errno;
                break;
            
            closemask |= 1u << fd[i];
            fd[i] = newfd;
        

    /* Close temporary descriptors. */
    if (closemask & (1u <<  STDIN_FILENO)) close(STDIN_FILENO);
    if (closemask & (1u << STDOUT_FILENO)) close(STDOUT_FILENO);
    if (closemask & (1u << STDERR_FILENO)) close(STDERR_FILENO);

    /* Success? */
    if (!err)
        return 0;

    /* Report error. */
    errno = err;
    return -1;


/* Return the number of peer processes.
   If an error occurs, returns zero; examine errno. */
size_t  peer_pids(const int connfd, pid_t *const pids, size_t maxpids)

    char   *in_data = NULL;
    size_t  in_size = 0;
    size_t  in_used = 0;
    size_t  n;
    int     binpipe[2], status;
    pid_t   child, p;

    /* Sanity check. */
    if (connfd == -1) 
        errno = EBADF;
        return 0;
    

    /* Create a pipe to transfer the PIDs (in binary). */
    if (pipe(binpipe) == -1)
        return 0; /* errno set by pipe(). */

    /* Make sure the binary pipe descriptors do not conflict with standard descriptors. */
    if (normalfds(binpipe, 2) == -1) 
        const int  saved_errno = errno;
        close(binpipe[0]);
        close(binpipe[1]);
        errno = saved_errno;
        return 0;
    

    /* Fork a child process. */
    child = fork();
    if (child == -1) 
        const int  saved_errno = errno;
        close(binpipe[0]);
        close(binpipe[1]);
        errno = saved_errno;
        return 0;
    

    if (!child) 
        /* This is the child process. */

#ifdef USE_SUDO
        const char  *cmd_path = SUDO_PATH;
        char *const  cmd_args[3] =  SUDO_NAME, HELPER_PATH, NULL ;
#else
        const char  *cmd_path = HELPER_PATH;
        char *const  cmd_args[2] =  HELPER_NAME, NULL ;
#endif

        /* The child runs in its own process group, for easier management. */
        setsid();

        /* Close read end of pipe. */
        close(binpipe[0]);

        /* Move established connection to standard input. */
        if (connfd != STDIN_FILENO) 
            if (dup2(connfd, STDIN_FILENO) != STDIN_FILENO)
                _Exit(99);
            close(connfd);
        

        /* Move write end of pipe to standard output. */
        if (dup2(binpipe[1], STDOUT_FILENO) != STDOUT_FILENO)
            _Exit(99);
        else
            close(binpipe[1]);

        /* Execute helper. */
        execv(cmd_path, cmd_args);

        /* Failed to execute helper. */
        _Exit(98);
    

    /* Parent process. */

    /* Close write end of pipe, so we detect when child exits. */
    close(binpipe[1]);

    /* Read all output from child. */
    status = 0;
    while (1) 
        ssize_t  bytes;

        if (in_used >= in_size) 
            const size_t  size = (in_used | 1023) + 1025 - 8;
            char         *temp;

            temp = realloc(in_data, in_size);
            if (!temp) 
                status = ENOMEM;
                break;
            
            in_data = temp;
            in_size = size;
        

        bytes = read(binpipe[0], in_data + in_used, in_size - in_used);
        if (bytes > 0) 
            in_used += bytes;
         else
        if (bytes == 0) 
            /* End of input condition. */
            break;
         else
        if (bytes != -1) 
            status = EIO;
            break;
         else
        if (errno != EINTR) 
            status = errno;
            break;
        
    

    /* Close the pipe. */
    close(binpipe[0]);

    /* Abort, if an error occurred. */
    if (status) 
        free(in_data);
        kill(-child, SIGKILL);
        do 
            p = waitpid(child, NULL, 0);
         while (p == -1 && errno == EINTR);
        errno = status;
        return 0;
    

    /* Reap the child process. */
    do 
        status = 0;
        p = waitpid(child, &status, 0);
     while (p == -1 && errno == EINTR);
    if (p == -1) 
        const int  saved_errno = errno;
        free(in_data);
        errno = saved_errno;
        return 0;
    
    if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) 
        free(in_data);
        errno = ESRCH; /* The helper command failed, really. */
        return 0;
    

    /* We expect an integer number of pid_t's. Check. */
    n = in_used / sizeof (pid_t);
    if ((in_used % sizeof (pid_t)) != 0) 
        free(in_data);
        errno = EIO;
        return 0;
    

    /* None found? */
    if (!n) 
        free(in_data);
        errno = ENOENT; /* Not found, really. */
        return 0;
    

    /* Be paranoid, and verify the pids look sane. */
    
        const pid_t *const pid = (const pid_t *const)in_data;
        size_t             i;

        for (i = 0; i < n; i++)
            if (pid[i] < 2) 
                free(in_data);
                errno = ESRCH; /* Helper failed */
                return 0;
            
    

    /* Copy to user buffer, if specified. */
    if (maxpids > n)
        memcpy(pids, in_data, n * sizeof (pid_t));
    else
    if (maxpids > 0)
        memcpy(pids, in_data, maxpids * sizeof (pid_t));

    /* The pid buffer is no longer needed. */
    free(in_data);

    /* Return the number of pids we actually received. */
    return n;



int main(int argc, char *argv[])

    struct addrinfo          hints, *list, *curr;
    const char              *node, *serv;
    int                      service_fd, err;

    struct sockaddr_storage  client_addr;
    socklen_t                client_addrlen;
    int                      client_fd;

    if (argc != 3) 
        fprintf(stderr, "\n");
        fprintf(stderr, "Usage: %s [ -h | --help ]\n", argv[0]);
        fprintf(stderr, "       %s HOST PORT\n", argv[0]);
        fprintf(stderr, "\n");
        return EXIT_FAILURE;
    

    /* Install signal handers for Ctrl+C, HUP, and TERM. */
    if (install_done(SIGINT) ||
        install_done(SIGHUP) ||
        install_done(SIGTERM)) 
        fprintf(stderr, "Cannot install signal handlers: %s.\n", strerror(errno));
        return EXIT_FAILURE;
    

    /* Empty or - or * is a wildcard host. */
    if (argv[1][0] == '\0' || !strcmp(argv[1], "-") || !strcmp(argv[1], "*"))
        node = NULL;
    else
        node = argv[1];
    serv = argv[2];

    memset(&hints, 0, sizeof hints);
    hints.ai_family = AF_UNSPEC; /* IPv4 or IPv6 */
    hints.ai_socktype = SOCK_STREAM; /* TCP */
    hints.ai_flags = AI_PASSIVE;
    hints.ai_protocol = 0;
    hints.ai_canonname = NULL;
    hints.ai_addr = NULL;
    hints.ai_next = NULL;
    list = NULL;
    err = getaddrinfo(node, serv, &hints, &list);
    if (err) 
        fprintf(stderr, "Invalid host and/or port: %s.\n", gai_strerror(err));
        return EXIT_FAILURE;
    

    service_fd = -1;
    err = 0;
    for (curr = list; curr != NULL; curr = curr->ai_next) 
        service_fd = socket(curr->ai_family, curr->ai_socktype, curr->ai_protocol);
        if (service_fd == -1)
            continue;

        errno = 0;
        if (bind(service_fd, curr->ai_addr, curr->ai_addrlen) == -1) 
            if (!err)
                if (errno == EADDRINUSE || errno == EADDRNOTAVAIL || errno == EACCES)
                    err = errno;
            close(service_fd);
            service_fd = -1;
            continue;
        

        if (listen(service_fd, 5) == -1) 
            if (!err)
                if (errno == EADDRINUSE)
                    err = errno;
            close(service_fd);
            service_fd = -1;
            continue;
        

        /* This socket works. */
        break;
    

    freeaddrinfo(list);
    list = curr = NULL;

    if (service_fd == -1) 
        if (err)
            fprintf(stderr, "Cannot listen for incoming connections on the specified host and port: %s.\n", strerror(err));
        else
            fprintf(stderr, "Cannot listen for incoming connections on the specified host and port.\n");
        return EXIT_FAILURE;
    

    /* Do not leak the listening socket to child processes. */
    fcntl(service_fd, F_SETFD, FD_CLOEXEC);

    /* We also want the listening socket to be nonblocking. */
    fcntl(service_fd, F_SETFL, O_NONBLOCK);

    fprintf(stderr, "Process %ld is waiting for incoming TCP connections.\n", (long)getpid());

    /* Incoming connection loop. */
    while (!done) 
        struct timeval t;
        char    client_host[64]; /* 64 for numeric, 1024 for non-numeric */
        char    client_port[32];
        pid_t   client_pid;
        fd_set  fds;

        t.tv_sec = 0;
        t.tv_usec = 100000; /* Max. 0.1s delay to react to done signal. */

        FD_ZERO(&fds);
        FD_SET(service_fd, &fds);

        if (select(service_fd + 1, &fds, NULL, NULL, &t) < 1)
            continue;

        client_addrlen = sizeof client_addr;
        client_fd = accept(service_fd, (struct sockaddr *)&client_addr, &client_addrlen);
        if (client_fd == -1) 
            if (errno == EINTR || errno == ECONNABORTED)
                continue;
            fprintf(stderr, "Error accepting an incoming connection: %s.\n", strerror(errno));
            continue;
        

        if (getnameinfo((const struct sockaddr *)&client_addr, client_addrlen,
                        client_host, sizeof client_host, client_port, sizeof client_port,
                        NI_NUMERICHOST | NI_NUMERICSERV) != 0) 
            fprintf(stderr, "Cannot resolve peer address for incoming connection, so dropping it.\n");
            close(client_fd);
            continue;
        

        printf("Incoming connection from %s:%s", client_host, client_port);
        fflush(stdout);

        if (peer_pids(client_fd, &client_pid, 1) != 1) 
            printf(", but cannot determine process ID. Dropped.\n");
            close(client_fd);
            continue;
        

        printf(" from local process %ld.\n", (long)client_pid);
        fflush(stdout);

        /*
         * Handle connection.
        */

        printf("Closing connection.\n");
        fflush(stdout);
        close(client_fd);
    

    /* Close service socket. */
    close(service_fd);

    switch (__atomic_load_n(&done, __ATOMIC_SEQ_CST)) 
    case SIGINT:
        fprintf(stderr, "Received INT signal.\n");
        break;
    case SIGHUP:
        fprintf(stderr, "Received HUP signal.\n");
        break;
    case SIGTERM:
        fprintf(stderr, "Received TERM signal.\n");
        break;
    

    return EXIT_SUCCESS;

peer_pids() 函数与辅助进程通信。它非常简单,尽管要小心不要返回不可靠的数据:它不会忽略错误或尝试从中恢复,而是报告失败。这允许主程序执行if (peer_pids(client_fd, &amp;pid, 1) != 1) /* Don't know! */ 并删除服务器不确定的任何连接——我认为这是一种合理的方法。

normalfds() 辅助函数经常被忽略。如果任何标准流关闭/关闭,它有助于避免问题。它只是将描述符集从三个标准流中移开,最多使用三个额外的描述符。

您可以在编译时定义USE_SUDO,使其在执行帮助程序时使用 sudo。分别将HELPER_PATHHELPER_NAME 定义为帮助程序的绝对路径及其文件名。 (现在,它们默认为./tcp-peer-pidtcp-peer-pid,以便于测试。)

服务器确实为 INT (Ctrl+C)、HUP(当用户关闭终端时发送)或 TERM 信号安装了信号处理程序,这些都会导致它停止接受新连接并以受控方式退出。 (因为信号处理程序是使用SA_RESTART标志安装的,它的传递不会中断慢速系统调用或导致errno == EINTR。这也意味着accept()不应该阻塞,否则信号传递将不会被注意到。所以,阻塞在select() 持续 0.1 秒,并检查其间是否传递了信号,这是一个很好的折衷方案,至少在示例服务器中是这样。)


在我的机器上,我在一个终端窗口中编译并测试了服务

gcc -Wall -O2 tcp-peer-pids.c -o tcp-peer-pids
gcc -Wall -O2 "-DHELPER_PATH=\"$PWD/tcp-peer-pids\"" server.c -o server
./server - 2400

这将报告Process # is waiting for incoming TCP connections。在另一个窗口中,使用 Bash 或 POSIX shell,我运行一个或多个测试 netcat 命令:

nc localhost 2400 & wait

在后台运行命令并立即等待它可能看起来很傻,但这样您就可以看到nc 进程的PID。

在我的系统上,所有环回 (127.x.y.z)、TCP/IPv4 和 TCP/IPv6(我的以太网和 WiFi 接口的地址)工作正常,并且可靠地报告了连接到示例服务器的进程的正确 PID .

在许多情况下,报告的 PID 数量可能会有所不同:例如,如果程序执行了一个子进程,但在子进程中连接的描述符也处于打开状态。 (这应该被认为是一个错误。)另一个典型的情况是程序在netstat 命令执行之前已经退出。

如果您发现任何错别字或错误或奇怪的行为,请在评论中告诉我,以便我进行验证和修复。我一口气写了两个程序,所以它们很可能包含错误。正如我所提到的,在有一位同事(或我自己几次,后来,以新的眼光)以批判/偏执的眼光审视它之前,我不会信任任何生产。


我个人只会将这种方法用于日志记录和统计,而不是访问控制本身。通过访问控制,我的意思是你应该配置一个 IP 过滤器(Linux 内核中内置的防火墙)来限制对受信任主机的访问;如果只代理本地应用程序,特别是不允许传入代理服务连接到代理服务,而不是依赖于检测所有远程连接。

对于特定于应用程序的日志记录/限制,请在 /proc/PID/exe 伪符号链接上使用 readlink()。这不能被伪造,但如果可执行文件不可访问,或者在目录树中太深,调用可能会失败。 (在这些情况下,我会完全拒绝代理连接。)

请注意,用户将可执行文件复制到他们拥有的任何目录并从那里执行它通常是微不足道的。这意味着要让应用程序特定的限制完全起作用,您应该默认对所有应用程序有严格的限制,放宽对特定可执行文件的限制。

【讨论】:

与其依赖netstat,不如使用特权“同伴”进程,该进程使用netlink 来获取与netstat 相同的信息,但只跟踪代理套接字。事实上,我相信这可能是比这里展示的更好的方法,因为开销更少;为了移植到其他系统,伴侣可以分别适应每个操作系统。

以上是关于如何在同一台机器上以编程方式获取通过 AF_INET 套接字连接到我的代理的进程的 PID?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Android 上以编程方式从 Mac 地址获取 IP 地址?

如何在 Android TV 上以编程方式更改电视频道

在运行 Windows IoT 的 RPi3 上以编程方式接受入站蓝牙配对请求

如何在 4.0.2 上以编程方式设置值

如何在 iOS 上以编程方式创建选项卡视图

如何在 Catalina (10.15) 上以编程方式启动键盘?