我有以下简单的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)
这是非常合乎逻辑的,因为我不是root
, SO_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一样工作)