Как программно получить PID процесса, подключающегося к моему прокси через сокеты AF_INET, на той же машине? - PullRequest
0 голосов
/ 26 ноября 2018

Я пишу небольшой http прокси сервер (на C) на Linux-машине, а точнее Ubuntu 18.04.1, и я пытался найти способ получить pid процесса, который к нему подключается.

Можно упомянуть, что прокси-сервер предназначен для прокси-соединений только для процессов, запущенных на той же машине, поэтому я думаю, что это должно сделать этозадача возможна.

Сервер использует сокеты семейства AF_INET вместе с операциями чтения / записи для выполнения своей работы;Я упоминаю об этом, потому что после некоторого исследования я столкнулся с потоками о «вспомогательных данных», например: Есть ли способ получить uid другого конца соединения сокета unix

Вспомогательные данные содержат учетные данные соединительного сокета (например, PID), но работают только с AF_UNIX сокетами, используемыми для локального IPC, и требуют, чтобы мы явно отправляли / принимали их с обеих сторон (клиент / сервер).В моем случае, хотя, как я уже говорил, сервер будет использовать только прокси-трафик на той же машине, что и сервер, мне нужно использовать сокеты AF_INET , чтобы каждый (например, веб-браузер) мог подключиться к нему..

Производительность не так критична;поэтому любые предложения (включая обходные пути с использованием системных вызовов и т. д.) приветствуются.

1 Ответ

0 голосов
/ 26 ноября 2018

Мы можем использовать вывод netstat -nptW, чтобы увидеть TCP-соединения каких локальных процессов.Поскольку выходные данные могут быть чувствительны к безопасности, привилегии суперпользователя требуются для просмотра процессов, принадлежащих всем пользователям.

Поскольку нет никаких причин для запуска прокси-службы с повышенными привилегиями (возможно, ожидается CAP_NET_BIND_SERVICE), привилегированного помощниканужна программа.

Я немного подумал над подходящей моделью безопасности и пришел к выводу, что помощник, который проверяет подключенный сокет, данный ему (как, скажем, стандартный ввод), и выводит только одноранговый PID (s), было бы самым безопасным: было бы чрезвычайно трудно злоупотреблять им, и даже если это возможно, был обнаружен только идентификатор процесса равноправного участника.

Вот примерный помощник, 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 не может там спрашивать пароль.Вероятно, я бы просто установил помощника в качестве пользователя root с именем setuid на /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 flags).


Помощник предоставляет серверу идентификаторы PID в двоичном виде, потому что в противном случае код сервера был бы слишком длинным, чтобы поместиться в одном посте.

Вот пример службы TCP, server.c , который использует вышеупомянутый помощник для определения PID равноправного конца сокета на локальном компьютере. (Чтобы избежать атак типа "отказ в обслуживании", вы должны установить фильтр IP, которыйотклоняет ACпереходит к вашему сервисному порту прокси вне компьютера.)

#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_PATH и HELPER_NAME для абсолютного пути к помощнику и его имени файла соответственно.(Как и сейчас, по умолчанию они ./tcp-peer-pid и tcp-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, я запускаю одну или несколько тестовых команд netcat:

nc localhost 2400 & wait

Может показаться глупым запускать команду в фоновом режиме и сразу же ждать ее, но таким образом выможно увидеть PID процесса nc.

В моей системе все петлевые (127.x.y.z), TCP / IPv4 и TCP / IPv6 (адреса моих интерфейсов Ethernet и WiFi) работали нормально и надежно сообщали правильный PID процесса, подключающегося к серверу примера.

В ряде случаев количество сообщаемых PID может варьироваться: например, если программа выполнила дочерний процесс, но оставила подключенный дескриптор открытым и в дочернем.(Это следует считать ошибкой.) Другой типичный случай - программа, завершившаяся до выполнения команды netstat.

Если вы обнаружите какие-либо опечатки, ошибки или странное поведение, дайте мне знать в комментарии, чтобы я мог проверить и исправить.Я написал обе программы за один раз, так что они могут содержать ошибки.Как я уже упоминал, я бы не стал доверять ни производству, ни тому, чтобы коллега (или я сам несколько раз, а потом со свежими глазами) прошел через него с критическим / параноидальным взглядом.


Я быЛично использовать этот подход только для ведения журнала и статистики, а не контроль доступа как таковой.Под контролем доступа я имею в виду, что вы должны настроить IP-фильтр (встроенный в ядро ​​Linux брандмауэр), чтобы ограничить доступ только доверенным хостам;и, в частности, не разрешать входящие прокси-соединения со службой прокси, если только локальные приложения должны быть проксированы, вместо того, чтобы полагаться на это при обнаружении всех удаленных соединений.

Для регистрации / ограничения для конкретного приложения используйте readlink() на псевдосимклине /proc/PID/exe.Это не может быть подделано, но вызов может завершиться ошибкой, если исполняемый файл недоступен или находится слишком глубоко в дереве каталогов.(В этих случаях я бы вообще отказался от прокси-соединения.)

Обратите внимание, что обычно пользователю тривиально скопировать исполняемый файл в любой каталог, которым он владеет, и выполнить его оттуда.Это означает, что для того, чтобы ограничение для конкретного приложения работало вообще, вы должны иметь жесткие ограничения для всех приложений по умолчанию и ослаблять ограничения для конкретных исполняемых файлов.

...