Problemas con la opción de socket SO_BINDTODEVICE Linux

13 minutos de lectura

Tengo una PC con dos tarjetas de red. Una (eth0) es para LAN/internet y el otro para comunicación UDP con un dispositivo microcontrolador. El microcontrolador tiene una IP (192.168.7.2) y una dirección MAC. El segundo adaptador de red de PC (eth1) tiene 192.168.7.1.

El microcontrolador tiene una pila de IP muy simple, por lo que la forma más fácil para que el mc envíe paquetes UDP es transmitirlos.

En el lado de la PC, me gustaría recibir las transmisiones, pero solo de eth1. Entonces trato de vincular el socket UDP al eth1 dispositivo.

Los problemas (código fuente a continuación):

  1. setsockopt(sock, SOL_SOCKET, SO_BINDTODEVICE, device, sizeof(device)) requiere privilegios de root, ¿por qué? (la configuración de otras opciones funciona como usuario)

  2. getsockopt(sock, SOL_SOCKET, SO_BINDTODEVICE, (void *)buffer, &opt_length) da “Protocolo no disponible”. Me gustaría volver a leer el dispositivo que configuré a través de setsockopt mando.

  3. ¿Dónde puedo encontrar buena información? Revisé algunos libros de red de programación de Linux, pero por ejemplo el SO_BINDTODEVICE opción que solo he encontrado en internet.

Mi extenso (sucio) programa de prueba muestra los problemas. Establecer y recuperar el SO_RCVTIMEO y SO_BROADCAST opciones funciona como se esperaba.

Ejecutar el código cuando el usuario sale con:

could not set SO_BINDTODEVICE (Operation not permitted)"

Ejecutar con sudo da:

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

Entonces, ¿establecer la opción parece funcionar pero no es posible volver a leerla?

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

  • ¿estás seguro de que necesitas todo eso? ¿No puedes simplemente vincular () el socket a la dirección 192.168.7.1? Esto funciona para mi.

    – Juliano

    23 de agosto de 2009 a las 23:10

  • @Juliano: enlazar () a una interfaz específica solo funciona en paquetes de transmisión en Windows.

    – Compholio

    3 de febrero de 2012 a las 18:44

  • ¿Ha intentado enlazar a 192.168.7.255 y asegurarse de que eth0 y eth1 tengan máscaras de red diferentes?

    – deslumbrante

    14 de diciembre de 2012 a las 23:41

avatar de usuario
austinmarton

He estado investigando esto durante un tiempo después de ver respuestas contradictorias sobre cómo se usa realmente SO_BINDTODEVICE. Algunas fuentes afirmar que el uso correcto es pasar en un struct ifreq puntero, que tiene el nombre del dispositivo y el índice obtenido a través de un ioctl. Por ejemplo:

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

Mientras que Tutorial de redes de Beej dice que pase el nombre del dispositivo como un puntero de caracteres. Por ejemplo:

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

Probé ambos métodos y ambos hacen lo que se requiere, pero quería señalar que el índice de dispositivo obtenido en el primer método es superfluo. Si observa el código del núcleo en red/núcleo/calcetín.c, sock_bindtodevice simplemente copia la cadena del nombre del dispositivo, llama dev_get_by_name_rcu para obtener el dispositivo y se une a él.

La razón por la que el primer enfoque funciona es que el nombre del dispositivo es el primer elemento en el ifreq estructura, ver http://linux.die.net/man/7/netdevice.

NOTA: SO_BINDTODEVICE requiere permisos elevados:

  • ejecutar el ejecutable con permiso completo de root
  • después de construir el ejecutable puedes usar sudo setcap para otorgar el permiso ejecutable para usar esta opción de socket específica, entonces puede ejecutar el ejecutable sin permiso de root y el ejecutable tiene permiso para usar el SO_BINDTODEVICE característica (a través de una llamada anterior a setcap).

  • Realmente aprecio profundizar en el código del kernel para mostrar concretamente el char * es el enfoque correcto.

    – Trevor Boyd Smith

    11 de marzo a las 16:22

OK, lo he investigado un poco más. SO_BINDTODEVICE se consideró “casi obsoleto” en 1999, y es solo raíz debido a algunas “implicaciones de seguridad” no especificadas (no pude averiguar exactamente qué).

Sin embargo, debería poder obtener el comportamiento que desea vinculando a INADDR_ANY y configurando el socketopt IP_PKTINFO. Esto pasará un mensaje adicional en el socket que contiene una estructura pktinfo que describe el paquete entrante. Esta estructura incluye el índice de la interfaz en la que entró el paquete:

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

El ipi_ifindex coincide con el ifr_ifindex de la estructura ifreq devuelta por los ioctls de netdevice como SIOCGIFCONF. Por lo tanto, debería poder usarlo para ignorar los paquetes recibidos en interfaces que no sean la que le interesa.

Doco para IP_PKTINFO está en ip(7) y para la interfaz ioctls en netdevice(7).

  • Utilizando IP_PKTINFO significa convertir de recv()/recvfrom() para recvmsg(), que no es exactamente fácil de usar. Pero seguro, IP_PKTINFO puede ser muy útil de usar, en particular si desea que su aplicación escuche varias (pero no todas) interfaces. Sin embargo, no encuentro ninguna referencia a SO_BINDTODEVICE siendo obsoleto/obsoleto, y es poco probable que Linux rompa el espacio de usuario al eliminarlo en el futuro. Entonces, para el caso de uso común, iría con SO_BINDTODEVICE en Linux todos los días de la semana.

    – troglobit

    9 de marzo de 2016 a las 7:59

avatar de usuario
Santosh

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

La línea de código anterior es suficiente para recibir mensajes de eth0 interface solamente. Probé esto en Linux.

NOTA: No funcionará si hay una interfaz de puente que controle las interfaces reales.

Un saludo, Santosh.

avatar de usuario
Dražen G.

Antes de Linux 3.8, esta opción de socket se podía configurar, pero no se podía recuperar con getockopt(). Desde Linux 3.8, es legible. los optlen El argumento debe contener el tamaño de búfer disponible para recibir el nombre del dispositivo y se recomienda que sean bytes IFNAMSZ. La longitud real del nombre del dispositivo se informa en el optlen argumento.

El problema con el que me encontré parece ser que Linux, Windows,…
http://www.developerweb.net/forum/showthread.php?t=5722

Ahora decidí resolver el problema (poca documentación y mala portabilidad) cambiando la pila TCP/IP del microcontrolador. Ya no enviará respuestas a la dirección de transmisión, sino que tomará la IP/MAC del paquete UDP entrante como la IP/MAC de destino. Entonces puedo (en el lado de la PC) simplemente vincular el socket a la IP de eth1.

Saludos, miguel

avatar de usuario
coste y flete

Simplemente busque la dirección IP de la interfaz que le interesa con getifaddrs() y vincule su socket a esa dirección IP con bind(). Si habilita SO_BROADCAST en el socket, solo recibirá transmisiones recibidas en esa interfaz.

O, de hecho, podría omitir la parte getifaddrs() y vincular directamente() a 192.168.7.1 si lo desea.

avatar de usuario
Tomi

Puedo confirmar que enviar multidifusión a una interfaz específica también funciona así. Vea los códigos de muestra a continuación. Sin embargo, no puedo hacer que el programa listener.c funcione si la interfaz está configurada por SO_BINDTODEVICE en mi interfaz secundaria eth4.

Usé una máquina completamente diferente para enviar los paquetes de multidifusión y el oyente funciona desde la interfaz eth3, no desde la interfaz eth4. Sin embargo, tcpdump muestra los paquetes en ambas interfaces (sudo tcpdump -i eth4 |grep UDP).

Estas son modificaciones al código de muestra de Antony Courtney:

remitente.c y oyente.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);
     }
}

¿Ha sido útil esta solución?

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con sus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Configurar y más información
Privacidad