Problèmes avec SO_BINDTODEVICE Linux option de socket
J'ai un PC avec deux cartes réseau. Un (eth0
) est LAN/internet et l'autre pour la communication UDP avec un microcontrôleur de l'appareil. Le microcontrôleur dispose d'une IP (192.168.7.2) et une adresse MAC. Le second pc adaptateur de réseau (eth1
) a 192.168.7.1.
Le microcontrôleur est très simple pile IP, de sorte que le moyen le plus facile pour le mc pour envoyer des paquets UDP est de les diffuser.
Sur le côté PC, j'aimerais recevoir les émissions, - mais seulement à partir de eth1
. J'ai donc essayer de lier les sockets UDP à la eth1
appareil.
Les problèmes (le code source ci-dessous):
-
setsockopt(sock, SOL_SOCKET, SO_BINDTODEVICE, device, sizeof(device))
nécessite les privilèges de root, pourquoi? (réglage d'autres options travaille en tant qu'utilisateur) -
getsockopt(sock, SOL_SOCKET, SO_BINDTODEVICE, (void *)buffer, &opt_length)
donne "Protocole n'est pas disponible". Je voudrais lire le dos de l'appareil j'ai réglé via lesetsockopt
commande. -
Où puis-je trouver la bonne info? J'ai vérifié certains Linux-programmation, réseau des livres, mais par exemple la
SO_BINDTODEVICE
option que j'ai trouvé sur internet.
Ma longue (sale) programme de test montre les problèmes. Réglage et revenir à l' SO_RCVTIMEO
et SO_BROADCAST
options fonctionne comme prévu.
L'exécution du code en tant qu'utilisateur quitte avec:
could not set SO_BINDTODEVICE (Operation not permitted)"
En cours d'exécution avec la commande sudo donne:
SO_BINDTODEVICE set
./mc-test: could not get SO_BINDTODEVICE (Protocol not available)
Ainsi, la définition de l'option semble fonctionner, mais la lecture en arrière n'est pas 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);
}
- êtes-vous sûr que vous avez besoin de tout cela? Ne pouvez-vous pas juste bind() de la douille de la 192.168.7.1 adresse? Il fonctionne pour moi.
- bind()ing à une interface spécifique ne fonctionne que sur les paquets de diffusion sur Windows.
- ont essayé de liaison à 192.168.7.255 et faire en sorte eth0 et eth1 ont différents masques réseau?
Vous devez vous connecter pour publier un commentaire.
J'ai été à la recherche dans ce pour un certain temps après avoir vu des réponses contradictoires à la façon dont SO_BINDTODEVICE est réellement utilisée. Certaines sources prétendre que le bon usage est de laisser un
struct ifreq
pointeur, ce qui a le nom de l'appareil et de l'indice obtenu par l'intermédiaire d'un ioctl. Par exemple:Où, comme Beej de réseautage tutoriel dit de passer le nom du périphérique comme un pointeur de char. Par exemple:
J'ai essayé les deux de ces méthodes, et ils ont tous deux faire ce qui est nécessaire, mais je voulais noter que l'appareil indice obtenu dans la première méthode est superflu. Si vous regardez le code du noyau dans net/core/chaussette.c,
sock_bindtodevice
copie tout simplement le nom de périphérique de la chaîne, les appelsdev_get_by_name_rcu
pour obtenir le dispositif et se lie à elle.La raison que la première approche fonctionne, c'est que le nom de l'appareil est le premier élément dans la
ifreq
structure, voir http://linux.die.net/man/7/netdevice.OK, j'ai regardé dans un peu plus. SO_BINDTODEVICE a été jugé "près obsolètes" en 1999, et qui est la racine seulement en raison de certaines indéterminée "implications en matière de sécurité" (je ne pouvais pas trouver exactement ce).
Toutefois, vous devriez être en mesure d'obtenir le comportement que vous voulez par la liaison INADDR_ANY et le réglage de la IP_PKTINFO socketopt. Cela va passer un message supplémentaire sur la prise qui contient un pktinfo structure décrivant le paquet entrant. Cette structure comprend l'index de l'interface sur laquelle le paquet est entré:
La ipi_ifindex correspond à la ifr_ifindex de la structure ifreq renvoyé par la netdevice ioctls comme SIOCGIFCONF. Donc, vous devriez être en mesure de l'utiliser pour ignorer les paquets reçus sur une interface autre que celui qui vous intéresse.
Doco pour IP_PKTINFO est dans ip(7) et pour l'interface ioctls dans netdevice(7).
IP_PKTINFO
moyens de conversion derecv()/recvfrom()
àrecvmsg()
, qui n'est pas très convivial. Mais bien sûr,IP_PKTINFO
peut être très pratique à utiliser, en particulier si vous voulez que votre demande d'écouter à plusieurs (mais pas tous) des interfaces. Cependant, je ne trouve aucune référence àSO_BINDTODEVICE
obsolètes/obsolète, et il n'est guère probable Linux le saut de l'espace utilisateur en supprimant dans le futur. Donc, pour le commun des cas d'utilisation, j'irais avecSO_BINDTODEVICE
sur Linux tous les jours de la semaine.- Dessus de la ligne de code suffit pour recevoir des messages de
eth0 interface
seulement.J'ai testé cela sur Linux.
REMARQUE: Cela ne fonctionnera pas si il y a un pont interface de contrôle réel des interfaces.
Meilleures salutations,
Santosh.
Avant de Linux 3.8, cette option de socket pourrait être fixé, mais il ne pouvait pas récupéré avec getsockopt(). Depuis Linux 3.8, il est lisible. Le
optlen
argument doit contenir la taille de la mémoire disponible pour recevoir le nom de l'appareil et il est recommandé d'être IFNAMSZ octets. Le vrai nom de l'appareil longueur est rapporté dans leoptlen
argument.Juste à la recherche de l'adresse IP de l'interface qui vous intéresse avec getifaddrs(), et de lier votre prise à l'adresse IP avec bind(). Si vous activez SO_BROADCAST sur le support que vous aurez seulement ensuite émissions reçus sur cette interface.
Ou, en fait, vous pouvez passer de l'getifaddrs() de la partie et vient directement de bind() pour 192.168.7.1 si vous le souhaitez.
bind()
affecte uniquement le receiption de paquets. Vous recevrez uniquement les paquets sur l'interface correspondante. Mais la pile IP est toujours utiliser l'algorithme de routage pour sélectionner l'interface indépendante de toutbind()
appels.Le problème que j'ai rencontré semble être que le fait de recevoir des émissions à partir d'une interface spécifique est traitée différemment par Linux, Windows,...
http://www.developerweb.net/forum/showthread.php?t=5722
J'ai maintenant décidé de résoudre le problème (peu de documentation et de mauvaise portabilité) en changeant la pile TCP/IP du microcontrôleur. Il ne sera plus possible d'envoyer des réponses à l'adresse de diffusion, mais au lieu de prendre l'adresse IP/MAC de la prochaine paquet UDP en tant que destination IP/MAC. Alors je peux (côté pc), il suffit de lier le support de l'IP de eth1.
Cheers,
Michael
Je peux confirmer que l'envoi de multidiffusion à l'interface spécifique fonctionne aussi comme ça. Voir l'exemple de codes ci-dessous.
Cependant je ne peux pas obtenir de l'auditeur.c programme de travail si l'interface est définie par SO_BINDTODEVICE de mon secondaire interface eth4.
J'ai utilisé complètement différent de la machine pour envoyer les paquets de multidiffusion et de l'auditeur travaille à partir de l'interface eth3, pas à partir de l'interface eth4. Cependant, tcpdump montre les paquets dans les deux interfaces (sudo tcpdump -i eth4 |grep UDP).
Ces sont les modifications apportées à Antony Courtney exemple de code:
de l'expéditeur.c et à l'écoute.c:
La réponse à la question 2 semble être que getsockopt est tout simplement pas pris en charge pour la SO_BINDTODEVICE option. Dans les sources du noyau Linux (2.6.27) l'option est uniquement traitées dans le sock_setsockopt fonction de linux-2.6.27.25-0.1/net/core/chaussette.c
Pour la question 3 il me semble, beaucoup de gens recommandent la "UNIX network programming" livre de W. Richard Stevens.
J'ai regardé à travers les options de prise de pages de google book en ligne de la version de la SO_BINDTODEVICE option n'est pas répertorié dans le tableau 7.1 et 7.2 🙁
...peut-être parce que cette option est seulement pour Linux?
Si vous ne parvenez pas à recevoir des paquets de multidiffusion sur la seconde interface, il pourrait bien être filtrage de chemin inverse qui est de les bloquer. Ce filtre les paquets reçus si ces paquets ne serait pas aller sur l'interface qu'ils sont les prochains sur.
Pour désactiver cette fonction, utilisez la commande suivante:
setsocketopt besoins de l'index de périphériques, pas de nom. En outre, vous devez utiliser struct ifreq pour passer l'indice:
J'ai résolu un problème similaire en ajoutant la ligne suivante à /etc/sudoers (ou dans un fichier dans /etc/sudoers.d):
Alors au lieu d'utiliser fping répertoire, utilisez
sudo fping
.