2009-07-30 2 views
13

У меня есть компьютер с двумя сетевыми картами. Один (eth0) предназначен для LAN/интернета, а другой для связи UDP с одним устройством микроконтроллера. Микроконтроллер имеет IP (192.168.7.2) и MAC-адрес. Второй сетевой адаптер (eth1) имеет 192.168.7.1.Проблемы с опцией сокета SO_BINDTODEVICE Linux

Микроконтроллер имеет очень простой стек IP, поэтому самый простой способ отправки mc-пакетов UDP - передавать их.

На стороне ПК я хотел бы получать трансляции - но только от eth1. Поэтому я пытаюсь связать сокет UDP с устройством eth1.

Проблемы (исходный код ниже):

  1. setsockopt(sock, SOL_SOCKET, SO_BINDTODEVICE, device, sizeof(device)) требует привилегий суперпользователя, то почему? (настройка других параметров работает как пользователь)

  2. getsockopt(sock, SOL_SOCKET, SO_BINDTODEVICE, (void *)buffer, &opt_length) дает «Протокол недоступен». Я хотел бы прочитать устройство, которое я установил с помощью команды setsockopt.

  3. Где я могу найти подробную информацию? Я проверил некоторые 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

Вы уверены, что вам нужно все это? Не можете ли вы просто связать() сокет с адресом 192.168.7.1? Меня устраивает. – Juliano

+0

@Juliano: привязка() к определенному интерфейсу работает только с широковещательными пакетами в Windows. – Compholio

+0

попытались привязать к 192.168.7.255 и убедиться, что eth0 и eth1 имеют разные сетевые маски? – dashesy

ответ

0

Ответ на вопрос 2, кажется, что getsockopt просто не поддерживается для опции SO_BINDTODEVICE. В источнике ядра Linux (2.6.27) опция обрабатывается только в функции sock_setsockopt из linux-2.6.27.25-0.1/net/core/sock.c

На вопрос 3, похоже, многие люди рекомендуют Книга «Сетевое программирование UNIX» У. Ричарда Стивенса. я просматривал варианты сокетов страниц Google версии книги онлайн - опция SO_BINDTODEVICE не перечисленных в таблице 7.1 и 7.2 :-( ... может быть, потому, что этот вариант Linux только

1

Просто выполните поиск IP-адреса интерфейса, который вас интересует с помощью getifaddrs(), и привяжите ваш сокет к этому IP-адресу с помощью bind(). Если вы включите SO_BROADCAST в сокете, вы получите только полученные в этом интерфейсе радиопередачи.

или действительно, вы можете пропустить часть getifaddrs() и просто напрямую привязать() к 192.168.7.1, если хотите.

+0

Это было первое, что я сделал. И я это проверил только сейчас. ПК НЕ распознает широковещательный ответ MC! Я вижу UDP-пакет с wirehark, но процедура приема отключается, не получая ничего. – 2009-07-31 07:52:48

+0

Что произойдет, если вы привяжетесь к 192.168.7.255? (Это используется широковещательная передача, или это 255.255.255.255?) – caf

+0

Это тоже не работает. Но я отправлю решение, которое я использую сейчас. Благодарим вас за проблему с сетью. – 2009-07-31 11:59:41

1

Проблема, с которой я столкнулся, кажется, заключается в том, что получение широкополосных каналов ts из определенного интерфейса обрабатывается по-разному Linux, Windows, ... http://www.developerweb.net/forum/showthread.php?t=5722

Теперь я решил решить проблему (небольшая документация и плохая переносимость), изменив стек TCP/IP микроконтроллера. Он больше не будет отправлять ответы на широковещательный адрес, а вместо этого берет IP/MAC из входящего пакета UDP в качестве целевого IP/MAC. Тогда я могу (на стороне ПК) просто привязать сокет к IP eth1.

Приветствия, Майкл

6

ОК, я посмотрел на это немного больше. SO_BINDTODEVICE считался «почти устаревшим» еще в 1999 году и является корневым только из-за некоторых неуказанных «последствий для безопасности» (я не мог точно выяснить, что именно).

Однако вы должны иметь возможность получить нужное поведение, привязав его к INADDR_ANY и установив socketopt IP_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 от структуры ifreq, возвращаемого в netdevice IOCTLs как SIOCGIFCONF. Таким образом, вы должны иметь возможность использовать это, чтобы игнорировать пакеты, полученные на интерфейсах, отличных от того, который вас интересует.

Doco для IP_PKTINFO находится в ip (7) и для интерфейса ioctls в netdevice (7).

+0

Использование 'IP_PKTINFO' означает преобразование из' recv()/recvfrom() 'в' recvmsg() ', что не совсем удобно. Но уверен, что «IP_PKTINFO» может быть очень удобен в использовании, особенно если вы хотите, чтобы ваше приложение прослушивало несколько (но не все) интерфейсов. Однако, я не нахожу ссылки на 'SO_BINDTODEVICE', устаревшие/устаревшие, и вряд ли Linux сломает пользовательское пространство, удалив его в будущем. Таким образом, для обычного варианта использования я каждый день недели буду работать с «SO_BINDTODEVICE» в Linux. – troglobit

-1

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

Не в соответствии с мужчиной 7 socket: http://linux.die.net/man/7/socket –

1

Я могу подтвердить, что отправка многоадресной рассылки для конкретного интерфейса работает также, как это. См. Примеры кодов ниже. Однако я не могу получить программу 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); 
    } 
} 
14

Я смотрел на это некоторое время после того, как видим противоречивые ответы на то, как на самом деле SO_BINDTODEVICE используемый. Some sources утверждают, что правильное использование должно передаваться в указателе 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's networking tutorial говорит передать имя устройства в качестве указателя полукокса. Например:

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.

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

Выше строки кода достаточно, чтобы получать сообщения только от eth0 interface. Я тестировал это на Linux.

ПРИМЕЧАНИЕ: Это не сработает, если имеется интерфейс моста, контролирующий фактические интерфейсы.

С уважением, Сантош.

0

Если вы не можете получать многоадресные пакеты на вторичном интерфейсе, это может быть фильтрация обратного пути, которая блокирует их. Это отфильтровывает принятые пакеты, если эти пакеты не выходят на интерфейс, в который они входят.

Чтобы отключить эту функцию, используйте следующее:

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

Я решил аналогичную проблему, добавив следующую строку в/и т.д./sudoers (или в файле в /etc/sudoers.d):

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

Затем, вместо использования Fping каталога используйте sudo fping.

Смежные вопросы