Problems with SO_BINDTODEVICE Linux socket option

51,480

Solution 1

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:

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:

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.

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.

Solution 2

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.

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

Best regards, Santosh.

Solution 3

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

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:

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.

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

Solution 4

Before Linux 3.8, this socket option could be set, but could not retrieved with getsockopt(). 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.

Solution 5

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.

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

These are modifications to Antony Courtney's sample code:

sender.c and 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);
     }
}
Share:
51,480
Admin
Author by

Admin

Updated on July 09, 2022

Comments

  • Admin
    Admin almost 2 years

    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.

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

    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.

    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)

    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.

    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.

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

    Running the code as user exits with:

    could not set SO_BINDTODEVICE (Operation not permitted)"
    

    Running with sudo gives:

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