为什么CAP_NET_RAW不适用于SO_BINDTODEVICE?

我有以下简单的testing程序来创build一个UDP套接字,并将其绑定到SO_BINDTODEVICE特定的接口,所以我可以然后bind() ,使得INADDR_ANY特别是在该接口上接收UDP广播。

 //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用户运行这个程序时,我得到以下输出:

 $ ./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) 

这是非常合乎逻辑的,因为我不是rootSO_BINDTODEVICE是一个特权操作。 但是它被包含在CAP_NET_RAW的能力中,正如我从Linux内核的这段代码中所理解的那样:

 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; 

那么当我这样做时,

 $ getcap bindtest $ sudo setcap cap_net_raw+ep bindtest $ getcap bindtest bindtest = cap_net_raw+ep 

我得到相同的错误输出:

 $ ./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 

那么他们为什么不按预期工作呢?

代码是正确的,使用getcap / setcap是正确的,所以别的东西必须阻止这个工作。

事实上,这是因为所有这些都是在/home/user ,在这个系统上安装了nosuid选项。

因此,简单地将二进制文件移到例如/usr/bin/或其他未安装nosuid将按照预期的方式工作。

(尽管你也需要CAP_NET_BIND_SERVICE来让bind()和端口333一样工作)