Проблемы с опцией SO_BINDTODEVICE Linux
У меня есть ПК с двумя сетевыми картами. Один (eth0
) предназначен для локальной сети/Интернета, а другой для связи UDP с одним устройством микроконтроллера. Микроконтроллер имеет IP (192.168.7.2) и MAC-адрес. Второй сетевой сетевой адаптер (eth1
) имеет 192.168.7.1.
Микроконтроллер имеет очень простой стек IP, поэтому самый простой способ отправки mc-пакетов UDP - передавать их.
На стороне ПК я хочу получать трансляции - но только от eth1
. Поэтому я пытаюсь связать сокет UDP с устройством eth1
.
Проблемы (исходный код ниже):
-
setsockopt(sock, SOL_SOCKET, SO_BINDTODEVICE, device, sizeof(device))
требует привилегий root, почему? (настройка других параметров работает как пользователь)
-
getsockopt(sock, SOL_SOCKET, SO_BINDTODEVICE, (void *)buffer, &opt_length)
дает "Протокол недоступен". Я хотел бы прочитать устройство, которое я установил с помощью команды setsockopt
.
-
Где я могу найти хорошую информацию? Я проверил некоторые Linux-программы, сетевые книги, но, например, параметр SO_BINDTODEVICE
, который я нашел только в Интернете.
Моя длительная (грязная) тестовая программа показывает проблемы. Настройка и возврат параметров SO_RCVTIMEO
и SO_BROADCAST
работает как ожидалось.
Запуск кода при выходе пользователя с помощью:
could not set SO_BINDTODEVICE (Operation not permitted)"
Запуск с помощью sudo дает:
SO_BINDTODEVICE set
./mc-test: could not get SO_BINDTODEVICE (Protocol not available)
Итак, установка опции, похоже, работает, но чтение ее невозможно?
/* 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
Я изучал это некоторое время, увидев противоречивые ответы на то, как SO_BINDTODEVICE фактически используется. Некоторые источники утверждают, что правильное использование должно передаваться указателем struct ifreq
, который имеет имя и индекс устройства, полученные через ioctl. Например:
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));
Где Учебное пособие по сети Beej говорит, чтобы передать имя устройства в качестве указателя char. Например:
char *devname = "eth0";
setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, devname, strlen(devname));
Я пробовал оба этих метода, и они оба делают то, что требуется, но я хотел бы отметить, что индекс устройства, полученный в первом методе, является излишним. Если вы посмотрите на код ядра в net/core/sock.c, sock_bindtodevice
просто скопирует строку имени устройства, вызовет dev_get_by_name_rcu
, чтобы получить устройство и связывается с ним.
Причина, по которой работает первый подход, заключается в том, что имя устройства является первым элементом структуры ifreq
, см. http://linux.die.net/man/7/netdevice,
Ответ 2
Хорошо, я просмотрел его немного больше. SO_BINDTODEVICE считался "почти устаревшим" еще в 1999 году и является корневым только из-за некоторых неуказанных "последствий для безопасности" (я не мог точно узнать, что именно).
Тем не менее, вы должны иметь возможность получить нужное поведение, привязав его к INADDR_ANY и установив socket_top_pKTINFO. Это передаст дополнительное сообщение в сокете, которое содержит структуру pktinfo, описывающую входящий пакет. Эта структура включает в себя индекс интерфейса, в который вступил пакет:
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 */
};
ipi_ifindex соответствует ifr_ifindex из struct ifreq, возвращаемого netdevice ioctls, как SIOCGIFCONF. Поэтому вы должны иметь возможность использовать это, чтобы игнорировать пакеты, полученные на интерфейсах, отличных от того, который вас интересует.
Doco для IP_PKTINFO находится в ip (7) и для интерфейса ioctls в netdevice (7).
Ответ 3
setsockopt(sock, SOL_SOCKET, SO_BINDTODEVICE, "eth0", 4);
Выше строки кода достаточно, чтобы получать сообщения только от eth0 interface
.
Я тестировал это на Linux.
ПРИМЕЧАНИЕ. Это не сработает, если есть интерфейс моста, управляющий реальными интерфейсами.
С уважением,
Сантош.
Ответ 4
Просто найдите IP-адрес интерфейса, который вас интересует, с помощью getifaddrs() и привяжите ваш сокет к этому IP-адресу с помощью bind(). Если вы включите SO_BROADCAST в сокете, вы получите только те передачи, которые были получены на этом интерфейсе.
Или вы можете пропустить часть getifaddrs() и просто привязать() к 192.168.7.1, если хотите.
Ответ 5
Проблема, с которой я столкнулся, заключается в том, что прием широковещательных сообщений с определенного интерфейса обрабатывается по-разному Linux, Windows,...
http://www.developerweb.net/forum/showthread.php?t=5722
Теперь я решил решить проблему (небольшая документация и плохая переносимость), изменив стек TCP/IP микроконтроллера. Он больше не будет отправлять ответы на широковещательный адрес, а вместо этого берет IP/MAC из входящего пакета UDP в качестве целевого IP/MAC. Тогда я могу (на стороне ПК) просто привязать сокет к IP eth1.
Cheers,
Майкл
Ответ 6
Я могу подтвердить, что отправка многоадресной рассылки на конкретный интерфейс работает так же. См. Примеры кодов ниже.
Однако я не могу заставить программу listener.c работать, если интерфейс установлен SO_BINDTODEVICE на мой вторичный интерфейс eth4.
Я использовал совершенно другую машину для отправки многоадресных пакетов, а слушатель работает с интерфейсом eth3, а не с интерфейса eth4. Тем не менее, tcpdump показывает пакеты в обоих интерфейсах (sudo tcpdump -i eth4 | grep UDP).
Это модификации образца кода Энтони Кортни:
sender.c и 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);
}
}
Ответ 7
Ответ на вопрос 2, похоже, что getockopt просто не поддерживается для опции SO_BINDTODEVICE. В источнике ядра Linux (2.6.27) опция обрабатывается только в функции sock_setsockopt для linux-2.6.27.25-0.1/net/core/sock.c
На вопрос 3, похоже, многие люди рекомендуют книгу "UNIX network programming" У. Ричарда Стивенса.
Я просмотрел страницы опций сокета в онлайн-версии книги Google - опция SO_BINDTODEVICE не указана в таблице 7.1 и 7.2:-(
... может быть, потому что этот вариант только для Linux?
Ответ 8
Если вы не можете получать многоадресные пакеты на вторичном интерфейсе, это может быть фильтрация обратного пути, которая блокирует их. Это отфильтровывает принятые пакеты, если эти пакеты не выходят на интерфейс, в который они входят.
Чтобы отключить эту функцию, используйте следующее:
sudo -i
echo 2 > /proc/sys/net/ipv4/conf/eth1/rp_filter
echo 2 > /proc/sys/net/ipv4/conf/all/rp_filter
exit
Ответ 9
setsocketopt требует индекс устройства, а не имя. Кроме того, вы должны использовать struct ifreq для передачи индекса:
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));
Ответ 10
Я решил подобную проблему, добавив следующее в /etc/sudoers (или в файл в /etc/sudoers.d):
myuser myhost=(root) NOPASSWD: /usr/bin/fping
Затем вместо использования каталога fping используйте sudo fping
.