2009-07-30 14 views
13

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

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

En el lado de la PC me gustaría recibir las transmisiones, pero solo desde eth1. Así que intento vincular el socket UDP al dispositivo eth1.

Los problemas (código fuente abajo):

  1. setsockopt(sock, SOL_SOCKET, SO_BINDTODEVICE, device, sizeof(device)) requiere privilegios de root, ¿por qué? (establecer 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 del comando setsockopt.

  3. ¿Dónde puedo encontrar buena información? Revisé algunos libros de red de Linux, pero por ejemplo la opción SO_BINDTODEVICE que solo encontré en Internet.

Mi programa de prueba longy (dirty) muestra los problemas. Configurar y recuperar las opciones SO_RCVTIMEO y SO_BROADCAST funciona como se esperaba.

Ejecutar el código como usuario sale con:

could not set SO_BINDTODEVICE (Operation not permitted)" 

Correr con sudo da:

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

Por lo tanto, el establecimiento de la opción parece funcionar pero la lectura de vuelta no es posible?

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

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

+0

@Juliano: bind() ing a una interfaz específica solo funciona en paquetes de difusión en Windows. – Compholio

+0

han intentado enlazar a 192.168.7.255 y asegurarse de que eth0 y eth1 tienen máscaras de red diferentes? – dashesy

Respuesta

0

La respuesta a la pregunta 2 parece ser que simplemente no getsockopt se admite para la opción SO_BINDTODEVICE. En la fuente del kernel de Linux (2.6.27) la opción solo se maneja en la función sock_setsockopt de linux-2.6.27.25-0.1/net/core/sock.c

Para la pregunta 3 parece que mucha gente recomienda Libro de "programación de red UNIX" de W. Richard Stevens. Miré a través de las páginas de opciones de conector de la versión del libro en línea Google - SO_BINDTODEVICE la opción no aparece en la tabla 7.1 y 7.2 :-( ... tal vez porque esta opción es sólo Linux

1

Sólo buscar la dirección IP de la interfaz que te interesa con getifaddrs(), y enlaza tu socket a esa dirección IP con bind(). Si habilitas SO_BROADCAST en el socket, solo obtendrás las transmisiones recibidas en esa interfaz.

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

+0

Eso fue lo primero que hice. Y lo verifiqué justo ahora. ¡La PC NO reconoce la respuesta de difusión del MC! Puedo ver el paquete UDP con wireshark pero la rutina de recepción se agota sin recibir nada. –

+0

¿Qué ocurre si te unes a 192.168.7.255? (¿Es esa la emisión que se está usando, o es la 255.255.255.255?) – caf

+0

Eso tampoco funciona. Pero publicaré la solución que estoy usando ahora. Gracias por mirar mi problema de red. –

1

El problema que encontré parece ser que la recepción de broadcas ts desde una interfaz específica es manejado de manera diferente por Linux, Windows, ... http://www.developerweb.net/forum/showthread.php?t=5722

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 difusión, sino que tomará el IP/MAC del paquete UDP entrante como IP/MAC de destino. Entonces puedo (en el lado de la PC) simplemente unir el socket a la IP de eth1.

Saludos, Michael

6

OK, he mirado en él un poco más. SO_BINDTODEVICE fue considerado "casi obsoleto" en 1999, y es root-only debido a algunas "implicaciones de seguridad" no especificadas (no pude averiguar exactamente qué).

Sin embargo, debe poder obtener el comportamiento que desea al enlazar a INADDR_ANY y configurar el IP_PKTINFO socketopt. 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 que el paquete entró en:

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 ifreq struct devuelto por los ioctls netdevice como SIOCGIFCONF. Por lo tanto, debería poder usar eso 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).

+0

Usar 'IP_PKTINFO' significa convertir de' recv()/recvfrom() 'a' recvmsg() ', que no es exactamente amigable para el usuario. Pero seguro, 'IP_PKTINFO' puede ser muy útil, especialmente si quieres que tu aplicación escuche varias (pero no todas) las interfaces. Sin embargo, no encuentro ninguna referencia a 'SO_BINDTODEVICE' como obsoleto/obsoleto, y es poco probable que Linux rompa el espacio de usuario eliminándolo en el futuro. Por lo tanto, para el caso de uso común iría con 'SO_BINDTODEVICE' en Linux todos los días de la semana. – troglobit

-1

setsocketopt necesita índice del dispositivo, no nombre. Además se debe utilizar ifreq estructura para pasar el índice:

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

No según el conector 7 del hombre: http://linux.die.net/man/7/socket –

1

puedo confirmar que el envío de multidifusión a la interfaz específica funciona también como esto. Vea los códigos de muestra a continuación. Sin embargo, no puedo hacer funcionar el programa listener.c si SO_BINDTODEVICE configura la interfaz en mi interfaz secundaria eth4.

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

Estos son modificaciones de código de ejemplo de Antonio, Courtney:

sender.c y 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); 
    } 
} 
14

He estado buscando en esto por un tiempo después de ver respuestas contradictorias a cómo es en realidad SO_BINDTODEVICE usado. Some sources afirman que el uso correcto debe pasar en un puntero struct ifreq, 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)); 

Donde como Beej's networking tutorial dice que pasar el nombre del dispositivo como un puntero char. Por ejemplo:

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

He intentado tanto de estos métodos y ambos hacer lo que se requiere, pero quería tener en cuenta que el índice de dispositivo obtenido en el primer método es superfluo. Si mira el código del kernel en net/core/sock.c, sock_bindtodevice solo copia la cadena del nombre del dispositivo, llama al dev_get_by_name_rcu para obtener el dispositivo y se une al mismo.

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

5
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 puente que controla las interfaces reales.

Recuerdos, Santosh.

0

Si no puede recibir paquetes de multidifusión en la interfaz secundaria, podría ser que el filtrado de ruta inversa los esté bloqueando. Esto filtra los paquetes recibidos si esos paquetes no saldrían en la interfaz en la que están ingresando.

Para desactivar esta función, utilice la siguiente:

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

he resuelto un problema similar añadiendo lo siguiente a/etc/sudoers (o en un archivo en /etc/sudoers.d):

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

a continuación, en lugar de utilizar el directorio fping, utilice sudo fping.

Cuestiones relacionadas