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

Tab*_*Eye 5 c sockets linux networking udp

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

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

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

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

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

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

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

Nom*_*mal 3

您可以使用以下命令安装二进制文件CAP_NET_RAWCAP_NET_BIND_SERVICE如果使用端口 \xe2\x89\xa4 1024);setcap \'cap_net_raw=ep\' yourdaemon作为根。对于IP,SO_BROADCAST不需要任何能力(特别CAP_NET_BROADCAST是不用于IP)。

\n\n

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

\n\n

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

\n\n

我会在两种基本方法之间进行选择:

\n\n
    \n
  1. 要求守护进程由 root 执行,或者具有CAP_NET_RAW(可选CAP_NET_BIND_SERVICE)功能。

    \n\n

    使用libcap 中的prctl()setgroups()initgroups()setresuid()、和、 、 、和通过切换到专用用户和组来放弃权限,但仅保留(和可选)功能和它们。setresgid()cap_init()cap_set_flag()cap_set_proc()CAP_NET_RAWCAP_NET_BIND_SERVICE

    \n\n

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

  2. \n
  3. 使用特权“加载程序”,打开所有必要的套接字,放弃特权,并执行实际的守护进程。

    \n\n

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

    \n\n

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

  4. \n
\n\n

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

\n\n
\n\n

这是一些示例代码。

\n\n

privileges.h:\n #ifndef PRIVILEGES_H\n #define PRIVILEGES_H

\n\n
#define   NEED_CAP_NET_ADMIN          (1U << 0)\n#define   NEED_CAP_NET_BIND_SERVICE   (1U << 1)\n#define   NEED_CAP_NET_RAW            (1U << 2)\n\nextern int drop_privileges(const char *const user, const unsigned int capabilities);\n\n#endif /* PRIVILEGES_H */\n
Run Code Online (Sandbox Code Playgroud)\n\n

privileges.c

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

udp-broadcast.h

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

udp-broadcast.c

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

main.c

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