2009-07-30 13 views
13

Ho un PC con due schede di rete. Uno (eth0) è per LAN/Internet e l'altro per la comunicazione UDP con un dispositivo microcontrollore. Il microcontrollore ha un indirizzo IP (192.168.7.2) e un indirizzo MAC. La seconda scheda di rete per PC (eth1) ha 192.168.7.1.Problemi con SO_BINDTODEVICE opzione socket Linux

Il microcontrollore ha uno stack IP molto semplice, quindi il modo più semplice per mc di inviare pacchetti UDP è quello di trasmetterli.

Per quanto riguarda il PC, desidero ricevere le trasmissioni, ma solo da eth1. Quindi provo a collegare il socket UDP al dispositivo eth1.

I problemi (codice sorgente sottostante):

  1. setsockopt(sock, SOL_SOCKET, SO_BINDTODEVICE, device, sizeof(device)) richiede i privilegi di root, perché? (l'impostazione di altre opzioni funziona come utente)

  2. getsockopt(sock, SOL_SOCKET, SO_BINDTODEVICE, (void *)buffer, &opt_length) fornisce "Protocollo non disponibile". Vorrei leggere il dispositivo che ho impostato tramite il comando setsockopt.

  3. Dove posso trovare informazioni utili? Ho controllato alcuni programmi di programmazione Linux, libri di rete, ma ad esempio l'opzione SO_BINDTODEVICE che ho trovato solo su Internet.

Il mio programma di test lungo (sporco) mostra i problemi. L'impostazione e il ripristino delle opzioni SO_RCVTIMEO e SO_BROADCAST funzionano come previsto.

L'esecuzione del codice come utente esce con:

could not set SO_BINDTODEVICE (Operation not permitted)" 

Correndo con sudo dà:

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

Così, impostando l'opzione sembra funzionare ma la lettura indietro non è possibile?

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

sei sicuro di aver bisogno di tutto questo? Non puoi semplicemente legare() il socket all'indirizzo 192.168.7.1? Per me funziona. – Juliano

+0

@Juliano: bind() in una specifica interfaccia funziona solo sui pacchetti di trasmissione su Windows. – Compholio

+0

hanno provato il binding a 192.168.7.255 e assicurandosi che eth0 e eth1 abbiano diverse maschere di rete? – dashesy

risposta

0

La risposta alla domanda 2 sembra essere che getsockopt non è solo supportata per l'opzione SO_BINDTODEVICE. Nel sorgente del kernel Linux (2.6.27) l'opzione viene gestita solo nella funzione sock_setsockopt di linux-2.6.27.25-0.1/net/core/sock.c

Per la domanda 3 sembra, molte persone consigliano Libro "programmazione di reti UNIX" di W. Richard Stevens. Ho guardato attraverso le pagine opzioni di socket della versione libro online di Google -? L'opzione SO_BINDTODEVICE non è elencato nella tabella 7.1 e 7.2 :-( ... forse perché questa opzione è solo Linux

1

Proprio Lookup l'indirizzo IP dell'interfaccia che ti interessa con getifaddrs() e associa il tuo socket a quell'indirizzo IP con bind(). Se abiliti SO_BROADCAST sul socket, riceverai solo le trasmissioni ricevute su quell'interfaccia.

Oppure infatti potresti saltare la parte getifaddrs() e legare direttamente() a 192.168.7.1 se lo desideri.

+0

Questa è stata la prima cosa che ho fatto. E l'ho verificato solo ora. Il PC NON riconosce la risposta di trasmissione dell'MC! Riesco a vedere il pacchetto UDP con wireshark ma la routine di ricezione scade senza ricevere nulla. –

+0

Cosa succede se si esegue il binding a 192.168.7.255? (È la trasmissione che viene utilizzata o è la 255.255.255.255?) – caf

+0

Anche questo non funziona. Ma posterò la soluzione che sto usando ora. Grazie per aver guardato il mio problema di rete. –

1

Il problema che mi sono imbattuto sembra essere quello di ricevere broadcas ts da un'interfaccia specifica viene gestita in modo diverso da Linux, Windows, ... http://www.developerweb.net/forum/showthread.php?t=5722

Ora ho deciso di risolvere il problema (poca documentazione e cattiva portabilità) cambiando lo stack TCP/IP del microcontrollore. Non invierà più le risposte all'indirizzo di trasmissione, ma prenderà invece l'IP/MAC dal pacchetto UDP in arrivo come IP/MAC di destinazione. Quindi posso (sul lato pc) legare semplicemente il socket all'IP di eth1.

Cheers, Michael

6

OK, ho guardato dentro un po 'di più. SO_BINDTODEVICE era considerato "quasi obsoleto" nel 1999, ed è root-only a causa di alcune "implicazioni di sicurezza" non specificate (non sono riuscito a scoprire esattamente cosa).

Tuttavia, si dovrebbe essere in grado di ottenere il comportamento desiderato legandosi a INADDR_ANY e impostando il socket IPtPKTINFO. Questo passerà un messaggio in più sul socket che contiene una struttura pktinfo che descrive il pacchetto in arrivo. Questa struttura comprende l'indice dell'interfaccia che il pacchetto è venuto in su:

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

L'ipi_ifindex corrisponde al ifr_ifindex dal ifreq struttura restituita dalle ioctls netdevice come SIOCGIFCONF. Così si dovrebbe essere in grado di utilizzare che per ignorare i pacchetti ricevuti su interfacce diverse da quella che ti interessa.

Doco per IP_PKTINFO è in ip (7) e per le ioctls di interfaccia in netdevice (7).

+0

Usare 'IP_PKTINFO' significa convertire da' recv()/recvfrom() 'a' recvmsg() ', che non è esattamente user-friendly. Ma sicuramente, 'IP_PKTINFO' può essere molto utile da usare, in particolare se vuoi che la tua applicazione ascolti diverse (ma non tutte) interfacce. Tuttavia, non trovo alcun riferimento al fatto che 'SO_BINDTODEVICE' sia obsoleto/deprecato e difficilmente è probabile che Linux interromperà lo spazio degli utenti rimuovendolo in futuro. Quindi, per il caso d'uso comune, userei 'SO_BINDTODEVICE' su Linux ogni giorno della settimana. – troglobit

-1

setocketopt ha bisogno dell'indice del dispositivo, non del nome. Inoltre si dovrebbe usare struct ifreq per passare l'indice:

 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

Non in base alla presa uomo 7: http://linux.die.net/man/7/socket –

1

Posso confermare che l'invio di multicast a un'interfaccia specifica funziona anche in questo modo. Vedi i codici di esempio qui sotto. Tuttavia non riesco a ottenere il funzionamento del programma listener.c se l'interfaccia è impostata da SO_BINDTODEVICE sulla mia interfaccia secondaria eth4.

Ho utilizzato una macchina completamente diversa per inviare i pacchetti multicast e l'ascoltatore funziona dall'interfaccia eth3, non dall'interfaccia eth4. Tuttavia, tcpdump mostra i pacchetti in entrambe le interfacce (sudo tcpdump -i eth4 | grep UDP).

Queste sono le modifiche al codice di esempio di Antony Courtney:

sender.c e 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

Ho cercato in questo per un po 'dopo aver visto le risposte contrastanti a come è in realtà SO_BINDTODEVICE Usato. Some sources afferma che l'utilizzo corretto deve passare in un puntatore struct ifreq, che ha il nome e l'indice del dispositivo ottenuti tramite un ioctl. Per esempio:

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

Dove come Beej's networking tutorial dice di passare il nome del dispositivo come un puntatore char. Ad esempio:

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

Ho provato entrambi questi metodi ed entrambi fare ciò che è necessario, ma volevo notare che l'indice dispositivo ottenuto nel primo metodo è superflua. Se si guarda il codice del kernel in net/core/sock.c, sock_bindtodevice copia semplicemente la stringa del nome del dispositivo, chiama dev_get_by_name_rcu per ottenere il dispositivo e si collega ad esso.

Il motivo per cui il primo approccio funziona è che il nome del dispositivo è il primo elemento nella struttura ifreq, vedere http://linux.die.net/man/7/netdevice.

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

Sopra la riga di codice è sufficiente ricevere messaggi solo da eth0 interface. L'ho provato su Linux.

NOTA: Non funzionerà se è presente un'interfaccia bridge che controlla le interfacce effettive.

Cordiali saluti, Santosh.

0

Se non è possibile ricevere pacchetti multicast sull'interfaccia secondaria, potrebbe essere il filtro del percorso inverso che li sta bloccando. Questo filtra i pacchetti ricevuti se quei pacchetti non uscissero sull'interfaccia su cui stanno arrivando.

Per disattivare questa funzione, utilizzare il seguente:

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

Ho risolto un problema simile con l'aggiunta del seguente a/etc/sudoers (o in un file in /etc/sudoers.d):

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

Poi invece di utilizzare directory fping, utilizzare sudo fping.

Problemi correlati