如何创建在多个接口上发送/接收UDP广播的服务

时间:2016-02-01 12:38:26

标签: c linux sockets networking udp

我需要在linux上重新创建一个服务,该服务曾经在运行LwIP堆栈(轻量级IP)的嵌入式系统上运行。

该服务正在使用UDP广播到INADDR_BROADCAST(255.255.255.255)来查找和配置同一物理子网上的设备。 它发送“扫描”,运行此服务的所有设备都会回复其完整的网络设置(所有NIC,所有的MAC和IP)。 然后,用户获得这些设备的列表,并可以更改IP设置(使用现有协议) [是的,我知道人们会使用DHCP,但我们在这里讨论的是工业部门,协议/服务已经存在,所以我别无选择,只能实现兼容的东西]

由于设备有多个网卡,我需要能够接收此广播,知道哪个网卡收到了它,并通过该网卡发送回复。 此外,该服务是可配置的,因此它不会在特定NIC上打开套接字。

LwIP堆栈没有Linux堆栈那么复杂,因此绑定到IP的套接字仍会接收到INADDR_BROADCAST的所有数据包。因此,实现这一点非常简单。

在Linux上,我认为我有几个选项可以做到这一点:

  • 使用SO_BROADCASTSO_BINDTODEVICE打开每个网卡的各个套接字,这样我就可以bind()将它们转移到INADDR_ANY并接收广播。当我通过该套接字发送回复时,Linux路由被忽略,并通过所需的NIC发送。
    但是:我希望该服务不能以root运行...
  • 有一个INADDR_ANY绑定套接字(可能带有IP_PKTINFO以便轻松知道数据包到达的网卡),每个网卡有一个套接字,绑定到有效地址,SO_BROADCAST并通过那些发送回复。如果我这样走,我想确保发送套接字永远不会收到任何东西(因为我从不在它们上面调用recv()。资源饥饿?)。
    也许SO_RCVBUFSIZE = 0就够了?

实现这个的正确方法是什么?

1 个答案:

答案 0 :(得分:2)

您可以使用CAP_NET_RAW安装二进制文件(如果使用端口≤1024,则安装CAP_NET_BIND_SERVICE);以setcap 'cap_net_raw=ep' yourdaemon为根。对于IP,SO_BROADCAST不需要任何功能(特别是,CAP_NET_BROADCAST不用于IP)。

(有关所需的确切功能,请参阅Linux内核源代码中的net/core/sock.c:sock_setbindtodevice()net/core/sock.c:sock_setsockopt()include/net/sock.h:sock_set_flag()以进行验证。)

但是,守护进程通常以root身份启动。在这里,上述内容是不够的,因为更改进程的用户ID(删除权限)也是clears the effective capabilities。然而,我也更喜欢我的服务以有限的权限运行。

我会选择两种基本方法:

  1. 要求守护程序由root或CAP_NET_RAW(以及可选的CAP_NET_BIND_SERVICE)功能执行。

    使用prctl()setgroups()initgroups()setresuid()setresgid()以及来自libcap,cap_init()cap_set_flag(),和cap_set_proc()通过切换到专用用户和组来删除权限,但仅保留CAP_NET_RAW(以及可选的CAP_NET_BIND_SERVICE)功能。

    这允许守护进程响应例如HUP信号没有完全重启,因为它具有枚举接口和读取自己的配置文件以打开新接口套接字的必要权限。

  2. 使用特权“加载器”,打开所有必需的套接字,删除权限并执行实际的守护程序。

    守护进程应该将套接字和接口详细信息作为命令行参数,或者通过标准输入获取。该守护进程完全没有特权。

    不幸的是,如果打开了新接口,或者配置发生了变化,守护进程除退出外无法做很多事情。 (它甚至无法执行特权加载器,因为特权已被删除。)

  3. 第一种方法更常见,更容易在实践中实施;特别是如果守护进程只应由root执行。 (请记住,守护程序可以响应配置更改,因为它具有必要的功能,但通常不具有root权限。)我只使用了第二种方法来处理我不信任的“黑盒子”二进制文件。

    以下是一些示例代码。

    privileges.h:     #ifndef PRIVILEGES_H     #define PRIVILEGES_H

    #define   NEED_CAP_NET_ADMIN          (1U << 0)
    #define   NEED_CAP_NET_BIND_SERVICE   (1U << 1)
    #define   NEED_CAP_NET_RAW            (1U << 2)
    
    extern int drop_privileges(const char *const user, const unsigned int capabilities);
    
    #endif /* PRIVILEGES_H */
    

    privileges.c

    #define _GNU_SOURCE
    #define _BSD_SOURCE
    #include <stdlib.h>
    #include <unistd.h>
    #include <sys/types.h>
    #include <sys/capability.h>
    #include <sys/prctl.h>
    #include <errno.h>
    #include <pwd.h>
    #include <grp.h>
    #include "privileges.h"
    
    /* Only three NEED_CAP_ constants defined. */
    #define MAX_CAPABILITIES 3
    
    static int permit_effective(cap_t caps, const unsigned int capabilities)
    {
        cap_value_t  value[MAX_CAPABILITIES];
        int          values = 0;
    
        if (capabilities & NEED_CAP_NET_ADMIN)
            value[values++] = CAP_NET_ADMIN;
    
        if (capabilities & NEED_CAP_NET_BIND_SERVICE)
            value[values++] = CAP_NET_BIND_SERVICE;
    
        if (capabilities & NEED_CAP_NET_RAW)
            value[values++] = CAP_NET_RAW;
    
        if (values < 1)
            return 0;
    
        if (cap_set_flag(caps, CAP_PERMITTED, values, value, CAP_SET) == -1)
            return errno;
        if (cap_set_flag(caps, CAP_EFFECTIVE, values, value, CAP_SET) == -1)
            return errno;
    
        return 0;
    }
    
    static int add_privileges(cap_t caps)
    {
        cap_value_t  value[3] = { CAP_SETPCAP, CAP_SETUID, CAP_SETGID };
    
        if (cap_set_flag(caps, CAP_PERMITTED, sizeof value / sizeof value[0], value, CAP_SET) == -1)
            return errno;
    
        if (cap_set_flag(caps, CAP_EFFECTIVE, sizeof value / sizeof value[0], value, CAP_SET) == -1)
            return errno;
    
        return 0;
    }
    
    int drop_privileges(const char *const user, const unsigned int capabilities)
    {
        uid_t uid;
        gid_t gid;
        cap_t caps;
    
        /* Make sure user is neither NULL nor empty. */
        if (!user || !user[0])
            return errno = EINVAL;
    
        /* Find the user. */
        {
            struct passwd *pw;
    
            pw = getpwnam(user);
            if (!pw
    #ifdef UID_MIN
                || pw->pw_uid < (uid_t)UID_MIN
    #endif
    #ifdef UID_MAX
                || pw->pw_uid > (uid_t)UID_MAX
    #endif
    #ifdef GID_MIN
                || pw->pw_gid < (gid_t)GID_MIN
    #endif
    #ifdef GID_MAX
                || pw->pw_gid > (gid_t)GID_MAX
    #endif
                   )
                return errno = EINVAL;
    
            uid = pw->pw_uid;
            gid = pw->pw_gid;
    
            endpwent();
        }
    
        /* Install privileged capabilities. */
        caps = cap_init();
        if (!caps)
            return errno = ENOMEM;
        if (permit_effective(caps, capabilities)) {
            const int cause = errno;
            cap_free(caps);
            return errno = cause;
        }
        if (add_privileges(caps)) {
            const int cause = errno;
            cap_free(caps);
            return errno = cause;
        }
        if (cap_set_proc(caps) == -1) {
            const int cause = errno;
            cap_free(caps);
            return errno = cause;
        }
        cap_free(caps);
    
        /* Retain permitted capabilities over the identity change. */
        prctl(PR_SET_KEEPCAPS, 1UL, 0UL,0UL,0UL);
    
        if (setresgid(gid, gid, gid) == -1)
            return errno = EPERM;
    
        if (initgroups(user, gid) == -1)
            return errno = EPERM;
    
        if (setresuid(uid, uid, uid) == -1)
            return errno = EPERM;
    
        /* Install unprivileged capabilities. */
        caps = cap_init();
        if (!caps)
            return errno = ENOMEM;
        if (permit_effective(caps, capabilities)) {
            const int cause = errno;
            cap_free(caps);
            return errno = cause;
        }
        if (cap_set_proc(caps) == -1) {
            const int cause = errno;
            cap_free(caps);
            return errno = cause;
        }
        cap_free(caps);
    
        /* Reset standard KEEPCAPS behaviour. */
        prctl(PR_SET_KEEPCAPS, 0UL, 0UL,0UL,0UL);
    
        /* Done. */
        return 0;
    }
    

    udp-broadcast.h

    #ifndef   UDP_BROADCAST_H
    #define   UDP_BROADCAST_H
    #include <stdlib.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    
    struct udp_socket {
        struct sockaddr_in  broadcast;  /* Broadcast address */
        unsigned int        if_index;   /* Interface index */
        int                 descriptor; /* Socket descriptor */
    };
    
    extern int open_udp_broadcast(struct udp_socket *const udpsocket,
                                  const char        *const interface,
                                  int                const port);
    
    extern int udp_broadcast(const struct udp_socket *const udpsocket,
                             const void *const              data,
                             const size_t                   size,
                             const int                      flags);
    
    extern size_t udp_receive(const struct udp_socket *const udpsocket,
                              void *const                    data,
                              const size_t                   size_max,
                              const int                      flags,
                              struct sockaddr_in      *const from_addr,
                              struct sockaddr_in      *const to_addr,
                              struct sockaddr_in      *const hdr_addr,
                              unsigned int            *const if_index);
    
    #endif /* UDP_BROADCAST_H */
    

    udp-broadcast.c

    #include <unistd.h>
    #include <string.h>
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <net/if.h>
    #include <errno.h>
    #include "udp-broadcast.h"
    
    
    int udp_broadcast(const struct udp_socket *const udpsocket,
                      const void *const              data,
                      const size_t                   size,
                      const int                      flags)
    {
        ssize_t  n;
    
        if (!udpsocket || udpsocket->broadcast.sin_family != AF_INET)
            return errno = EINVAL;
    
        if (!data || size < 1)
            return 0;
    
        n = sendto(udpsocket->descriptor, data, size, flags,
                   (const struct sockaddr *)&(udpsocket->broadcast),
                   sizeof (struct sockaddr_in));
    
        if (n == (ssize_t)-1)
            return errno;
        if (n == (ssize_t)size)
            return 0;
        return errno = EIO;    
    }
    
    
    size_t udp_receive(const struct udp_socket *const udpsocket,
                       void *const                    data,
                       const size_t                   size_max,
                       const int                      flags,
                       struct sockaddr_in      *const from_addr,
                       struct sockaddr_in      *const to_addr,
                       struct sockaddr_in      *const hdr_addr,
                       unsigned int            *const if_index)
    {
        char            ancillary[512];
        struct msghdr   msg;
        struct iovec    iov[1];
        struct cmsghdr *cmsg;
        ssize_t         n;
    
        if (!data || size_max < 1 || !udpsocket) {
            errno = EINVAL;
            return (size_t)0;
        }
    
        /* Clear results, just in case. */
        if (from_addr) {
            memset(from_addr, 0, sizeof *from_addr);
            from_addr->sin_family = AF_UNSPEC;
        }
        if (to_addr) {
            memset(to_addr, 0, sizeof *to_addr);
            to_addr->sin_family = AF_UNSPEC;
        }
        if (hdr_addr) {
            memset(hdr_addr, 0, sizeof *hdr_addr);
            hdr_addr->sin_family = AF_UNSPEC;
        }
        if (if_index)
            *if_index = 0U;
    
        iov[0].iov_base = data;
        iov[0].iov_len  = size_max;
    
        if (from_addr) {
            msg.msg_name = from_addr;
            msg.msg_namelen = sizeof (struct sockaddr_in);
        } else {
            msg.msg_name = NULL;
            msg.msg_namelen = 0;
        }
    
        msg.msg_iov = iov;
        msg.msg_iovlen = 1;
    
        msg.msg_control = ancillary;
        msg.msg_controllen = sizeof ancillary;
    
        msg.msg_flags = 0;
    
        n = recvmsg(udpsocket->descriptor, &msg, flags);
        if (n == (ssize_t)-1)
            return (size_t)0; /* errno set by recvmsg(). */
        if (n < (ssize_t)1) {
            errno = EIO;
            return (size_t)0;
        }
    
        /* Populate data from ancillary message, if requested. */
        if (to_addr || hdr_addr || if_index)
            for (cmsg = CMSG_FIRSTHDR(&msg); cmsg != NULL; cmsg = CMSG_NXTHDR(&msg, cmsg))
                if (cmsg->cmsg_level == IPPROTO_IP && cmsg->cmsg_type == IP_PKTINFO) {
                    const struct in_pktinfo *const info = CMSG_DATA(cmsg);
                    if (!info)
                        continue;
                    if (if_index)
                        *if_index = info->ipi_ifindex;
                    if (to_addr) {
                        to_addr->sin_family = AF_INET;
                        to_addr->sin_port = udpsocket->broadcast.sin_port; /* This is a guess. */
                        to_addr->sin_addr = info->ipi_spec_dst;
                    }
                    if (hdr_addr) {
                        hdr_addr->sin_family = AF_INET;
                        hdr_addr->sin_port = udpsocket->broadcast.sin_port; /* A guess, again. */
                        hdr_addr->sin_addr = info->ipi_addr;
                    }
                }
    
        errno = 0;
        return (size_t)n;
    }
    
    int open_udp_broadcast(struct udp_socket *const udpsocket,
                           const char        *const interface,
                           int                const port)
    {
        const size_t interface_len = (interface) ? strlen(interface) : 0;
        const int    set_flag = 1;
        int          sockfd;
    
        if (udpsocket) {
            memset(udpsocket, 0, sizeof *udpsocket);
            udpsocket->broadcast.sin_family = AF_INET;
            udpsocket->broadcast.sin_addr.s_addr = INADDR_BROADCAST;
            if (port >= 1 && port <= 65535)
                udpsocket->broadcast.sin_port = htons(port);
            udpsocket->descriptor = -1;
        }
    
        if (!udpsocket || interface_len < 1 || port < 1 || port > 65535)
            return errno = EINVAL;
    
        /* Generic UDP socket. */
        sockfd = socket(AF_INET, SOCK_DGRAM, 0);
        if (sockfd == -1)
            return errno;
    
        /* Set SO_REUSEADDR if possible. */
        setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &set_flag, sizeof set_flag);
    
        /* Set IP_FREEBIND if possible. */
        setsockopt(sockfd, IPPROTO_IP, IP_FREEBIND, &set_flag, sizeof set_flag);
    
        /* We need broadcast capability. */
        if (setsockopt(sockfd, SOL_SOCKET, SO_BROADCAST, &set_flag, sizeof set_flag) == -1) {
            const int real_errno = errno;
            close(sockfd);
            return errno = real_errno;
        }
    
        /* We want the IP_PKTINFO ancillary messages, to determine target address
         * and interface index. */ 
        if (setsockopt(sockfd, IPPROTO_IP, IP_PKTINFO, &set_flag, sizeof set_flag) == -1) {
            const int real_errno = errno;
            close(sockfd);
            return errno = real_errno;
        }
    
        /* We bind to the broadcast address. */
        if (bind(sockfd, (const struct sockaddr *)&(udpsocket->broadcast), sizeof udpsocket->broadcast) == -1) {
            const int real_errno = errno;
            close(sockfd);
            return errno = real_errno;
        }
    
        /* Finally, we bind to the specified interface. */
        if (setsockopt(sockfd, SOL_SOCKET, SO_BINDTODEVICE, interface, interface_len) == -1) {
            const int real_errno = errno;
            close(sockfd);
            return errno = real_errno;
        }
    
        udpsocket->descriptor = sockfd;
    
        udpsocket->if_index = if_nametoindex(interface);
    
        errno = 0;
        return 0;
    }
    

    main.c

    #include <stdlib.h>
    #include <unistd.h>
    #include <string.h>
    #include <signal.h>
    #include <stdio.h>
    #include <netdb.h>
    #include <errno.h>
    #include "privileges.h"
    #include "udp-broadcast.h"
    
    static volatile sig_atomic_t    done_triggered = 0;
    static volatile sig_atomic_t    reload_triggered = 0;
    
    static void done_handler(int signum)
    {
        __sync_bool_compare_and_swap(&done_triggered, (sig_atomic_t)0, (sig_atomic_t)signum);
    }
    
    static void reload_handler(int signum)
    {
        __sync_bool_compare_and_swap(&reload_triggered, (sig_atomic_t)0, (sig_atomic_t)signum);
    }
    
    static int install_handler(const int signum, void (*handler)(int))
    {
        struct sigaction act;
        memset(&act, 0, sizeof act);
        sigemptyset(&act.sa_mask);
        act.sa_handler = handler;
        act.sa_flags = 0;
        if (sigaction(signum, &act, NULL) == -1)
            return errno;
        return 0;
    }
    
    /* Return 0 if done_triggered or reload_triggered, nonzero otherwise.
     * Always clears reload_triggered.
    */
    static inline int keep_running(void)
    {
        if (done_triggered)
            return 0;
        return !__sync_fetch_and_and(&reload_triggered, (sig_atomic_t)0);
    }
    
    static const char *ipv4_address(const void *const addr)
    {
        static char    buffer[16];
        char          *end = buffer + sizeof buffer;
        unsigned char  byte[4];
    
        if (!addr)
            return "(none)";
    
        memcpy(byte, addr, 4);
    
        *(--end) = '\0';
        do {
            *(--end) = '0' + (byte[3] % 10);
            byte[3] /= 10U;
        } while (byte[3]);
        *(--end) = '.';
        do {
            *(--end) = '0' + (byte[2] % 10);
            byte[2] /= 10U;
        } while (byte[2]);
        *(--end) = '.';
        do {
            *(--end) = '0' + (byte[1] % 10);
            byte[1] /= 10U;
        } while (byte[1]);
        *(--end) = '.';
        do {
            *(--end) = '0' + (byte[0] % 10);
            byte[0] /= 10U;
        } while (byte[0]);
    
        return (const char *)end;
    }
    
    int main(int argc, char *argv[])
    {
        int  port;
        char dummy;
    
        /* Check usage. */
        if (argc != 4 || !strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")) {
            fprintf(stderr, "\n");
            fprintf(stderr, "Usage: %s [ -h | --help ]\n", argv[0]);
            fprintf(stderr, "       %s USERNAME INTERFACE PORT\n", argv[0]);
            fprintf(stderr, "Where:\n");
            fprintf(stderr, "       USERNAME    is the unprivileged user to run as,\n");
            fprintf(stderr, "       INTERFACE   is the interface to bind to, and\n");
            fprintf(stderr, "       PORT        is the UDP/IPv4 port number to use.\n");
            fprintf(stderr, "\n");
            return EXIT_FAILURE;
        }
    
        /* Parse the port into a number. */
        if (sscanf(argv[3], "%d %c", &port, &dummy) != 1 || port < 1 || port > 65535) {
            struct servent *serv = getservbyname(argv[3], "udp");
            if (serv && serv->s_port > 1 && serv->s_port < 65536) {
                port = serv->s_port;
                endservent();
            } else {
                endservent();
                fprintf(stderr, "%s: Invalid port.\n", argv[3]);
                return EXIT_FAILURE;
            }
        }
    
        /* Drop privileges. */
        if (drop_privileges(argv[1], NEED_CAP_NET_RAW)) {
            fprintf(stderr, "%s.\n", strerror(errno));
            return EXIT_FAILURE;
        }
    
        /* Install signal handlers. */
        if (install_handler(SIGINT, done_handler) ||
            install_handler(SIGTERM, done_handler) ||
            install_handler(SIGHUP, reload_handler) ||
            install_handler(SIGUSR1, reload_handler)) {
            fprintf(stderr, "Cannot install signal handlers: %s.\n", strerror(errno));
            return EXIT_FAILURE;
        }
    
        fprintf(stderr, "Send a SIGINT (Ctrl+C) or SIGTERM to stop the service:\n");
        fprintf(stderr, "\tkill -SIGTERM %ld\n", (long)getpid());
        fprintf(stderr, "Send a SIGHUP or SIGUSR1 to have the service reload and rebroadcast:\n");
        fprintf(stderr, "\tkill -SIGHUP %ld\n", (long)getpid());
        fprintf(stderr, "Privileges dropped successfully.\n\n");
        fflush(stderr);
    
        while (!done_triggered) {
            struct udp_socket  s;
    
            if (open_udp_broadcast(&s, argv[2], port)) {
                fprintf(stderr, "%s port %s: %s.\n", argv[2], argv[3], strerror(errno));
                return EXIT_FAILURE;
            }
    
            if (udp_broadcast(&s, "Hello?", 6, MSG_NOSIGNAL)) {
                fprintf(stderr, "%s port %s: Broadcast failed: %s.\n", argv[2], argv[3], strerror(errno));
                close(s.descriptor);
                return EXIT_FAILURE;
            }
    
            if (s.if_index)
                fprintf(stderr, "Broadcast sent using interface %s (index %u); waiting for responses.\n", argv[2], s.if_index);
            else
                fprintf(stderr, "Broadcast sent using interface %s; waiting for responses.\n", argv[2]);
            fflush(stderr);
    
            while (keep_running()) {
                struct sockaddr_in  from_addr, to_addr, hdr_addr;
                unsigned char       data[512];
                unsigned int        if_index;
                size_t              size, i;
    
                size = udp_receive(&s, data, sizeof data, 0, &from_addr, &to_addr, &hdr_addr, &if_index);
                if (size > 0) {
                    printf("Received %zu bytes:", size);
                    for (i = 0; i < size; i++)
                        if (i & 15)
                            printf(" %02x", data[i]);
                        else
                            printf("\n\t%02x", data[i]);
                    if (if_index)
                        printf("\n\t Index: %u", if_index);
                    printf("\n\t  From: %s", ipv4_address(&from_addr.sin_addr));
                    printf("\n\t    To: %s", ipv4_address(&to_addr.sin_addr));
                    printf("\n\tHeader: %s", ipv4_address(&hdr_addr.sin_addr));
                    printf("\n");
                    fflush(stdout);
                } else
                if (errno != EINTR) {
                    fprintf(stderr, "%s\n", strerror(errno));
                    break;
                }
            }
    
            close(s.descriptor);
        }
    
        fprintf(stderr, "Exiting.\n");
        return EXIT_SUCCESS;
    }
    

    使用

    进行编译
    gcc -Wall -Wextra -O2 -c privileges.c
    gcc -Wall -Wextra -O2 -c udp-broadcast.c
    gcc -Wall -Wextra -O2 -c main.c
    gcc -Wall -Wextra main.o udp-broadcast.o privileges.o -lcap -o example
    

    以root身份运行example,指定要运行的非特权用户名,要绑定的接口以及作为参数的UDP端口号:

    sudo ./example yourdaemonuser eth0 4000
    

    现在我只有一台笔记本电脑在使用,所以接收方基本上没有经过测试。我知道CAP_NET_RAW就足够了(x86-64上的Linux内核4.2.0-27),并且UDP广播发送显示为从以太网接口地址传递到255.255.255.255:port,但我没有其他机器向守护进程发送示例响应(这很容易使用,例如NetCat:printf 'Response!' | nc -u4 -q2y interface-address port)。

    请注意,上面的代码质量仅为初始测试成绩。由于我自己并不需要这些任何东西,只想验证我不是在讨论我的屁股,所以我没有花费任何精力使代码清洁或可靠。

    有问题吗?评论