SO_BINDTODEVICE Linux socket选项的问题

时间:2023-01-06 23:58:33

I have a PC with two network cards. One (eth0) is for LAN/internet and the other for UDP communication with one microcontroller device. The microcontroller has an IP (192.168.7.2) and a MAC address. The second pc network adapter (eth1) has 192.168.7.1.

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

The microcontroller has a very simple IP stack, so the easiest way for the mc to send UDP packets is to broadcast them.

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

On the PC side I'd like to receive the broadcasts - but only from eth1. So I try to bind the UDP socket to the eth1 device.

在PC端,我想接收广播-但只从eth1。所以我尝试将UDP套接字绑定到eth1设备上。

The problems (source code below):

问题(源代码如下):

  1. setsockopt(sock, SOL_SOCKET, SO_BINDTODEVICE, device, sizeof(device)) requires root privileges, why? (setting other options works as user)

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

  2. getsockopt(sock, SOL_SOCKET, SO_BINDTODEVICE, (void *)buffer, &opt_length) gives "Protocol not available". I would like to read back the device I set via setsockopt command.

    getsockopt(sock, SOL_SOCKET, SO_BINDTODEVICE, (void *)缓冲区,&opt_length)给出“协议不可用”。我想要回读我通过setsockopt命令设置的设备。

  3. Where can I find good info? I checked some Linux-programming, network books, but for example the SO_BINDTODEVICE option I've only found on the internet.

    我在哪里可以找到好的信息?我检查了一些linux编程、网络书籍,但是例如我只在internet上找到的SO_BINDTODEVICE选项。

My lengthy (dirty) test program shows the problems. Setting and getting back the SO_RCVTIMEO and SO_BROADCAST options works as expected.

我冗长的(肮脏的)测试程序显示了问题。设置并返回SO_RCVTIMEO和SO_BROADCAST选项,效果与预期一致。

Running the code as user exits with:

以用户退出的方式运行代码:

could not set SO_BINDTODEVICE (Operation not permitted)"

Running with sudo gives:

运行sudo给:

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

So, setting the option seems to work but reading it back is not possible?

所以,设置选项似乎是可行的,但不可能把它读回来?

/* 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 个解决方案

#1


14  

I have been looking into this for a while after seeing conflicting answers to how SO_BINDTODEVICE is actually used. Some sources claim that the correct usage is to pass in a struct ifreq pointer, which has the device name and index obtained via an ioctl. For example:

在看到了关于SO_BINDTODEVICE实际使用方式的相互矛盾的答案之后,我已经研究这个问题有一段时间了。一些消息称,正确的用法是传入一个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));

Where as Beej's networking tutorial says to pass the device name as a char pointer. For example:

正如Beej的网络教程所说的,将设备名称作为char指针传递。例如:

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

I have tried both of these methods and they both do what is required, but I wanted to note that the device index obtained in the first method is superfluous. If you look at the kernel code in net/core/sock.c, sock_bindtodevice just copies the device name string, calls dev_get_by_name_rcu to get the device and binds to it.

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

The reason that the first approach works is that the device name is the first element in the ifreq structure, see http://linux.die.net/man/7/netdevice.

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

#2


6  

OK, I've looked into it a little more. SO_BINDTODEVICE was considered "near obsolete" back in 1999, and is root-only due to some unspecified "security implications" (I couldn't find out exactly what).

好吧,我已经仔细研究过了。SO_BINDTODEVICE在1999年被认为是“近乎过时的”,并且是root用户,只是因为一些未指明的“安全问题”(我不知道到底是什么)。

However, you should be able to get the behaviour you want by binding to INADDR_ANY and setting the IP_PKTINFO socketopt. This will pass an extra message on the socket that contains a pktinfo structure describing the incoming packet. This structure includes the index of the interface that the packet came in on:

但是,您应该能够通过绑定到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 */
};

The ipi_ifindex matches with the ifr_ifindex from the struct ifreq returned by the netdevice ioctls like SIOCGIFCONF. So you should be able to use that to ignore packets received on interfaces other than the one you're interested in.

ipi_ifindex与SIOCGIFCONF等netdevice ioctls返回的结构ifr_ifindex匹配。因此,您应该能够使用它来忽略在接口上接收的数据包,而不是您感兴趣的那个接口。

Doco for IP_PKTINFO is in ip(7) and for the interface ioctls in netdevice(7).

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

#3


5  

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

Above line of code is enough to receive messages from eth0 interface only. I tested this on Linux.

以上代码行仅能接收来自eth0接口的消息。我在Linux上测试过这个。

NOTE: It won't work if there is a bridge interface controlling actual interfaces.

注意:如果存在控制实际接口的桥接接口,那么它将无法工作。

Best regards, Santosh.

最好的问候,桑托什。

#4


2  

Before Linux 3.8, this socket option could be set, but could not retrieved with getsockopt(2). Since Linux 3.8, it is readable. The optlen argument should contain the buffer size available to receive the device name and is recommended to be IFNAMSZ bytes. The real device name length is reported back in the optlen argument.

在Linux 3.8之前,可以设置这个套接字选项,但是不能使用getsockopt(2)检索。因为Linux 3.8,所以是可读的。optlen参数应该包含可用于接收设备名称的缓冲区大小,并推荐为IFNAMSZ字节。在optlen参数中报告实际设备名称长度。

#5


1  

Just lookup the IP address of the interface you're interested in with getifaddrs(), and bind your socket to that IP address with bind(). If you enable SO_BROADCAST on the socket you'll then only get broadcasts recieved on that interface.

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

Or indeed you could skip the getifaddrs() part and just directly bind() to 192.168.7.1 if you like.

或者您可以跳过getifaddrs()部分,直接将()绑定到192.168.7.1。

#6


1  

The problem I ran into seems to be that receiving broadcasts from a specific interface is handled differently by Linux, Windows,... http://www.developerweb.net/forum/showthread.php?t=5722

我遇到的问题似乎是,Linux、Windows、…http://www.developerweb.net/forum/showthread.php?t=5722

I now decided to solve the problem (little documentation and bad portability) by changing the TCP/IP stack of the microcontroller. It will no longer send answers to the broadcast address but instead take the IP/MAC from the incoming UDP packet as the destination IP/MAC. Then I can (on the pc side) simply bind the socket to the IP of eth1.

我现在决定通过改变微控制器的TCP/IP栈来解决这个问题(小文档和糟糕的可移植性)。它将不再向广播地址发送答案,而是将来自传入UDP包的IP/MAC作为目标IP/MAC。然后我可以(在pc端)简单地将套接字绑定到eth1的IP。

Cheers, Michael

干杯,迈克尔

#7


1  

I can confirm that sending multicast to specific interface works also like this. See the sample codes below. However I can't get listener.c program working if the interface is set by SO_BINDTODEVICE to my secondary interface eth4.

我可以确认发送组播到特定接口也像这样工作。请参阅下面的示例代码。但是我不能得到听众。c程序工作,如果接口是由SO_BINDTODEVICE设置到我的辅助接口eth4。

I used completely different machine to send the multicast packets and the listener works from interface eth3, not from interface eth4. However, tcpdump shows the packets in both interfaces (sudo tcpdump -i eth4 |grep UDP).

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

These are modifications to Antony Courtney's sample code:

这些是对Antony Courtney的样本代码的修改:

sender.c and listener.c:

发送者。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);
     }
}

#8


0  

The answer to question 2 seems to be that getsockopt is just not supported for the SO_BINDTODEVICE option. In the Linux kernel source (2.6.27) the option is only handled in the sock_setsockopt function of linux-2.6.27.25-0.1/net/core/sock.c

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

For question 3 it seems, lots of people recommend the "UNIX network programming" book by W. Richard Stevens. I looked through the socket options pages of the google book online version - the SO_BINDTODEVICE option is not listed in table 7.1 and 7.2 :-( ...maybe because this option is Linux only?

对于问题3,似乎有很多人推荐W. Richard Stevens的《UNIX网络编程》这本书。我查看了谷歌在线版本的套接字选项页面——SO_BINDTODEVICE选项没有列在表7.1和7.2中:-(……)也许因为这个选项是Linux操作系统?

#9


0  

If you are unable to receive multicast packets on the secondary interface, it could well be reverse path filtering that is blocking them. This filters out received packets if those packets would not go out on the interface they are coming in on.

如果无法在辅助接口上接收多播包,很可能是反向路径过滤阻塞了它们。如果这些数据包不能在它们即将到达的接口上发出,那么这个过滤器就会过滤掉接收到的数据包。

To disable this feature, use the following:

要禁用此功能,请使用以下方法:

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

#10


-1  

setsocketopt needs device index, not name. Furthermore you should use struct ifreq to pass the index:

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

#11


-2  

I've solved a similar problem by adding the following to /etc/sudoers (or in a file in /etc/sudoers.d):

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

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

Then instead of using fping directory, use sudo fping.

然后使用sudo fping而不是使用fping目录。

#1


14  

I have been looking into this for a while after seeing conflicting answers to how SO_BINDTODEVICE is actually used. Some sources claim that the correct usage is to pass in a struct ifreq pointer, which has the device name and index obtained via an ioctl. For example:

在看到了关于SO_BINDTODEVICE实际使用方式的相互矛盾的答案之后,我已经研究这个问题有一段时间了。一些消息称,正确的用法是传入一个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));

Where as Beej's networking tutorial says to pass the device name as a char pointer. For example:

正如Beej的网络教程所说的,将设备名称作为char指针传递。例如:

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

I have tried both of these methods and they both do what is required, but I wanted to note that the device index obtained in the first method is superfluous. If you look at the kernel code in net/core/sock.c, sock_bindtodevice just copies the device name string, calls dev_get_by_name_rcu to get the device and binds to it.

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

The reason that the first approach works is that the device name is the first element in the ifreq structure, see http://linux.die.net/man/7/netdevice.

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

#2


6  

OK, I've looked into it a little more. SO_BINDTODEVICE was considered "near obsolete" back in 1999, and is root-only due to some unspecified "security implications" (I couldn't find out exactly what).

好吧,我已经仔细研究过了。SO_BINDTODEVICE在1999年被认为是“近乎过时的”,并且是root用户,只是因为一些未指明的“安全问题”(我不知道到底是什么)。

However, you should be able to get the behaviour you want by binding to INADDR_ANY and setting the IP_PKTINFO socketopt. This will pass an extra message on the socket that contains a pktinfo structure describing the incoming packet. This structure includes the index of the interface that the packet came in on:

但是,您应该能够通过绑定到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 */
};

The ipi_ifindex matches with the ifr_ifindex from the struct ifreq returned by the netdevice ioctls like SIOCGIFCONF. So you should be able to use that to ignore packets received on interfaces other than the one you're interested in.

ipi_ifindex与SIOCGIFCONF等netdevice ioctls返回的结构ifr_ifindex匹配。因此,您应该能够使用它来忽略在接口上接收的数据包,而不是您感兴趣的那个接口。

Doco for IP_PKTINFO is in ip(7) and for the interface ioctls in netdevice(7).

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

#3


5  

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

Above line of code is enough to receive messages from eth0 interface only. I tested this on Linux.

以上代码行仅能接收来自eth0接口的消息。我在Linux上测试过这个。

NOTE: It won't work if there is a bridge interface controlling actual interfaces.

注意:如果存在控制实际接口的桥接接口,那么它将无法工作。

Best regards, Santosh.

最好的问候,桑托什。

#4


2  

Before Linux 3.8, this socket option could be set, but could not retrieved with getsockopt(2). Since Linux 3.8, it is readable. The optlen argument should contain the buffer size available to receive the device name and is recommended to be IFNAMSZ bytes. The real device name length is reported back in the optlen argument.

在Linux 3.8之前,可以设置这个套接字选项,但是不能使用getsockopt(2)检索。因为Linux 3.8,所以是可读的。optlen参数应该包含可用于接收设备名称的缓冲区大小,并推荐为IFNAMSZ字节。在optlen参数中报告实际设备名称长度。

#5


1  

Just lookup the IP address of the interface you're interested in with getifaddrs(), and bind your socket to that IP address with bind(). If you enable SO_BROADCAST on the socket you'll then only get broadcasts recieved on that interface.

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

Or indeed you could skip the getifaddrs() part and just directly bind() to 192.168.7.1 if you like.

或者您可以跳过getifaddrs()部分,直接将()绑定到192.168.7.1。

#6


1  

The problem I ran into seems to be that receiving broadcasts from a specific interface is handled differently by Linux, Windows,... http://www.developerweb.net/forum/showthread.php?t=5722

我遇到的问题似乎是,Linux、Windows、…http://www.developerweb.net/forum/showthread.php?t=5722

I now decided to solve the problem (little documentation and bad portability) by changing the TCP/IP stack of the microcontroller. It will no longer send answers to the broadcast address but instead take the IP/MAC from the incoming UDP packet as the destination IP/MAC. Then I can (on the pc side) simply bind the socket to the IP of eth1.

我现在决定通过改变微控制器的TCP/IP栈来解决这个问题(小文档和糟糕的可移植性)。它将不再向广播地址发送答案,而是将来自传入UDP包的IP/MAC作为目标IP/MAC。然后我可以(在pc端)简单地将套接字绑定到eth1的IP。

Cheers, Michael

干杯,迈克尔

#7


1  

I can confirm that sending multicast to specific interface works also like this. See the sample codes below. However I can't get listener.c program working if the interface is set by SO_BINDTODEVICE to my secondary interface eth4.

我可以确认发送组播到特定接口也像这样工作。请参阅下面的示例代码。但是我不能得到听众。c程序工作,如果接口是由SO_BINDTODEVICE设置到我的辅助接口eth4。

I used completely different machine to send the multicast packets and the listener works from interface eth3, not from interface eth4. However, tcpdump shows the packets in both interfaces (sudo tcpdump -i eth4 |grep UDP).

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

These are modifications to Antony Courtney's sample code:

这些是对Antony Courtney的样本代码的修改:

sender.c and listener.c:

发送者。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);
     }
}

#8


0  

The answer to question 2 seems to be that getsockopt is just not supported for the SO_BINDTODEVICE option. In the Linux kernel source (2.6.27) the option is only handled in the sock_setsockopt function of linux-2.6.27.25-0.1/net/core/sock.c

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

For question 3 it seems, lots of people recommend the "UNIX network programming" book by W. Richard Stevens. I looked through the socket options pages of the google book online version - the SO_BINDTODEVICE option is not listed in table 7.1 and 7.2 :-( ...maybe because this option is Linux only?

对于问题3,似乎有很多人推荐W. Richard Stevens的《UNIX网络编程》这本书。我查看了谷歌在线版本的套接字选项页面——SO_BINDTODEVICE选项没有列在表7.1和7.2中:-(……)也许因为这个选项是Linux操作系统?

#9


0  

If you are unable to receive multicast packets on the secondary interface, it could well be reverse path filtering that is blocking them. This filters out received packets if those packets would not go out on the interface they are coming in on.

如果无法在辅助接口上接收多播包,很可能是反向路径过滤阻塞了它们。如果这些数据包不能在它们即将到达的接口上发出,那么这个过滤器就会过滤掉接收到的数据包。

To disable this feature, use the following:

要禁用此功能,请使用以下方法:

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

#10


-1  

setsocketopt needs device index, not name. Furthermore you should use struct ifreq to pass the index:

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

#11


-2  

I've solved a similar problem by adding the following to /etc/sudoers (or in a file in /etc/sudoers.d):

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

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

Then instead of using fping directory, use sudo fping.

然后使用sudo fping而不是使用fping目录。