gpt4 book ai didi

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

转载 作者:IT王子 更新时间:2023-10-29 00:37:42 24 4
gpt4 key购买 nike

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

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

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

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

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

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

实现它的正确方法是什么?

最佳答案

您可以使用CAP_NET_RAW(和CAP_NET_BIND_SERVICE,如果使用的端口≤ 1024)安装二进制文件; setcap 'cap_net_raw=ep' yourdaemon 为 root。对于 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 发出信号而无需完全重新启动,因为它具有枚举接口(interface)和读取自己的配置文件以打开新接口(interface)套接字的必要权限。

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

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

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

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


这是一些示例代码。

privileges.h: #ifndef 特权_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,指定要运行的非特权用户名、要绑定(bind)的接口(interface)以及 UDP 端口号作为参数:

sudo ./example yourdaemonuser eth0 4000

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

请注意,以上代码质量仅为初始测试等级。因为我自己不需要这个,只是想验证我不是在胡说八道,所以我没有花任何精力使代码干净或可靠。

有问题吗?评论?

关于c - 如何创建在多个接口(interface)上发送/接收 UDP 广播的服务,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/35130743/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com