问题描述
我有以下简单的测试程序来创建UDP套接字,并使用 SO_BINDTODEVICE
将其绑定到特定的接口,这样我就可以 bind()
了,因此INADDR_ANY
专门在该接口上接收UDP广播.
I have the following simple test program to create a UDP socket and bind it to a specific interface with SO_BINDTODEVICE
so I can then bind()
it so INADDR_ANY
to recieve UDP broadcasts specifically on that interface.
//filename: bindtest.c
#include <sys/socket.h>
#include <netinet/in.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#define MY_PORT (333)
#define MY_DEVICE "enp0s3"
#define BUFFERSIZE (1000)
/* global variables */
int sock;
struct sockaddr_in sa;
struct sockaddr_in my_addr;
char buffer[BUFFERSIZE];
int main(int argc, char *argv[])
{
unsigned int echolen, clientlen;
int rc, n;
char opt_buffer[1000];
struct protoent *udp_protoent;
struct timeval receive_timeout;
int optval;
socklen_t opt_length;
sleep(1);
/* Create the UDP socket */
if ((sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0)
{
printf ("%s: failed to create UDP socket (%s) \n",
argv[0], strerror(errno));
exit (EXIT_FAILURE);
}
printf ("UDP socket created\n");
/* set the recvfrom timeout value */
receive_timeout.tv_sec = 5;
receive_timeout.tv_usec = 0;
rc=setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, &receive_timeout, sizeof(receive_timeout));
if (rc != 0)
{
printf ("%s: could not set SO_RCVTIMEO (%s)\n",
argv[0], strerror(errno));
exit (EXIT_FAILURE);
}
printf ("set timeout to time [s]: %d time [ms]: %d\n", receive_timeout.tv_sec, receive_timeout.tv_usec);
/* allow broadcast messages for the socket */
int true = 1;
rc=setsockopt(sock, SOL_SOCKET, SO_BROADCAST, &true, sizeof(true));
if (rc != 0)
{
printf ("%s: could not set SO_BROADCAST (%s)\n",
argv[0], strerror(errno));
exit (EXIT_FAILURE);
}
printf ("set SO_BROADCAST worked\n");
/* bind to a specific interface */
char device[] = MY_DEVICE;
rc=setsockopt(sock, SOL_SOCKET, SO_BINDTODEVICE, device, sizeof(device));
if (rc != 0)
{
printf ("%s: could not set SO_BINDTODEVICE (%s)\n",
argv[0], strerror(errno));
exit (EXIT_FAILURE);
}
printf ("SO_BINDTODEVICE worked\n");
/* bind my own Port */
my_addr.sin_family = AF_INET;
my_addr.sin_addr.s_addr = INADDR_ANY;
my_addr.sin_port = htons(MY_PORT);
rc = bind (sock, (struct sockaddr *) &my_addr, sizeof(my_addr));
if (rc < 0)
{
printf ("%s: could not bind port (%s)\n",
argv[0], strerror(errno));
exit (EXIT_FAILURE);
}
printf ("bind() worked\n");
sa.sin_family = AF_INET;
sa.sin_addr.s_addr = INADDR_BROADCAST;
sa.sin_port = htons(MY_PORT);
char data[20];
sprintf(data,"FOOBAR");
int res = sendto(sock, &data, strlen(data), 0, (struct sockaddr*)&sa, sizeof(sa));
if(res < 0){
printf("could not send\n");
} else {
printf("data sent\n");
}
close(sock);
printf ("socket closed\n");
exit(0);
}
当我以非root用户身份运行该程序时,将得到以下输出:
when I run this program as a non-root user I get the following output:
$ ./bindtest
UDP socket created
set timeout to time [s]: 5 time [ms]: 0
set SO_BROADCAST worked
./bindtest: could not set SO_BINDTODEVICE (Operation not permitted)
这很合乎逻辑,因为我不是 root
,并且 SO_BINDTODEVICE
是特权操作.但据我从 CAP_NET_RAW 中"nofollow">来自Linux内核的这段代码:
which is pretty logical because I'm not root
and SO_BINDTODEVICE
is a priviledged operation. but it is included in the capability CAP_NET_RAW
as I understand from this snippet of code from the Linux kernel:
static int sock_setbindtodevice(struct sock *sk, char __user *optval,
int optlen)
{
int ret = -ENOPROTOOPT;
#ifdef CONFIG_NETDEVICES
struct net *net = sock_net(sk);
char devname[IFNAMSIZ];
int index;
/* Sorry... */
ret = -EPERM;
if (!ns_capable(net->user_ns, CAP_NET_RAW))
goto out;
当我这样做时,还是会:
Well still when I do:
$ getcap bindtest
$ sudo setcap cap_net_raw+ep bindtest
$ getcap bindtest
bindtest = cap_net_raw+ep
我得到相同的错误输出:
I get the same error output:
$ ./bindtest
UDP socket created
set timeout to time [s]: 5 time [ms]: 0
set SO_BROADCAST worked
./bindtest: could not set SO_BINDTODEVICE (Operation not permitted)
当然,它也可以作为 root
:
$ sudo ./bindtest
UDP socket created
set timeout to time [s]: 5 time [ms]: 0
set SO_BROADCAST worked
SO_BINDTODEVICE worked
bind() worked
data sent
socket closed
那他们为什么不按预期工作呢?
so why don't they work as expected?
推荐答案
代码正确无误, getcap
/ setcap
的用法正确,因此必须正确阻止它工作.
The code is correct, the usage of getcap
/setcap
is correct, so something else must be blocking this from working.
确实是因为所有这些都是在/home/user
中完成的,在此系统上,该系统是通过 nosuid
选项安装的.
And indeed it is because all of this was done in /home/user
which on this system is mounted with the nosuid
option among others.
因此只需将二进制文件移至/usr/bin/
或未安装的任何其他部分 nosuid
首先将按预期工作.
So simply moving the binary to e.g. /usr/bin/
or any other part that is not mounted nosuid
will work as expected in the first place.
(尽管您还需要 bind()
的 CAP_NET_BIND_SERVICE
才能与示例中的端口333一起使用)
(Although you also need CAP_NET_BIND_SERVICE
for the bind()
to work with port 333 as in the example)
这篇关于CAP_NET_RAW为什么不能与SO_BINDTODEVICE一起使用?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!