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

问题描述

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

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

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

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

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

标签: clinuxsocketspid

解决方案


我们可以使用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,请确保使用 rule proxyuser ALL = NOPASSWD: /path/to/helper,因为sudo那里无法询问密码。我可能只是将帮助程序安装为 setuid root at /usr/lib/yourproxy/tcp-peer-pid,所有者 root ,将您的代理服务组分组,并且不能访问其他用户(root:proxygroup -r-sr-x---)。

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

address:port与 netstat 输出中的“本地地址”和“外国地址”匹配的比较字符串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, &pid, 1) != 1) /* Don't know! */和删除服务器不确定的任何连接——我认为这是一种理智的方法。

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

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

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


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

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 内核中内置的防火墙)来限制对受信任主机的访问;如果只代理本地应用程序,特别是不允许传入代理连接到代理服务,而不是依赖于检测所有远程连接。

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

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


推荐阅读