2016-02-05 1 views
0

У меня есть следующая простая тестовая программа для создания UDP-сокета и привязки его к определенному интерфейсу с SO_BINDTODEVICE, поэтому я могу тогда bind() это так INADDR_ANY, чтобы получать широковещательные передачи UDP именно на этом интерфейсе.Почему CAP_NET_RAW не работает с SO_BINDTODEVICE?

//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); 
} 

когда я запускаю эту программу как пользователь внекорневого я получаю следующий вывод:

$ ./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 как я понимаю из 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; 

Ну еще, когда я делаю:

$ 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 

так почему же они не работают должным образом?

ответ

1

Правильно указан код getcap/setcap, поэтому что-то еще должно блокировать это от работы.

И это действительно потому, что все это было сделано в /home/user, которое на этой системе смонтировано с опцией nosuid.

Таким образом, просто перемещение двоичного файла, например. /usr/bin/ или любая другая деталь, которая не смонтирована nosuid будет работать, как ожидалось, в первую очередь.

(Хотя вы также должны CAP_NET_BIND_SERVICE для bind() работать с портом 333, как в приведенном выше примере)

Смежные вопросы