我需要在linux上重新创建一个服务,该服务曾经在运行LwIP堆栈(轻量级IP)的嵌入式系统上运行。
该服务正在使用UDP广播到INADDR_BROADCAST
(255.255.255.255)来查找和配置同一物理子网上的设备。
它发送“扫描”,运行此服务的所有设备都会回复其完整的网络设置(所有NIC,所有的MAC和IP)。
然后,用户获得这些设备的列表,并可以更改IP设置(使用现有协议)
[是的,我知道人们会使用DHCP,但我们在这里讨论的是工业部门,协议/服务已经存在,所以我别无选择,只能实现兼容的东西]
由于设备有多个网卡,我需要能够接收此广播,知道哪个网卡收到了它,并通过该网卡发送回复。 此外,该服务是可配置的,因此它不会在特定NIC上打开套接字。
LwIP堆栈没有Linux堆栈那么复杂,因此绑定到IP的套接字仍会接收到INADDR_BROADCAST
的所有数据包。因此,实现这一点非常简单。
在Linux上,我认为我有几个选项可以做到这一点:
SO_BROADCAST
和SO_BINDTODEVICE
打开每个网卡的各个套接字,这样我就可以bind()
将它们转移到INADDR_ANY
并接收广播。当我通过该套接字发送回复时,Linux路由被忽略,并通过所需的NIC发送。root
运行... INADDR_ANY
绑定套接字(可能带有IP_PKTINFO
以便轻松知道数据包到达的网卡),每个网卡有一个套接字,绑定到有效地址,SO_BROADCAST
并通过那些发送回复。如果我这样走,我想确保发送套接字永远不会收到任何东西(因为我从不在它们上面调用recv()。资源饥饿?)。SO_RCVBUFSIZE = 0
就够了?实现这个的正确方法是什么?
答案 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。然而,我也更喜欢我的服务以有限的权限运行。
我会选择两种基本方法:
要求守护程序由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信号没有完全重启,因为它具有枚举接口和读取自己的配置文件以打开新接口套接字的必要权限。
使用特权“加载器”,打开所有必需的套接字,删除权限并执行实际的守护程序。
守护进程应该将套接字和接口详细信息作为命令行参数,或者通过标准输入获取。该守护进程完全没有特权。
不幸的是,如果打开了新接口,或者配置发生了变化,守护进程除退出外无法做很多事情。 (它甚至无法执行特权加载器,因为特权已被删除。)
第一种方法更常见,更容易在实践中实施;特别是如果守护进程只应由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
)。
请注意,上面的代码质量仅为初始测试成绩。由于我自己并不需要这些任何东西,只想验证我不是在讨论我的屁股,所以我没有花费任何精力使代码清洁或可靠。
有问题吗?评论