您可以使用
CAP_NET_RAW
(如果使用端口≤1024,则还需要
CAP_NET_BIND_SERVICE
)来安装二进制文件;以root身份运行
setcap 'cap_net_raw=ep' yourdaemon
。对于IP,
SO_BROADCAST
不需要任何特殊权限(特别是不需要使用
CAP_NET_BROADCAST
)。
(有关所需确切权限,请参见Linux内核源代码中的
net/core/sock.c:sock_setbindtodevice(),
net/core/sock.c:sock_setsockopt(), 和
include/net/sock.h:sock_set_flag()进行验证。)
然而,守护进程通常以root权限启动。在这种情况下,仅以上述方法是不够的,因为更改进程的用户ID(以降低特权级别)也会
清除有效能力。但是,我也希望我的服务以有限的特权运行。
我会选择两种基本方法之一:
要求守护程序由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.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"
#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;
if (!user || !user[0])
return errno = EINVAL;
{
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();
}
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);
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;
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);
prctl(PR_SET_KEEPCAPS, 0UL, 0UL,0UL,0UL);
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;
unsigned int if_index;
int 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.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;
}
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;
if (n < (ssize_t)1) {
errno = EIO;
return (size_t)0;
}
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;
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;
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;
sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (sockfd == -1)
return errno;
setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &set_flag, sizeof set_flag);
setsockopt(sockfd, IPPROTO_IP, IP_FREEBIND, &set_flag, sizeof set_flag);
if (setsockopt(sockfd, SOL_SOCKET, SO_BROADCAST, &set_flag, sizeof set_flag) == -1) {
const int real_errno = errno;
close(sockfd);
return errno = real_errno;
}
if (setsockopt(sockfd, IPPROTO_IP, IP_PKTINFO, &set_flag, sizeof set_flag) == -1) {
const int real_errno = errno;
close(sockfd);
return errno = real_errno;
}
if (bind(sockfd, (const struct sockaddr *)&(udpsocket->broadcast), sizeof udpsocket->broadcast) == -1) {
const int real_errno = errno;
close(sockfd);
return errno = real_errno;
}
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;
}
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;
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;
}
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;
}
}
if (drop_privileges(argv[1], NEED_CAP_NET_RAW)) {
fprintf(stderr, "%s.\n", strerror(errno));
return EXIT_FAILURE;
}
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
在这里是足够的(Linux内核4.2.0-27在x86-64上),并且UDP广播发送将显示为从以太网接口地址到255.255.255.255:port
的出站流量,但我没有另一台机器向守护程序发送示例响应(可以使用例如NetCat轻松完成:printf 'Response!' | nc -u4 -q2y interface-address port
)。
请注意,上面的代码质量仅为初始测试等级。由于我自己不需要这个东西,只想验证我没有胡说八道,所以我没有花费任何精力使代码更加清洁或可靠。
有问题?评论?
SO_RCVBUFSIZE = 0
变得没用。你可以使用shutdown(sockfd, SHUT_RD)
来禁止进一步读取,但我倾向于正确编写程序 :) - John Hascall