SO_BINDTODEVICE Linux套接字选项的问题

时间:2009-07-30 16:29:51

标签: c linux sockets udp broadcast

我有一台带有两张网卡的电脑。一个(eth0)用于LAN /互联网,另一个用于与一个微控制器设备进行UDP通信。微控制器具有IP(192.168.7.2)和MAC地址。第二个pc网络适配器(eth1)有192.168.7.1。

微控制器有一个非常简单的IP堆栈,因此mc发送UDP数据包的最简单方法是广播它们。

在PC方面,我希望收到广播 - 但仅限于eth1。所以我尝试将UDP套接字绑定到eth1设备。

问题(源代码如下):

  1. setsockopt(sock, SOL_SOCKET, SO_BINDTODEVICE, device, sizeof(device))需要root权限,为什么? (设置其他选项作为用户)

  2. getsockopt(sock, SOL_SOCKET, SO_BINDTODEVICE, (void *)buffer, &opt_length)提供“协议不可用”。我想通过setsockopt命令回读我设置的设备。

  3. 我在哪里可以找到好消息?我检查了一些Linux编程,网络书籍,但是例如我只在互联网上找到的SO_BINDTODEVICE选项。

  4. 我的冗长(脏)测试程序显示了问题。设置和取回SO_RCVTIMEOSO_BROADCAST选项可以按预期工作。

    以用户退出时运行代码:

    could not set SO_BINDTODEVICE (Operation not permitted)"
    

    使用sudo运行:

    SO_BINDTODEVICE set
    ./mc-test: could not get SO_BINDTODEVICE (Protocol not available)
    

    所以,设置选项似乎有效,但无法读回来吗?

    /* SO_BINDTODEVICE test */ 
    
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>
    #include <netdb.h>
    #include <stdio.h>
    #include <unistd.h>
    #include <string.h>
    #include <stdlib.h>
    #include <sys/time.h>
    #include <errno.h>
    
    #define MC_IP "192.168.7.2"
    #define MC_PORT (54321)
    #define MY_PORT (54321)
    #define MY_DEVICE "eth1"
    
    #define BUFFERSIZE (1000)
    
    /* global variables */
    int sock;
    struct sockaddr_in MC_addr;
    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;
    
      /* 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\ntime [s]: %d\ntime [ms]: %d\n", receive_timeout.tv_sec, receive_timeout.tv_usec);
      /* verify the recvfrom timeout value */
      rc=getsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, &receive_timeout, &opt_length);
      if (rc != 0) 
      {
         printf ("%s: could not get socket options (%s)\n",
            argv[0], strerror(errno));
         exit (EXIT_FAILURE);
      }
      printf ("timeout value\ntime [s]: %d\ntime [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\n");
      /* verify SO_BROADCAST setting */
      rc=getsockopt(sock, SOL_SOCKET, SO_BROADCAST, &optval, &opt_length);
      if (optval != 0) 
      {
        printf("SO_BROADCAST is enabled\n");
      }
    
      /* bind the socket to one network device */
      const 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 set\n");
      /* verify SO_BINDTODEVICE setting */
      rc = getsockopt(sock, SOL_SOCKET, SO_BINDTODEVICE, (void *)buffer, &opt_length);
      if (rc != 0) 
      {
         printf ("%s: could not get SO_BINDTODEVICE (%s)\n",
            argv[0], strerror(errno));
         exit (EXIT_FAILURE);
      }
      if (rc == 0) 
      {
        printf("SO_BINDTODEVICE is: %s\n", buffer);
      }
    
    
      /* Construct the server sockaddr_in structure */
      memset(&MC_addr, 0, sizeof(MC_addr));     /* Clear struct */
      MC_addr.sin_family = AF_INET;         /* Internet/IP */
      MC_addr.sin_addr.s_addr = inet_addr(MC_IP);   /* IP address */
      MC_addr.sin_port = htons(MC_PORT);        /* server port */
    
      /* bind my own Port */
      my_addr.sin_family = AF_INET;
      my_addr.sin_addr.s_addr = INADDR_ANY; /* INADDR_ANY all local addresses */
      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 ("port bound\n");
    
      /* identify mc */
      buffer[0] = (char)1;
      buffer[1] = (char)0;
      send_data (buffer, 2);  
      printf ("sent command: %d\n", (char)buffer[0]);
    
      rc=receive_data(buffer);
      printf ("%d bytes received\n", rc);
      buffer[rc] = (char)0; /* string end symbol */
      printf ("%d - %s\n", (int)(char)buffer[0], &buffer[1]);
    
      close(sock);
      printf ("socket closed\n");
    
      exit(0);
    }
    
    /* send data to the MC *****************************************************/
    /* buffer points to the bytes to send */
    /* buf_length is the number of bytes to send */
    /* returns allways 0 */
    int send_data( char *buffer, int buf_length )
    {
      int rc;
    
      rc = sendto (sock, buffer, buf_length, 0,
                     (struct sockaddr *) &MC_addr,
                     sizeof(MC_addr));
      if (rc < 0) 
      {
        printf ("could not send data\n");
        close (sock);
        exit (EXIT_FAILURE);
      }
      return(0);
    }
    
    /* receive data from the MC *****************************************************/
    /* buffer points to the memory for the received data */
    /* max BUFFERSIZE bytes can be received */
    /* returns number of bytes received */
    int receive_data(char *buffer)
    {
      int rc, MC_addr_length;
    
      MC_addr_length = sizeof(MC_addr);
      rc = recvfrom (sock, buffer, BUFFERSIZE, 0,
                     (struct sockaddr *) &MC_addr,
                     &MC_addr_length);
      if (rc < 0) 
      {
        printf ("could not receive data\n");
        close (sock);
        exit (EXIT_FAILURE);
      }
      return(rc);
    }
    

11 个答案:

答案 0 :(得分:15)

在看到SO_BINDTODEVICE如何实际使用的相互矛盾的答案之后,我一直在研究这个问题。 Some sources声称正确的用法是传入struct ifreq指针,该指针具有通过ioctl获取的设备名称和索引。例如:

struct ifreq ifr;
memset(&ifr, 0, sizeof(struct ifreq));
snprintf(ifr.ifr_name, sizeof(ifr.ifr_name), "eth0");
ioctl(fd, SIOCGIFINDEX, &ifr);
setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE,  (void*)&ifr, sizeof(struct ifreq));

Beej's networking tutorial表示将设备名称作为char指针传递的位置。例如:

char *devname = "eth0";
setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, devname, strlen(devname));

我已经尝试了这两种方法,但它们都做了所需要的,但我想注意在第一种方法中获得的设备索引是多余的。如果你查看net/core/sock.c中的内核代码,sock_bindtodevice只需复制设备名称字符串,调用dev_get_by_name_rcu来获取设备并绑定它。

第一种方法的作用是设备名称是ifreq结构中的第一个元素,请参阅http://linux.die.net/man/7/netdevice

答案 1 :(得分:6)

好的,我已经对它进行了一些调查。 SO_BINDTODEVICE在1999年被认为是“接近过时”,并且由于一些未指明的“安全隐患”(我无法确切知道是什么)而仅仅是根目录。

但是,您应该能够通过绑定到INADDR_ANY并设置IP_PKTINFO socketopt来获得所需的行为。这将在套接字上传递一条额外消息,该消息包含描述传入数据包的pktinfo结构。此结构包括数据包所在的接口索引:

struct in_pktinfo {
    unsigned int   ipi_ifindex;  /* Interface index */
    struct in_addr ipi_spec_dst; /* Local address */
    struct in_addr ipi_addr;     /* Header Destination address */
};

ipi_ifindex与netdevice ioctls(如SIOCGIFCONF)返回的struct ifreq中的ifr_ifindex匹配。因此,您应该能够使用它来忽略在您感兴趣的接口以外的接口上接收的数据包。

Doco for IP_PKTINFO位于ip(7)中,接口ioctls位于netdevice(7)中。

答案 2 :(得分:5)

setsockopt(sock, SOL_SOCKET, SO_BINDTODEVICE, "eth0", 4);

以上代码行足以仅从eth0 interface接收消息。 我在Linux上测试了这个。

注意:如果有一个桥接接口控制实际接口,它将无法工作。

祝你好运, 桑托什。

答案 3 :(得分:2)

在Linux 3.8之前,可以设置此套接字选项,但无法使用 getsockopt()检索。从Linux 3.8开始,它是可读的。 optlen参数应包含可用于接收设备名称的缓冲区大小,建议为IFNAMSZ字节。真实的设备名称长度将在optlen参数中报告。

答案 4 :(得分:1)

只需使用getifaddrs()查找您感兴趣的接口的IP地址,然后使用bind()将套接字绑定到该IP地址。如果你在套接字上启用了SO_BROADCAST,那么你只能在该接口上收到广播。

或者你可以跳过getifaddrs()部分,如果你愿意的话,直接将()绑定到192.168.7.1。

答案 5 :(得分:1)

我遇到的问题似乎是从Linux,Windows等处理来自特定接口的广播的处理方式不同 http://www.developerweb.net/forum/showthread.php?t=5722

我现在决定通过更改微控制器的TCP / IP堆栈来解决问题(文档很少,可移植性差)。它将不再发送广播地址的答案,而是从传入的UDP数据包中取出IP / MAC作为目标IP / MAC。然后我可以(在PC端)简单地将套接字绑定到eth1的IP。

干杯, 迈克尔

答案 6 :(得分:1)

我可以确认向特定接口发送多播也是这样的。请参阅下面的示例代码。 但是,如果接口由SO_BINDTODEVICE设置为我的辅助接口eth4,则无法获得listener.c程序。

我使用完全不同的机器发送多播数据包,并且侦听器从接口eth3工作,而不是从接口eth4工作。但是,tcpdump显示两个接口中的数据包(sudo tcpdump -i eth4 | grep UDP)。

这些是对Antony Courtney的示例代码的修改:

sender.c和listener.c:

/*
 * sender.c -- multicasts "hello, world!" to a multicast group once a second
 *
 * Antony Courtney, 25/11/94
 */

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <time.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#include <net/if.h>  

#define HELLO_PORT 12345
#define HELLO_GROUP "225.0.0.37"

main(int argc, char *argv[])
{
     struct sockaddr_in addr;
     int fd, cnt;
     struct ip_mreq mreq;
     char *message="Hello, World!";
    char com[1000];

     /* create what looks like an ordinary UDP socket */
     if ((fd=socket(AF_INET,SOCK_DGRAM,0)) < 0) {
      perror("socket");
      exit(1);
     }

     /* set up destination address */
     memset(&addr,0,sizeof(addr));
     addr.sin_family=AF_INET;
     addr.sin_addr.s_addr=inet_addr(HELLO_GROUP);
     addr.sin_port=htons(HELLO_PORT);



     u_char ttl=7;
     setsockopt(fd, IPPROTO_IP, IP_MULTICAST_TTL, &ttl, sizeof(ttl));
     struct ifreq ifr;
       memset(&ifr, 0, sizeof(struct ifreq));
       snprintf(ifr.ifr_name, sizeof(ifr.ifr_name), "eth4");
       ioctl(fd, SIOCGIFINDEX, &ifr);

 printf("[[%d]]\n", ifr.ifr_ifindex );
       setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE,  (void*)&ifr, sizeof(struct ifreq));


     inet_ntop(AF_INET, &(addr), com, INET_ADDRSTRLEN);
     printf("addr=%s\n", com );


     /* now just sendto() our destination! */
     while (1) {
      if (sendto(fd,message,strlen(message),0,(struct sockaddr *) &addr,
             sizeof(addr)) < 0) {
           perror("sendto");
           exit(1);
      }
      sleep(1);
     }
}


listener.c :

/*
 * listener.c -- joins a multicast group and echoes all data it receives from
 *      the group to its stdout...
 *
 * Antony Courtney, 25/11/94
 * Modified by: Frédéric Bastien (25/03/04)
 * to compile without warning and work correctly
 */

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <time.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#include <net/if.h>  

#define HELLO_PORT 12345
#define HELLO_GROUP "225.0.0.37"
#define MSGBUFSIZE 256

main(int argc, char *argv[])
{
     struct sockaddr_in addr;
     int fd, nbytes,addrlen;
     struct ip_mreq mreq;
     char msgbuf[MSGBUFSIZE];

     u_int yes=1;            /*** MODIFICATION TO ORIGINAL */

     /* create what looks like an ordinary UDP socket */
     if ((fd=socket(AF_INET,SOCK_DGRAM,0)) < 0) {
      perror("socket");
      exit(1);
     }
     struct ifreq ifr;
     memset(&ifr, 0, sizeof(struct ifreq));
     snprintf(ifr.ifr_name, sizeof(ifr.ifr_name), "eth4");
     ioctl(fd, SIOCGIFINDEX, &ifr);

     printf("[[%d]]\n", ifr.ifr_ifindex );

     if(  setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE,  (void*)&ifr, sizeof(struct ifreq)) < 0 )
       {
     perror("SO_BINDTODEVICE");
     exit(1);
       }

/**** MODIFICATION TO ORIGINAL */
    /* allow multiple sockets to use the same PORT number */
    if (setsockopt(fd,SOL_SOCKET,SO_REUSEADDR,&yes,sizeof(yes)) < 0) {
       perror("Reusing ADDR failed");
       exit(1);
       }
/*** END OF MODIFICATION TO ORIGINAL */


     /* set up destination address */
     memset(&addr,0,sizeof(addr));
     addr.sin_family=AF_INET;
     addr.sin_addr.s_addr=htonl(INADDR_ANY); /* N.B.: differs from sender */
     addr.sin_port=htons(HELLO_PORT);


     /* bind to receive address */
     if (bind(fd,(struct sockaddr *) &addr,sizeof(addr)) < 0) {
      perror("bind");
      exit(1);
     }


      /*
      ifr.ifr_flags = IFF_UP | IFF_ALLMULTI | IFF_MULTICAST;
      ioctl(fd, SIOCSIFFLAGS, &ifr );
      */

      /* use setsockopt() to request that the kernel join a multicast group */
     mreq.imr_multiaddr.s_addr=inet_addr(HELLO_GROUP);
     mreq.imr_interface.s_addr=htonl(INADDR_ANY);
     if (setsockopt(fd,IPPROTO_IP,IP_ADD_MEMBERSHIP,&mreq,sizeof(mreq)) < 0) {
      perror("setsockopt");
      exit(1);
     }

     /* now just enter a read-print loop */
     while (1) {
      addrlen=sizeof(addr);
      if ((nbytes=recvfrom(fd,msgbuf,MSGBUFSIZE,0,
                   (struct sockaddr *) &addr,&addrlen)) < 0) {
           perror("recvfrom");
           exit(1);
      }
      msgbuf[nbytes]='\0';
      puts(msgbuf);
     }
}

答案 7 :(得分:0)

问题2的答案似乎是SO_BINDTODEVICE选项不支持getsockopt。在Linux内核源代码(2.6.27)中,该选项仅在linux-2.6.27.25-0.1 / net / core / sock.c的sock_setsockopt函数中处理。

对于问题3,很多人推荐W. Richard Stevens撰写的“UNIX网络编程”一书。 我浏览了谷歌图书在线版的套接字选项页面 - 表7.1和7.2中没有列出SO_BINDTODEVICE选项:-( ...也许是因为这个选项只是Linux?

答案 8 :(得分:0)

如果您无法在辅助接口上接收多播数据包,则可能是阻止它们的反向路径过滤。如果这些数据包不会在它们进入的接口上发出,则会过滤掉收到的数据包。

要禁用此功能,请使用以下命令:

sudo -i
echo 2 > /proc/sys/net/ipv4/conf/eth1/rp_filter
echo 2 > /proc/sys/net/ipv4/conf/all/rp_filter
exit

答案 9 :(得分:-1)

setsocketopt需要设备索引,而不是名称。此外,您应该使用struct ifreq来传递索引:

        struct ifreq ifr;
        memset(&ifr, 0, sizeof(ifr));
        snprintf(ifr.ifr_name, sizeof(ifr.ifr_name), "eth3");

        ioctl(s, SIOCGIFINDEX, &ifr)
        setsockopt(s, SOL_SOCKET, SO_BINDTODEVICE,  (void*)&ifr, sizeof(ifr));

答案 10 :(得分:-2)

我通过将以下内容添加到/ etc / sudoers(或在/etc/sudoers.d中的文件中)解决了类似的问题:

myuser myhost=(root) NOPASSWD: /usr/bin/fping

然后使用sudo fping

而不是使用fping目录