views:

1910

answers:

4

Hello network experts,

I have a pc with two network cards. One (eth0) is for LAN/internet and the other for UDP communication with one microcontroller (mc) device. The mc has an IP(192.168.7.2) and a MAC address. The second pc network adapter (eth1) has 192.168.7.1. The mc 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 just 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 option 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 to find good info? I checked some Linux-programming, network books, but for example the SO_BINDTODEVICE option I just found in the internet.

My lengthy (dirty) testprogram shows the problems. Setting and getting back the SO_RCVTIMEO and SO_BROADCAST options works as expected. Running the code as user end in an "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?

Please help, Michael

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

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

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?

A: 

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.

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

caf
That was the first thing I did. And I verified it just now.The pc does NOT recognise the broadcast answer of the MC! I can see the UDP packet with wireshark but the receive routine times out without receiving anything.
What happens if you bind to 192.168.7.255? (Is that the broadcast that's being used, or is it the 255.255.255.255 one?)
caf
That doesn't work either. But I will post the solution I'm using now.Thank you for looking at my network problem.
+1  A: 

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

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.

Cheers, Michael

+1  A: 

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

caf